Merge branch 'main' of github.com:vmware-tanzu/pinniped into supervisor-docs
This commit is contained in:
commit
7c9f40b6d9
@ -15,7 +15,6 @@ COPY generated ./generated
|
||||
COPY cmd ./cmd
|
||||
COPY pkg ./pkg
|
||||
COPY internal ./internal
|
||||
COPY tools ./tools
|
||||
COPY hack ./hack
|
||||
|
||||
# Build the executable binary (CGO_ENABLED=0 means static linking)
|
||||
|
@ -92,7 +92,7 @@ func startControllers(
|
||||
kubeInformers kubeinformers.SharedInformerFactory,
|
||||
pinnipedInformers pinnipedinformers.SharedInformerFactory,
|
||||
) {
|
||||
opInformer := pinnipedInformers.Config().V1alpha1().FederationDomains()
|
||||
federationDomainInformer := pinnipedInformers.Config().V1alpha1().FederationDomains()
|
||||
secretInformer := kubeInformers.Core().V1().Secrets()
|
||||
|
||||
// Create controller manager.
|
||||
@ -102,7 +102,7 @@ func startControllers(
|
||||
supervisorstorage.GarbageCollectorController(
|
||||
clock.RealClock{},
|
||||
kubeClient,
|
||||
kubeInformers.Core().V1().Secrets(),
|
||||
secretInformer,
|
||||
controllerlib.WithInformer,
|
||||
),
|
||||
singletonWorker,
|
||||
@ -112,7 +112,7 @@ func startControllers(
|
||||
issuerManager,
|
||||
clock.RealClock{},
|
||||
pinnipedClient,
|
||||
opInformer,
|
||||
federationDomainInformer,
|
||||
controllerlib.WithInformer,
|
||||
),
|
||||
singletonWorker,
|
||||
@ -123,7 +123,7 @@ func startControllers(
|
||||
kubeClient,
|
||||
pinnipedClient,
|
||||
secretInformer,
|
||||
opInformer,
|
||||
federationDomainInformer,
|
||||
controllerlib.WithInformer,
|
||||
),
|
||||
singletonWorker,
|
||||
@ -132,7 +132,7 @@ func startControllers(
|
||||
supervisorconfig.NewJWKSObserverController(
|
||||
dynamicJWKSProvider,
|
||||
secretInformer,
|
||||
opInformer,
|
||||
federationDomainInformer,
|
||||
controllerlib.WithInformer,
|
||||
),
|
||||
singletonWorker,
|
||||
@ -142,7 +142,7 @@ func startControllers(
|
||||
dynamicTLSCertProvider,
|
||||
cfg.NamesConfig.DefaultTLSCertificateSecret,
|
||||
secretInformer,
|
||||
opInformer,
|
||||
federationDomainInformer,
|
||||
controllerlib.WithInformer,
|
||||
),
|
||||
singletonWorker,
|
||||
@ -180,7 +180,7 @@ func startControllers(
|
||||
kubeClient,
|
||||
pinnipedClient,
|
||||
secretInformer,
|
||||
opInformer,
|
||||
federationDomainInformer,
|
||||
controllerlib.WithInformer,
|
||||
),
|
||||
singletonWorker,
|
||||
@ -203,7 +203,7 @@ func startControllers(
|
||||
kubeClient,
|
||||
pinnipedClient,
|
||||
secretInformer,
|
||||
opInformer,
|
||||
federationDomainInformer,
|
||||
controllerlib.WithInformer,
|
||||
),
|
||||
singletonWorker,
|
||||
@ -226,7 +226,7 @@ func startControllers(
|
||||
kubeClient,
|
||||
pinnipedClient,
|
||||
secretInformer,
|
||||
opInformer,
|
||||
federationDomainInformer,
|
||||
controllerlib.WithInformer,
|
||||
),
|
||||
singletonWorker,
|
||||
@ -236,7 +236,7 @@ func startControllers(
|
||||
dynamicUpstreamIDPProvider,
|
||||
pinnipedClient,
|
||||
pinnipedInformers.IDP().V1alpha1().OIDCIdentityProviders(),
|
||||
kubeInformers.Core().V1().Secrets(),
|
||||
secretInformer,
|
||||
klogr.New()),
|
||||
singletonWorker)
|
||||
|
||||
|
@ -8,11 +8,7 @@ import (
|
||||
)
|
||||
|
||||
//nolint: gochecknoglobals
|
||||
var getCmd = &cobra.Command{
|
||||
Use: "get",
|
||||
Short: "get",
|
||||
SilenceUsage: true, // do not print usage message when commands fail
|
||||
}
|
||||
var getCmd = &cobra.Command{Use: "get", Short: "get"}
|
||||
|
||||
//nolint: gochecknoinits
|
||||
func init() {
|
||||
|
@ -81,7 +81,7 @@ func TestGetKubeconfig(t *testing.T) {
|
||||
args: []string{},
|
||||
getPathToSelfErr: fmt.Errorf("some OS error"),
|
||||
wantError: true,
|
||||
wantStdout: here.Doc(`
|
||||
wantStderr: here.Doc(`
|
||||
Error: could not determine the Pinniped executable path: some OS error
|
||||
`),
|
||||
},
|
||||
@ -91,7 +91,7 @@ func TestGetKubeconfig(t *testing.T) {
|
||||
"--oidc-ca-bundle", "./does/not/exist",
|
||||
},
|
||||
wantError: true,
|
||||
wantStdout: here.Doc(`
|
||||
wantStderr: here.Doc(`
|
||||
Error: could not read --oidc-ca-bundle: open ./does/not/exist: no such file or directory
|
||||
`),
|
||||
},
|
||||
@ -101,7 +101,7 @@ func TestGetKubeconfig(t *testing.T) {
|
||||
"--kubeconfig", "./does/not/exist",
|
||||
},
|
||||
wantError: true,
|
||||
wantStdout: here.Doc(`
|
||||
wantStderr: here.Doc(`
|
||||
Error: could not load --kubeconfig: stat ./does/not/exist: no such file or directory
|
||||
`),
|
||||
},
|
||||
@ -112,7 +112,7 @@ func TestGetKubeconfig(t *testing.T) {
|
||||
"--kubeconfig-context", "invalid",
|
||||
},
|
||||
wantError: true,
|
||||
wantStdout: here.Doc(`
|
||||
wantStderr: here.Doc(`
|
||||
Error: could not load --kubeconfig/--kubeconfig-context: no such context "invalid"
|
||||
`),
|
||||
},
|
||||
@ -123,7 +123,7 @@ func TestGetKubeconfig(t *testing.T) {
|
||||
},
|
||||
getClientsetErr: fmt.Errorf("some kube error"),
|
||||
wantError: true,
|
||||
wantStdout: here.Doc(`
|
||||
wantStderr: here.Doc(`
|
||||
Error: could not configure Kubernetes client: some kube error
|
||||
`),
|
||||
},
|
||||
@ -135,7 +135,7 @@ func TestGetKubeconfig(t *testing.T) {
|
||||
"--concierge-authenticator-name", "test-authenticator",
|
||||
},
|
||||
wantError: true,
|
||||
wantStdout: here.Doc(`
|
||||
wantStderr: here.Doc(`
|
||||
Error: webhookauthenticators.authentication.concierge.pinniped.dev "test-authenticator" not found
|
||||
`),
|
||||
},
|
||||
@ -147,7 +147,7 @@ func TestGetKubeconfig(t *testing.T) {
|
||||
"--concierge-authenticator-name", "test-authenticator",
|
||||
},
|
||||
wantError: true,
|
||||
wantStdout: here.Doc(`
|
||||
wantStderr: here.Doc(`
|
||||
Error: jwtauthenticators.authentication.concierge.pinniped.dev "test-authenticator" not found
|
||||
`),
|
||||
},
|
||||
@ -159,7 +159,7 @@ func TestGetKubeconfig(t *testing.T) {
|
||||
"--concierge-authenticator-name", "test-authenticator",
|
||||
},
|
||||
wantError: true,
|
||||
wantStdout: here.Doc(`
|
||||
wantStderr: here.Doc(`
|
||||
Error: invalid authenticator type "invalid", supported values are "webhook" and "jwt"
|
||||
`),
|
||||
},
|
||||
@ -178,7 +178,7 @@ func TestGetKubeconfig(t *testing.T) {
|
||||
},
|
||||
},
|
||||
wantError: true,
|
||||
wantStdout: here.Doc(`
|
||||
wantStderr: here.Doc(`
|
||||
Error: failed to list JWTAuthenticator objects for autodiscovery: some list error
|
||||
`),
|
||||
},
|
||||
@ -197,7 +197,7 @@ func TestGetKubeconfig(t *testing.T) {
|
||||
},
|
||||
},
|
||||
wantError: true,
|
||||
wantStdout: here.Doc(`
|
||||
wantStderr: here.Doc(`
|
||||
Error: failed to list WebhookAuthenticator objects for autodiscovery: some list error
|
||||
`),
|
||||
},
|
||||
@ -207,7 +207,7 @@ func TestGetKubeconfig(t *testing.T) {
|
||||
"--kubeconfig", "./testdata/kubeconfig.yaml",
|
||||
},
|
||||
wantError: true,
|
||||
wantStdout: here.Doc(`
|
||||
wantStderr: here.Doc(`
|
||||
Error: no authenticators were found in namespace "pinniped-concierge" (try setting --concierge-namespace)
|
||||
`),
|
||||
},
|
||||
@ -224,7 +224,7 @@ func TestGetKubeconfig(t *testing.T) {
|
||||
&conciergev1alpha1.WebhookAuthenticator{ObjectMeta: metav1.ObjectMeta{Name: "test-authenticator-4", Namespace: "test-namespace"}},
|
||||
},
|
||||
wantError: true,
|
||||
wantStdout: here.Doc(`
|
||||
wantStderr: here.Doc(`
|
||||
Error: multiple authenticators were found in namespace "test-namespace", so the --concierge-authenticator-type/--concierge-authenticator-name flags must be specified
|
||||
`),
|
||||
},
|
||||
@ -238,7 +238,7 @@ func TestGetKubeconfig(t *testing.T) {
|
||||
&conciergev1alpha1.WebhookAuthenticator{ObjectMeta: metav1.ObjectMeta{Name: "test-authenticator", Namespace: "test-namespace"}},
|
||||
},
|
||||
wantError: true,
|
||||
wantStdout: here.Doc(`
|
||||
wantStderr: here.Doc(`
|
||||
Error: could not autodiscover --oidc-issuer, and none was provided
|
||||
`),
|
||||
},
|
||||
@ -259,7 +259,7 @@ func TestGetKubeconfig(t *testing.T) {
|
||||
},
|
||||
},
|
||||
wantError: true,
|
||||
wantStdout: here.Doc(`
|
||||
wantStderr: here.Doc(`
|
||||
Error: tried to autodiscover --oidc-ca-bundle, but JWTAuthenticator test-namespace/test-authenticator has invalid spec.tls.certificateAuthorityData: illegal base64 data at input byte 7
|
||||
`),
|
||||
},
|
||||
@ -275,7 +275,7 @@ func TestGetKubeconfig(t *testing.T) {
|
||||
&conciergev1alpha1.WebhookAuthenticator{ObjectMeta: metav1.ObjectMeta{Name: "test-authenticator", Namespace: "test-namespace"}},
|
||||
},
|
||||
wantError: true,
|
||||
wantStdout: here.Doc(`
|
||||
wantStderr: here.Doc(`
|
||||
Error: only one of --static-token and --static-token-env can be specified
|
||||
`),
|
||||
},
|
||||
|
@ -79,7 +79,7 @@ func TestLoginOIDCCommand(t *testing.T) {
|
||||
name: "missing required flags",
|
||||
args: []string{},
|
||||
wantError: true,
|
||||
wantStdout: here.Doc(`
|
||||
wantStderr: here.Doc(`
|
||||
Error: required flag(s) "issuer" not set
|
||||
`),
|
||||
},
|
||||
@ -91,7 +91,7 @@ func TestLoginOIDCCommand(t *testing.T) {
|
||||
"--enable-concierge",
|
||||
},
|
||||
wantError: true,
|
||||
wantStdout: here.Doc(`
|
||||
wantStderr: here.Doc(`
|
||||
Error: invalid concierge parameters: endpoint must not be empty
|
||||
`),
|
||||
},
|
||||
@ -103,7 +103,7 @@ func TestLoginOIDCCommand(t *testing.T) {
|
||||
"--ca-bundle", "./does/not/exist",
|
||||
},
|
||||
wantError: true,
|
||||
wantStdout: here.Doc(`
|
||||
wantStderr: here.Doc(`
|
||||
Error: could not read --ca-bundle: open ./does/not/exist: no such file or directory
|
||||
`),
|
||||
},
|
||||
@ -115,7 +115,7 @@ func TestLoginOIDCCommand(t *testing.T) {
|
||||
"--ca-bundle-data", "invalid-base64",
|
||||
},
|
||||
wantError: true,
|
||||
wantStdout: here.Doc(`
|
||||
wantStderr: here.Doc(`
|
||||
Error: could not read --ca-bundle-data: illegal base64 data at input byte 7
|
||||
`),
|
||||
},
|
||||
@ -128,7 +128,7 @@ func TestLoginOIDCCommand(t *testing.T) {
|
||||
loginErr: fmt.Errorf("some login error"),
|
||||
wantOptionsCount: 3,
|
||||
wantError: true,
|
||||
wantStdout: here.Doc(`
|
||||
wantStderr: here.Doc(`
|
||||
Error: could not complete Pinniped login: some login error
|
||||
`),
|
||||
},
|
||||
@ -145,7 +145,7 @@ func TestLoginOIDCCommand(t *testing.T) {
|
||||
conciergeErr: fmt.Errorf("some concierge error"),
|
||||
wantOptionsCount: 3,
|
||||
wantError: true,
|
||||
wantStdout: here.Doc(`
|
||||
wantStderr: here.Doc(`
|
||||
Error: could not complete concierge credential exchange: some concierge error
|
||||
`),
|
||||
},
|
||||
|
@ -66,7 +66,7 @@ func TestLoginStaticCommand(t *testing.T) {
|
||||
name: "missing required flags",
|
||||
args: []string{},
|
||||
wantError: true,
|
||||
wantStdout: here.Doc(`
|
||||
wantStderr: here.Doc(`
|
||||
Error: one of --token or --token-env must be set
|
||||
`),
|
||||
},
|
||||
@ -77,7 +77,7 @@ func TestLoginStaticCommand(t *testing.T) {
|
||||
"--enable-concierge",
|
||||
},
|
||||
wantError: true,
|
||||
wantStdout: here.Doc(`
|
||||
wantStderr: here.Doc(`
|
||||
Error: invalid concierge parameters: endpoint must not be empty
|
||||
`),
|
||||
},
|
||||
@ -87,7 +87,7 @@ func TestLoginStaticCommand(t *testing.T) {
|
||||
"--token-env", "TEST_TOKEN_ENV",
|
||||
},
|
||||
wantError: true,
|
||||
wantStdout: here.Doc(`
|
||||
wantStderr: here.Doc(`
|
||||
Error: --token-env variable "TEST_TOKEN_ENV" is not set
|
||||
`),
|
||||
},
|
||||
@ -100,7 +100,7 @@ func TestLoginStaticCommand(t *testing.T) {
|
||||
"TEST_TOKEN_ENV": "",
|
||||
},
|
||||
wantError: true,
|
||||
wantStdout: here.Doc(`
|
||||
wantStderr: here.Doc(`
|
||||
Error: --token-env variable "TEST_TOKEN_ENV" is empty
|
||||
`),
|
||||
},
|
||||
@ -125,7 +125,7 @@ func TestLoginStaticCommand(t *testing.T) {
|
||||
},
|
||||
conciergeErr: fmt.Errorf("some concierge error"),
|
||||
wantError: true,
|
||||
wantStdout: here.Doc(`
|
||||
wantStderr: here.Doc(`
|
||||
Error: could not complete concierge credential exchange: some concierge error
|
||||
`),
|
||||
},
|
||||
|
@ -7,6 +7,7 @@ import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"go.pinniped.dev/internal/here"
|
||||
@ -41,7 +42,7 @@ func TestNewVersionCmd(t *testing.T) {
|
||||
args []string
|
||||
wantError bool
|
||||
wantStdoutRegexp string
|
||||
wantStderr string
|
||||
wantStderrRegexp string
|
||||
}{
|
||||
{
|
||||
name: "no flags",
|
||||
@ -57,7 +58,8 @@ func TestNewVersionCmd(t *testing.T) {
|
||||
name: "arg passed",
|
||||
args: []string{"tuna"},
|
||||
wantError: true,
|
||||
wantStdoutRegexp: `Error: unknown command "tuna" for "version"` + "\n" + knownGoodUsageRegexpForVersion,
|
||||
wantStderrRegexp: `Error: unknown command "tuna" for "version"`,
|
||||
wantStdoutRegexp: knownGoodUsageRegexpForVersion,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
@ -76,8 +78,8 @@ func TestNewVersionCmd(t *testing.T) {
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
require.Regexp(t, tt.wantStdoutRegexp, stdout.String(), "unexpected stdout")
|
||||
require.Equal(t, tt.wantStderr, stderr.String(), "unexpected stderr")
|
||||
assert.Regexp(t, tt.wantStdoutRegexp, stdout.String(), "unexpected stdout")
|
||||
assert.Regexp(t, tt.wantStderrRegexp, stderr.String(), "unexpected stderr")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -25,7 +25,7 @@ custom_labels: {} #! e.g. {myCustomLabelName: myCustomLabelValue, otherCustomLab
|
||||
replicas: 2
|
||||
|
||||
#! Specify either an image_digest or an image_tag. If both are given, only image_digest will be used.
|
||||
image_repo: docker.io/getpinniped/pinniped-server
|
||||
image_repo: projects.registry.vmware.com/pinniped/pinniped-server
|
||||
image_digest: #! e.g. sha256:f3c4fdfd3ef865d4b97a1fd295d94acc3f0c654c46b6f27ffad5cf80216903c8
|
||||
image_tag: latest
|
||||
|
||||
|
@ -5,7 +5,7 @@
|
||||
---
|
||||
|
||||
#! Specify either an image_digest or an image_tag. If both are given, only image_digest will be used.
|
||||
image_repo: docker.io/getpinniped/pinniped-server
|
||||
image_repo: projects.registry.vmware.com/pinniped/pinniped-server
|
||||
image_digest: #! e.g. sha256:f3c4fdfd3ef865d4b97a1fd295d94acc3f0c654c46b6f27ffad5cf80216903c8
|
||||
image_tag: latest
|
||||
|
||||
|
@ -25,7 +25,7 @@ custom_labels: {} #! e.g. {myCustomLabelName: myCustomLabelValue, otherCustomLab
|
||||
replicas: 2
|
||||
|
||||
#! Specify either an image_digest or an image_tag. If both are given, only image_digest will be used.
|
||||
image_repo: docker.io/getpinniped/pinniped-server
|
||||
image_repo: projects.registry.vmware.com/pinniped/pinniped-server
|
||||
image_digest: #! e.g. sha256:f3c4fdfd3ef865d4b97a1fd295d94acc3f0c654c46b6f27ffad5cf80216903c8
|
||||
image_tag: latest
|
||||
|
||||
|
4
generated/1.19/apis/go.mod
generated
4
generated/1.19/apis/go.mod
generated
@ -4,6 +4,6 @@ module go.pinniped.dev/generated/1.19/apis
|
||||
go 1.13
|
||||
|
||||
require (
|
||||
k8s.io/api v0.19.2
|
||||
k8s.io/apimachinery v0.19.2
|
||||
k8s.io/api v0.19.5
|
||||
k8s.io/apimachinery v0.19.5
|
||||
)
|
||||
|
17
generated/1.19/apis/go.sum
generated
17
generated/1.19/apis/go.sum
generated
@ -101,8 +101,8 @@ golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73r
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381 h1:VXak5I6aEWmAXeQjA+QSZzlgNrpq9mjcfDemuexIKsU=
|
||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b h1:uwuIcX0g4Yl1NC5XAz37xsr2lTtcqevgzYNVt49waME=
|
||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@ -113,8 +113,9 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200622214017-ed371f2e16b4 h1:5/PjkGUjvEU5Gl6BxmvKRPpqo2uNMv4rcHBMwzk/st8=
|
||||
golang.org/x/sys v0.0.0-20200622214017-ed371f2e16b4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201112073958-5cba982894dd h1:5CtCZbICpIOFdgO940moixOPjc0178IU44m4EjOO5IY=
|
||||
golang.org/x/sys v0.0.0-20201112073958-5cba982894dd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
@ -157,10 +158,10 @@ gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
k8s.io/api v0.19.2 h1:q+/krnHWKsL7OBZg/rxnycsl9569Pud76UJ77MvKXms=
|
||||
k8s.io/api v0.19.2/go.mod h1:IQpK0zFQ1xc5iNIQPqzgoOwuFugaYHK4iCknlAQP9nI=
|
||||
k8s.io/apimachinery v0.19.2 h1:5Gy9vQpAGTKHPVOh5c4plE274X8D/6cuEiTO2zve7tc=
|
||||
k8s.io/apimachinery v0.19.2/go.mod h1:DnPGDnARWFvYa3pMHgSxtbZb7gpzzAZ1pTfaUNDVlmA=
|
||||
k8s.io/api v0.19.5 h1:p0MRzyhokJ9Kn5jcJAHNup0s+COMBPfn1mTasls6mMg=
|
||||
k8s.io/api v0.19.5/go.mod h1:yGZReuNa0vj56op6eT+NLrXJne0R0u9ktexZ8jdJzpc=
|
||||
k8s.io/apimachinery v0.19.5 h1:Yvz6dOE0WbVE+FXBEFqc9lSvo87VPtq6mCSsrtC95HI=
|
||||
k8s.io/apimachinery v0.19.5/go.mod h1:6sRbGRAVY5DOCuZwB5XkqguBqpqLU6q/kOaOdk29z6Q=
|
||||
k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
|
||||
k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE=
|
||||
k8s.io/klog/v2 v2.2.0 h1:XRvcwJozkgZ1UQJmfMGpvRthQHOvihEhYtDfAaxMz/A=
|
||||
|
4
generated/1.19/client/go.mod
generated
4
generated/1.19/client/go.mod
generated
@ -6,8 +6,8 @@ go 1.13
|
||||
require (
|
||||
github.com/go-openapi/spec v0.19.9
|
||||
go.pinniped.dev/generated/1.19/apis v0.0.0-00010101000000-000000000000
|
||||
k8s.io/apimachinery v0.19.2
|
||||
k8s.io/client-go v0.19.2
|
||||
k8s.io/apimachinery v0.19.5
|
||||
k8s.io/client-go v0.19.5
|
||||
k8s.io/kube-openapi v0.0.0-20200805222855-6aeccd4b50c6
|
||||
)
|
||||
|
||||
|
21
generated/1.19/client/go.sum
generated
21
generated/1.19/client/go.sum
generated
@ -209,8 +209,8 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL
|
||||
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381 h1:VXak5I6aEWmAXeQjA+QSZzlgNrpq9mjcfDemuexIKsU=
|
||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b h1:uwuIcX0g4Yl1NC5XAz37xsr2lTtcqevgzYNVt49waME=
|
||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
@ -235,8 +235,9 @@ golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200622214017-ed371f2e16b4 h1:5/PjkGUjvEU5Gl6BxmvKRPpqo2uNMv4rcHBMwzk/st8=
|
||||
golang.org/x/sys v0.0.0-20200622214017-ed371f2e16b4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201112073958-5cba982894dd h1:5CtCZbICpIOFdgO940moixOPjc0178IU44m4EjOO5IY=
|
||||
golang.org/x/sys v0.0.0-20201112073958-5cba982894dd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
@ -327,12 +328,12 @@ honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWh
|
||||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
k8s.io/api v0.19.2 h1:q+/krnHWKsL7OBZg/rxnycsl9569Pud76UJ77MvKXms=
|
||||
k8s.io/api v0.19.2/go.mod h1:IQpK0zFQ1xc5iNIQPqzgoOwuFugaYHK4iCknlAQP9nI=
|
||||
k8s.io/apimachinery v0.19.2 h1:5Gy9vQpAGTKHPVOh5c4plE274X8D/6cuEiTO2zve7tc=
|
||||
k8s.io/apimachinery v0.19.2/go.mod h1:DnPGDnARWFvYa3pMHgSxtbZb7gpzzAZ1pTfaUNDVlmA=
|
||||
k8s.io/client-go v0.19.2 h1:gMJuU3xJZs86L1oQ99R4EViAADUPMHHtS9jFshasHSc=
|
||||
k8s.io/client-go v0.19.2/go.mod h1:S5wPhCqyDNAlzM9CnEdgTGV4OqhsW3jGO1UM1epwfJA=
|
||||
k8s.io/api v0.19.5 h1:p0MRzyhokJ9Kn5jcJAHNup0s+COMBPfn1mTasls6mMg=
|
||||
k8s.io/api v0.19.5/go.mod h1:yGZReuNa0vj56op6eT+NLrXJne0R0u9ktexZ8jdJzpc=
|
||||
k8s.io/apimachinery v0.19.5 h1:Yvz6dOE0WbVE+FXBEFqc9lSvo87VPtq6mCSsrtC95HI=
|
||||
k8s.io/apimachinery v0.19.5/go.mod h1:6sRbGRAVY5DOCuZwB5XkqguBqpqLU6q/kOaOdk29z6Q=
|
||||
k8s.io/client-go v0.19.5 h1:Y7LsFwgbm9+5oVXER04KNCSPhY6TblYRgG1DQdVq+ig=
|
||||
k8s.io/client-go v0.19.5/go.mod h1:BSG3iuxI40Bs0nNDLS1JRa/7ReBQDHzf0x8nZZrK0fo=
|
||||
k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
|
||||
k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE=
|
||||
k8s.io/klog/v2 v2.2.0 h1:XRvcwJozkgZ1UQJmfMGpvRthQHOvihEhYtDfAaxMz/A=
|
||||
|
43
go.mod
43
go.mod
@ -3,40 +3,49 @@ module go.pinniped.dev
|
||||
go 1.14
|
||||
|
||||
require (
|
||||
cloud.google.com/go v0.60.0 // indirect
|
||||
github.com/MakeNowJust/heredoc/v2 v2.0.1
|
||||
github.com/blang/semver v3.5.1+incompatible // indirect
|
||||
github.com/coreos/go-oidc v2.2.1+incompatible
|
||||
github.com/davecgh/go-spew v1.1.1
|
||||
github.com/go-logr/logr v0.2.1
|
||||
github.com/go-logr/logr v0.3.0
|
||||
github.com/go-logr/stdr v0.2.0
|
||||
github.com/gofrs/flock v0.8.0
|
||||
github.com/golang/mock v1.4.4
|
||||
github.com/golangci/golangci-lint v1.31.0
|
||||
github.com/google/go-cmp v0.5.2
|
||||
github.com/google/gofuzz v1.1.0
|
||||
github.com/google/go-cmp v0.5.4
|
||||
github.com/google/gofuzz v1.2.0
|
||||
github.com/gorilla/securecookie v1.1.1
|
||||
github.com/ory/fosite v0.35.1
|
||||
github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
|
||||
github.com/onsi/ginkgo v1.13.0 // indirect
|
||||
github.com/ory/fosite v0.36.0
|
||||
github.com/pkg/browser v0.0.0-20201207095918-0426ae3fba23
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/sclevine/agouti v3.0.0+incompatible
|
||||
github.com/sclevine/spec v1.4.0
|
||||
github.com/spf13/cobra v1.0.0
|
||||
github.com/spf13/cobra v1.1.1
|
||||
github.com/spf13/pflag v1.0.5
|
||||
github.com/stretchr/testify v1.6.1
|
||||
go.pinniped.dev/generated/1.19/apis v0.0.0-00010101000000-000000000000
|
||||
go.pinniped.dev/generated/1.19/client v0.0.0-00010101000000-000000000000
|
||||
golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899
|
||||
golang.org/x/crypto v0.0.0-20201217014255-9d1352758620
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d
|
||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208
|
||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a
|
||||
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d // indirect
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
||||
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
|
||||
gopkg.in/square/go-jose.v2 v2.5.1
|
||||
k8s.io/api v0.19.2
|
||||
k8s.io/apimachinery v0.19.2
|
||||
k8s.io/apiserver v0.19.2
|
||||
k8s.io/client-go v0.19.2
|
||||
k8s.io/component-base v0.19.2
|
||||
k8s.io/klog/v2 v2.3.0
|
||||
k8s.io/kube-aggregator v0.19.2
|
||||
k8s.io/utils v0.0.0-20200729134348-d5654de09c73
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 // indirect
|
||||
k8s.io/api v0.19.5
|
||||
k8s.io/apimachinery v0.19.5
|
||||
k8s.io/apiserver v0.19.5
|
||||
k8s.io/client-go v0.19.5
|
||||
k8s.io/component-base v0.19.5
|
||||
k8s.io/klog/v2 v2.4.0
|
||||
k8s.io/kube-aggregator v0.19.5
|
||||
k8s.io/utils v0.0.0-20201110183641-67b214c5f920
|
||||
sigs.k8s.io/yaml v1.2.0
|
||||
)
|
||||
|
||||
|
@ -1,3 +1,3 @@
|
||||
1.19.2
|
||||
1.19.5
|
||||
1.18.2
|
||||
1.17.11
|
||||
|
@ -13,7 +13,7 @@ export GO111MODULE="on"
|
||||
if [[ -z "${CONTAINED:-}" ]]; then
|
||||
for kubeVersion in "${KUBE_VERSIONS[@]}"; do
|
||||
# CODEGEN_IMAGE is the container image to use when running
|
||||
CODEGEN_IMAGE="docker.io/getpinniped/k8s-code-generator-$(echo "$kubeVersion" | cut -d"." -f1-2):latest"
|
||||
CODEGEN_IMAGE="projects.registry.vmware.com/pinniped/k8s-code-generator-$(echo "$kubeVersion" | cut -d"." -f1-2):latest"
|
||||
|
||||
echo "generating code for ${kubeVersion} using ${CODEGEN_IMAGE}..."
|
||||
docker run --rm \
|
||||
|
@ -12,12 +12,7 @@ function tidy_cmd() {
|
||||
}
|
||||
|
||||
function lint_cmd() {
|
||||
if [ -x "$(command -v golangci-lint)" ]; then
|
||||
cmd='golangci-lint'
|
||||
else
|
||||
cmd='go run github.com/golangci/golangci-lint/cmd/golangci-lint'
|
||||
fi
|
||||
echo "${cmd} run --modules-download-mode=readonly --timeout=10m"
|
||||
echo "golangci-lint run --modules-download-mode=readonly --timeout=10m"
|
||||
}
|
||||
|
||||
function test_cmd() {
|
||||
|
@ -130,7 +130,7 @@ if ! tilt_mode; then
|
||||
fi
|
||||
fi
|
||||
|
||||
registry="docker.io"
|
||||
registry="pinniped.local"
|
||||
repo="test/build"
|
||||
registry_repo="$registry/$repo"
|
||||
tag=$(uuidgen) # always a new tag to force K8s to reload the image on redeploy
|
||||
|
@ -29,14 +29,14 @@ import (
|
||||
// If there are no longer any valid issuers, then it can be called with no arguments.
|
||||
// Implementations of this type should be thread-safe to support calls from multiple goroutines.
|
||||
type ProvidersSetter interface {
|
||||
SetProviders(federationDomains ...*provider.FederationDomain)
|
||||
SetProviders(federationDomains ...*provider.FederationDomainIssuer)
|
||||
}
|
||||
|
||||
type federationDomainWatcherController struct {
|
||||
providerSetter ProvidersSetter
|
||||
clock clock.Clock
|
||||
client pinnipedclientset.Interface
|
||||
opcInformer configinformers.FederationDomainInformer
|
||||
providerSetter ProvidersSetter
|
||||
clock clock.Clock
|
||||
client pinnipedclientset.Interface
|
||||
federationDomainInformer configinformers.FederationDomainInformer
|
||||
}
|
||||
|
||||
// NewFederationDomainWatcherController creates a controllerlib.Controller that watches
|
||||
@ -45,21 +45,21 @@ func NewFederationDomainWatcherController(
|
||||
providerSetter ProvidersSetter,
|
||||
clock clock.Clock,
|
||||
client pinnipedclientset.Interface,
|
||||
opcInformer configinformers.FederationDomainInformer,
|
||||
federationDomainInformer configinformers.FederationDomainInformer,
|
||||
withInformer pinnipedcontroller.WithInformerOptionFunc,
|
||||
) controllerlib.Controller {
|
||||
return controllerlib.New(
|
||||
controllerlib.Config{
|
||||
Name: "FederationDomainWatcherController",
|
||||
Syncer: &federationDomainWatcherController{
|
||||
providerSetter: providerSetter,
|
||||
clock: clock,
|
||||
client: client,
|
||||
opcInformer: opcInformer,
|
||||
providerSetter: providerSetter,
|
||||
clock: clock,
|
||||
client: client,
|
||||
federationDomainInformer: federationDomainInformer,
|
||||
},
|
||||
},
|
||||
withInformer(
|
||||
opcInformer,
|
||||
federationDomainInformer,
|
||||
pinnipedcontroller.MatchAnythingFilter(pinnipedcontroller.SingletonQueue()),
|
||||
controllerlib.InformerOption{},
|
||||
),
|
||||
@ -68,7 +68,7 @@ func NewFederationDomainWatcherController(
|
||||
|
||||
// Sync implements controllerlib.Syncer.
|
||||
func (c *federationDomainWatcherController) Sync(ctx controllerlib.Context) error {
|
||||
all, err := c.opcInformer.Lister().List(labels.Everything())
|
||||
federationDomains, err := c.federationDomainInformer.Lister().List(labels.Everything())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -89,8 +89,8 @@ func (c *federationDomainWatcherController) Sync(ctx controllerlib.Context) erro
|
||||
uniqueSecretNamesPerIssuerAddress := make(map[string]map[string]bool)
|
||||
issuerURLToHostnameKey := lowercaseHostWithoutPort
|
||||
|
||||
for _, opc := range all {
|
||||
issuerURL, err := url.Parse(opc.Spec.Issuer)
|
||||
for _, federationDomain := range federationDomains {
|
||||
issuerURL, err := url.Parse(federationDomain.Spec.Issuer)
|
||||
if err != nil {
|
||||
continue // Skip url parse errors because they will be validated again below.
|
||||
}
|
||||
@ -102,26 +102,26 @@ func (c *federationDomainWatcherController) Sync(ctx controllerlib.Context) erro
|
||||
setOfSecretNames = make(map[string]bool)
|
||||
uniqueSecretNamesPerIssuerAddress[issuerURLToHostnameKey(issuerURL)] = setOfSecretNames
|
||||
}
|
||||
if opc.Spec.TLS != nil {
|
||||
setOfSecretNames[opc.Spec.TLS.SecretName] = true
|
||||
if federationDomain.Spec.TLS != nil {
|
||||
setOfSecretNames[federationDomain.Spec.TLS.SecretName] = true
|
||||
}
|
||||
}
|
||||
|
||||
errs := multierror.New()
|
||||
|
||||
federationDomains := make([]*provider.FederationDomain, 0)
|
||||
for _, opc := range all {
|
||||
issuerURL, urlParseErr := url.Parse(opc.Spec.Issuer)
|
||||
federationDomainIssuers := make([]*provider.FederationDomainIssuer, 0)
|
||||
for _, federationDomain := range federationDomains {
|
||||
issuerURL, urlParseErr := url.Parse(federationDomain.Spec.Issuer)
|
||||
|
||||
// Skip url parse errors because they will be validated below.
|
||||
if urlParseErr == nil {
|
||||
if issuerCount := issuerCounts[issuerURLToIssuerKey(issuerURL)]; issuerCount > 1 {
|
||||
if err := c.updateStatus(
|
||||
ctx.Context,
|
||||
opc.Namespace,
|
||||
opc.Name,
|
||||
federationDomain.Namespace,
|
||||
federationDomain.Name,
|
||||
configv1alpha1.DuplicateFederationDomainStatusCondition,
|
||||
"Duplicate issuer: "+opc.Spec.Issuer,
|
||||
"Duplicate issuer: "+federationDomain.Spec.Issuer,
|
||||
); err != nil {
|
||||
errs.Add(fmt.Errorf("could not update status: %w", err))
|
||||
}
|
||||
@ -133,8 +133,8 @@ func (c *federationDomainWatcherController) Sync(ctx controllerlib.Context) erro
|
||||
if urlParseErr == nil && len(uniqueSecretNamesPerIssuerAddress[issuerURLToHostnameKey(issuerURL)]) > 1 {
|
||||
if err := c.updateStatus(
|
||||
ctx.Context,
|
||||
opc.Namespace,
|
||||
opc.Name,
|
||||
federationDomain.Namespace,
|
||||
federationDomain.Name,
|
||||
configv1alpha1.SameIssuerHostMustUseSameSecretFederationDomainStatusCondition,
|
||||
"Issuers with the same DNS hostname (address not including port) must use the same secretName: "+issuerURLToHostnameKey(issuerURL),
|
||||
); err != nil {
|
||||
@ -143,12 +143,12 @@ func (c *federationDomainWatcherController) Sync(ctx controllerlib.Context) erro
|
||||
continue
|
||||
}
|
||||
|
||||
federationDomain, err := provider.NewFederationDomain(opc.Spec.Issuer) // This validates the Issuer URL.
|
||||
federationDomainIssuer, err := provider.NewFederationDomainIssuer(federationDomain.Spec.Issuer) // This validates the Issuer URL.
|
||||
if err != nil {
|
||||
if err := c.updateStatus(
|
||||
ctx.Context,
|
||||
opc.Namespace,
|
||||
opc.Name,
|
||||
federationDomain.Namespace,
|
||||
federationDomain.Name,
|
||||
configv1alpha1.InvalidFederationDomainStatusCondition,
|
||||
"Invalid: "+err.Error(),
|
||||
); err != nil {
|
||||
@ -159,18 +159,19 @@ func (c *federationDomainWatcherController) Sync(ctx controllerlib.Context) erro
|
||||
|
||||
if err := c.updateStatus(
|
||||
ctx.Context,
|
||||
opc.Namespace,
|
||||
opc.Name,
|
||||
federationDomain.Namespace,
|
||||
federationDomain.Name,
|
||||
configv1alpha1.SuccessFederationDomainStatusCondition,
|
||||
"Provider successfully created",
|
||||
); err != nil {
|
||||
errs.Add(fmt.Errorf("could not update status: %w", err))
|
||||
continue
|
||||
}
|
||||
federationDomains = append(federationDomains, federationDomain)
|
||||
|
||||
federationDomainIssuers = append(federationDomainIssuers, federationDomainIssuer)
|
||||
}
|
||||
|
||||
c.providerSetter.SetProviders(federationDomains...)
|
||||
c.providerSetter.SetProviders(federationDomainIssuers...)
|
||||
|
||||
return errs.ErrOrNil()
|
||||
}
|
||||
@ -182,28 +183,28 @@ func (c *federationDomainWatcherController) updateStatus(
|
||||
message string,
|
||||
) error {
|
||||
return retry.RetryOnConflict(retry.DefaultRetry, func() error {
|
||||
opc, err := c.client.ConfigV1alpha1().FederationDomains(namespace).Get(ctx, name, metav1.GetOptions{})
|
||||
federationDomain, err := c.client.ConfigV1alpha1().FederationDomains(namespace).Get(ctx, name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("get failed: %w", err)
|
||||
}
|
||||
|
||||
if opc.Status.Status == status && opc.Status.Message == message {
|
||||
if federationDomain.Status.Status == status && federationDomain.Status.Message == message {
|
||||
return nil
|
||||
}
|
||||
|
||||
plog.Debug(
|
||||
"attempting status update",
|
||||
"federationdomainconfig",
|
||||
"federationdomain",
|
||||
klog.KRef(namespace, name),
|
||||
"status",
|
||||
status,
|
||||
"message",
|
||||
message,
|
||||
)
|
||||
opc.Status.Status = status
|
||||
opc.Status.Message = message
|
||||
opc.Status.LastUpdateTime = timePtr(metav1.NewTime(c.clock.Now()))
|
||||
_, err = c.client.ConfigV1alpha1().FederationDomains(namespace).Update(ctx, opc, metav1.UpdateOptions{})
|
||||
federationDomain.Status.Status = status
|
||||
federationDomain.Status.Message = message
|
||||
federationDomain.Status.LastUpdateTime = timePtr(metav1.NewTime(c.clock.Now()))
|
||||
_, err = c.client.ConfigV1alpha1().FederationDomains(namespace).Update(ctx, federationDomain, metav1.UpdateOptions{})
|
||||
return err
|
||||
})
|
||||
}
|
@ -40,15 +40,15 @@ func TestInformerFilters(t *testing.T) {
|
||||
it.Before(func() {
|
||||
r = require.New(t)
|
||||
observableWithInformerOption = testutil.NewObservableWithInformerOption()
|
||||
opcInformer := pinnipedinformers.NewSharedInformerFactoryWithOptions(nil, 0).Config().V1alpha1().FederationDomains()
|
||||
federationDomainInformer := pinnipedinformers.NewSharedInformerFactoryWithOptions(nil, 0).Config().V1alpha1().FederationDomains()
|
||||
_ = NewFederationDomainWatcherController(
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
opcInformer,
|
||||
federationDomainInformer,
|
||||
observableWithInformerOption.WithInformer, // make it possible to observe the behavior of the Filters
|
||||
)
|
||||
configMapInformerFilter = observableWithInformerOption.GetFilterForInformer(opcInformer)
|
||||
configMapInformerFilter = observableWithInformerOption.GetFilterForInformer(federationDomainInformer)
|
||||
})
|
||||
|
||||
when("watching FederationDomain objects", func() {
|
||||
@ -84,10 +84,10 @@ func TestInformerFilters(t *testing.T) {
|
||||
|
||||
type fakeProvidersSetter struct {
|
||||
SetProvidersWasCalled bool
|
||||
FederationDomainsReceived []*provider.FederationDomain
|
||||
FederationDomainsReceived []*provider.FederationDomainIssuer
|
||||
}
|
||||
|
||||
func (f *fakeProvidersSetter) SetProviders(federationDomains ...*provider.FederationDomain) {
|
||||
func (f *fakeProvidersSetter) SetProviders(federationDomains ...*provider.FederationDomainIssuer) {
|
||||
f.SetProvidersWasCalled = true
|
||||
f.FederationDomainsReceived = federationDomains
|
||||
}
|
||||
@ -99,8 +99,8 @@ func TestSync(t *testing.T) {
|
||||
var r *require.Assertions
|
||||
|
||||
var subject controllerlib.Controller
|
||||
var opcInformerClient *pinnipedfake.Clientset
|
||||
var opcInformers pinnipedinformers.SharedInformerFactory
|
||||
var federationDomainInformerClient *pinnipedfake.Clientset
|
||||
var federationDomainInformers pinnipedinformers.SharedInformerFactory
|
||||
var pinnipedAPIClient *pinnipedfake.Clientset
|
||||
var timeoutContext context.Context
|
||||
var timeoutContextCancel context.CancelFunc
|
||||
@ -117,7 +117,7 @@ func TestSync(t *testing.T) {
|
||||
providersSetter,
|
||||
clock.NewFakeClock(frozenNow),
|
||||
pinnipedAPIClient,
|
||||
opcInformers.Config().V1alpha1().FederationDomains(),
|
||||
federationDomainInformers.Config().V1alpha1().FederationDomains(),
|
||||
controllerlib.WithInformer,
|
||||
)
|
||||
|
||||
@ -132,7 +132,7 @@ func TestSync(t *testing.T) {
|
||||
}
|
||||
|
||||
// Must start informers before calling TestRunSynchronously()
|
||||
opcInformers.Start(timeoutContext.Done())
|
||||
federationDomainInformers.Start(timeoutContext.Done())
|
||||
controllerlib.TestRunSynchronously(t, subject)
|
||||
}
|
||||
|
||||
@ -144,8 +144,8 @@ func TestSync(t *testing.T) {
|
||||
|
||||
timeoutContext, timeoutContextCancel = context.WithTimeout(context.Background(), time.Second*3)
|
||||
|
||||
opcInformerClient = pinnipedfake.NewSimpleClientset()
|
||||
opcInformers = pinnipedinformers.NewSharedInformerFactory(opcInformerClient, 0)
|
||||
federationDomainInformerClient = pinnipedfake.NewSimpleClientset()
|
||||
federationDomainInformers = pinnipedinformers.NewSharedInformerFactory(federationDomainInformerClient, 0)
|
||||
pinnipedAPIClient = pinnipedfake.NewSimpleClientset()
|
||||
|
||||
federationDomainGVR = schema.GroupVersionResource{
|
||||
@ -171,14 +171,14 @@ func TestSync(t *testing.T) {
|
||||
Spec: v1alpha1.FederationDomainSpec{Issuer: "https://issuer1.com"},
|
||||
}
|
||||
r.NoError(pinnipedAPIClient.Tracker().Add(federationDomain1))
|
||||
r.NoError(opcInformerClient.Tracker().Add(federationDomain1))
|
||||
r.NoError(federationDomainInformerClient.Tracker().Add(federationDomain1))
|
||||
|
||||
federationDomain2 = &v1alpha1.FederationDomain{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "config2", Namespace: namespace},
|
||||
Spec: v1alpha1.FederationDomainSpec{Issuer: "https://issuer2.com"},
|
||||
}
|
||||
r.NoError(pinnipedAPIClient.Tracker().Add(federationDomain2))
|
||||
r.NoError(opcInformerClient.Tracker().Add(federationDomain2))
|
||||
r.NoError(federationDomainInformerClient.Tracker().Add(federationDomain2))
|
||||
})
|
||||
|
||||
it("calls the ProvidersSetter", func() {
|
||||
@ -186,15 +186,15 @@ func TestSync(t *testing.T) {
|
||||
err := controllerlib.TestSync(t, subject, *syncContext)
|
||||
r.NoError(err)
|
||||
|
||||
provider1, err := provider.NewFederationDomain(federationDomain1.Spec.Issuer)
|
||||
provider1, err := provider.NewFederationDomainIssuer(federationDomain1.Spec.Issuer)
|
||||
r.NoError(err)
|
||||
|
||||
provider2, err := provider.NewFederationDomain(federationDomain2.Spec.Issuer)
|
||||
provider2, err := provider.NewFederationDomainIssuer(federationDomain2.Spec.Issuer)
|
||||
r.NoError(err)
|
||||
|
||||
r.True(providersSetter.SetProvidersWasCalled)
|
||||
r.ElementsMatch(
|
||||
[]*provider.FederationDomain{
|
||||
[]*provider.FederationDomainIssuer{
|
||||
provider1,
|
||||
provider2,
|
||||
},
|
||||
@ -247,7 +247,7 @@ func TestSync(t *testing.T) {
|
||||
federationDomain1.Status.LastUpdateTime = timePtr(metav1.NewTime(frozenNow))
|
||||
|
||||
r.NoError(pinnipedAPIClient.Tracker().Update(federationDomainGVR, federationDomain1, federationDomain1.Namespace))
|
||||
r.NoError(opcInformerClient.Tracker().Update(federationDomainGVR, federationDomain1, federationDomain1.Namespace))
|
||||
r.NoError(federationDomainInformerClient.Tracker().Update(federationDomainGVR, federationDomain1, federationDomain1.Namespace))
|
||||
})
|
||||
|
||||
it("only updates the out-of-date FederationDomain", func() {
|
||||
@ -284,15 +284,15 @@ func TestSync(t *testing.T) {
|
||||
err := controllerlib.TestSync(t, subject, *syncContext)
|
||||
r.NoError(err)
|
||||
|
||||
provider1, err := provider.NewFederationDomain(federationDomain1.Spec.Issuer)
|
||||
provider1, err := provider.NewFederationDomainIssuer(federationDomain1.Spec.Issuer)
|
||||
r.NoError(err)
|
||||
|
||||
provider2, err := provider.NewFederationDomain(federationDomain2.Spec.Issuer)
|
||||
provider2, err := provider.NewFederationDomainIssuer(federationDomain2.Spec.Issuer)
|
||||
r.NoError(err)
|
||||
|
||||
r.True(providersSetter.SetProvidersWasCalled)
|
||||
r.ElementsMatch(
|
||||
[]*provider.FederationDomain{
|
||||
[]*provider.FederationDomainIssuer{
|
||||
provider1,
|
||||
provider2,
|
||||
},
|
||||
@ -322,10 +322,10 @@ func TestSync(t *testing.T) {
|
||||
err := controllerlib.TestSync(t, subject, *syncContext)
|
||||
r.EqualError(err, "1 error(s):\n- could not update status: some update error")
|
||||
|
||||
provider1, err := provider.NewFederationDomain(federationDomain1.Spec.Issuer)
|
||||
provider1, err := provider.NewFederationDomainIssuer(federationDomain1.Spec.Issuer)
|
||||
r.NoError(err)
|
||||
|
||||
provider2, err := provider.NewFederationDomain(federationDomain2.Spec.Issuer)
|
||||
provider2, err := provider.NewFederationDomainIssuer(federationDomain2.Spec.Issuer)
|
||||
r.NoError(err)
|
||||
|
||||
r.True(providersSetter.SetProvidersWasCalled)
|
||||
@ -387,7 +387,7 @@ func TestSync(t *testing.T) {
|
||||
Spec: v1alpha1.FederationDomainSpec{Issuer: "https://issuer.com"},
|
||||
}
|
||||
r.NoError(pinnipedAPIClient.Tracker().Add(federationDomain))
|
||||
r.NoError(opcInformerClient.Tracker().Add(federationDomain))
|
||||
r.NoError(federationDomainInformerClient.Tracker().Add(federationDomain))
|
||||
})
|
||||
|
||||
when("there is a conflict while updating an FederationDomain", func() {
|
||||
@ -521,14 +521,14 @@ func TestSync(t *testing.T) {
|
||||
Spec: v1alpha1.FederationDomainSpec{Issuer: "https://valid-issuer.com"},
|
||||
}
|
||||
r.NoError(pinnipedAPIClient.Tracker().Add(validFederationDomain))
|
||||
r.NoError(opcInformerClient.Tracker().Add(validFederationDomain))
|
||||
r.NoError(federationDomainInformerClient.Tracker().Add(validFederationDomain))
|
||||
|
||||
invalidFederationDomain = &v1alpha1.FederationDomain{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "invalid-config", Namespace: namespace},
|
||||
Spec: v1alpha1.FederationDomainSpec{Issuer: "https://invalid-issuer.com?some=query"},
|
||||
}
|
||||
r.NoError(pinnipedAPIClient.Tracker().Add(invalidFederationDomain))
|
||||
r.NoError(opcInformerClient.Tracker().Add(invalidFederationDomain))
|
||||
r.NoError(federationDomainInformerClient.Tracker().Add(invalidFederationDomain))
|
||||
})
|
||||
|
||||
it("calls the ProvidersSetter with the valid provider", func() {
|
||||
@ -536,12 +536,12 @@ func TestSync(t *testing.T) {
|
||||
err := controllerlib.TestSync(t, subject, *syncContext)
|
||||
r.NoError(err)
|
||||
|
||||
validProvider, err := provider.NewFederationDomain(validFederationDomain.Spec.Issuer)
|
||||
validProvider, err := provider.NewFederationDomainIssuer(validFederationDomain.Spec.Issuer)
|
||||
r.NoError(err)
|
||||
|
||||
r.True(providersSetter.SetProvidersWasCalled)
|
||||
r.Equal(
|
||||
[]*provider.FederationDomain{
|
||||
[]*provider.FederationDomainIssuer{
|
||||
validProvider,
|
||||
},
|
||||
providersSetter.FederationDomainsReceived,
|
||||
@ -593,8 +593,8 @@ func TestSync(t *testing.T) {
|
||||
"federationdomains",
|
||||
func(action coretesting.Action) (bool, runtime.Object, error) {
|
||||
updateAction := action.(coretesting.UpdateActionImpl)
|
||||
opc := updateAction.Object.(*v1alpha1.FederationDomain)
|
||||
if opc.Name == validFederationDomain.Name {
|
||||
federationDomain := updateAction.Object.(*v1alpha1.FederationDomain)
|
||||
if federationDomain.Name == validFederationDomain.Name {
|
||||
return true, nil, nil
|
||||
}
|
||||
|
||||
@ -608,12 +608,12 @@ func TestSync(t *testing.T) {
|
||||
err := controllerlib.TestSync(t, subject, *syncContext)
|
||||
r.EqualError(err, "1 error(s):\n- could not update status: some update error")
|
||||
|
||||
validProvider, err := provider.NewFederationDomain(validFederationDomain.Spec.Issuer)
|
||||
validProvider, err := provider.NewFederationDomainIssuer(validFederationDomain.Spec.Issuer)
|
||||
r.NoError(err)
|
||||
|
||||
r.True(providersSetter.SetProvidersWasCalled)
|
||||
r.Equal(
|
||||
[]*provider.FederationDomain{
|
||||
[]*provider.FederationDomainIssuer{
|
||||
validProvider,
|
||||
},
|
||||
providersSetter.FederationDomainsReceived,
|
||||
@ -675,20 +675,20 @@ func TestSync(t *testing.T) {
|
||||
Spec: v1alpha1.FederationDomainSpec{Issuer: "https://iSSueR-duPlicAte.cOm/a"},
|
||||
}
|
||||
r.NoError(pinnipedAPIClient.Tracker().Add(federationDomainDuplicate1))
|
||||
r.NoError(opcInformerClient.Tracker().Add(federationDomainDuplicate1))
|
||||
r.NoError(federationDomainInformerClient.Tracker().Add(federationDomainDuplicate1))
|
||||
federationDomainDuplicate2 = &v1alpha1.FederationDomain{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "duplicate2", Namespace: namespace},
|
||||
Spec: v1alpha1.FederationDomainSpec{Issuer: "https://issuer-duplicate.com/a"},
|
||||
}
|
||||
r.NoError(pinnipedAPIClient.Tracker().Add(federationDomainDuplicate2))
|
||||
r.NoError(opcInformerClient.Tracker().Add(federationDomainDuplicate2))
|
||||
r.NoError(federationDomainInformerClient.Tracker().Add(federationDomainDuplicate2))
|
||||
|
||||
federationDomain = &v1alpha1.FederationDomain{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "not-duplicate", Namespace: namespace},
|
||||
Spec: v1alpha1.FederationDomainSpec{Issuer: "https://issuer-duplicate.com/A"}, // different path
|
||||
}
|
||||
r.NoError(pinnipedAPIClient.Tracker().Add(federationDomain))
|
||||
r.NoError(opcInformerClient.Tracker().Add(federationDomain))
|
||||
r.NoError(federationDomainInformerClient.Tracker().Add(federationDomain))
|
||||
})
|
||||
|
||||
it("calls the ProvidersSetter with the non-duplicate", func() {
|
||||
@ -696,12 +696,12 @@ func TestSync(t *testing.T) {
|
||||
err := controllerlib.TestSync(t, subject, *syncContext)
|
||||
r.NoError(err)
|
||||
|
||||
nonDuplicateProvider, err := provider.NewFederationDomain(federationDomain.Spec.Issuer)
|
||||
nonDuplicateProvider, err := provider.NewFederationDomainIssuer(federationDomain.Spec.Issuer)
|
||||
r.NoError(err)
|
||||
|
||||
r.True(providersSetter.SetProvidersWasCalled)
|
||||
r.Equal(
|
||||
[]*provider.FederationDomain{
|
||||
[]*provider.FederationDomainIssuer{
|
||||
nonDuplicateProvider,
|
||||
},
|
||||
providersSetter.FederationDomainsReceived,
|
||||
@ -824,7 +824,7 @@ func TestSync(t *testing.T) {
|
||||
},
|
||||
}
|
||||
r.NoError(pinnipedAPIClient.Tracker().Add(federationDomainSameIssuerAddress1))
|
||||
r.NoError(opcInformerClient.Tracker().Add(federationDomainSameIssuerAddress1))
|
||||
r.NoError(federationDomainInformerClient.Tracker().Add(federationDomainSameIssuerAddress1))
|
||||
federationDomainSameIssuerAddress2 = &v1alpha1.FederationDomain{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "provider2", Namespace: namespace},
|
||||
Spec: v1alpha1.FederationDomainSpec{
|
||||
@ -835,7 +835,7 @@ func TestSync(t *testing.T) {
|
||||
},
|
||||
}
|
||||
r.NoError(pinnipedAPIClient.Tracker().Add(federationDomainSameIssuerAddress2))
|
||||
r.NoError(opcInformerClient.Tracker().Add(federationDomainSameIssuerAddress2))
|
||||
r.NoError(federationDomainInformerClient.Tracker().Add(federationDomainSameIssuerAddress2))
|
||||
|
||||
federationDomainDifferentIssuerAddress = &v1alpha1.FederationDomain{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "differentIssuerAddressProvider", Namespace: namespace},
|
||||
@ -845,7 +845,7 @@ func TestSync(t *testing.T) {
|
||||
},
|
||||
}
|
||||
r.NoError(pinnipedAPIClient.Tracker().Add(federationDomainDifferentIssuerAddress))
|
||||
r.NoError(opcInformerClient.Tracker().Add(federationDomainDifferentIssuerAddress))
|
||||
r.NoError(federationDomainInformerClient.Tracker().Add(federationDomainDifferentIssuerAddress))
|
||||
|
||||
// Also add one with a URL that cannot be parsed to make sure that the error handling
|
||||
// for the duplicate issuers and secret names are not confused by invalid URLs.
|
||||
@ -860,7 +860,7 @@ func TestSync(t *testing.T) {
|
||||
},
|
||||
}
|
||||
r.NoError(pinnipedAPIClient.Tracker().Add(federationDomainWithInvalidIssuerURL))
|
||||
r.NoError(opcInformerClient.Tracker().Add(federationDomainWithInvalidIssuerURL))
|
||||
r.NoError(federationDomainInformerClient.Tracker().Add(federationDomainWithInvalidIssuerURL))
|
||||
})
|
||||
|
||||
it("calls the ProvidersSetter with the non-duplicate", func() {
|
||||
@ -868,12 +868,12 @@ func TestSync(t *testing.T) {
|
||||
err := controllerlib.TestSync(t, subject, *syncContext)
|
||||
r.NoError(err)
|
||||
|
||||
nonDuplicateProvider, err := provider.NewFederationDomain(federationDomainDifferentIssuerAddress.Spec.Issuer)
|
||||
nonDuplicateProvider, err := provider.NewFederationDomainIssuer(federationDomainDifferentIssuerAddress.Spec.Issuer)
|
||||
r.NoError(err)
|
||||
|
||||
r.True(providersSetter.SetProvidersWasCalled)
|
||||
r.Equal(
|
||||
[]*provider.FederationDomain{
|
||||
[]*provider.FederationDomainIssuer{
|
||||
nonDuplicateProvider,
|
||||
},
|
||||
providersSetter.FederationDomainsReceived,
|
@ -24,13 +24,17 @@ import (
|
||||
"go.pinniped.dev/internal/plog"
|
||||
)
|
||||
|
||||
const (
|
||||
federationDomainKind = "FederationDomain"
|
||||
)
|
||||
|
||||
type federationDomainSecretsController struct {
|
||||
secretHelper SecretHelper
|
||||
secretRefFunc func(domain *configv1alpha1.FederationDomain) *corev1.LocalObjectReference
|
||||
kubeClient kubernetes.Interface
|
||||
pinnipedClient pinnipedclientset.Interface
|
||||
opcInformer configinformers.FederationDomainInformer
|
||||
secretInformer corev1informers.SecretInformer
|
||||
secretHelper SecretHelper
|
||||
secretRefFunc func(domain *configv1alpha1.FederationDomain) *corev1.LocalObjectReference
|
||||
kubeClient kubernetes.Interface
|
||||
pinnipedClient pinnipedclientset.Interface
|
||||
federationDomainInformer configinformers.FederationDomainInformer
|
||||
secretInformer corev1informers.SecretInformer
|
||||
}
|
||||
|
||||
// NewFederationDomainSecretsController returns a controllerlib.Controller that ensures a child Secret
|
||||
@ -42,27 +46,27 @@ func NewFederationDomainSecretsController(
|
||||
kubeClient kubernetes.Interface,
|
||||
pinnipedClient pinnipedclientset.Interface,
|
||||
secretInformer corev1informers.SecretInformer,
|
||||
opcInformer configinformers.FederationDomainInformer,
|
||||
federationDomainInformer configinformers.FederationDomainInformer,
|
||||
withInformer pinnipedcontroller.WithInformerOptionFunc,
|
||||
) controllerlib.Controller {
|
||||
return controllerlib.New(
|
||||
controllerlib.Config{
|
||||
Name: fmt.Sprintf("%s%s", secretHelper.NamePrefix(), "controller"),
|
||||
Syncer: &federationDomainSecretsController{
|
||||
secretHelper: secretHelper,
|
||||
secretRefFunc: secretRefFunc,
|
||||
kubeClient: kubeClient,
|
||||
pinnipedClient: pinnipedClient,
|
||||
secretInformer: secretInformer,
|
||||
opcInformer: opcInformer,
|
||||
secretHelper: secretHelper,
|
||||
secretRefFunc: secretRefFunc,
|
||||
kubeClient: kubeClient,
|
||||
pinnipedClient: pinnipedClient,
|
||||
secretInformer: secretInformer,
|
||||
federationDomainInformer: federationDomainInformer,
|
||||
},
|
||||
},
|
||||
// We want to be notified when a OPC's secret gets updated or deleted. When this happens, we
|
||||
// should get notified via the corresponding OPC key.
|
||||
// We want to be notified when a FederationDomain's secret gets updated or deleted. When this happens, we
|
||||
// should get notified via the corresponding FederationDomain key.
|
||||
withInformer(
|
||||
secretInformer,
|
||||
pinnipedcontroller.SimpleFilter(isOPControllee, func(obj metav1.Object) controllerlib.Key {
|
||||
if isOPControllee(obj) {
|
||||
pinnipedcontroller.SimpleFilter(isFederationDomainControllee, func(obj metav1.Object) controllerlib.Key {
|
||||
if isFederationDomainControllee(obj) {
|
||||
controller := metav1.GetControllerOf(obj)
|
||||
return controllerlib.Key{
|
||||
Name: controller.Name,
|
||||
@ -73,9 +77,9 @@ func NewFederationDomainSecretsController(
|
||||
}),
|
||||
controllerlib.InformerOption{},
|
||||
),
|
||||
// We want to be notified when anything happens to an OPC.
|
||||
// We want to be notified when anything happens to an FederationDomain.
|
||||
withInformer(
|
||||
opcInformer,
|
||||
federationDomainInformer,
|
||||
pinnipedcontroller.MatchAnythingFilter(nil), // nil parent func is fine because each event is distinct
|
||||
controllerlib.InformerOption{},
|
||||
),
|
||||
@ -83,7 +87,7 @@ func NewFederationDomainSecretsController(
|
||||
}
|
||||
|
||||
func (c *federationDomainSecretsController) Sync(ctx controllerlib.Context) error {
|
||||
op, err := c.opcInformer.Lister().FederationDomains(ctx.Key.Namespace).Get(ctx.Key.Name)
|
||||
federationDomain, err := c.federationDomainInformer.Lister().FederationDomains(ctx.Key.Namespace).Get(ctx.Key.Name)
|
||||
notFound := k8serrors.IsNotFound(err)
|
||||
if err != nil && !notFound {
|
||||
return fmt.Errorf(
|
||||
@ -95,8 +99,8 @@ func (c *federationDomainSecretsController) Sync(ctx controllerlib.Context) erro
|
||||
}
|
||||
|
||||
if notFound {
|
||||
// The corresponding secret to this OP should have been garbage collected since it should have
|
||||
// had this OP as its owner.
|
||||
// The corresponding secret to this FederationDomain should have been garbage collected since it should have
|
||||
// had this FederationDomain as its owner.
|
||||
plog.Debug(
|
||||
"federationdomain deleted",
|
||||
"federationdomain",
|
||||
@ -105,13 +109,13 @@ func (c *federationDomainSecretsController) Sync(ctx controllerlib.Context) erro
|
||||
return nil
|
||||
}
|
||||
|
||||
op = op.DeepCopy()
|
||||
newSecret, err := c.secretHelper.Generate(op)
|
||||
federationDomain = federationDomain.DeepCopy()
|
||||
newSecret, err := c.secretHelper.Generate(federationDomain)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to generate secret: %w", err)
|
||||
}
|
||||
|
||||
secretNeedsUpdate, existingSecret, err := c.secretNeedsUpdate(op, newSecret.Name)
|
||||
secretNeedsUpdate, existingSecret, err := c.secretNeedsUpdate(federationDomain, newSecret.Name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to determine secret status: %w", err)
|
||||
}
|
||||
@ -120,44 +124,44 @@ func (c *federationDomainSecretsController) Sync(ctx controllerlib.Context) erro
|
||||
plog.Debug(
|
||||
"secret is up to date",
|
||||
"federationdomain",
|
||||
klog.KObj(op),
|
||||
klog.KObj(federationDomain),
|
||||
"secret",
|
||||
klog.KObj(existingSecret),
|
||||
)
|
||||
|
||||
op = c.secretHelper.ObserveActiveSecretAndUpdateParentFederationDomain(op, existingSecret)
|
||||
if err := c.updateFederationDomain(ctx.Context, op); err != nil {
|
||||
federationDomain = c.secretHelper.ObserveActiveSecretAndUpdateParentFederationDomain(federationDomain, existingSecret)
|
||||
if err := c.updateFederationDomain(ctx.Context, federationDomain); err != nil {
|
||||
return fmt.Errorf("failed to update federationdomain: %w", err)
|
||||
}
|
||||
plog.Debug("updated federationdomain", "federationdomain", klog.KObj(op), "secret", klog.KObj(newSecret))
|
||||
plog.Debug("updated federationdomain", "federationdomain", klog.KObj(federationDomain), "secret", klog.KObj(newSecret))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// If the OP does not have a secret associated with it, that secret does not exist, or the secret
|
||||
// If the FederationDomain does not have a secret associated with it, that secret does not exist, or the secret
|
||||
// is invalid, we will create a new secret.
|
||||
if err := c.createOrUpdateSecret(ctx.Context, op, &newSecret); err != nil {
|
||||
if err := c.createOrUpdateSecret(ctx.Context, federationDomain, &newSecret); err != nil {
|
||||
return fmt.Errorf("failed to create or update secret: %w", err)
|
||||
}
|
||||
plog.Debug("created/updated secret", "federationdomain", klog.KObj(op), "secret", klog.KObj(newSecret))
|
||||
plog.Debug("created/updated secret", "federationdomain", klog.KObj(federationDomain), "secret", klog.KObj(newSecret))
|
||||
|
||||
op = c.secretHelper.ObserveActiveSecretAndUpdateParentFederationDomain(op, newSecret)
|
||||
if err := c.updateFederationDomain(ctx.Context, op); err != nil {
|
||||
federationDomain = c.secretHelper.ObserveActiveSecretAndUpdateParentFederationDomain(federationDomain, newSecret)
|
||||
if err := c.updateFederationDomain(ctx.Context, federationDomain); err != nil {
|
||||
return fmt.Errorf("failed to update federationdomain: %w", err)
|
||||
}
|
||||
plog.Debug("updated federationdomain", "federationdomain", klog.KObj(op), "secret", klog.KObj(newSecret))
|
||||
plog.Debug("updated federationdomain", "federationdomain", klog.KObj(federationDomain), "secret", klog.KObj(newSecret))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// secretNeedsUpdate returns whether or not the Secret, with name secretName, for FederationDomain op
|
||||
// secretNeedsUpdate returns whether or not the Secret, with name secretName, for the federationDomain param
|
||||
// needs to be updated. It returns the existing secret as its second argument.
|
||||
func (c *federationDomainSecretsController) secretNeedsUpdate(
|
||||
op *configv1alpha1.FederationDomain,
|
||||
federationDomain *configv1alpha1.FederationDomain,
|
||||
secretName string,
|
||||
) (bool, *corev1.Secret, error) {
|
||||
// This OPC says it has a secret associated with it. Let's try to get it from the cache.
|
||||
secret, err := c.secretInformer.Lister().Secrets(op.Namespace).Get(secretName)
|
||||
// This FederationDomain says it has a secret associated with it. Let's try to get it from the cache.
|
||||
secret, err := c.secretInformer.Lister().Secrets(federationDomain.Namespace).Get(secretName)
|
||||
notFound := k8serrors.IsNotFound(err)
|
||||
if err != nil && !notFound {
|
||||
return false, nil, fmt.Errorf("cannot get secret: %w", err)
|
||||
@ -167,7 +171,7 @@ func (c *federationDomainSecretsController) secretNeedsUpdate(
|
||||
return true, nil, nil
|
||||
}
|
||||
|
||||
if !c.secretHelper.IsValid(op, secret) {
|
||||
if !c.secretHelper.IsValid(federationDomain, secret) {
|
||||
// If this secret is invalid, we need to generate a new one.
|
||||
return true, secret, nil
|
||||
}
|
||||
@ -177,7 +181,7 @@ func (c *federationDomainSecretsController) secretNeedsUpdate(
|
||||
|
||||
func (c *federationDomainSecretsController) createOrUpdateSecret(
|
||||
ctx context.Context,
|
||||
op *configv1alpha1.FederationDomain,
|
||||
federationDomain *configv1alpha1.FederationDomain,
|
||||
newSecret **corev1.Secret,
|
||||
) error {
|
||||
secretClient := c.kubeClient.CoreV1().Secrets((*newSecret).Namespace)
|
||||
@ -198,7 +202,7 @@ func (c *federationDomainSecretsController) createOrUpdateSecret(
|
||||
}
|
||||
|
||||
// New secret already exists, so ensure it is up to date.
|
||||
if c.secretHelper.IsValid(op, oldSecret) {
|
||||
if c.secretHelper.IsValid(federationDomain, oldSecret) {
|
||||
// If the secret already has valid a valid Secret, then we are good to go and we don't need an
|
||||
// update.
|
||||
*newSecret = oldSecret
|
||||
@ -216,23 +220,31 @@ func (c *federationDomainSecretsController) createOrUpdateSecret(
|
||||
|
||||
func (c *federationDomainSecretsController) updateFederationDomain(
|
||||
ctx context.Context,
|
||||
newOP *configv1alpha1.FederationDomain,
|
||||
newFederationDomain *configv1alpha1.FederationDomain,
|
||||
) error {
|
||||
opcClient := c.pinnipedClient.ConfigV1alpha1().FederationDomains(newOP.Namespace)
|
||||
federationDomainClient := c.pinnipedClient.ConfigV1alpha1().FederationDomains(newFederationDomain.Namespace)
|
||||
return retry.RetryOnConflict(retry.DefaultRetry, func() error {
|
||||
oldOP, err := opcClient.Get(ctx, newOP.Name, metav1.GetOptions{})
|
||||
oldFederationDomain, err := federationDomainClient.Get(ctx, newFederationDomain.Name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get federationdomain %s/%s: %w", newOP.Namespace, newOP.Name, err)
|
||||
return fmt.Errorf("failed to get federationdomain %s/%s: %w", newFederationDomain.Namespace, newFederationDomain.Name, err)
|
||||
}
|
||||
|
||||
oldOPSecretRef := c.secretRefFunc(oldOP)
|
||||
newOPSecretRef := c.secretRefFunc(newOP)
|
||||
if reflect.DeepEqual(oldOPSecretRef, newOPSecretRef) {
|
||||
oldFederationDomainSecretRef := c.secretRefFunc(oldFederationDomain)
|
||||
newFederationDomainSecretRef := c.secretRefFunc(newFederationDomain)
|
||||
if reflect.DeepEqual(oldFederationDomainSecretRef, newFederationDomainSecretRef) {
|
||||
return nil
|
||||
}
|
||||
|
||||
*oldOPSecretRef = *newOPSecretRef
|
||||
_, err = opcClient.Update(ctx, oldOP, metav1.UpdateOptions{})
|
||||
*oldFederationDomainSecretRef = *newFederationDomainSecretRef
|
||||
_, err = federationDomainClient.Update(ctx, oldFederationDomain, metav1.UpdateOptions{})
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
// isFederationDomainControllee returns whether the provided obj is controlled by an FederationDomain.
|
||||
func isFederationDomainControllee(obj metav1.Object) bool {
|
||||
controller := metav1.GetControllerOf(obj)
|
||||
return controller != nil &&
|
||||
controller.APIVersion == configv1alpha1.SchemeGroupVersion.String() &&
|
||||
controller.Kind == federationDomainKind
|
||||
}
|
@ -150,7 +150,7 @@ func TestFederationDomainControllerFilterSecret(t *testing.T) {
|
||||
kubernetesfake.NewSimpleClientset(),
|
||||
0,
|
||||
).Core().V1().Secrets()
|
||||
opcInformer := pinnipedinformers.NewSharedInformerFactory(
|
||||
federationDomainInformer := pinnipedinformers.NewSharedInformerFactory(
|
||||
pinnipedfake.NewSimpleClientset(),
|
||||
0,
|
||||
).Config().V1alpha1().FederationDomains()
|
||||
@ -161,7 +161,7 @@ func TestFederationDomainControllerFilterSecret(t *testing.T) {
|
||||
nil, // kubeClient, not needed
|
||||
nil, // pinnipedClient, not needed
|
||||
secretInformer,
|
||||
opcInformer,
|
||||
federationDomainInformer,
|
||||
withInformer.WithInformer,
|
||||
)
|
||||
|
||||
@ -176,24 +176,24 @@ func TestFederationDomainControllerFilterSecret(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewFederationDomainSecretsControllerFilterOPC(t *testing.T) {
|
||||
func TestNewFederationDomainSecretsControllerFilterFederationDomain(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
opc configv1alpha1.FederationDomain
|
||||
wantAdd bool
|
||||
wantUpdate bool
|
||||
wantDelete bool
|
||||
wantParent controllerlib.Key
|
||||
name string
|
||||
federationDomain configv1alpha1.FederationDomain
|
||||
wantAdd bool
|
||||
wantUpdate bool
|
||||
wantDelete bool
|
||||
wantParent controllerlib.Key
|
||||
}{
|
||||
{
|
||||
name: "anything goes",
|
||||
opc: configv1alpha1.FederationDomain{},
|
||||
wantAdd: true,
|
||||
wantUpdate: true,
|
||||
wantDelete: true,
|
||||
wantParent: controllerlib.Key{},
|
||||
name: "anything goes",
|
||||
federationDomain: configv1alpha1.FederationDomain{},
|
||||
wantAdd: true,
|
||||
wantUpdate: true,
|
||||
wantDelete: true,
|
||||
wantParent: controllerlib.Key{},
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
@ -210,7 +210,7 @@ func TestNewFederationDomainSecretsControllerFilterOPC(t *testing.T) {
|
||||
kubernetesfake.NewSimpleClientset(),
|
||||
0,
|
||||
).Core().V1().Secrets()
|
||||
opcInformer := pinnipedinformers.NewSharedInformerFactory(
|
||||
federationDomainInformer := pinnipedinformers.NewSharedInformerFactory(
|
||||
pinnipedfake.NewSimpleClientset(),
|
||||
0,
|
||||
).Config().V1alpha1().FederationDomains()
|
||||
@ -221,17 +221,17 @@ func TestNewFederationDomainSecretsControllerFilterOPC(t *testing.T) {
|
||||
nil, // kubeClient, not needed
|
||||
nil, // pinnipedClient, not needed
|
||||
secretInformer,
|
||||
opcInformer,
|
||||
federationDomainInformer,
|
||||
withInformer.WithInformer,
|
||||
)
|
||||
|
||||
unrelated := configv1alpha1.FederationDomain{}
|
||||
filter := withInformer.GetFilterForInformer(opcInformer)
|
||||
require.Equal(t, test.wantAdd, filter.Add(&test.opc))
|
||||
require.Equal(t, test.wantUpdate, filter.Update(&unrelated, &test.opc))
|
||||
require.Equal(t, test.wantUpdate, filter.Update(&test.opc, &unrelated))
|
||||
require.Equal(t, test.wantDelete, filter.Delete(&test.opc))
|
||||
require.Equal(t, test.wantParent, filter.Parent(&test.opc))
|
||||
filter := withInformer.GetFilterForInformer(federationDomainInformer)
|
||||
require.Equal(t, test.wantAdd, filter.Add(&test.federationDomain))
|
||||
require.Equal(t, test.wantUpdate, filter.Update(&unrelated, &test.federationDomain))
|
||||
require.Equal(t, test.wantUpdate, filter.Update(&test.federationDomain, &unrelated))
|
||||
require.Equal(t, test.wantDelete, filter.Delete(&test.federationDomain))
|
||||
require.Equal(t, test.wantParent, filter.Parent(&test.federationDomain))
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -242,14 +242,14 @@ func TestFederationDomainSecretsControllerSync(t *testing.T) {
|
||||
const (
|
||||
namespace = "some-namespace"
|
||||
|
||||
opName = "op-name"
|
||||
opUID = "op-uid"
|
||||
federationDomainName = "federationDomain-name"
|
||||
federationDomainUID = "federationDomain-uid"
|
||||
|
||||
secretName = "secret-name"
|
||||
secretUID = "secret-uid"
|
||||
)
|
||||
|
||||
opGVR := schema.GroupVersionResource{
|
||||
federationDomainGVR := schema.GroupVersionResource{
|
||||
Group: configv1alpha1.SchemeGroupVersion.Group,
|
||||
Version: configv1alpha1.SchemeGroupVersion.Version,
|
||||
Resource: "federationdomains",
|
||||
@ -261,11 +261,11 @@ func TestFederationDomainSecretsControllerSync(t *testing.T) {
|
||||
Resource: "secrets",
|
||||
}
|
||||
|
||||
goodOP := &configv1alpha1.FederationDomain{
|
||||
goodFederationDomain := &configv1alpha1.FederationDomain{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: opName,
|
||||
Name: federationDomainName,
|
||||
Namespace: namespace,
|
||||
UID: opUID,
|
||||
UID: federationDomainUID,
|
||||
},
|
||||
}
|
||||
|
||||
@ -276,10 +276,10 @@ func TestFederationDomainSecretsControllerSync(t *testing.T) {
|
||||
UID: secretUID,
|
||||
OwnerReferences: []metav1.OwnerReference{
|
||||
{
|
||||
APIVersion: opGVR.GroupVersion().String(),
|
||||
APIVersion: federationDomainGVR.GroupVersion().String(),
|
||||
Kind: "FederationDomain",
|
||||
Name: opName,
|
||||
UID: opUID,
|
||||
Name: federationDomainName,
|
||||
UID: federationDomainUID,
|
||||
BlockOwnerDeletion: boolPtr(true),
|
||||
Controller: boolPtr(true),
|
||||
},
|
||||
@ -295,14 +295,14 @@ func TestFederationDomainSecretsControllerSync(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
goodOPWithTokenSigningKey := goodOP.DeepCopy()
|
||||
goodOPWithTokenSigningKey.Status.Secrets.TokenSigningKey.Name = goodSecret.Name
|
||||
goodFederationDomainWithTokenSigningKey := goodFederationDomain.DeepCopy()
|
||||
goodFederationDomainWithTokenSigningKey.Status.Secrets.TokenSigningKey.Name = goodSecret.Name
|
||||
|
||||
goodOPWithJWKS := goodOP.DeepCopy()
|
||||
goodOPWithJWKS.Status.Secrets.JWKS.Name = "some-jwks-key"
|
||||
goodFederationDomainWithJWKS := goodFederationDomain.DeepCopy()
|
||||
goodFederationDomainWithJWKS.Status.Secrets.JWKS.Name = "some-jwks-key"
|
||||
|
||||
goodOPWithJWKSAndTokenSigningKey := goodOPWithJWKS.DeepCopy()
|
||||
goodOPWithJWKSAndTokenSigningKey.Status.Secrets.TokenSigningKey = goodOPWithTokenSigningKey.Status.Secrets.TokenSigningKey
|
||||
goodFederationDomainWithJWKSAndTokenSigningKey := goodFederationDomainWithJWKS.DeepCopy()
|
||||
goodFederationDomainWithJWKSAndTokenSigningKey.Status.Secrets.TokenSigningKey = goodFederationDomainWithTokenSigningKey.Status.Secrets.TokenSigningKey
|
||||
|
||||
invalidSecret := &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
@ -311,10 +311,10 @@ func TestFederationDomainSecretsControllerSync(t *testing.T) {
|
||||
UID: secretUID,
|
||||
OwnerReferences: []metav1.OwnerReference{
|
||||
{
|
||||
APIVersion: opGVR.GroupVersion().String(),
|
||||
APIVersion: federationDomainGVR.GroupVersion().String(),
|
||||
Kind: "FederationDomain",
|
||||
Name: opName,
|
||||
UID: opUID,
|
||||
Name: federationDomainName,
|
||||
UID: federationDomainUID,
|
||||
BlockOwnerDeletion: boolPtr(true),
|
||||
Controller: boolPtr(true),
|
||||
},
|
||||
@ -323,39 +323,39 @@ func TestFederationDomainSecretsControllerSync(t *testing.T) {
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
storage func(**configv1alpha1.FederationDomain, **corev1.Secret)
|
||||
client func(*pinnipedfake.Clientset, *kubernetesfake.Clientset)
|
||||
secretHelper func(*mocksecrethelper.MockSecretHelper)
|
||||
wantOPActions []kubetesting.Action
|
||||
wantSecretActions []kubetesting.Action
|
||||
wantError string
|
||||
name string
|
||||
storage func(**configv1alpha1.FederationDomain, **corev1.Secret)
|
||||
client func(*pinnipedfake.Clientset, *kubernetesfake.Clientset)
|
||||
secretHelper func(*mocksecrethelper.MockSecretHelper)
|
||||
wantFederationDomainActions []kubetesting.Action
|
||||
wantSecretActions []kubetesting.Action
|
||||
wantError string
|
||||
}{
|
||||
{
|
||||
name: "FederationDomain does not exist and secret does not exist",
|
||||
storage: func(op **configv1alpha1.FederationDomain, s **corev1.Secret) {
|
||||
*op = nil
|
||||
storage: func(federationDomain **configv1alpha1.FederationDomain, s **corev1.Secret) {
|
||||
*federationDomain = nil
|
||||
*s = nil
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "FederationDomain does not exist and secret exists",
|
||||
storage: func(op **configv1alpha1.FederationDomain, s **corev1.Secret) {
|
||||
*op = nil
|
||||
storage: func(federationDomain **configv1alpha1.FederationDomain, s **corev1.Secret) {
|
||||
*federationDomain = nil
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "FederationDomain exists and secret does not exist",
|
||||
storage: func(op **configv1alpha1.FederationDomain, s **corev1.Secret) {
|
||||
storage: func(federationDomain **configv1alpha1.FederationDomain, s **corev1.Secret) {
|
||||
*s = nil
|
||||
},
|
||||
secretHelper: func(secretHelper *mocksecrethelper.MockSecretHelper) {
|
||||
secretHelper.EXPECT().Generate(goodOP).Times(1).Return(goodSecret, nil)
|
||||
secretHelper.EXPECT().ObserveActiveSecretAndUpdateParentFederationDomain(goodOP, goodSecret).Times(1).Return(goodOPWithTokenSigningKey)
|
||||
secretHelper.EXPECT().Generate(goodFederationDomain).Times(1).Return(goodSecret, nil)
|
||||
secretHelper.EXPECT().ObserveActiveSecretAndUpdateParentFederationDomain(goodFederationDomain, goodSecret).Times(1).Return(goodFederationDomainWithTokenSigningKey)
|
||||
},
|
||||
wantOPActions: []kubetesting.Action{
|
||||
kubetesting.NewGetAction(opGVR, namespace, goodOP.Name),
|
||||
kubetesting.NewUpdateAction(opGVR, namespace, goodOPWithTokenSigningKey),
|
||||
wantFederationDomainActions: []kubetesting.Action{
|
||||
kubetesting.NewGetAction(federationDomainGVR, namespace, goodFederationDomain.Name),
|
||||
kubetesting.NewUpdateAction(federationDomainGVR, namespace, goodFederationDomainWithTokenSigningKey),
|
||||
},
|
||||
wantSecretActions: []kubetesting.Action{
|
||||
kubetesting.NewGetAction(secretGVR, namespace, goodSecret.Name),
|
||||
@ -364,21 +364,21 @@ func TestFederationDomainSecretsControllerSync(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "FederationDomain exists and secret does not exist and upon updating FederationDomain we learn a new status field has been set",
|
||||
storage: func(op **configv1alpha1.FederationDomain, s **corev1.Secret) {
|
||||
storage: func(federationDomain **configv1alpha1.FederationDomain, s **corev1.Secret) {
|
||||
*s = nil
|
||||
},
|
||||
secretHelper: func(secretHelper *mocksecrethelper.MockSecretHelper) {
|
||||
secretHelper.EXPECT().Generate(goodOP).Times(1).Return(goodSecret, nil)
|
||||
secretHelper.EXPECT().ObserveActiveSecretAndUpdateParentFederationDomain(goodOP, goodSecret).Times(1).Return(goodOPWithTokenSigningKey)
|
||||
secretHelper.EXPECT().Generate(goodFederationDomain).Times(1).Return(goodSecret, nil)
|
||||
secretHelper.EXPECT().ObserveActiveSecretAndUpdateParentFederationDomain(goodFederationDomain, goodSecret).Times(1).Return(goodFederationDomainWithTokenSigningKey)
|
||||
},
|
||||
client: func(c *pinnipedfake.Clientset, _ *kubernetesfake.Clientset) {
|
||||
c.PrependReactor("get", "federationdomains", func(_ kubetesting.Action) (bool, runtime.Object, error) {
|
||||
return true, goodOPWithJWKS, nil
|
||||
return true, goodFederationDomainWithJWKS, nil
|
||||
})
|
||||
},
|
||||
wantOPActions: []kubetesting.Action{
|
||||
kubetesting.NewGetAction(opGVR, namespace, goodOP.Name),
|
||||
kubetesting.NewUpdateAction(opGVR, namespace, goodOPWithJWKSAndTokenSigningKey),
|
||||
wantFederationDomainActions: []kubetesting.Action{
|
||||
kubetesting.NewGetAction(federationDomainGVR, namespace, goodFederationDomain.Name),
|
||||
kubetesting.NewUpdateAction(federationDomainGVR, namespace, goodFederationDomainWithJWKSAndTokenSigningKey),
|
||||
},
|
||||
wantSecretActions: []kubetesting.Action{
|
||||
kubetesting.NewGetAction(secretGVR, namespace, goodSecret.Name),
|
||||
@ -387,20 +387,20 @@ func TestFederationDomainSecretsControllerSync(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "FederationDomain exists and secret does not exist and upon updating FederationDomain we learn all status fields have been set",
|
||||
storage: func(op **configv1alpha1.FederationDomain, s **corev1.Secret) {
|
||||
storage: func(federationDomain **configv1alpha1.FederationDomain, s **corev1.Secret) {
|
||||
*s = nil
|
||||
},
|
||||
secretHelper: func(secretHelper *mocksecrethelper.MockSecretHelper) {
|
||||
secretHelper.EXPECT().Generate(goodOP).Times(1).Return(goodSecret, nil)
|
||||
secretHelper.EXPECT().ObserveActiveSecretAndUpdateParentFederationDomain(goodOP, goodSecret).Times(1).Return(goodOPWithTokenSigningKey)
|
||||
secretHelper.EXPECT().Generate(goodFederationDomain).Times(1).Return(goodSecret, nil)
|
||||
secretHelper.EXPECT().ObserveActiveSecretAndUpdateParentFederationDomain(goodFederationDomain, goodSecret).Times(1).Return(goodFederationDomainWithTokenSigningKey)
|
||||
},
|
||||
client: func(c *pinnipedfake.Clientset, _ *kubernetesfake.Clientset) {
|
||||
c.PrependReactor("get", "federationdomains", func(_ kubetesting.Action) (bool, runtime.Object, error) {
|
||||
return true, goodOPWithJWKSAndTokenSigningKey, nil
|
||||
return true, goodFederationDomainWithJWKSAndTokenSigningKey, nil
|
||||
})
|
||||
},
|
||||
wantOPActions: []kubetesting.Action{
|
||||
kubetesting.NewGetAction(opGVR, namespace, goodOP.Name),
|
||||
wantFederationDomainActions: []kubetesting.Action{
|
||||
kubetesting.NewGetAction(federationDomainGVR, namespace, goodFederationDomain.Name),
|
||||
},
|
||||
wantSecretActions: []kubetesting.Action{
|
||||
kubetesting.NewGetAction(secretGVR, namespace, goodSecret.Name),
|
||||
@ -409,17 +409,17 @@ func TestFederationDomainSecretsControllerSync(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "FederationDomain exists and invalid secret exists",
|
||||
storage: func(op **configv1alpha1.FederationDomain, s **corev1.Secret) {
|
||||
storage: func(federationDomain **configv1alpha1.FederationDomain, s **corev1.Secret) {
|
||||
*s = invalidSecret.DeepCopy()
|
||||
},
|
||||
secretHelper: func(secretHelper *mocksecrethelper.MockSecretHelper) {
|
||||
secretHelper.EXPECT().Generate(goodOP).Times(1).Return(goodSecret, nil)
|
||||
secretHelper.EXPECT().IsValid(goodOP, invalidSecret).Times(2).Return(false)
|
||||
secretHelper.EXPECT().ObserveActiveSecretAndUpdateParentFederationDomain(goodOP, goodSecret).Times(1).Return(goodOPWithTokenSigningKey)
|
||||
secretHelper.EXPECT().Generate(goodFederationDomain).Times(1).Return(goodSecret, nil)
|
||||
secretHelper.EXPECT().IsValid(goodFederationDomain, invalidSecret).Times(2).Return(false)
|
||||
secretHelper.EXPECT().ObserveActiveSecretAndUpdateParentFederationDomain(goodFederationDomain, goodSecret).Times(1).Return(goodFederationDomainWithTokenSigningKey)
|
||||
},
|
||||
wantOPActions: []kubetesting.Action{
|
||||
kubetesting.NewGetAction(opGVR, namespace, goodOP.Name),
|
||||
kubetesting.NewUpdateAction(opGVR, namespace, goodOPWithTokenSigningKey),
|
||||
wantFederationDomainActions: []kubetesting.Action{
|
||||
kubetesting.NewGetAction(federationDomainGVR, namespace, goodFederationDomain.Name),
|
||||
kubetesting.NewUpdateAction(federationDomainGVR, namespace, goodFederationDomainWithTokenSigningKey),
|
||||
},
|
||||
wantSecretActions: []kubetesting.Action{
|
||||
kubetesting.NewGetAction(secretGVR, namespace, goodSecret.Name),
|
||||
@ -429,7 +429,7 @@ func TestFederationDomainSecretsControllerSync(t *testing.T) {
|
||||
{
|
||||
name: "FederationDomain exists and generating a secret fails",
|
||||
secretHelper: func(secretHelper *mocksecrethelper.MockSecretHelper) {
|
||||
secretHelper.EXPECT().Generate(goodOP).Times(1).Return(nil, errors.New("some generate error"))
|
||||
secretHelper.EXPECT().Generate(goodFederationDomain).Times(1).Return(nil, errors.New("some generate error"))
|
||||
},
|
||||
wantError: "failed to generate secret: some generate error",
|
||||
},
|
||||
@ -439,14 +439,14 @@ func TestFederationDomainSecretsControllerSync(t *testing.T) {
|
||||
otherSecret := goodSecret.DeepCopy()
|
||||
otherSecret.UID = "other-secret-uid"
|
||||
|
||||
secretHelper.EXPECT().Generate(goodOP).Times(1).Return(otherSecret, nil)
|
||||
secretHelper.EXPECT().IsValid(goodOP, goodSecret).Times(1).Return(false)
|
||||
secretHelper.EXPECT().IsValid(goodOP, goodSecret).Times(1).Return(true)
|
||||
secretHelper.EXPECT().ObserveActiveSecretAndUpdateParentFederationDomain(goodOP, goodSecret).Times(1).Return(goodOPWithTokenSigningKey)
|
||||
secretHelper.EXPECT().Generate(goodFederationDomain).Times(1).Return(otherSecret, nil)
|
||||
secretHelper.EXPECT().IsValid(goodFederationDomain, goodSecret).Times(1).Return(false)
|
||||
secretHelper.EXPECT().IsValid(goodFederationDomain, goodSecret).Times(1).Return(true)
|
||||
secretHelper.EXPECT().ObserveActiveSecretAndUpdateParentFederationDomain(goodFederationDomain, goodSecret).Times(1).Return(goodFederationDomainWithTokenSigningKey)
|
||||
},
|
||||
wantOPActions: []kubetesting.Action{
|
||||
kubetesting.NewGetAction(opGVR, namespace, goodOP.Name),
|
||||
kubetesting.NewUpdateAction(opGVR, namespace, goodOPWithTokenSigningKey),
|
||||
wantFederationDomainActions: []kubetesting.Action{
|
||||
kubetesting.NewGetAction(federationDomainGVR, namespace, goodFederationDomain.Name),
|
||||
kubetesting.NewUpdateAction(federationDomainGVR, namespace, goodFederationDomainWithTokenSigningKey),
|
||||
},
|
||||
wantSecretActions: []kubetesting.Action{
|
||||
kubetesting.NewGetAction(secretGVR, namespace, goodSecret.Name),
|
||||
@ -455,8 +455,8 @@ func TestFederationDomainSecretsControllerSync(t *testing.T) {
|
||||
{
|
||||
name: "FederationDomain exists and invalid secret exists and getting secret fails",
|
||||
secretHelper: func(secretHelper *mocksecrethelper.MockSecretHelper) {
|
||||
secretHelper.EXPECT().Generate(goodOP).Times(1).Return(goodSecret, nil)
|
||||
secretHelper.EXPECT().IsValid(goodOP, goodSecret).Times(1).Return(false)
|
||||
secretHelper.EXPECT().Generate(goodFederationDomain).Times(1).Return(goodSecret, nil)
|
||||
secretHelper.EXPECT().IsValid(goodFederationDomain, goodSecret).Times(1).Return(false)
|
||||
},
|
||||
client: func(_ *pinnipedfake.Clientset, c *kubernetesfake.Clientset) {
|
||||
c.PrependReactor("get", "secrets", func(_ kubetesting.Action) (bool, runtime.Object, error) {
|
||||
@ -470,11 +470,11 @@ func TestFederationDomainSecretsControllerSync(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "FederationDomain exists and secret does not exist and creating secret fails",
|
||||
storage: func(op **configv1alpha1.FederationDomain, s **corev1.Secret) {
|
||||
storage: func(federationDomain **configv1alpha1.FederationDomain, s **corev1.Secret) {
|
||||
*s = nil
|
||||
},
|
||||
secretHelper: func(secretHelper *mocksecrethelper.MockSecretHelper) {
|
||||
secretHelper.EXPECT().Generate(goodOP).Times(1).Return(goodSecret, nil)
|
||||
secretHelper.EXPECT().Generate(goodFederationDomain).Times(1).Return(goodSecret, nil)
|
||||
},
|
||||
client: func(_ *pinnipedfake.Clientset, c *kubernetesfake.Clientset) {
|
||||
c.PrependReactor("create", "secrets", func(_ kubetesting.Action) (bool, runtime.Object, error) {
|
||||
@ -490,8 +490,8 @@ func TestFederationDomainSecretsControllerSync(t *testing.T) {
|
||||
{
|
||||
name: "FederationDomain exists and invalid secret exists and updating secret fails",
|
||||
secretHelper: func(secretHelper *mocksecrethelper.MockSecretHelper) {
|
||||
secretHelper.EXPECT().Generate(goodOP).Times(1).Return(goodSecret, nil)
|
||||
secretHelper.EXPECT().IsValid(goodOP, goodSecret).Times(2).Return(false)
|
||||
secretHelper.EXPECT().Generate(goodFederationDomain).Times(1).Return(goodSecret, nil)
|
||||
secretHelper.EXPECT().IsValid(goodFederationDomain, goodSecret).Times(2).Return(false)
|
||||
},
|
||||
client: func(_ *pinnipedfake.Clientset, c *kubernetesfake.Clientset) {
|
||||
c.PrependReactor("update", "secrets", func(_ kubetesting.Action) (bool, runtime.Object, error) {
|
||||
@ -506,13 +506,13 @@ func TestFederationDomainSecretsControllerSync(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "FederationDomain exists and invalid secret exists and updating secret fails due to conflict",
|
||||
storage: func(op **configv1alpha1.FederationDomain, s **corev1.Secret) {
|
||||
storage: func(federationDomain **configv1alpha1.FederationDomain, s **corev1.Secret) {
|
||||
*s = invalidSecret.DeepCopy()
|
||||
},
|
||||
secretHelper: func(secretHelper *mocksecrethelper.MockSecretHelper) {
|
||||
secretHelper.EXPECT().Generate(goodOP).Times(1).Return(goodSecret, nil)
|
||||
secretHelper.EXPECT().IsValid(goodOP, invalidSecret).Times(3).Return(false)
|
||||
secretHelper.EXPECT().ObserveActiveSecretAndUpdateParentFederationDomain(goodOP, goodSecret).Times(1).Return(goodOPWithTokenSigningKey)
|
||||
secretHelper.EXPECT().Generate(goodFederationDomain).Times(1).Return(goodSecret, nil)
|
||||
secretHelper.EXPECT().IsValid(goodFederationDomain, invalidSecret).Times(3).Return(false)
|
||||
secretHelper.EXPECT().ObserveActiveSecretAndUpdateParentFederationDomain(goodFederationDomain, goodSecret).Times(1).Return(goodFederationDomainWithTokenSigningKey)
|
||||
},
|
||||
client: func(_ *pinnipedfake.Clientset, c *kubernetesfake.Clientset) {
|
||||
once := sync.Once{}
|
||||
@ -522,9 +522,9 @@ func TestFederationDomainSecretsControllerSync(t *testing.T) {
|
||||
return true, nil, err
|
||||
})
|
||||
},
|
||||
wantOPActions: []kubetesting.Action{
|
||||
kubetesting.NewGetAction(opGVR, namespace, goodOP.Name),
|
||||
kubetesting.NewUpdateAction(opGVR, namespace, goodOPWithTokenSigningKey),
|
||||
wantFederationDomainActions: []kubetesting.Action{
|
||||
kubetesting.NewGetAction(federationDomainGVR, namespace, goodFederationDomain.Name),
|
||||
kubetesting.NewUpdateAction(federationDomainGVR, namespace, goodFederationDomainWithTokenSigningKey),
|
||||
},
|
||||
wantSecretActions: []kubetesting.Action{
|
||||
kubetesting.NewGetAction(secretGVR, namespace, goodSecret.Name),
|
||||
@ -535,37 +535,37 @@ func TestFederationDomainSecretsControllerSync(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "FederationDomain exists and invalid secret exists and getting FederationDomain fails",
|
||||
storage: func(op **configv1alpha1.FederationDomain, s **corev1.Secret) {
|
||||
storage: func(federationDomain **configv1alpha1.FederationDomain, s **corev1.Secret) {
|
||||
*s = invalidSecret.DeepCopy()
|
||||
},
|
||||
secretHelper: func(secretHelper *mocksecrethelper.MockSecretHelper) {
|
||||
secretHelper.EXPECT().Generate(goodOP).Times(1).Return(goodSecret, nil)
|
||||
secretHelper.EXPECT().IsValid(goodOP, invalidSecret).Times(2).Return(false)
|
||||
secretHelper.EXPECT().ObserveActiveSecretAndUpdateParentFederationDomain(goodOP, goodSecret).Times(1).Return(goodOPWithTokenSigningKey)
|
||||
secretHelper.EXPECT().Generate(goodFederationDomain).Times(1).Return(goodSecret, nil)
|
||||
secretHelper.EXPECT().IsValid(goodFederationDomain, invalidSecret).Times(2).Return(false)
|
||||
secretHelper.EXPECT().ObserveActiveSecretAndUpdateParentFederationDomain(goodFederationDomain, goodSecret).Times(1).Return(goodFederationDomainWithTokenSigningKey)
|
||||
},
|
||||
client: func(c *pinnipedfake.Clientset, _ *kubernetesfake.Clientset) {
|
||||
c.PrependReactor("get", "federationdomains", func(_ kubetesting.Action) (bool, runtime.Object, error) {
|
||||
return true, nil, errors.New("some get error")
|
||||
})
|
||||
},
|
||||
wantOPActions: []kubetesting.Action{
|
||||
kubetesting.NewGetAction(opGVR, namespace, goodOP.Name),
|
||||
wantFederationDomainActions: []kubetesting.Action{
|
||||
kubetesting.NewGetAction(federationDomainGVR, namespace, goodFederationDomain.Name),
|
||||
},
|
||||
wantSecretActions: []kubetesting.Action{
|
||||
kubetesting.NewGetAction(secretGVR, namespace, goodSecret.Name),
|
||||
kubetesting.NewUpdateAction(secretGVR, namespace, goodSecret),
|
||||
},
|
||||
wantError: fmt.Sprintf("failed to update federationdomain: failed to get federationdomain %s/%s: some get error", goodOPWithTokenSigningKey.Namespace, goodOPWithTokenSigningKey.Name),
|
||||
wantError: fmt.Sprintf("failed to update federationdomain: failed to get federationdomain %s/%s: some get error", goodFederationDomainWithTokenSigningKey.Namespace, goodFederationDomainWithTokenSigningKey.Name),
|
||||
},
|
||||
{
|
||||
name: "FederationDomain exists and invalid secret exists and updating FederationDomain fails due to conflict",
|
||||
storage: func(op **configv1alpha1.FederationDomain, s **corev1.Secret) {
|
||||
storage: func(federationDomain **configv1alpha1.FederationDomain, s **corev1.Secret) {
|
||||
*s = invalidSecret.DeepCopy()
|
||||
},
|
||||
secretHelper: func(secretHelper *mocksecrethelper.MockSecretHelper) {
|
||||
secretHelper.EXPECT().Generate(goodOP).Times(1).Return(goodSecret, nil)
|
||||
secretHelper.EXPECT().IsValid(goodOP, invalidSecret).Times(2).Return(false)
|
||||
secretHelper.EXPECT().ObserveActiveSecretAndUpdateParentFederationDomain(goodOP, goodSecret).Times(1).Return(goodOPWithTokenSigningKey)
|
||||
secretHelper.EXPECT().Generate(goodFederationDomain).Times(1).Return(goodSecret, nil)
|
||||
secretHelper.EXPECT().IsValid(goodFederationDomain, invalidSecret).Times(2).Return(false)
|
||||
secretHelper.EXPECT().ObserveActiveSecretAndUpdateParentFederationDomain(goodFederationDomain, goodSecret).Times(1).Return(goodFederationDomainWithTokenSigningKey)
|
||||
},
|
||||
client: func(c *pinnipedfake.Clientset, _ *kubernetesfake.Clientset) {
|
||||
once := sync.Once{}
|
||||
@ -575,11 +575,11 @@ func TestFederationDomainSecretsControllerSync(t *testing.T) {
|
||||
return true, nil, err
|
||||
})
|
||||
},
|
||||
wantOPActions: []kubetesting.Action{
|
||||
kubetesting.NewGetAction(opGVR, namespace, goodOP.Name),
|
||||
kubetesting.NewUpdateAction(opGVR, namespace, goodOPWithTokenSigningKey),
|
||||
kubetesting.NewGetAction(opGVR, namespace, goodOP.Name),
|
||||
kubetesting.NewUpdateAction(opGVR, namespace, goodOPWithTokenSigningKey),
|
||||
wantFederationDomainActions: []kubetesting.Action{
|
||||
kubetesting.NewGetAction(federationDomainGVR, namespace, goodFederationDomain.Name),
|
||||
kubetesting.NewUpdateAction(federationDomainGVR, namespace, goodFederationDomainWithTokenSigningKey),
|
||||
kubetesting.NewGetAction(federationDomainGVR, namespace, goodFederationDomain.Name),
|
||||
kubetesting.NewUpdateAction(federationDomainGVR, namespace, goodFederationDomainWithTokenSigningKey),
|
||||
},
|
||||
wantSecretActions: []kubetesting.Action{
|
||||
kubetesting.NewGetAction(secretGVR, namespace, goodSecret.Name),
|
||||
@ -601,14 +601,14 @@ func TestFederationDomainSecretsControllerSync(t *testing.T) {
|
||||
kubeAPIClient := kubernetesfake.NewSimpleClientset()
|
||||
kubeInformerClient := kubernetesfake.NewSimpleClientset()
|
||||
|
||||
op := goodOP.DeepCopy()
|
||||
federationDomain := goodFederationDomain.DeepCopy()
|
||||
secret := goodSecret.DeepCopy()
|
||||
if test.storage != nil {
|
||||
test.storage(&op, &secret)
|
||||
test.storage(&federationDomain, &secret)
|
||||
}
|
||||
if op != nil {
|
||||
require.NoError(t, pinnipedAPIClient.Tracker().Add(op))
|
||||
require.NoError(t, pinnipedInformerClient.Tracker().Add(op))
|
||||
if federationDomain != nil {
|
||||
require.NoError(t, pinnipedAPIClient.Tracker().Add(federationDomain))
|
||||
require.NoError(t, pinnipedInformerClient.Tracker().Add(federationDomain))
|
||||
}
|
||||
if secret != nil {
|
||||
require.NoError(t, kubeAPIClient.Tracker().Add(secret))
|
||||
@ -657,7 +657,7 @@ func TestFederationDomainSecretsControllerSync(t *testing.T) {
|
||||
Context: ctx,
|
||||
Key: controllerlib.Key{
|
||||
Namespace: namespace,
|
||||
Name: opName,
|
||||
Name: federationDomainName,
|
||||
},
|
||||
})
|
||||
if test.wantError != "" {
|
||||
@ -666,10 +666,10 @@ func TestFederationDomainSecretsControllerSync(t *testing.T) {
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
if test.wantOPActions == nil {
|
||||
test.wantOPActions = []kubetesting.Action{}
|
||||
if test.wantFederationDomainActions == nil {
|
||||
test.wantFederationDomainActions = []kubetesting.Action{}
|
||||
}
|
||||
require.Equal(t, test.wantOPActions, pinnipedAPIClient.Actions())
|
||||
require.Equal(t, test.wantFederationDomainActions, pinnipedAPIClient.Actions())
|
||||
if test.wantSecretActions == nil {
|
||||
test.wantSecretActions = []kubetesting.Action{}
|
||||
}
|
@ -1,104 +0,0 @@
|
||||
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package generator
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
|
||||
configv1alpha1 "go.pinniped.dev/generated/1.19/apis/supervisor/config/v1alpha1"
|
||||
)
|
||||
|
||||
const (
|
||||
opKind = "FederationDomain"
|
||||
)
|
||||
|
||||
func generateSymmetricKey() ([]byte, error) {
|
||||
b := make([]byte, symmetricKeySize)
|
||||
if _, err := rand.Read(b); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func isValid(secret *corev1.Secret, labels map[string]string) bool {
|
||||
if secret.Type != symmetricSecretType {
|
||||
return false
|
||||
}
|
||||
|
||||
data, ok := secret.Data[symmetricSecretDataKey]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
if len(data) != symmetricKeySize {
|
||||
return false
|
||||
}
|
||||
|
||||
for key, value := range labels {
|
||||
if secret.Labels[key] != value {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func secretDataFunc() (map[string][]byte, error) {
|
||||
symmetricKey, err := generateKey()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return map[string][]byte{
|
||||
symmetricSecretDataKey: symmetricKey,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func generateSecret(namespace, name string, labels map[string]string, secretDataFunc func() (map[string][]byte, error), owner metav1.Object) (*corev1.Secret, error) {
|
||||
secretData, err := secretDataFunc()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
deploymentGVK := schema.GroupVersionKind{
|
||||
Group: appsv1.SchemeGroupVersion.Group,
|
||||
Version: appsv1.SchemeGroupVersion.Version,
|
||||
Kind: "Deployment",
|
||||
}
|
||||
|
||||
blockOwnerDeletion := true
|
||||
isController := false
|
||||
|
||||
return &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
OwnerReferences: []metav1.OwnerReference{
|
||||
{
|
||||
APIVersion: deploymentGVK.GroupVersion().String(),
|
||||
Kind: deploymentGVK.Kind,
|
||||
Name: owner.GetName(),
|
||||
UID: owner.GetUID(),
|
||||
BlockOwnerDeletion: &blockOwnerDeletion,
|
||||
Controller: &isController,
|
||||
},
|
||||
},
|
||||
Labels: labels,
|
||||
},
|
||||
Type: symmetricSecretType,
|
||||
Data: secretData,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// isOPCControlle returns whether the provided obj is controlled by an OPC.
|
||||
func isOPControllee(obj metav1.Object) bool {
|
||||
controller := metav1.GetControllerOf(obj)
|
||||
return controller != nil &&
|
||||
controller.APIVersion == configv1alpha1.SchemeGroupVersion.String() &&
|
||||
controller.Kind == opKind
|
||||
}
|
@ -12,7 +12,6 @@ import (
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
|
||||
configv1alpha1 "go.pinniped.dev/generated/1.19/apis/supervisor/config/v1alpha1"
|
||||
"go.pinniped.dev/internal/plog"
|
||||
)
|
||||
|
||||
// SecretHelper describes an object that can Generate() a Secret and determine whether a Secret
|
||||
@ -27,8 +26,18 @@ type SecretHelper interface {
|
||||
}
|
||||
|
||||
const (
|
||||
// symmetricSecretType is corev1.Secret.Type of all corev1.Secret's generated by this helper.
|
||||
symmetricSecretType = "secrets.pinniped.dev/symmetric"
|
||||
// SupervisorCSRFSigningKeySecretType for the Secret storing the CSRF signing key.
|
||||
SupervisorCSRFSigningKeySecretType corev1.SecretType = "secrets.pinniped.dev/supervisor-csrf-signing-key"
|
||||
|
||||
// FederationDomainTokenSigningKeyType for the Secret storing the FederationDomain token signing key.
|
||||
FederationDomainTokenSigningKeyType corev1.SecretType = "secrets.pinniped.dev/federation-domain-token-signing-key"
|
||||
|
||||
// FederationDomainStateSigningKeyType for the Secret storing the FederationDomain state signing key.
|
||||
FederationDomainStateSigningKeyType corev1.SecretType = "secrets.pinniped.dev/federation-domain-state-signing-key"
|
||||
|
||||
// FederationDomainStateEncryptionKeyType for the Secret storing the FederationDomain state encryption key.
|
||||
FederationDomainStateEncryptionKeyType corev1.SecretType = "secrets.pinniped.dev/federation-domain-state-encryption-key"
|
||||
|
||||
// symmetricSecretDataKey is the corev1.Secret.Data key for the symmetric key value generated by this helper.
|
||||
symmetricSecretDataKey = "key"
|
||||
|
||||
@ -96,7 +105,7 @@ func (s *symmetricSecretHelper) Generate(parent *configv1alpha1.FederationDomain
|
||||
}),
|
||||
},
|
||||
},
|
||||
Type: symmetricSecretType,
|
||||
Type: s.secretType(),
|
||||
Data: map[string][]byte{
|
||||
symmetricSecretDataKey: key,
|
||||
},
|
||||
@ -109,7 +118,7 @@ func (s *symmetricSecretHelper) IsValid(parent *configv1alpha1.FederationDomain,
|
||||
return false
|
||||
}
|
||||
|
||||
if secret.Type != symmetricSecretType {
|
||||
if secret.Type != s.secretType() {
|
||||
return false
|
||||
}
|
||||
|
||||
@ -126,26 +135,34 @@ func (s *symmetricSecretHelper) IsValid(parent *configv1alpha1.FederationDomain,
|
||||
|
||||
// ObserveActiveSecretAndUpdateParentFederationDomain implements SecretHelper.ObserveActiveSecretAndUpdateParentFederationDomain().
|
||||
func (s *symmetricSecretHelper) ObserveActiveSecretAndUpdateParentFederationDomain(
|
||||
op *configv1alpha1.FederationDomain,
|
||||
federationDomain *configv1alpha1.FederationDomain,
|
||||
secret *corev1.Secret,
|
||||
) *configv1alpha1.FederationDomain {
|
||||
var cacheKey string
|
||||
if op != nil {
|
||||
cacheKey = op.Spec.Issuer
|
||||
}
|
||||
|
||||
s.updateCacheFunc(cacheKey, secret.Data[symmetricSecretDataKey])
|
||||
s.updateCacheFunc(federationDomain.Spec.Issuer, secret.Data[symmetricSecretDataKey])
|
||||
|
||||
switch s.secretUsage {
|
||||
case SecretUsageTokenSigningKey:
|
||||
op.Status.Secrets.TokenSigningKey.Name = secret.Name
|
||||
federationDomain.Status.Secrets.TokenSigningKey.Name = secret.Name
|
||||
case SecretUsageStateSigningKey:
|
||||
op.Status.Secrets.StateSigningKey.Name = secret.Name
|
||||
federationDomain.Status.Secrets.StateSigningKey.Name = secret.Name
|
||||
case SecretUsageStateEncryptionKey:
|
||||
op.Status.Secrets.StateEncryptionKey.Name = secret.Name
|
||||
federationDomain.Status.Secrets.StateEncryptionKey.Name = secret.Name
|
||||
default:
|
||||
plog.Warning("unknown secret usage enum value: %d", s.secretUsage)
|
||||
panic(fmt.Sprintf("unknown secret usage enum value: %d", s.secretUsage))
|
||||
}
|
||||
|
||||
return op
|
||||
return federationDomain
|
||||
}
|
||||
|
||||
func (s *symmetricSecretHelper) secretType() corev1.SecretType {
|
||||
switch s.secretUsage {
|
||||
case SecretUsageTokenSigningKey:
|
||||
return FederationDomainTokenSigningKeyType
|
||||
case SecretUsageStateSigningKey:
|
||||
return FederationDomainStateSigningKeyType
|
||||
case SecretUsageStateEncryptionKey:
|
||||
return FederationDomainStateEncryptionKeyType
|
||||
default:
|
||||
panic(fmt.Sprintf("unknown secret usage enum value: %d", s.secretUsage))
|
||||
}
|
||||
}
|
||||
|
@ -23,27 +23,31 @@ func TestSymmetricSecretHelper(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
secretUsage SecretUsage
|
||||
wantSecretType corev1.SecretType
|
||||
wantSetFederationDomainField func(*configv1alpha1.FederationDomain) string
|
||||
}{
|
||||
{
|
||||
name: "token signing key",
|
||||
secretUsage: SecretUsageTokenSigningKey,
|
||||
wantSetFederationDomainField: func(op *configv1alpha1.FederationDomain) string {
|
||||
return op.Status.Secrets.TokenSigningKey.Name
|
||||
name: "token signing key",
|
||||
secretUsage: SecretUsageTokenSigningKey,
|
||||
wantSecretType: "secrets.pinniped.dev/federation-domain-token-signing-key",
|
||||
wantSetFederationDomainField: func(federationDomain *configv1alpha1.FederationDomain) string {
|
||||
return federationDomain.Status.Secrets.TokenSigningKey.Name
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "state signing key",
|
||||
secretUsage: SecretUsageStateSigningKey,
|
||||
wantSetFederationDomainField: func(op *configv1alpha1.FederationDomain) string {
|
||||
return op.Status.Secrets.StateSigningKey.Name
|
||||
name: "state signing key",
|
||||
secretUsage: SecretUsageStateSigningKey,
|
||||
wantSecretType: "secrets.pinniped.dev/federation-domain-state-signing-key",
|
||||
wantSetFederationDomainField: func(federationDomain *configv1alpha1.FederationDomain) string {
|
||||
return federationDomain.Status.Secrets.StateSigningKey.Name
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "state encryption key",
|
||||
secretUsage: SecretUsageStateEncryptionKey,
|
||||
wantSetFederationDomainField: func(op *configv1alpha1.FederationDomain) string {
|
||||
return op.Status.Secrets.StateEncryptionKey.Name
|
||||
name: "state encryption key",
|
||||
secretUsage: SecretUsageStateEncryptionKey,
|
||||
wantSecretType: "secrets.pinniped.dev/federation-domain-state-encryption-key",
|
||||
wantSetFederationDomainField: func(federationDomain *configv1alpha1.FederationDomain) string {
|
||||
return federationDomain.Status.Secrets.StateEncryptionKey.Name
|
||||
},
|
||||
},
|
||||
}
|
||||
@ -92,7 +96,7 @@ func TestSymmetricSecretHelper(t *testing.T) {
|
||||
}),
|
||||
},
|
||||
},
|
||||
Type: "secrets.pinniped.dev/symmetric",
|
||||
Type: test.wantSecretType,
|
||||
Data: map[string][]byte{
|
||||
"key": []byte(keyWith32Bytes),
|
||||
},
|
||||
@ -110,55 +114,69 @@ func TestSymmetricSecretHelper(t *testing.T) {
|
||||
|
||||
func TestSymmetricSecretHelperIsValid(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
child func(*corev1.Secret)
|
||||
parent func(*configv1alpha1.FederationDomain)
|
||||
want bool
|
||||
name string
|
||||
secretUsage SecretUsage
|
||||
child func(*corev1.Secret)
|
||||
parent func(*configv1alpha1.FederationDomain)
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "wrong type",
|
||||
name: "wrong type",
|
||||
secretUsage: SecretUsageTokenSigningKey,
|
||||
child: func(s *corev1.Secret) {
|
||||
s.Type = "wrong"
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "empty type",
|
||||
name: "empty type",
|
||||
secretUsage: SecretUsageTokenSigningKey,
|
||||
child: func(s *corev1.Secret) {
|
||||
s.Type = ""
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "data key is too short",
|
||||
name: "data key is too short",
|
||||
secretUsage: SecretUsageTokenSigningKey,
|
||||
child: func(s *corev1.Secret) {
|
||||
s.Type = FederationDomainTokenSigningKeyType
|
||||
s.Data["key"] = []byte("short")
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "data key does not exist",
|
||||
name: "data key does not exist",
|
||||
secretUsage: SecretUsageTokenSigningKey,
|
||||
child: func(s *corev1.Secret) {
|
||||
s.Type = FederationDomainTokenSigningKeyType
|
||||
delete(s.Data, "key")
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "child not owned by parent",
|
||||
parent: func(op *configv1alpha1.FederationDomain) {
|
||||
op.UID = "wrong"
|
||||
name: "child not owned by parent",
|
||||
secretUsage: SecretUsageTokenSigningKey,
|
||||
child: func(s *corev1.Secret) {
|
||||
s.Type = FederationDomainTokenSigningKeyType
|
||||
},
|
||||
parent: func(federationDomain *configv1alpha1.FederationDomain) {
|
||||
federationDomain.UID = "wrong"
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "happy path",
|
||||
want: true,
|
||||
name: "happy path",
|
||||
secretUsage: SecretUsageTokenSigningKey,
|
||||
child: func(s *corev1.Secret) {
|
||||
s.Type = FederationDomainTokenSigningKeyType
|
||||
}, want: true,
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
test := test
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
h := NewSymmetricSecretHelper("none of these args matter", nil, nil, SecretUsageTokenSigningKey, nil)
|
||||
h := NewSymmetricSecretHelper("none of these args matter", nil, nil, test.secretUsage, nil)
|
||||
|
||||
parent := &configv1alpha1.FederationDomain{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
@ -179,7 +197,7 @@ func TestSymmetricSecretHelperIsValid(t *testing.T) {
|
||||
}),
|
||||
},
|
||||
},
|
||||
Type: "secrets.pinniped.dev/symmetric",
|
||||
Type: "invalid default",
|
||||
Data: map[string][]byte{
|
||||
"key": []byte(keyWith32Bytes),
|
||||
},
|
||||
|
@ -6,12 +6,14 @@ package generator
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
k8serrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
corev1informers "k8s.io/client-go/informers/core/v1"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/util/retry"
|
||||
@ -143,3 +145,80 @@ func (c *supervisorSecretsController) updateSecret(ctx context.Context, newSecre
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
func generateSymmetricKey() ([]byte, error) {
|
||||
b := make([]byte, symmetricKeySize)
|
||||
if _, err := rand.Read(b); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func isValid(secret *corev1.Secret, labels map[string]string) bool {
|
||||
if secret.Type != SupervisorCSRFSigningKeySecretType {
|
||||
return false
|
||||
}
|
||||
|
||||
data, ok := secret.Data[symmetricSecretDataKey]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
if len(data) != symmetricKeySize {
|
||||
return false
|
||||
}
|
||||
|
||||
for key, value := range labels {
|
||||
if secret.Labels[key] != value {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func secretDataFunc() (map[string][]byte, error) {
|
||||
symmetricKey, err := generateKey()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return map[string][]byte{
|
||||
symmetricSecretDataKey: symmetricKey,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func generateSecret(namespace, name string, labels map[string]string, secretDataFunc func() (map[string][]byte, error), owner metav1.Object) (*corev1.Secret, error) {
|
||||
secretData, err := secretDataFunc()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
deploymentGVK := schema.GroupVersionKind{
|
||||
Group: appsv1.SchemeGroupVersion.Group,
|
||||
Version: appsv1.SchemeGroupVersion.Version,
|
||||
Kind: "Deployment",
|
||||
}
|
||||
|
||||
blockOwnerDeletion := true
|
||||
isController := false
|
||||
|
||||
return &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
OwnerReferences: []metav1.OwnerReference{
|
||||
{
|
||||
APIVersion: deploymentGVK.GroupVersion().String(),
|
||||
Kind: deploymentGVK.Kind,
|
||||
Name: owner.GetName(),
|
||||
UID: owner.GetUID(),
|
||||
BlockOwnerDeletion: &blockOwnerDeletion,
|
||||
Controller: &isController,
|
||||
},
|
||||
},
|
||||
Labels: labels,
|
||||
},
|
||||
Type: SupervisorCSRFSigningKeySecretType,
|
||||
Data: secretData,
|
||||
}, nil
|
||||
}
|
||||
|
@ -258,7 +258,7 @@ func TestSupervisorSecretsControllerSync(t *testing.T) {
|
||||
},
|
||||
Labels: labels,
|
||||
},
|
||||
Type: "secrets.pinniped.dev/symmetric",
|
||||
Type: "secrets.pinniped.dev/supervisor-csrf-signing-key",
|
||||
Data: map[string][]byte{
|
||||
"key": generatedSymmetricKey,
|
||||
},
|
||||
@ -280,7 +280,7 @@ func TestSupervisorSecretsControllerSync(t *testing.T) {
|
||||
},
|
||||
Labels: labels,
|
||||
},
|
||||
Type: "secrets.pinniped.dev/symmetric",
|
||||
Type: "secrets.pinniped.dev/supervisor-csrf-signing-key",
|
||||
Data: map[string][]byte{
|
||||
"key": otherGeneratedSymmetricKey,
|
||||
},
|
||||
|
@ -30,7 +30,7 @@ import (
|
||||
"go.pinniped.dev/internal/plog"
|
||||
)
|
||||
|
||||
// These constants are the keys in an OPC's Secret's Data map.
|
||||
// These constants are the keys in a FederationDomain's Secret's Data map.
|
||||
const (
|
||||
// activeJWKKey points to the current private key used for signing tokens.
|
||||
//
|
||||
@ -40,10 +40,12 @@ const (
|
||||
//
|
||||
// Note! The value for this key will contain only public key material!
|
||||
jwksKey = "jwks"
|
||||
|
||||
jwksSecretTypeValue corev1.SecretType = "secrets.pinniped.dev/federation-domain-jwks"
|
||||
)
|
||||
|
||||
const (
|
||||
opcKind = "FederationDomain"
|
||||
federationDomainKind = "FederationDomain"
|
||||
)
|
||||
|
||||
// generateKey is stubbed out for the purpose of testing. The default behavior is to generate an EC key.
|
||||
@ -54,44 +56,44 @@ func generateECKey(r io.Reader) (interface{}, error) {
|
||||
return ecdsa.GenerateKey(elliptic.P256(), r)
|
||||
}
|
||||
|
||||
// jwkController holds the fields necessary for the JWKS controller to communicate with OPC's and
|
||||
// jwkController holds the fields necessary for the JWKS controller to communicate with FederationDomains and
|
||||
// secrets, both via a cache and via the API.
|
||||
type jwksWriterController struct {
|
||||
jwksSecretLabels map[string]string
|
||||
pinnipedClient pinnipedclientset.Interface
|
||||
kubeClient kubernetes.Interface
|
||||
opcInformer configinformers.FederationDomainInformer
|
||||
secretInformer corev1informers.SecretInformer
|
||||
jwksSecretLabels map[string]string
|
||||
pinnipedClient pinnipedclientset.Interface
|
||||
kubeClient kubernetes.Interface
|
||||
federationDomainInformer configinformers.FederationDomainInformer
|
||||
secretInformer corev1informers.SecretInformer
|
||||
}
|
||||
|
||||
// NewJWKSWriterController returns a controllerlib.Controller that ensures an OPC has a corresponding
|
||||
// NewJWKSWriterController returns a controllerlib.Controller that ensures a FederationDomain has a corresponding
|
||||
// Secret that contains a valid active JWK and JWKS.
|
||||
func NewJWKSWriterController(
|
||||
jwksSecretLabels map[string]string,
|
||||
kubeClient kubernetes.Interface,
|
||||
pinnipedClient pinnipedclientset.Interface,
|
||||
secretInformer corev1informers.SecretInformer,
|
||||
opcInformer configinformers.FederationDomainInformer,
|
||||
federationDomainInformer configinformers.FederationDomainInformer,
|
||||
withInformer pinnipedcontroller.WithInformerOptionFunc,
|
||||
) controllerlib.Controller {
|
||||
return controllerlib.New(
|
||||
controllerlib.Config{
|
||||
Name: "JWKSController",
|
||||
Syncer: &jwksWriterController{
|
||||
jwksSecretLabels: jwksSecretLabels,
|
||||
kubeClient: kubeClient,
|
||||
pinnipedClient: pinnipedClient,
|
||||
secretInformer: secretInformer,
|
||||
opcInformer: opcInformer,
|
||||
jwksSecretLabels: jwksSecretLabels,
|
||||
kubeClient: kubeClient,
|
||||
pinnipedClient: pinnipedClient,
|
||||
secretInformer: secretInformer,
|
||||
federationDomainInformer: federationDomainInformer,
|
||||
},
|
||||
},
|
||||
// We want to be notified when a OPC's secret gets updated or deleted. When this happens, we
|
||||
// should get notified via the corresponding OPC key.
|
||||
// We want to be notified when a FederationDomain's secret gets updated or deleted. When this happens, we
|
||||
// should get notified via the corresponding FederationDomain key.
|
||||
withInformer(
|
||||
secretInformer,
|
||||
controllerlib.FilterFuncs{
|
||||
ParentFunc: func(obj metav1.Object) controllerlib.Key {
|
||||
if isOPCControllee(obj) {
|
||||
if isFederationDomainControllee(obj) {
|
||||
controller := metav1.GetControllerOf(obj)
|
||||
return controllerlib.Key{
|
||||
Name: controller.Name,
|
||||
@ -100,17 +102,17 @@ func NewJWKSWriterController(
|
||||
}
|
||||
return controllerlib.Key{}
|
||||
},
|
||||
AddFunc: isOPCControllee,
|
||||
AddFunc: isFederationDomainControllee,
|
||||
UpdateFunc: func(oldObj, newObj metav1.Object) bool {
|
||||
return isOPCControllee(oldObj) || isOPCControllee(newObj)
|
||||
return isFederationDomainControllee(oldObj) || isFederationDomainControllee(newObj)
|
||||
},
|
||||
DeleteFunc: isOPCControllee,
|
||||
DeleteFunc: isFederationDomainControllee,
|
||||
},
|
||||
controllerlib.InformerOption{},
|
||||
),
|
||||
// We want to be notified when anything happens to an OPC.
|
||||
// We want to be notified when anything happens to an FederationDomain.
|
||||
withInformer(
|
||||
opcInformer,
|
||||
federationDomainInformer,
|
||||
pinnipedcontroller.MatchAnythingFilter(nil), // nil parent func is fine because each event is distinct
|
||||
controllerlib.InformerOption{},
|
||||
),
|
||||
@ -119,7 +121,7 @@ func NewJWKSWriterController(
|
||||
|
||||
// Sync implements controllerlib.Syncer.
|
||||
func (c *jwksWriterController) Sync(ctx controllerlib.Context) error {
|
||||
opc, err := c.opcInformer.Lister().FederationDomains(ctx.Key.Namespace).Get(ctx.Key.Name)
|
||||
federationDomain, err := c.federationDomainInformer.Lister().FederationDomains(ctx.Key.Namespace).Get(ctx.Key.Name)
|
||||
notFound := k8serrors.IsNotFound(err)
|
||||
if err != nil && !notFound {
|
||||
return fmt.Errorf(
|
||||
@ -131,17 +133,17 @@ func (c *jwksWriterController) Sync(ctx controllerlib.Context) error {
|
||||
}
|
||||
|
||||
if notFound {
|
||||
// The corresponding secret to this OPC should have been garbage collected since it should have
|
||||
// had this OPC as its owner.
|
||||
// The corresponding secret to this FederationDomain should have been garbage collected since it should have
|
||||
// had this FederationDomain as its owner.
|
||||
plog.Debug(
|
||||
"federationdomain deleted",
|
||||
"FederationDomain deleted",
|
||||
"federationdomain",
|
||||
klog.KRef(ctx.Key.Namespace, ctx.Key.Name),
|
||||
)
|
||||
return nil
|
||||
}
|
||||
|
||||
secretNeedsUpdate, err := c.secretNeedsUpdate(opc)
|
||||
secretNeedsUpdate, err := c.secretNeedsUpdate(federationDomain)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot determine secret status: %w", err)
|
||||
}
|
||||
@ -155,9 +157,9 @@ func (c *jwksWriterController) Sync(ctx controllerlib.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// If the OPC does not have a secret associated with it, that secret does not exist, or the secret
|
||||
// If the FederationDomain does not have a secret associated with it, that secret does not exist, or the secret
|
||||
// is invalid, we will generate a new secret (i.e., a JWKS).
|
||||
secret, err := c.generateSecret(opc)
|
||||
secret, err := c.generateSecret(federationDomain)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot generate secret: %w", err)
|
||||
}
|
||||
@ -167,25 +169,25 @@ func (c *jwksWriterController) Sync(ctx controllerlib.Context) error {
|
||||
}
|
||||
plog.Debug("created/updated secret", "secret", klog.KObj(secret))
|
||||
|
||||
// Ensure that the OPC points to the secret.
|
||||
newOPC := opc.DeepCopy()
|
||||
newOPC.Status.Secrets.JWKS.Name = secret.Name
|
||||
if err := c.updateOPC(ctx.Context, newOPC); err != nil {
|
||||
return fmt.Errorf("cannot update opc: %w", err)
|
||||
// Ensure that the FederationDomain points to the secret.
|
||||
newFederationDomain := federationDomain.DeepCopy()
|
||||
newFederationDomain.Status.Secrets.JWKS.Name = secret.Name
|
||||
if err := c.updateFederationDomain(ctx.Context, newFederationDomain); err != nil {
|
||||
return fmt.Errorf("cannot update FederationDomain: %w", err)
|
||||
}
|
||||
plog.Debug("updated federationdomain", "federationdomain", klog.KObj(newOPC))
|
||||
plog.Debug("updated FederationDomain", "federationdomain", klog.KObj(newFederationDomain))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *jwksWriterController) secretNeedsUpdate(opc *configv1alpha1.FederationDomain) (bool, error) {
|
||||
if opc.Status.Secrets.JWKS.Name == "" {
|
||||
// If the OPC says it doesn't have a secret associated with it, then let's create one.
|
||||
func (c *jwksWriterController) secretNeedsUpdate(federationDomain *configv1alpha1.FederationDomain) (bool, error) {
|
||||
if federationDomain.Status.Secrets.JWKS.Name == "" {
|
||||
// If the FederationDomain says it doesn't have a secret associated with it, then let's create one.
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// This OPC says it has a secret associated with it. Let's try to get it from the cache.
|
||||
secret, err := c.secretInformer.Lister().Secrets(opc.Namespace).Get(opc.Status.Secrets.JWKS.Name)
|
||||
// This FederationDomain says it has a secret associated with it. Let's try to get it from the cache.
|
||||
secret, err := c.secretInformer.Lister().Secrets(federationDomain.Namespace).Get(federationDomain.Status.Secrets.JWKS.Name)
|
||||
notFound := k8serrors.IsNotFound(err)
|
||||
if err != nil && !notFound {
|
||||
return false, fmt.Errorf("cannot get secret: %w", err)
|
||||
@ -203,9 +205,9 @@ func (c *jwksWriterController) secretNeedsUpdate(opc *configv1alpha1.FederationD
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (c *jwksWriterController) generateSecret(opc *configv1alpha1.FederationDomain) (*corev1.Secret, error) {
|
||||
// Note! This is where we could potentially add more handling of OPC spec fields which tell us how
|
||||
// this OIDC provider should sign and verify ID tokens (e.g., hardcoded token secret, gRPC
|
||||
func (c *jwksWriterController) generateSecret(federationDomain *configv1alpha1.FederationDomain) (*corev1.Secret, error) {
|
||||
// Note! This is where we could potentially add more handling of FederationDomain spec fields which tell us how
|
||||
// this FederationDomain should sign and verify ID tokens (e.g., hardcoded token secret, gRPC
|
||||
// connection to KMS, etc).
|
||||
//
|
||||
// For now, we just generate an new RSA keypair and put that in the secret.
|
||||
@ -236,14 +238,14 @@ func (c *jwksWriterController) generateSecret(opc *configv1alpha1.FederationDoma
|
||||
|
||||
s := corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: opc.Name + "-jwks",
|
||||
Namespace: opc.Namespace,
|
||||
Name: federationDomain.Name + "-jwks",
|
||||
Namespace: federationDomain.Namespace,
|
||||
Labels: c.jwksSecretLabels,
|
||||
OwnerReferences: []metav1.OwnerReference{
|
||||
*metav1.NewControllerRef(opc, schema.GroupVersionKind{
|
||||
*metav1.NewControllerRef(federationDomain, schema.GroupVersionKind{
|
||||
Group: configv1alpha1.SchemeGroupVersion.Group,
|
||||
Version: configv1alpha1.SchemeGroupVersion.Version,
|
||||
Kind: opcKind,
|
||||
Kind: federationDomainKind,
|
||||
}),
|
||||
},
|
||||
},
|
||||
@ -251,6 +253,7 @@ func (c *jwksWriterController) generateSecret(opc *configv1alpha1.FederationDoma
|
||||
activeJWKKey: jwkData,
|
||||
jwksKey: jwksData,
|
||||
},
|
||||
Type: jwksSecretTypeValue,
|
||||
}
|
||||
|
||||
return &s, nil
|
||||
@ -285,43 +288,49 @@ func (c *jwksWriterController) createOrUpdateSecret(
|
||||
}
|
||||
|
||||
oldSecret.Data = newSecret.Data
|
||||
oldSecret.Type = jwksSecretTypeValue
|
||||
_, err = secretClient.Update(ctx, oldSecret, metav1.UpdateOptions{})
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
func (c *jwksWriterController) updateOPC(
|
||||
func (c *jwksWriterController) updateFederationDomain(
|
||||
ctx context.Context,
|
||||
newOPC *configv1alpha1.FederationDomain,
|
||||
newFederationDomain *configv1alpha1.FederationDomain,
|
||||
) error {
|
||||
opcClient := c.pinnipedClient.ConfigV1alpha1().FederationDomains(newOPC.Namespace)
|
||||
federationDomainClient := c.pinnipedClient.ConfigV1alpha1().FederationDomains(newFederationDomain.Namespace)
|
||||
return retry.RetryOnConflict(retry.DefaultRetry, func() error {
|
||||
oldOPC, err := opcClient.Get(ctx, newOPC.Name, metav1.GetOptions{})
|
||||
oldFederationDomain, err := federationDomainClient.Get(ctx, newFederationDomain.Name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot get opc: %w", err)
|
||||
return fmt.Errorf("cannot get FederationDomain: %w", err)
|
||||
}
|
||||
|
||||
if newOPC.Status.Secrets.JWKS.Name == oldOPC.Status.Secrets.JWKS.Name {
|
||||
// If the existing OPC is up to date, we don't need to update it.
|
||||
if newFederationDomain.Status.Secrets.JWKS.Name == oldFederationDomain.Status.Secrets.JWKS.Name {
|
||||
// If the existing FederationDomain is up to date, we don't need to update it.
|
||||
return nil
|
||||
}
|
||||
|
||||
oldOPC.Status.Secrets.JWKS.Name = newOPC.Status.Secrets.JWKS.Name
|
||||
_, err = opcClient.Update(ctx, oldOPC, metav1.UpdateOptions{})
|
||||
oldFederationDomain.Status.Secrets.JWKS.Name = newFederationDomain.Status.Secrets.JWKS.Name
|
||||
_, err = federationDomainClient.Update(ctx, oldFederationDomain, metav1.UpdateOptions{})
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
// isOPCControlle returns whether the provided obj is controlled by an OPC.
|
||||
func isOPCControllee(obj metav1.Object) bool {
|
||||
// isFederationDomainControlle returns whether the provided obj is controlled by a FederationDomain.
|
||||
func isFederationDomainControllee(obj metav1.Object) bool {
|
||||
controller := metav1.GetControllerOf(obj)
|
||||
return controller != nil &&
|
||||
controller.APIVersion == configv1alpha1.SchemeGroupVersion.String() &&
|
||||
controller.Kind == opcKind
|
||||
controller.Kind == federationDomainKind
|
||||
}
|
||||
|
||||
// isValid returns whether the provided secret contains a valid active JWK and verification JWKS.
|
||||
func isValid(secret *corev1.Secret) bool {
|
||||
if secret.Type != jwksSecretTypeValue {
|
||||
plog.Debug("secret does not have the expected type", "expectedType", jwksSecretTypeValue, "actualType", secret.Type)
|
||||
return false
|
||||
}
|
||||
|
||||
jwkData, ok := secret.Data[activeJWKKey]
|
||||
if !ok {
|
||||
plog.Debug("secret does not contain active jwk")
|
||||
|
@ -145,7 +145,7 @@ func TestJWKSWriterControllerFilterSecret(t *testing.T) {
|
||||
kubernetesfake.NewSimpleClientset(),
|
||||
0,
|
||||
).Core().V1().Secrets()
|
||||
opcInformer := pinnipedinformers.NewSharedInformerFactory(
|
||||
federationDomainInformer := pinnipedinformers.NewSharedInformerFactory(
|
||||
pinnipedfake.NewSimpleClientset(),
|
||||
0,
|
||||
).Config().V1alpha1().FederationDomains()
|
||||
@ -155,7 +155,7 @@ func TestJWKSWriterControllerFilterSecret(t *testing.T) {
|
||||
nil, // kubeClient, not needed
|
||||
nil, // pinnipedClient, not needed
|
||||
secretInformer,
|
||||
opcInformer,
|
||||
federationDomainInformer,
|
||||
withInformer.WithInformer,
|
||||
)
|
||||
|
||||
@ -170,24 +170,24 @@ func TestJWKSWriterControllerFilterSecret(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestJWKSWriterControllerFilterOPC(t *testing.T) {
|
||||
func TestJWKSWriterControllerFilterFederationDomain(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
opc configv1alpha1.FederationDomain
|
||||
wantAdd bool
|
||||
wantUpdate bool
|
||||
wantDelete bool
|
||||
wantParent controllerlib.Key
|
||||
name string
|
||||
federationDomain configv1alpha1.FederationDomain
|
||||
wantAdd bool
|
||||
wantUpdate bool
|
||||
wantDelete bool
|
||||
wantParent controllerlib.Key
|
||||
}{
|
||||
{
|
||||
name: "anything goes",
|
||||
opc: configv1alpha1.FederationDomain{},
|
||||
wantAdd: true,
|
||||
wantUpdate: true,
|
||||
wantDelete: true,
|
||||
wantParent: controllerlib.Key{},
|
||||
name: "anything goes",
|
||||
federationDomain: configv1alpha1.FederationDomain{},
|
||||
wantAdd: true,
|
||||
wantUpdate: true,
|
||||
wantDelete: true,
|
||||
wantParent: controllerlib.Key{},
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
@ -199,7 +199,7 @@ func TestJWKSWriterControllerFilterOPC(t *testing.T) {
|
||||
kubernetesfake.NewSimpleClientset(),
|
||||
0,
|
||||
).Core().V1().Secrets()
|
||||
opcInformer := pinnipedinformers.NewSharedInformerFactory(
|
||||
federationDomainInformer := pinnipedinformers.NewSharedInformerFactory(
|
||||
pinnipedfake.NewSimpleClientset(),
|
||||
0,
|
||||
).Config().V1alpha1().FederationDomains()
|
||||
@ -209,17 +209,17 @@ func TestJWKSWriterControllerFilterOPC(t *testing.T) {
|
||||
nil, // kubeClient, not needed
|
||||
nil, // pinnipedClient, not needed
|
||||
secretInformer,
|
||||
opcInformer,
|
||||
federationDomainInformer,
|
||||
withInformer.WithInformer,
|
||||
)
|
||||
|
||||
unrelated := configv1alpha1.FederationDomain{}
|
||||
filter := withInformer.GetFilterForInformer(opcInformer)
|
||||
require.Equal(t, test.wantAdd, filter.Add(&test.opc))
|
||||
require.Equal(t, test.wantUpdate, filter.Update(&unrelated, &test.opc))
|
||||
require.Equal(t, test.wantUpdate, filter.Update(&test.opc, &unrelated))
|
||||
require.Equal(t, test.wantDelete, filter.Delete(&test.opc))
|
||||
require.Equal(t, test.wantParent, filter.Parent(&test.opc))
|
||||
filter := withInformer.GetFilterForInformer(federationDomainInformer)
|
||||
require.Equal(t, test.wantAdd, filter.Add(&test.federationDomain))
|
||||
require.Equal(t, test.wantUpdate, filter.Update(&unrelated, &test.federationDomain))
|
||||
require.Equal(t, test.wantUpdate, filter.Update(&test.federationDomain, &unrelated))
|
||||
require.Equal(t, test.wantDelete, filter.Delete(&test.federationDomain))
|
||||
require.Equal(t, test.wantParent, filter.Parent(&test.federationDomain))
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -236,24 +236,24 @@ func TestJWKSWriterControllerSync(t *testing.T) {
|
||||
goodKey, err := x509.ParseECPrivateKey(block.Bytes)
|
||||
require.NoError(t, err)
|
||||
|
||||
opcGVR := schema.GroupVersionResource{
|
||||
federationDomainGVR := schema.GroupVersionResource{
|
||||
Group: configv1alpha1.SchemeGroupVersion.Group,
|
||||
Version: configv1alpha1.SchemeGroupVersion.Version,
|
||||
Resource: "federationdomains",
|
||||
}
|
||||
|
||||
goodOPC := &configv1alpha1.FederationDomain{
|
||||
goodFederationDomain := &configv1alpha1.FederationDomain{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "good-opc",
|
||||
Name: "good-federationDomain",
|
||||
Namespace: namespace,
|
||||
UID: "good-opc-uid",
|
||||
UID: "good-federationDomain-uid",
|
||||
},
|
||||
Spec: configv1alpha1.FederationDomainSpec{
|
||||
Issuer: "https://some-issuer.com",
|
||||
},
|
||||
}
|
||||
goodOPCWithStatus := goodOPC.DeepCopy()
|
||||
goodOPCWithStatus.Status.Secrets.JWKS.Name = goodOPCWithStatus.Name + "-jwks"
|
||||
goodFederationDomainWithStatus := goodFederationDomain.DeepCopy()
|
||||
goodFederationDomainWithStatus.Status.Secrets.JWKS.Name = goodFederationDomainWithStatus.Name + "-jwks"
|
||||
|
||||
secretGVR := schema.GroupVersionResource{
|
||||
Group: corev1.SchemeGroupVersion.Group,
|
||||
@ -264,7 +264,7 @@ func TestJWKSWriterControllerSync(t *testing.T) {
|
||||
newSecret := func(activeJWKPath, jwksPath string) *corev1.Secret {
|
||||
s := corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: goodOPCWithStatus.Status.Secrets.JWKS.Name,
|
||||
Name: goodFederationDomainWithStatus.Status.Secrets.JWKS.Name,
|
||||
Namespace: namespace,
|
||||
Labels: map[string]string{
|
||||
"myLabelKey1": "myLabelValue1",
|
||||
@ -272,15 +272,16 @@ func TestJWKSWriterControllerSync(t *testing.T) {
|
||||
},
|
||||
OwnerReferences: []metav1.OwnerReference{
|
||||
{
|
||||
APIVersion: opcGVR.GroupVersion().String(),
|
||||
APIVersion: federationDomainGVR.GroupVersion().String(),
|
||||
Kind: "FederationDomain",
|
||||
Name: goodOPC.Name,
|
||||
UID: goodOPC.UID,
|
||||
Name: goodFederationDomain.Name,
|
||||
UID: goodFederationDomain.UID,
|
||||
BlockOwnerDeletion: boolPtr(true),
|
||||
Controller: boolPtr(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
Type: "secrets.pinniped.dev/federation-domain-jwks",
|
||||
}
|
||||
s.Data = make(map[string][]byte)
|
||||
if activeJWKPath != "" {
|
||||
@ -294,40 +295,43 @@ func TestJWKSWriterControllerSync(t *testing.T) {
|
||||
|
||||
goodSecret := newSecret("testdata/good-jwk.json", "testdata/good-jwks.json")
|
||||
|
||||
secretWithWrongType := newSecret("testdata/good-jwk.json", "testdata/good-jwks.json")
|
||||
secretWithWrongType.Type = "not-the-right-type"
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
key controllerlib.Key
|
||||
secrets []*corev1.Secret
|
||||
configKubeClient func(*kubernetesfake.Clientset)
|
||||
configPinnipedClient func(*pinnipedfake.Clientset)
|
||||
opcs []*configv1alpha1.FederationDomain
|
||||
generateKeyErr error
|
||||
wantGenerateKeyCount int
|
||||
wantSecretActions []kubetesting.Action
|
||||
wantOPCActions []kubetesting.Action
|
||||
wantError string
|
||||
name string
|
||||
key controllerlib.Key
|
||||
secrets []*corev1.Secret
|
||||
configKubeClient func(*kubernetesfake.Clientset)
|
||||
configPinnipedClient func(*pinnipedfake.Clientset)
|
||||
federationDomains []*configv1alpha1.FederationDomain
|
||||
generateKeyErr error
|
||||
wantGenerateKeyCount int
|
||||
wantSecretActions []kubetesting.Action
|
||||
wantFederationDomainActions []kubetesting.Action
|
||||
wantError string
|
||||
}{
|
||||
{
|
||||
name: "new opc with no secret",
|
||||
key: controllerlib.Key{Namespace: goodOPC.Namespace, Name: goodOPC.Name},
|
||||
opcs: []*configv1alpha1.FederationDomain{
|
||||
goodOPC,
|
||||
name: "new federationDomain with no secret",
|
||||
key: controllerlib.Key{Namespace: goodFederationDomain.Namespace, Name: goodFederationDomain.Name},
|
||||
federationDomains: []*configv1alpha1.FederationDomain{
|
||||
goodFederationDomain,
|
||||
},
|
||||
wantGenerateKeyCount: 1,
|
||||
wantSecretActions: []kubetesting.Action{
|
||||
kubetesting.NewGetAction(secretGVR, namespace, goodSecret.Name),
|
||||
kubetesting.NewCreateAction(secretGVR, namespace, goodSecret),
|
||||
},
|
||||
wantOPCActions: []kubetesting.Action{
|
||||
kubetesting.NewGetAction(opcGVR, namespace, goodOPC.Name),
|
||||
kubetesting.NewUpdateAction(opcGVR, namespace, goodOPCWithStatus),
|
||||
wantFederationDomainActions: []kubetesting.Action{
|
||||
kubetesting.NewGetAction(federationDomainGVR, namespace, goodFederationDomain.Name),
|
||||
kubetesting.NewUpdateAction(federationDomainGVR, namespace, goodFederationDomainWithStatus),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "opc without status with existing secret",
|
||||
key: controllerlib.Key{Namespace: goodOPC.Namespace, Name: goodOPC.Name},
|
||||
opcs: []*configv1alpha1.FederationDomain{
|
||||
goodOPC,
|
||||
name: "federationDomain without status with existing secret",
|
||||
key: controllerlib.Key{Namespace: goodFederationDomain.Namespace, Name: goodFederationDomain.Name},
|
||||
federationDomains: []*configv1alpha1.FederationDomain{
|
||||
goodFederationDomain,
|
||||
},
|
||||
secrets: []*corev1.Secret{
|
||||
goodSecret,
|
||||
@ -336,46 +340,46 @@ func TestJWKSWriterControllerSync(t *testing.T) {
|
||||
wantSecretActions: []kubetesting.Action{
|
||||
kubetesting.NewGetAction(secretGVR, namespace, goodSecret.Name),
|
||||
},
|
||||
wantOPCActions: []kubetesting.Action{
|
||||
kubetesting.NewGetAction(opcGVR, namespace, goodOPC.Name),
|
||||
kubetesting.NewUpdateAction(opcGVR, namespace, goodOPCWithStatus),
|
||||
wantFederationDomainActions: []kubetesting.Action{
|
||||
kubetesting.NewGetAction(federationDomainGVR, namespace, goodFederationDomain.Name),
|
||||
kubetesting.NewUpdateAction(federationDomainGVR, namespace, goodFederationDomainWithStatus),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "existing opc with no secret",
|
||||
key: controllerlib.Key{Namespace: goodOPC.Namespace, Name: goodOPC.Name},
|
||||
opcs: []*configv1alpha1.FederationDomain{
|
||||
goodOPCWithStatus,
|
||||
name: "existing federationDomain with no secret",
|
||||
key: controllerlib.Key{Namespace: goodFederationDomain.Namespace, Name: goodFederationDomain.Name},
|
||||
federationDomains: []*configv1alpha1.FederationDomain{
|
||||
goodFederationDomainWithStatus,
|
||||
},
|
||||
wantGenerateKeyCount: 1,
|
||||
wantSecretActions: []kubetesting.Action{
|
||||
kubetesting.NewGetAction(secretGVR, namespace, goodSecret.Name),
|
||||
kubetesting.NewCreateAction(secretGVR, namespace, goodSecret),
|
||||
},
|
||||
wantOPCActions: []kubetesting.Action{
|
||||
kubetesting.NewGetAction(opcGVR, namespace, goodOPC.Name),
|
||||
wantFederationDomainActions: []kubetesting.Action{
|
||||
kubetesting.NewGetAction(federationDomainGVR, namespace, goodFederationDomain.Name),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "existing opc with existing secret",
|
||||
key: controllerlib.Key{Namespace: goodOPC.Namespace, Name: goodOPC.Name},
|
||||
opcs: []*configv1alpha1.FederationDomain{
|
||||
goodOPCWithStatus,
|
||||
name: "existing federationDomain with existing secret",
|
||||
key: controllerlib.Key{Namespace: goodFederationDomain.Namespace, Name: goodFederationDomain.Name},
|
||||
federationDomains: []*configv1alpha1.FederationDomain{
|
||||
goodFederationDomainWithStatus,
|
||||
},
|
||||
secrets: []*corev1.Secret{
|
||||
goodSecret,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "deleted opc",
|
||||
key: controllerlib.Key{Namespace: goodOPC.Namespace, Name: goodOPC.Name},
|
||||
name: "deleted federationDomain",
|
||||
key: controllerlib.Key{Namespace: goodFederationDomain.Namespace, Name: goodFederationDomain.Name},
|
||||
// Nothing to do here since Kube will garbage collect our child secret via its OwnerReference.
|
||||
},
|
||||
{
|
||||
name: "missing jwk in secret",
|
||||
key: controllerlib.Key{Namespace: goodOPC.Namespace, Name: goodOPC.Name},
|
||||
opcs: []*configv1alpha1.FederationDomain{
|
||||
goodOPCWithStatus,
|
||||
key: controllerlib.Key{Namespace: goodFederationDomain.Namespace, Name: goodFederationDomain.Name},
|
||||
federationDomains: []*configv1alpha1.FederationDomain{
|
||||
goodFederationDomainWithStatus,
|
||||
},
|
||||
secrets: []*corev1.Secret{
|
||||
newSecret("", "testdata/good-jwks.json"),
|
||||
@ -385,15 +389,15 @@ func TestJWKSWriterControllerSync(t *testing.T) {
|
||||
kubetesting.NewGetAction(secretGVR, namespace, goodSecret.Name),
|
||||
kubetesting.NewUpdateAction(secretGVR, namespace, goodSecret),
|
||||
},
|
||||
wantOPCActions: []kubetesting.Action{
|
||||
kubetesting.NewGetAction(opcGVR, namespace, goodOPC.Name),
|
||||
wantFederationDomainActions: []kubetesting.Action{
|
||||
kubetesting.NewGetAction(federationDomainGVR, namespace, goodFederationDomain.Name),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "missing jwks in secret",
|
||||
key: controllerlib.Key{Namespace: goodOPC.Namespace, Name: goodOPC.Name},
|
||||
opcs: []*configv1alpha1.FederationDomain{
|
||||
goodOPCWithStatus,
|
||||
key: controllerlib.Key{Namespace: goodFederationDomain.Namespace, Name: goodFederationDomain.Name},
|
||||
federationDomains: []*configv1alpha1.FederationDomain{
|
||||
goodFederationDomainWithStatus,
|
||||
},
|
||||
secrets: []*corev1.Secret{
|
||||
newSecret("testdata/good-jwk.json", ""),
|
||||
@ -403,15 +407,33 @@ func TestJWKSWriterControllerSync(t *testing.T) {
|
||||
kubetesting.NewGetAction(secretGVR, namespace, goodSecret.Name),
|
||||
kubetesting.NewUpdateAction(secretGVR, namespace, goodSecret),
|
||||
},
|
||||
wantOPCActions: []kubetesting.Action{
|
||||
kubetesting.NewGetAction(opcGVR, namespace, goodOPC.Name),
|
||||
wantFederationDomainActions: []kubetesting.Action{
|
||||
kubetesting.NewGetAction(federationDomainGVR, namespace, goodFederationDomain.Name),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "wrong type in secret",
|
||||
key: controllerlib.Key{Namespace: goodFederationDomain.Namespace, Name: goodFederationDomain.Name},
|
||||
federationDomains: []*configv1alpha1.FederationDomain{
|
||||
goodFederationDomainWithStatus,
|
||||
},
|
||||
secrets: []*corev1.Secret{
|
||||
secretWithWrongType,
|
||||
},
|
||||
wantGenerateKeyCount: 1,
|
||||
wantSecretActions: []kubetesting.Action{
|
||||
kubetesting.NewGetAction(secretGVR, namespace, goodSecret.Name),
|
||||
kubetesting.NewUpdateAction(secretGVR, namespace, goodSecret),
|
||||
},
|
||||
wantFederationDomainActions: []kubetesting.Action{
|
||||
kubetesting.NewGetAction(federationDomainGVR, namespace, goodFederationDomain.Name),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid jwk JSON in secret",
|
||||
key: controllerlib.Key{Namespace: goodOPC.Namespace, Name: goodOPC.Name},
|
||||
opcs: []*configv1alpha1.FederationDomain{
|
||||
goodOPCWithStatus,
|
||||
key: controllerlib.Key{Namespace: goodFederationDomain.Namespace, Name: goodFederationDomain.Name},
|
||||
federationDomains: []*configv1alpha1.FederationDomain{
|
||||
goodFederationDomainWithStatus,
|
||||
},
|
||||
secrets: []*corev1.Secret{
|
||||
newSecret("testdata/not-json.txt", "testdata/good-jwks.json"),
|
||||
@ -421,15 +443,15 @@ func TestJWKSWriterControllerSync(t *testing.T) {
|
||||
kubetesting.NewGetAction(secretGVR, namespace, goodSecret.Name),
|
||||
kubetesting.NewUpdateAction(secretGVR, namespace, goodSecret),
|
||||
},
|
||||
wantOPCActions: []kubetesting.Action{
|
||||
kubetesting.NewGetAction(opcGVR, namespace, goodOPC.Name),
|
||||
wantFederationDomainActions: []kubetesting.Action{
|
||||
kubetesting.NewGetAction(federationDomainGVR, namespace, goodFederationDomain.Name),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid jwks JSON in secret",
|
||||
key: controllerlib.Key{Namespace: goodOPC.Namespace, Name: goodOPC.Name},
|
||||
opcs: []*configv1alpha1.FederationDomain{
|
||||
goodOPCWithStatus,
|
||||
key: controllerlib.Key{Namespace: goodFederationDomain.Namespace, Name: goodFederationDomain.Name},
|
||||
federationDomains: []*configv1alpha1.FederationDomain{
|
||||
goodFederationDomainWithStatus,
|
||||
},
|
||||
secrets: []*corev1.Secret{
|
||||
newSecret("testdata/good-jwk.json", "testdata/not-json.txt"),
|
||||
@ -439,15 +461,15 @@ func TestJWKSWriterControllerSync(t *testing.T) {
|
||||
kubetesting.NewGetAction(secretGVR, namespace, goodSecret.Name),
|
||||
kubetesting.NewUpdateAction(secretGVR, namespace, goodSecret),
|
||||
},
|
||||
wantOPCActions: []kubetesting.Action{
|
||||
kubetesting.NewGetAction(opcGVR, namespace, goodOPC.Name),
|
||||
wantFederationDomainActions: []kubetesting.Action{
|
||||
kubetesting.NewGetAction(federationDomainGVR, namespace, goodFederationDomain.Name),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "public jwk in secret",
|
||||
key: controllerlib.Key{Namespace: goodOPC.Namespace, Name: goodOPC.Name},
|
||||
opcs: []*configv1alpha1.FederationDomain{
|
||||
goodOPCWithStatus,
|
||||
key: controllerlib.Key{Namespace: goodFederationDomain.Namespace, Name: goodFederationDomain.Name},
|
||||
federationDomains: []*configv1alpha1.FederationDomain{
|
||||
goodFederationDomainWithStatus,
|
||||
},
|
||||
secrets: []*corev1.Secret{
|
||||
newSecret("testdata/public-jwk.json", "testdata/good-jwks.json"),
|
||||
@ -457,15 +479,15 @@ func TestJWKSWriterControllerSync(t *testing.T) {
|
||||
kubetesting.NewGetAction(secretGVR, namespace, goodSecret.Name),
|
||||
kubetesting.NewUpdateAction(secretGVR, namespace, goodSecret),
|
||||
},
|
||||
wantOPCActions: []kubetesting.Action{
|
||||
kubetesting.NewGetAction(opcGVR, namespace, goodOPC.Name),
|
||||
wantFederationDomainActions: []kubetesting.Action{
|
||||
kubetesting.NewGetAction(federationDomainGVR, namespace, goodFederationDomain.Name),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "private jwks in secret",
|
||||
key: controllerlib.Key{Namespace: goodOPC.Namespace, Name: goodOPC.Name},
|
||||
opcs: []*configv1alpha1.FederationDomain{
|
||||
goodOPCWithStatus,
|
||||
key: controllerlib.Key{Namespace: goodFederationDomain.Namespace, Name: goodFederationDomain.Name},
|
||||
federationDomains: []*configv1alpha1.FederationDomain{
|
||||
goodFederationDomainWithStatus,
|
||||
},
|
||||
secrets: []*corev1.Secret{
|
||||
newSecret("testdata/good-jwk.json", "testdata/private-jwks.json"),
|
||||
@ -475,15 +497,15 @@ func TestJWKSWriterControllerSync(t *testing.T) {
|
||||
kubetesting.NewGetAction(secretGVR, namespace, goodSecret.Name),
|
||||
kubetesting.NewUpdateAction(secretGVR, namespace, goodSecret),
|
||||
},
|
||||
wantOPCActions: []kubetesting.Action{
|
||||
kubetesting.NewGetAction(opcGVR, namespace, goodOPC.Name),
|
||||
wantFederationDomainActions: []kubetesting.Action{
|
||||
kubetesting.NewGetAction(federationDomainGVR, namespace, goodFederationDomain.Name),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid jwk key in secret",
|
||||
key: controllerlib.Key{Namespace: goodOPC.Namespace, Name: goodOPC.Name},
|
||||
opcs: []*configv1alpha1.FederationDomain{
|
||||
goodOPCWithStatus,
|
||||
key: controllerlib.Key{Namespace: goodFederationDomain.Namespace, Name: goodFederationDomain.Name},
|
||||
federationDomains: []*configv1alpha1.FederationDomain{
|
||||
goodFederationDomainWithStatus,
|
||||
},
|
||||
secrets: []*corev1.Secret{
|
||||
newSecret("testdata/invalid-key-jwk.json", "testdata/good-jwks.json"),
|
||||
@ -493,15 +515,15 @@ func TestJWKSWriterControllerSync(t *testing.T) {
|
||||
kubetesting.NewGetAction(secretGVR, namespace, goodSecret.Name),
|
||||
kubetesting.NewUpdateAction(secretGVR, namespace, goodSecret),
|
||||
},
|
||||
wantOPCActions: []kubetesting.Action{
|
||||
kubetesting.NewGetAction(opcGVR, namespace, goodOPC.Name),
|
||||
wantFederationDomainActions: []kubetesting.Action{
|
||||
kubetesting.NewGetAction(federationDomainGVR, namespace, goodFederationDomain.Name),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid jwks key in secret",
|
||||
key: controllerlib.Key{Namespace: goodOPC.Namespace, Name: goodOPC.Name},
|
||||
opcs: []*configv1alpha1.FederationDomain{
|
||||
goodOPCWithStatus,
|
||||
key: controllerlib.Key{Namespace: goodFederationDomain.Namespace, Name: goodFederationDomain.Name},
|
||||
federationDomains: []*configv1alpha1.FederationDomain{
|
||||
goodFederationDomainWithStatus,
|
||||
},
|
||||
secrets: []*corev1.Secret{
|
||||
newSecret("testdata/good-jwk.json", "testdata/invalid-key-jwks.json"),
|
||||
@ -511,15 +533,15 @@ func TestJWKSWriterControllerSync(t *testing.T) {
|
||||
kubetesting.NewGetAction(secretGVR, namespace, goodSecret.Name),
|
||||
kubetesting.NewUpdateAction(secretGVR, namespace, goodSecret),
|
||||
},
|
||||
wantOPCActions: []kubetesting.Action{
|
||||
kubetesting.NewGetAction(opcGVR, namespace, goodOPC.Name),
|
||||
wantFederationDomainActions: []kubetesting.Action{
|
||||
kubetesting.NewGetAction(federationDomainGVR, namespace, goodFederationDomain.Name),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "missing active jwks in secret",
|
||||
key: controllerlib.Key{Namespace: goodOPC.Namespace, Name: goodOPC.Name},
|
||||
opcs: []*configv1alpha1.FederationDomain{
|
||||
goodOPCWithStatus,
|
||||
key: controllerlib.Key{Namespace: goodFederationDomain.Namespace, Name: goodFederationDomain.Name},
|
||||
federationDomains: []*configv1alpha1.FederationDomain{
|
||||
goodFederationDomainWithStatus,
|
||||
},
|
||||
secrets: []*corev1.Secret{
|
||||
newSecret("testdata/good-jwk.json", "testdata/missing-active-jwks.json"),
|
||||
@ -529,24 +551,24 @@ func TestJWKSWriterControllerSync(t *testing.T) {
|
||||
kubetesting.NewGetAction(secretGVR, namespace, goodSecret.Name),
|
||||
kubetesting.NewUpdateAction(secretGVR, namespace, goodSecret),
|
||||
},
|
||||
wantOPCActions: []kubetesting.Action{
|
||||
kubetesting.NewGetAction(opcGVR, namespace, goodOPC.Name),
|
||||
wantFederationDomainActions: []kubetesting.Action{
|
||||
kubetesting.NewGetAction(federationDomainGVR, namespace, goodFederationDomain.Name),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "generate key fails",
|
||||
key: controllerlib.Key{Namespace: goodOPC.Namespace, Name: goodOPC.Name},
|
||||
opcs: []*configv1alpha1.FederationDomain{
|
||||
goodOPCWithStatus,
|
||||
key: controllerlib.Key{Namespace: goodFederationDomain.Namespace, Name: goodFederationDomain.Name},
|
||||
federationDomains: []*configv1alpha1.FederationDomain{
|
||||
goodFederationDomainWithStatus,
|
||||
},
|
||||
generateKeyErr: errors.New("some generate error"),
|
||||
wantError: "cannot generate secret: cannot generate key: some generate error",
|
||||
},
|
||||
{
|
||||
name: "get secret fails",
|
||||
key: controllerlib.Key{Namespace: goodOPC.Namespace, Name: goodOPC.Name},
|
||||
opcs: []*configv1alpha1.FederationDomain{
|
||||
goodOPC,
|
||||
key: controllerlib.Key{Namespace: goodFederationDomain.Namespace, Name: goodFederationDomain.Name},
|
||||
federationDomains: []*configv1alpha1.FederationDomain{
|
||||
goodFederationDomain,
|
||||
},
|
||||
configKubeClient: func(client *kubernetesfake.Clientset) {
|
||||
client.PrependReactor("get", "secrets", func(_ kubetesting.Action) (bool, runtime.Object, error) {
|
||||
@ -557,9 +579,9 @@ func TestJWKSWriterControllerSync(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "create secret fails",
|
||||
key: controllerlib.Key{Namespace: goodOPC.Namespace, Name: goodOPC.Name},
|
||||
opcs: []*configv1alpha1.FederationDomain{
|
||||
goodOPC,
|
||||
key: controllerlib.Key{Namespace: goodFederationDomain.Namespace, Name: goodFederationDomain.Name},
|
||||
federationDomains: []*configv1alpha1.FederationDomain{
|
||||
goodFederationDomain,
|
||||
},
|
||||
configKubeClient: func(client *kubernetesfake.Clientset) {
|
||||
client.PrependReactor("create", "secrets", func(_ kubetesting.Action) (bool, runtime.Object, error) {
|
||||
@ -570,9 +592,9 @@ func TestJWKSWriterControllerSync(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "update secret fails",
|
||||
key: controllerlib.Key{Namespace: goodOPC.Namespace, Name: goodOPC.Name},
|
||||
opcs: []*configv1alpha1.FederationDomain{
|
||||
goodOPC,
|
||||
key: controllerlib.Key{Namespace: goodFederationDomain.Namespace, Name: goodFederationDomain.Name},
|
||||
federationDomains: []*configv1alpha1.FederationDomain{
|
||||
goodFederationDomain,
|
||||
},
|
||||
secrets: []*corev1.Secret{
|
||||
newSecret("", ""),
|
||||
@ -585,30 +607,30 @@ func TestJWKSWriterControllerSync(t *testing.T) {
|
||||
wantError: "cannot create or update secret: some update error",
|
||||
},
|
||||
{
|
||||
name: "get opc fails",
|
||||
key: controllerlib.Key{Namespace: goodOPC.Namespace, Name: goodOPC.Name},
|
||||
opcs: []*configv1alpha1.FederationDomain{
|
||||
goodOPC,
|
||||
name: "get FederationDomain fails",
|
||||
key: controllerlib.Key{Namespace: goodFederationDomain.Namespace, Name: goodFederationDomain.Name},
|
||||
federationDomains: []*configv1alpha1.FederationDomain{
|
||||
goodFederationDomain,
|
||||
},
|
||||
configPinnipedClient: func(client *pinnipedfake.Clientset) {
|
||||
client.PrependReactor("get", "federationdomains", func(_ kubetesting.Action) (bool, runtime.Object, error) {
|
||||
return true, nil, errors.New("some get error")
|
||||
})
|
||||
},
|
||||
wantError: "cannot update opc: cannot get opc: some get error",
|
||||
wantError: "cannot update FederationDomain: cannot get FederationDomain: some get error",
|
||||
},
|
||||
{
|
||||
name: "update opc fails",
|
||||
key: controllerlib.Key{Namespace: goodOPC.Namespace, Name: goodOPC.Name},
|
||||
opcs: []*configv1alpha1.FederationDomain{
|
||||
goodOPC,
|
||||
name: "update federationDomain fails",
|
||||
key: controllerlib.Key{Namespace: goodFederationDomain.Namespace, Name: goodFederationDomain.Name},
|
||||
federationDomains: []*configv1alpha1.FederationDomain{
|
||||
goodFederationDomain,
|
||||
},
|
||||
configPinnipedClient: func(client *pinnipedfake.Clientset) {
|
||||
client.PrependReactor("update", "federationdomains", func(_ kubetesting.Action) (bool, runtime.Object, error) {
|
||||
return true, nil, errors.New("some update error")
|
||||
})
|
||||
},
|
||||
wantError: "cannot update opc: some update error",
|
||||
wantError: "cannot update FederationDomain: some update error",
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
@ -636,9 +658,9 @@ func TestJWKSWriterControllerSync(t *testing.T) {
|
||||
|
||||
pinnipedAPIClient := pinnipedfake.NewSimpleClientset()
|
||||
pinnipedInformerClient := pinnipedfake.NewSimpleClientset()
|
||||
for _, opc := range test.opcs {
|
||||
require.NoError(t, pinnipedAPIClient.Tracker().Add(opc))
|
||||
require.NoError(t, pinnipedInformerClient.Tracker().Add(opc))
|
||||
for _, federationDomain := range test.federationDomains {
|
||||
require.NoError(t, pinnipedAPIClient.Tracker().Add(federationDomain))
|
||||
require.NoError(t, pinnipedInformerClient.Tracker().Add(federationDomain))
|
||||
}
|
||||
if test.configPinnipedClient != nil {
|
||||
test.configPinnipedClient(pinnipedAPIClient)
|
||||
@ -685,8 +707,8 @@ func TestJWKSWriterControllerSync(t *testing.T) {
|
||||
if test.wantSecretActions != nil {
|
||||
require.Equal(t, test.wantSecretActions, kubeAPIClient.Actions())
|
||||
}
|
||||
if test.wantOPCActions != nil {
|
||||
require.Equal(t, test.wantOPCActions, pinnipedAPIClient.Actions())
|
||||
if test.wantFederationDomainActions != nil {
|
||||
require.Equal(t, test.wantFederationDomainActions, pinnipedAPIClient.Actions())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ import (
|
||||
"github.com/coreos/go-oidc"
|
||||
"github.com/go-logr/logr"
|
||||
"golang.org/x/oauth2"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/equality"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
@ -39,9 +40,10 @@ const (
|
||||
controllerName = "upstream-observer"
|
||||
|
||||
// Constants related to the client credentials Secret.
|
||||
oidcClientSecretType = "secrets.pinniped.dev/oidc-client"
|
||||
clientIDDataKey = "clientID"
|
||||
clientSecretDataKey = "clientSecret"
|
||||
oidcClientSecretType corev1.SecretType = "secrets.pinniped.dev/oidc-client"
|
||||
|
||||
clientIDDataKey = "clientID"
|
||||
clientSecretDataKey = "clientSecret"
|
||||
|
||||
// Constants related to the OIDC provider discovery cache. These do not affect the cache of JWKS.
|
||||
validatorCacheTTL = 15 * time.Minute
|
||||
@ -97,12 +99,12 @@ func (c *lruValidatorCache) cacheKey(spec *v1alpha1.OIDCIdentityProviderSpec) in
|
||||
}
|
||||
|
||||
type controller struct {
|
||||
cache IDPCache
|
||||
log logr.Logger
|
||||
client pinnipedclientset.Interface
|
||||
providers idpinformers.OIDCIdentityProviderInformer
|
||||
secrets corev1informers.SecretInformer
|
||||
validatorCache interface {
|
||||
cache IDPCache
|
||||
log logr.Logger
|
||||
client pinnipedclientset.Interface
|
||||
oidcIdentityProviderInformer idpinformers.OIDCIdentityProviderInformer
|
||||
secretInformer corev1informers.SecretInformer
|
||||
validatorCache interface {
|
||||
getProvider(*v1alpha1.OIDCIdentityProviderSpec) (*oidc.Provider, *http.Client)
|
||||
putProvider(*v1alpha1.OIDCIdentityProviderSpec, *oidc.Provider, *http.Client)
|
||||
}
|
||||
@ -112,29 +114,29 @@ type controller struct {
|
||||
func New(
|
||||
idpCache IDPCache,
|
||||
client pinnipedclientset.Interface,
|
||||
providers idpinformers.OIDCIdentityProviderInformer,
|
||||
secrets corev1informers.SecretInformer,
|
||||
oidcIdentityProviderInformer idpinformers.OIDCIdentityProviderInformer,
|
||||
secretInformer corev1informers.SecretInformer,
|
||||
log logr.Logger,
|
||||
) controllerlib.Controller {
|
||||
c := controller{
|
||||
cache: idpCache,
|
||||
log: log.WithName(controllerName),
|
||||
client: client,
|
||||
providers: providers,
|
||||
secrets: secrets,
|
||||
validatorCache: &lruValidatorCache{cache: cache.NewExpiring()},
|
||||
cache: idpCache,
|
||||
log: log.WithName(controllerName),
|
||||
client: client,
|
||||
oidcIdentityProviderInformer: oidcIdentityProviderInformer,
|
||||
secretInformer: secretInformer,
|
||||
validatorCache: &lruValidatorCache{cache: cache.NewExpiring()},
|
||||
}
|
||||
filter := pinnipedcontroller.MatchAnythingFilter(pinnipedcontroller.SingletonQueue())
|
||||
return controllerlib.New(
|
||||
controllerlib.Config{Name: controllerName, Syncer: &c},
|
||||
controllerlib.WithInformer(providers, filter, controllerlib.InformerOption{}),
|
||||
controllerlib.WithInformer(secrets, filter, controllerlib.InformerOption{}),
|
||||
controllerlib.WithInformer(oidcIdentityProviderInformer, filter, controllerlib.InformerOption{}),
|
||||
controllerlib.WithInformer(secretInformer, filter, controllerlib.InformerOption{}),
|
||||
)
|
||||
}
|
||||
|
||||
// Sync implements controllerlib.Syncer.
|
||||
func (c *controller) Sync(ctx controllerlib.Context) error {
|
||||
actualUpstreams, err := c.providers.Lister().List(labels.Everything())
|
||||
actualUpstreams, err := c.oidcIdentityProviderInformer.Lister().List(labels.Everything())
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to list OIDCIdentityProviders: %w", err)
|
||||
}
|
||||
@ -196,7 +198,7 @@ func (c *controller) validateSecret(upstream *v1alpha1.OIDCIdentityProvider, res
|
||||
secretName := upstream.Spec.Client.SecretName
|
||||
|
||||
// Fetch the Secret from informer cache.
|
||||
secret, err := c.secrets.Lister().Secrets(upstream.Namespace).Get(secretName)
|
||||
secret, err := c.secretInformer.Lister().Secrets(upstream.Namespace).Get(secretName)
|
||||
if err != nil {
|
||||
return &v1alpha1.Condition{
|
||||
Type: typeClientCredsValid,
|
||||
|
@ -86,7 +86,7 @@ func (a *accessTokenStorage) getSession(ctx context.Context, signature string) (
|
||||
rv, err := a.storage.Get(ctx, signature, session)
|
||||
|
||||
if errors.IsNotFound(err) {
|
||||
return nil, "", fosite.ErrNotFound.WithCause(err).WithDebug(err.Error())
|
||||
return nil, "", fosite.ErrNotFound.WithWrap(err).WithDebug(err.Error())
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
|
@ -110,7 +110,7 @@ func (a *authorizeCodeStorage) getSession(ctx context.Context, signature string)
|
||||
rv, err := a.storage.Get(ctx, signature, session)
|
||||
|
||||
if errors.IsNotFound(err) {
|
||||
return nil, "", fosite.ErrNotFound.WithCause(err).WithDebug(err.Error())
|
||||
return nil, "", fosite.ErrNotFound.WithWrap(err).WithDebug(err.Error())
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
@ -168,161 +168,161 @@ func (e *errSerializationFailureWithCause) Error() string {
|
||||
// ExpectedAuthorizeCodeSessionJSONFromFuzzing is used for round tripping tests.
|
||||
// It is exported to allow integration tests to use it.
|
||||
const ExpectedAuthorizeCodeSessionJSONFromFuzzing = `{
|
||||
"active": true,
|
||||
"request": {
|
||||
"id": "嫎l蟲aƖ啘艿",
|
||||
"active": true,
|
||||
"request": {
|
||||
"id": "曑x螠Gæ鄋楨",
|
||||
"requestedAt": "2082-11-10T18:36:11.627253638Z",
|
||||
"client": {
|
||||
"id": "!ſɄĈp[述齛ʘUȻ.5ȿE",
|
||||
"client_secret": "UQ==",
|
||||
"redirect_uris": [
|
||||
"ǣ珑 ʑ飶畛Ȳ螤Yɫüeɯ紤邥翔勋\\",
|
||||
"Bʒ;",
|
||||
"鿃攴Ųęʍ鎾ʦ©cÏN,Ġ/_"
|
||||
],
|
||||
"grant_types": [
|
||||
"憉sHĒ尥窘挼Ŀʼn"
|
||||
],
|
||||
"response_types": [
|
||||
"4",
|
||||
"ʄÔ@}i{絧遗Ū^ȝĸ谋Vʋ鱴閇T"
|
||||
],
|
||||
"scopes": [
|
||||
"R鴝順諲ŮŚ节ȭŀȋc剠鏯ɽÿ¸"
|
||||
],
|
||||
"audience": [
|
||||
"Ƥ"
|
||||
],
|
||||
"public": true,
|
||||
"jwks_uri": "BA瘪囷ɫCʄɢ雐譄uée'",
|
||||
"jwks": {
|
||||
"keys": [
|
||||
{
|
||||
"kty": "OKP",
|
||||
"crv": "Ed25519",
|
||||
"x": "nK9xgX_iN7u3u_i8YOO7ZRT_WK028Vd_nhtsUu7Eo6E",
|
||||
"x5u": {
|
||||
"Scheme": "",
|
||||
"Opaque": "",
|
||||
"User": null,
|
||||
"Host": "",
|
||||
"Path": "",
|
||||
"RawPath": "",
|
||||
"ForceQuery": false,
|
||||
"RawQuery": "",
|
||||
"Fragment": "",
|
||||
"RawFragment": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"kty": "OKP",
|
||||
"crv": "Ed25519",
|
||||
"x": "UbbswQgzWhfGCRlwQmMp6fw_HoIoqkIaKT-2XN2fuYU",
|
||||
"x5u": {
|
||||
"Scheme": "",
|
||||
"Opaque": "",
|
||||
"User": null,
|
||||
"Host": "",
|
||||
"Path": "",
|
||||
"RawPath": "",
|
||||
"ForceQuery": false,
|
||||
"RawQuery": "",
|
||||
"Fragment": "",
|
||||
"RawFragment": ""
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"token_endpoint_auth_method": "ŚǗƳȕ暭Q0ņP羾,塐",
|
||||
"request_uris": [
|
||||
"lj翻LH^俤µDzɹ@©|\u003eɃ",
|
||||
"[:c顎疻紵D"
|
||||
],
|
||||
"request_object_signing_alg": "m1Ì恣S@T嵇LJV,Æ櫔袆鋹奘",
|
||||
"token_endpoint_auth_signing_alg": "Fãƻʚ肈ą8O+a駣"
|
||||
"id": ":NJ¸Ɣ8(黋馛ÄRɴJa¶z",
|
||||
"client_secret": "UQ==",
|
||||
"redirect_uris": [
|
||||
"ǖ枭kʍ切厦ȳ箦;¥ʊXĝ奨誷傥祩d",
|
||||
"zŇZ",
|
||||
"優蒼ĊɌț訫DŽǽeʀO2ƚ&N"
|
||||
],
|
||||
"grant_types": [
|
||||
"唐W6ɻ橩斚薛ɑƐ"
|
||||
],
|
||||
"response_types": [
|
||||
"w",
|
||||
"ǔŭe[u@阽羂ŷ-Ĵ½輢OÅ濲喾H"
|
||||
],
|
||||
"scopes": [
|
||||
"G螩歐湡ƙı唡ɸğƎ&胢輢Ƈĵƚ"
|
||||
],
|
||||
"audience": [
|
||||
"ě"
|
||||
],
|
||||
"public": false,
|
||||
"jwks_uri": "o*泞羅ʘ Ⱦķ瀊垰7ã\")",
|
||||
"jwks": {
|
||||
"keys": [
|
||||
{
|
||||
"kty": "OKP",
|
||||
"crv": "Ed25519",
|
||||
"x": "nK9xgX_iN7u3u_i8YOO7ZRT_WK028Vd_nhtsUu7Eo6E",
|
||||
"x5u": {
|
||||
"Scheme": "",
|
||||
"Opaque": "",
|
||||
"User": null,
|
||||
"Host": "",
|
||||
"Path": "",
|
||||
"RawPath": "",
|
||||
"ForceQuery": false,
|
||||
"RawQuery": "",
|
||||
"Fragment": "",
|
||||
"RawFragment": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"kty": "OKP",
|
||||
"crv": "Ed25519",
|
||||
"x": "UbbswQgzWhfGCRlwQmMp6fw_HoIoqkIaKT-2XN2fuYU",
|
||||
"x5u": {
|
||||
"Scheme": "",
|
||||
"Opaque": "",
|
||||
"User": null,
|
||||
"Host": "",
|
||||
"Path": "",
|
||||
"RawPath": "",
|
||||
"ForceQuery": false,
|
||||
"RawQuery": "",
|
||||
"Fragment": "",
|
||||
"RawFragment": ""
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"token_endpoint_auth_method": "ƿʥǟȒ伉<x¹T鼓c吏",
|
||||
"request_uris": [
|
||||
"Ć捘j]=谅ʑɑɮ$Ól4Ȟ",
|
||||
",Q7钎漡臧n"
|
||||
],
|
||||
"request_object_signing_alg": "3@¡廜+v,淬Ʋ4Dʧ呩锏緍场",
|
||||
"token_endpoint_auth_signing_alg": "(ưƓǴ罷ǹ~]ea胠"
|
||||
},
|
||||
"scopes": [
|
||||
"ɼk瘸'鴵yſǮŁ±\u003eFA曎餄FxD溪",
|
||||
"綻N镪p赌h%桙dĽ"
|
||||
"ĩv絹b垇IŕĩǀŻQ'k頂箨J-a稆",
|
||||
"啶#昏Q遐*\\髎bŸ"
|
||||
],
|
||||
"grantedScopes": [
|
||||
"癗E]Ņʘʟ車s"
|
||||
"慂UFƼĮǡ鑻Z"
|
||||
],
|
||||
"form": {
|
||||
"蹬器ķ8ŷ萒寎廭#疶昄Ą-Ƃƞ轵": [
|
||||
"熞ĝƌĆ1ȇyǴ濎=Tʉȼʁŀ\u003c",
|
||||
"耡q戨稞R÷mȵg釽[ƞ@",
|
||||
"đ[嬧鱒Ȁ彆媚杨嶒ĤGÀ吧Lŷ"
|
||||
],
|
||||
"餟": [
|
||||
"蒍z\u0026(K鵢Kj ŏ9Q韉Ķ%",
|
||||
"輫ǘ(¨Ƞ亱6ě#嫀^xz ",
|
||||
"@耢ɝ^¡!犃ĹĐJí¿ō擫"
|
||||
]
|
||||
"褾攚ŝlĆ厦駳骪l拁乖¡J¿Ƈ妔": [
|
||||
"懧¥ɂĵ~Čyʊ恀c\"NJřðȿ/",
|
||||
"裢?霃谥vƘ:ƿ/濔Aʉ<",
|
||||
"ȭ$奍囀Dž悷鵱民撲ʓeŘ嬀j¤"
|
||||
],
|
||||
"诞": [
|
||||
"狲N<Cq罉ZPſĝEK郊©l",
|
||||
"餚LJ/ɷȑ潠[ĝU噤'pX ",
|
||||
"Y妶ǵ!ȁu狍ɶȳsčɦƦ诱"
|
||||
]
|
||||
},
|
||||
"session": {
|
||||
"Claims": {
|
||||
"JTI": "懫砰¿C筽娴ƓaPu镈賆ŗɰ",
|
||||
"Issuer": "皶竇瞍涘¹焕iǢǽɽĺŧ",
|
||||
"Subject": "矠M6ɡǜg炾ʙ$%o6肿Ȫ",
|
||||
"Audience": [
|
||||
"ƌÙ鯆GQơ鮫R嫁ɍUƞ9+u!Ȱ踾$"
|
||||
"Claims": {
|
||||
"JTI": "攬林Ñz焁糳¿o>Q鱙翑ȲŻ",
|
||||
"Issuer": "锰劝旣樎Ȱ鍌#ȳńƩŴȭ",
|
||||
"Subject": "绝TFNJĆw宵ɚeY48珎²",
|
||||
"Audience": [
|
||||
"éã越|j¦鲶H股ƲLŋZ-{5£踉4"
|
||||
],
|
||||
"Nonce": "5^驜Ŗ~ů崧軒q腟u尿",
|
||||
"ExpiresAt": "2065-11-30T13:47:03.613000626Z",
|
||||
"IssuedAt": "1976-02-22T09:57:20.479850437Z",
|
||||
"RequestedAt": "2016-04-13T04:18:53.648949323Z",
|
||||
"AuthTime": "2098-07-12T04:38:54.034043015Z",
|
||||
"AccessTokenHash": "嫯R",
|
||||
"AuthenticationContextClassReference": "¤'+ʣ",
|
||||
"AuthenticationMethodsReference": "L&ɽ艄ʬʏ",
|
||||
"CodeHash": "ğǫ\\aȊ4ț髄Al",
|
||||
"Extra": {
|
||||
"PƢ曰": {
|
||||
"ĸŴB岺Ð嫹Sx镯荫ő": [
|
||||
843216989
|
||||
],
|
||||
"Nonce": "us旸Ť/Õ薝隧;綡,鼞",
|
||||
"ExpiresAt": "2065-11-30T13:47:03.613000626Z",
|
||||
"IssuedAt": "1976-02-22T09:57:20.479850437Z",
|
||||
"RequestedAt": "2016-04-13T04:18:53.648949323Z",
|
||||
"AuthTime": "2098-07-12T04:38:54.034043015Z",
|
||||
"AccessTokenHash": "滮]",
|
||||
"AuthenticationContextClassReference": "°3\u003eÙ",
|
||||
"AuthenticationMethodsReference": "k?µ鱔ǤÂ",
|
||||
"CodeHash": "Țƒ1v¸KĶ跭};",
|
||||
"Extra": {
|
||||
"=ſ氆": {
|
||||
"Ƿī,廖ʡ彑V\\廳蟕Ț": [
|
||||
843216989
|
||||
],
|
||||
"蔯ʠ浵Ī": {
|
||||
"H\"nǕ=rlƆ褡{ǏSȳŅ": {
|
||||
"Žg": false
|
||||
},
|
||||
"枱鰧ɛ鸁A渇": null
|
||||
}
|
||||
},
|
||||
"斻遟a衪荖舃9闄岈锘肺ńʥƕU}j%": 2520197933
|
||||
"疂ư墫ɓ": {
|
||||
"\\BRë_g\"ʎ啴SƇMǃļ": {
|
||||
"ʦ4": false
|
||||
},
|
||||
"鶡萷ɵ啜s攦": null
|
||||
}
|
||||
},
|
||||
"Headers": {
|
||||
"Extra": {
|
||||
"熒ɘȏıȒ諃龟ŴŠ'耐Ƭ扵ƹ玄ɕwL": {
|
||||
"ýÏʥZq7烱藌\\捀¿őŧQ": {
|
||||
"微'X焌襱ǭɕņ殥!_": null,
|
||||
"荇届UȚ?戋璖$9\u00269舋": {
|
||||
"ɕ餦ÑEǰ哤癨浦浏1Rk頓ć§蚲6": true
|
||||
}
|
||||
},
|
||||
"鲒鿮禗O暒aJP鐜?ĮV嫎h譭ȉ]DĘ": [
|
||||
954647573
|
||||
]
|
||||
},
|
||||
"皩Ƭ}Ɇ.雬Ɨ´唁": 1572524915
|
||||
}
|
||||
},
|
||||
"ExpiresAt": {
|
||||
"\u003cqċ譈8ŪɎP绿MÅ": "2031-10-18T22:07:34.950803105Z",
|
||||
"ȸěaʜD捛?½ʀ+Ċ偢镳ʬÍɷȓ\u003c": "2049-05-13T15:27:20.968432454Z"
|
||||
},
|
||||
"Username": "1藍殙菥趏酱Nʎ\u0026^横懋ƶ峦Fïȫƅw",
|
||||
"Subject": "檾ĩĆ爨4犹|v炩f柏ʒ鴙*鸆偡"
|
||||
},
|
||||
"曓蓳n匟鯘磹*金爃鶴滱ůĮǐ_c3#": 2520197933
|
||||
}
|
||||
},
|
||||
"Headers": {
|
||||
"Extra": {
|
||||
"寱ĊƑ÷Ƒ螞费Ďğ~劰û橸ɽ銐ƭ?}": {
|
||||
"ȜʁɁ;Bd謺錳4帳ŅǃĊd": {
|
||||
"翢砜Fȏl鐉诳DT=3骜": {
|
||||
"ų厷ɁOƪ穋嶿鳈恱va|载ǰɱ汶C": false
|
||||
},
|
||||
"鸨EJ毕懴řĬń戹%c": null
|
||||
},
|
||||
"室癑勦e骲v0H晦XŘO溪V蔓Ȍ+~ē": [
|
||||
954647573
|
||||
]
|
||||
},
|
||||
"麈ƵDǀ\\郂üţ垂": 1572524915
|
||||
}
|
||||
},
|
||||
"ExpiresAt": {
|
||||
"'=ĸ闒NȢȰ.醋fʜ": "2031-10-18T22:07:34.950803105Z",
|
||||
"ɦüHêQ仏1őƖ2Ė暮唍ǞʜƢú4": "2049-05-13T15:27:20.968432454Z"
|
||||
},
|
||||
"Username": "+韁臯氃妪婝rȤ\"h丬鎒ơ娻}ɼƟȥE",
|
||||
"Subject": "龳ǽÙ龦O亾EW莛8嘶×姮c恭企"
|
||||
},
|
||||
"requestedAudience": [
|
||||
"肯Ûx穞Ƀ",
|
||||
"ź蕴3ǐ薝Ƅ腲=ʐ诂鱰屾Ê窢ɋ鄊qɠ谫"
|
||||
"邖ɐ5檄¬",
|
||||
"Ĭ葜SŦ餧Ĭ倏4ĵ嶼仒篻ɥ闣ʬ橳(ý綃"
|
||||
],
|
||||
"grantedAudience": [
|
||||
"ǵƕ牀1鞊\\ȹ)}鉍商OɄƣ圔,xĪ",
|
||||
"悾xn冏裻摼0Ʈ蚵Ȼ塕»£#稏扟X"
|
||||
"ʚƟ覣k眐4ĈtC嵽痊w©Ź榨Q|ô",
|
||||
"猊Ia瓕巈環_ɑ彨ƍ蛊ʚ£:設虝2"
|
||||
]
|
||||
},
|
||||
"version": "1"
|
||||
}`
|
||||
},
|
||||
"version": "1"
|
||||
}`
|
||||
|
@ -391,5 +391,5 @@ func TestFuzzAndJSONNewValidEmptyAuthorizeCodeSession(t *testing.T) {
|
||||
// while the fuzzer will panic if AuthorizeRequest changes in a way that cannot be fuzzed,
|
||||
// if it adds a new field that can be fuzzed, this check will fail
|
||||
// thus if AuthorizeRequest changes, we will detect it here (though we could possibly miss an omitempty field)
|
||||
require.Equal(t, ExpectedAuthorizeCodeSessionJSONFromFuzzing, authorizeCodeSessionJSONFromFuzzing)
|
||||
require.JSONEq(t, ExpectedAuthorizeCodeSessionJSONFromFuzzing, authorizeCodeSessionJSONFromFuzzing)
|
||||
}
|
||||
|
@ -88,7 +88,7 @@ func (a *openIDConnectRequestStorage) getSession(ctx context.Context, signature
|
||||
rv, err := a.storage.Get(ctx, signature, session)
|
||||
|
||||
if errors.IsNotFound(err) {
|
||||
return nil, "", fosite.ErrNotFound.WithCause(err).WithDebug(err.Error())
|
||||
return nil, "", fosite.ErrNotFound.WithWrap(err).WithDebug(err.Error())
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
|
@ -72,7 +72,7 @@ func (a *pkceStorage) getSession(ctx context.Context, signature string) (*sessio
|
||||
rv, err := a.storage.Get(ctx, signature, session)
|
||||
|
||||
if errors.IsNotFound(err) {
|
||||
return nil, "", fosite.ErrNotFound.WithCause(err).WithDebug(err.Error())
|
||||
return nil, "", fosite.ErrNotFound.WithWrap(err).WithDebug(err.Error())
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
|
@ -86,7 +86,7 @@ func (a *refreshTokenStorage) getSession(ctx context.Context, signature string)
|
||||
rv, err := a.storage.Get(ctx, signature, session)
|
||||
|
||||
if errors.IsNotFound(err) {
|
||||
return nil, "", fosite.ErrNotFound.WithCause(err).WithDebug(err.Error())
|
||||
return nil, "", fosite.ErrNotFound.WithWrap(err).WithDebug(err.Error())
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
|
@ -41,76 +41,62 @@ func TestAuthorizationEndpoint(t *testing.T) {
|
||||
fositeInvalidClientErrorBody = here.Doc(`
|
||||
{
|
||||
"error": "invalid_client",
|
||||
"error_verbose": "Client authentication failed (e.g., unknown client, no client authentication included, or unsupported authentication method)",
|
||||
"error_description": "Client authentication failed (e.g., unknown client, no client authentication included, or unsupported authentication method)\n\nThe requested OAuth 2.0 Client does not exist.",
|
||||
"error_hint": "The requested OAuth 2.0 Client does not exist.",
|
||||
"status_code": 401
|
||||
"error_description": "Client authentication failed (e.g., unknown client, no client authentication included, or unsupported authentication method). The requested OAuth 2.0 Client does not exist."
|
||||
}
|
||||
`)
|
||||
|
||||
fositeInvalidRedirectURIErrorBody = here.Doc(`
|
||||
{
|
||||
"error": "invalid_request",
|
||||
"error_verbose": "The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed",
|
||||
"error_description": "The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed\n\nThe \"redirect_uri\" parameter does not match any of the OAuth 2.0 Client's pre-registered redirect urls.",
|
||||
"error_hint": "The \"redirect_uri\" parameter does not match any of the OAuth 2.0 Client's pre-registered redirect urls.",
|
||||
"status_code": 400
|
||||
"error_description": "The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed. The 'redirect_uri' parameter does not match any of the OAuth 2.0 Client's pre-registered redirect urls."
|
||||
}
|
||||
`)
|
||||
|
||||
fositePromptHasNoneAndOtherValueErrorQuery = map[string]string{
|
||||
"error": "invalid_request",
|
||||
"error_description": "The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed\n\nParameter \"prompt\" was set to \"none\", but contains other values as well which is not allowed.",
|
||||
"error_hint": "Parameter \"prompt\" was set to \"none\", but contains other values as well which is not allowed.",
|
||||
"error_description": "The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed. Parameter 'prompt' was set to 'none', but contains other values as well which is not allowed.",
|
||||
"state": happyState,
|
||||
}
|
||||
|
||||
fositeMissingCodeChallengeErrorQuery = map[string]string{
|
||||
"error": "invalid_request",
|
||||
"error_description": "The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed\n\nClients must include a code_challenge when performing the authorize code flow, but it is missing.",
|
||||
"error_hint": "Clients must include a code_challenge when performing the authorize code flow, but it is missing.",
|
||||
"error_description": "The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed. Clients must include a code_challenge when performing the authorize code flow, but it is missing.",
|
||||
"state": happyState,
|
||||
}
|
||||
|
||||
fositeMissingCodeChallengeMethodErrorQuery = map[string]string{
|
||||
"error": "invalid_request",
|
||||
"error_description": "The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed\n\nClients must use code_challenge_method=S256, plain is not allowed.",
|
||||
"error_hint": "Clients must use code_challenge_method=S256, plain is not allowed.",
|
||||
"error_description": "The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed. Clients must use code_challenge_method=S256, plain is not allowed.",
|
||||
"state": happyState,
|
||||
}
|
||||
|
||||
fositeInvalidCodeChallengeErrorQuery = map[string]string{
|
||||
"error": "invalid_request",
|
||||
"error_description": "The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed\n\nThe code_challenge_method is not supported, use S256 instead.",
|
||||
"error_hint": "The code_challenge_method is not supported, use S256 instead.",
|
||||
"error_description": "The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed. The code_challenge_method is not supported, use S256 instead.",
|
||||
"state": happyState,
|
||||
}
|
||||
|
||||
fositeUnsupportedResponseTypeErrorQuery = map[string]string{
|
||||
"error": "unsupported_response_type",
|
||||
"error_description": "The authorization server does not support obtaining a token using this method\n\nThe client is not allowed to request response_type \"unsupported\".",
|
||||
"error_hint": `The client is not allowed to request response_type "unsupported".`,
|
||||
"error_description": "The authorization server does not support obtaining a token using this method. The client is not allowed to request response_type 'unsupported'.",
|
||||
"state": happyState,
|
||||
}
|
||||
|
||||
fositeInvalidScopeErrorQuery = map[string]string{
|
||||
"error": "invalid_scope",
|
||||
"error_description": "The requested scope is invalid, unknown, or malformed\n\nThe OAuth 2.0 Client is not allowed to request scope \"tuna\".",
|
||||
"error_hint": `The OAuth 2.0 Client is not allowed to request scope "tuna".`,
|
||||
"error_description": "The requested scope is invalid, unknown, or malformed. The OAuth 2.0 Client is not allowed to request scope 'tuna'.",
|
||||
"state": happyState,
|
||||
}
|
||||
|
||||
fositeInvalidStateErrorQuery = map[string]string{
|
||||
"error": "invalid_state",
|
||||
"error_description": "The state is missing or does not have enough characters and is therefore considered too weak\n\nRequest parameter \"state\" must be at least be 8 characters long to ensure sufficient entropy.",
|
||||
"error_hint": `Request parameter "state" must be at least be 8 characters long to ensure sufficient entropy.`,
|
||||
"error_description": "The state is missing or does not have enough characters and is therefore considered too weak. Request parameter 'state' must be at least be 8 characters long to ensure sufficient entropy.",
|
||||
"state": "short",
|
||||
}
|
||||
|
||||
fositeMissingResponseTypeErrorQuery = map[string]string{
|
||||
"error": "unsupported_response_type",
|
||||
"error_description": "The authorization server does not support obtaining a token using this method\n\nThe request is missing the \"response_type\"\" parameter.",
|
||||
"error_hint": `The request is missing the "response_type"" parameter.`,
|
||||
"error_description": "The authorization server does not support obtaining a token using this method. `The request is missing the 'response_type' parameter.",
|
||||
"state": happyState,
|
||||
}
|
||||
)
|
||||
|
@ -356,7 +356,7 @@ func TestCallbackEndpoint(t *testing.T) {
|
||||
).String(),
|
||||
csrfCookie: happyCSRFCookie,
|
||||
wantStatus: http.StatusFound,
|
||||
wantRedirectLocationRegexp: downstreamRedirectURI + `\?code=([^&]+)&scope=openid%20offline_access&state=` + happyDownstreamState,
|
||||
wantRedirectLocationRegexp: downstreamRedirectURI + `\?code=([^&]+)&scope=openid\+offline_access&state=` + happyDownstreamState,
|
||||
wantDownstreamIDTokenUsername: upstreamUsername,
|
||||
wantDownstreamIDTokenSubject: upstreamIssuer + "?sub=" + upstreamSubject,
|
||||
wantDownstreamRequestedScopes: []string{"openid", "offline_access"},
|
||||
|
@ -50,7 +50,7 @@ func (s *dynamicOpenIDConnectECDSAStrategy) GenerateIDToken(
|
||||
_, activeJwk := s.jwksProvider.GetJWKS(s.fositeConfig.IDTokenIssuer)
|
||||
if activeJwk == nil {
|
||||
plog.Debug("no JWK found for issuer", "issuer", s.fositeConfig.IDTokenIssuer)
|
||||
return "", fosite.ErrTemporarilyUnavailable.WithCause(constable.Error("no JWK found for issuer"))
|
||||
return "", fosite.ErrTemporarilyUnavailable.WithWrap(constable.Error("no JWK found for issuer"))
|
||||
}
|
||||
key, ok := activeJwk.Key.(*ecdsa.PrivateKey)
|
||||
if !ok {
|
||||
@ -65,7 +65,7 @@ func (s *dynamicOpenIDConnectECDSAStrategy) GenerateIDToken(
|
||||
"actualType",
|
||||
actualType,
|
||||
)
|
||||
return "", fosite.ErrServerError.WithCause(constable.Error("JWK must be of type ecdsa"))
|
||||
return "", fosite.ErrServerError.WithWrap(constable.Error("JWK must be of type ecdsa"))
|
||||
}
|
||||
|
||||
return compose.NewOpenIDConnectECDSAStrategy(s.fositeConfig, key).GenerateIDToken(ctx, requester)
|
||||
|
@ -266,11 +266,11 @@ func FositeErrorForLog(err error) []interface{} {
|
||||
rfc6749Error := fosite.ErrorToRFC6749Error(err)
|
||||
keysAndValues := make([]interface{}, 0)
|
||||
keysAndValues = append(keysAndValues, "name")
|
||||
keysAndValues = append(keysAndValues, rfc6749Error.Name)
|
||||
keysAndValues = append(keysAndValues, rfc6749Error.ErrorField)
|
||||
keysAndValues = append(keysAndValues, "status")
|
||||
keysAndValues = append(keysAndValues, rfc6749Error.Status())
|
||||
keysAndValues = append(keysAndValues, "description")
|
||||
keysAndValues = append(keysAndValues, rfc6749Error.Description)
|
||||
keysAndValues = append(keysAndValues, rfc6749Error.DescriptionField)
|
||||
return keysAndValues
|
||||
}
|
||||
|
||||
|
@ -11,15 +11,16 @@ import (
|
||||
"go.pinniped.dev/internal/constable"
|
||||
)
|
||||
|
||||
// FederationDomain represents all of the settings and state for an OIDC provider.
|
||||
type FederationDomain struct {
|
||||
// FederationDomainIssuer represents all of the settings and state for a downstream OIDC provider
|
||||
// as defined by a FederationDomain.
|
||||
type FederationDomainIssuer struct {
|
||||
issuer string
|
||||
issuerHost string
|
||||
issuerPath string
|
||||
}
|
||||
|
||||
func NewFederationDomain(issuer string) (*FederationDomain, error) {
|
||||
p := FederationDomain{issuer: issuer}
|
||||
func NewFederationDomainIssuer(issuer string) (*FederationDomainIssuer, error) {
|
||||
p := FederationDomainIssuer{issuer: issuer}
|
||||
err := p.validate()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -27,9 +28,9 @@ func NewFederationDomain(issuer string) (*FederationDomain, error) {
|
||||
return &p, nil
|
||||
}
|
||||
|
||||
func (p *FederationDomain) validate() error {
|
||||
func (p *FederationDomainIssuer) validate() error {
|
||||
if p.issuer == "" {
|
||||
return constable.Error("provider must have an issuer")
|
||||
return constable.Error("federation domain must have an issuer")
|
||||
}
|
||||
|
||||
issuerURL, err := url.Parse(p.issuer)
|
||||
@ -63,14 +64,14 @@ func (p *FederationDomain) validate() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *FederationDomain) Issuer() string {
|
||||
func (p *FederationDomainIssuer) Issuer() string {
|
||||
return p.issuer
|
||||
}
|
||||
|
||||
func (p *FederationDomain) IssuerHost() string {
|
||||
func (p *FederationDomainIssuer) IssuerHost() string {
|
||||
return p.issuerHost
|
||||
}
|
||||
|
||||
func (p *FederationDomain) IssuerPath() string {
|
||||
func (p *FederationDomainIssuer) IssuerPath() string {
|
||||
return p.issuerPath
|
||||
}
|
@ -9,16 +9,16 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestFederationDomainValidations(t *testing.T) {
|
||||
func TestFederationDomainIssuerValidations(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
issuer string
|
||||
wantError string
|
||||
}{
|
||||
{
|
||||
name: "provider must have an issuer",
|
||||
name: "must have an issuer",
|
||||
issuer: "",
|
||||
wantError: "provider must have an issuer",
|
||||
wantError: "federation domain must have an issuer",
|
||||
},
|
||||
{
|
||||
name: "no scheme",
|
||||
@ -72,7 +72,7 @@ func TestFederationDomainValidations(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
_, err := NewFederationDomain(tt.issuer)
|
||||
_, err := NewFederationDomainIssuer(tt.issuer)
|
||||
if tt.wantError != "" {
|
||||
require.EqualError(t, err, tt.wantError)
|
||||
} else {
|
@ -32,7 +32,7 @@ import (
|
||||
// It is thread-safe.
|
||||
type Manager struct {
|
||||
mu sync.RWMutex
|
||||
providers []*provider.FederationDomain
|
||||
providers []*provider.FederationDomainIssuer
|
||||
providerHandlers map[string]http.Handler // map of all routes for all providers
|
||||
nextHandler http.Handler // the next handler in a chain, called when this manager didn't know how to handle a request
|
||||
dynamicJWKSProvider jwks.DynamicJWKSProvider // in-memory cache of per-issuer JWKS data
|
||||
@ -68,9 +68,9 @@ func NewManager(
|
||||
// It also removes any providerHandlers that were previously added but were not passed in to
|
||||
// the current invocation.
|
||||
//
|
||||
// This method assumes that all of the FederationDomain arguments have already been validated
|
||||
// This method assumes that all of the FederationDomainIssuer arguments have already been validated
|
||||
// by someone else before they are passed to this method.
|
||||
func (m *Manager) SetProviders(federationDomains ...*provider.FederationDomain) {
|
||||
func (m *Manager) SetProviders(federationDomains ...*provider.FederationDomainIssuer) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
|
@ -348,9 +348,9 @@ func TestManager(t *testing.T) {
|
||||
|
||||
when("given some valid providers via SetProviders()", func() {
|
||||
it.Before(func() {
|
||||
p1, err := provider.NewFederationDomain(issuer1)
|
||||
p1, err := provider.NewFederationDomainIssuer(issuer1)
|
||||
r.NoError(err)
|
||||
p2, err := provider.NewFederationDomain(issuer2)
|
||||
p2, err := provider.NewFederationDomainIssuer(issuer2)
|
||||
r.NoError(err)
|
||||
subject.SetProviders(p1, p2)
|
||||
|
||||
@ -391,9 +391,9 @@ func TestManager(t *testing.T) {
|
||||
|
||||
when("given the same valid providers as arguments to SetProviders() in reverse order", func() {
|
||||
it.Before(func() {
|
||||
p1, err := provider.NewFederationDomain(issuer1)
|
||||
p1, err := provider.NewFederationDomainIssuer(issuer1)
|
||||
r.NoError(err)
|
||||
p2, err := provider.NewFederationDomain(issuer2)
|
||||
p2, err := provider.NewFederationDomainIssuer(issuer2)
|
||||
r.NoError(err)
|
||||
subject.SetProviders(p2, p1)
|
||||
|
||||
|
@ -78,138 +78,99 @@ var (
|
||||
return here.Docf(`
|
||||
{
|
||||
"error": "invalid_request",
|
||||
"error_verbose": "The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed",
|
||||
"error_description": "The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed\n\nHTTP method is \"%s\", expected \"POST\".",
|
||||
"error_hint": "HTTP method is \"%s\", expected \"POST\".",
|
||||
"status_code": 400
|
||||
"error_description": "The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed. HTTP method is '%s', expected 'POST'."
|
||||
}
|
||||
`, actual, actual)
|
||||
`, actual)
|
||||
}
|
||||
|
||||
fositeMissingGrantTypeErrorBody = here.Docf(`
|
||||
{
|
||||
"error": "invalid_request",
|
||||
"error_verbose": "The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed",
|
||||
"error_description": "The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed\n\nRequest parameter \"grant_type\"\" is missing",
|
||||
"error_hint": "Request parameter \"grant_type\"\" is missing",
|
||||
"status_code": 400
|
||||
"error_description": "The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed. Request parameter 'grant_type' is missing"
|
||||
}
|
||||
`)
|
||||
|
||||
fositeEmptyPayloadErrorBody = here.Doc(`
|
||||
{
|
||||
"error": "invalid_request",
|
||||
"error_verbose": "The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed",
|
||||
"error_description": "The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed\n\nThe POST body can not be empty.",
|
||||
"error_hint": "The POST body can not be empty.",
|
||||
"status_code": 400
|
||||
"error_description": "The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed. The POST body can not be empty."
|
||||
}
|
||||
`)
|
||||
|
||||
fositeInvalidPayloadErrorBody = here.Doc(`
|
||||
{
|
||||
"error": "invalid_request",
|
||||
"error_verbose": "The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed",
|
||||
"error_description": "The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed\n\nUnable to parse HTTP body, make sure to send a properly formatted form request body.",
|
||||
"error_hint": "Unable to parse HTTP body, make sure to send a properly formatted form request body.",
|
||||
"status_code": 400
|
||||
"error_description": "The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed. Unable to parse HTTP body, make sure to send a properly formatted form request body."
|
||||
}
|
||||
`)
|
||||
|
||||
fositeInvalidRequestErrorBody = here.Doc(`
|
||||
{
|
||||
"error": "invalid_request",
|
||||
"error_verbose": "The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed",
|
||||
"error_description": "The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed\n\nMake sure that the various parameters are correct, be aware of case sensitivity and trim your parameters. Make sure that the client you are using has exactly whitelisted the redirect_uri you specified.",
|
||||
"error_hint": "Make sure that the various parameters are correct, be aware of case sensitivity and trim your parameters. Make sure that the client you are using has exactly whitelisted the redirect_uri you specified.",
|
||||
"status_code": 400
|
||||
"error_description": "The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed. Make sure that the various parameters are correct, be aware of case sensitivity and trim your parameters. Make sure that the client you are using has exactly whitelisted the redirect_uri you specified."
|
||||
}
|
||||
`)
|
||||
|
||||
fositeInvalidRequestMissingGrantTypeErrorBody = here.Doc(`
|
||||
{
|
||||
"error": "invalid_request",
|
||||
"error_description": "The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed\n\nRequest parameter \"grant_type\"\" is missing",
|
||||
"error_hint": "Request parameter \"grant_type\"\" is missing",
|
||||
"error_verbose": "The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed",
|
||||
"status_code": 400
|
||||
"error_description": "The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed. Request parameter 'grant_type' is missing"
|
||||
}
|
||||
`)
|
||||
|
||||
fositeMissingClientErrorBody = here.Doc(`
|
||||
{
|
||||
"error": "invalid_request",
|
||||
"error_verbose": "The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed",
|
||||
"error_description": "The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed\n\nClient credentials missing or malformed in both HTTP Authorization header and HTTP POST body.",
|
||||
"error_hint": "Client credentials missing or malformed in both HTTP Authorization header and HTTP POST body.",
|
||||
"status_code": 400
|
||||
"error_description": "The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed. Client credentials missing or malformed in both HTTP Authorization header and HTTP POST body."
|
||||
}
|
||||
`)
|
||||
|
||||
fositeInvalidClientErrorBody = here.Doc(`
|
||||
{
|
||||
"error": "invalid_client",
|
||||
"error_verbose": "Client authentication failed (e.g., unknown client, no client authentication included, or unsupported authentication method)",
|
||||
"error_description": "Client authentication failed (e.g., unknown client, no client authentication included, or unsupported authentication method)",
|
||||
"status_code": 401
|
||||
"error_description": "Client authentication failed (e.g., unknown client, no client authentication included, or unsupported authentication method)."
|
||||
}
|
||||
`)
|
||||
|
||||
fositeInvalidAuthCodeErrorBody = here.Doc(`
|
||||
{
|
||||
"error": "invalid_grant",
|
||||
"error_verbose": "The provided authorization grant (e.g., authorization code, resource owner credentials) or refresh token is invalid, expired, revoked, does not match the redirection URI used in the authorization request, or was issued to another client",
|
||||
"error_description": "The provided authorization grant (e.g., authorization code, resource owner credentials) or refresh token is invalid, expired, revoked, does not match the redirection URI used in the authorization request, or was issued to another client",
|
||||
"status_code": 400
|
||||
"error_description": "The provided authorization grant (e.g., authorization code, resource owner credentials) or refresh token is invalid, expired, revoked, does not match the redirection URI used in the authorization request, or was issued to another client."
|
||||
}
|
||||
`)
|
||||
|
||||
fositeReusedAuthCodeErrorBody = here.Doc(`
|
||||
{
|
||||
"error": "invalid_grant",
|
||||
"error_verbose": "The provided authorization grant (e.g., authorization code, resource owner credentials) or refresh token is invalid, expired, revoked, does not match the redirection URI used in the authorization request, or was issued to another client",
|
||||
"error_description": "The provided authorization grant (e.g., authorization code, resource owner credentials) or refresh token is invalid, expired, revoked, does not match the redirection URI used in the authorization request, or was issued to another client\n\nThe authorization code has already been used.",
|
||||
"error_hint": "The authorization code has already been used.",
|
||||
"status_code": 400
|
||||
"error_description": "The provided authorization grant (e.g., authorization code, resource owner credentials) or refresh token is invalid, expired, revoked, does not match the redirection URI used in the authorization request, or was issued to another client. The authorization code has already been used."
|
||||
}
|
||||
`)
|
||||
|
||||
fositeInvalidRedirectURIErrorBody = here.Doc(`
|
||||
{
|
||||
"error": "invalid_grant",
|
||||
"error_verbose": "The provided authorization grant (e.g., authorization code, resource owner credentials) or refresh token is invalid, expired, revoked, does not match the redirection URI used in the authorization request, or was issued to another client",
|
||||
"error_description": "The provided authorization grant (e.g., authorization code, resource owner credentials) or refresh token is invalid, expired, revoked, does not match the redirection URI used in the authorization request, or was issued to another client\n\nThe \"redirect_uri\" from this request does not match the one from the authorize request.",
|
||||
"error_hint": "The \"redirect_uri\" from this request does not match the one from the authorize request.",
|
||||
"status_code": 400
|
||||
"error_description": "The provided authorization grant (e.g., authorization code, resource owner credentials) or refresh token is invalid, expired, revoked, does not match the redirection URI used in the authorization request, or was issued to another client. The 'redirect_uri' from this request does not match the one from the authorize request."
|
||||
}
|
||||
`)
|
||||
|
||||
fositeMissingPKCEVerifierErrorBody = here.Doc(`
|
||||
{
|
||||
"error": "invalid_grant",
|
||||
"error_verbose": "The provided authorization grant (e.g., authorization code, resource owner credentials) or refresh token is invalid, expired, revoked, does not match the redirection URI used in the authorization request, or was issued to another client",
|
||||
"error_description": "The provided authorization grant (e.g., authorization code, resource owner credentials) or refresh token is invalid, expired, revoked, does not match the redirection URI used in the authorization request, or was issued to another client\n\nThe PKCE code verifier must be at least 43 characters.",
|
||||
"error_hint": "The PKCE code verifier must be at least 43 characters.",
|
||||
"status_code": 400
|
||||
"error_description": "The provided authorization grant (e.g., authorization code, resource owner credentials) or refresh token is invalid, expired, revoked, does not match the redirection URI used in the authorization request, or was issued to another client. The PKCE code verifier must be at least 43 characters."
|
||||
}
|
||||
`)
|
||||
|
||||
fositeWrongPKCEVerifierErrorBody = here.Doc(`
|
||||
{
|
||||
"error": "invalid_grant",
|
||||
"error_verbose": "The provided authorization grant (e.g., authorization code, resource owner credentials) or refresh token is invalid, expired, revoked, does not match the redirection URI used in the authorization request, or was issued to another client",
|
||||
"error_description": "The provided authorization grant (e.g., authorization code, resource owner credentials) or refresh token is invalid, expired, revoked, does not match the redirection URI used in the authorization request, or was issued to another client\n\nThe PKCE code challenge did not match the code verifier.",
|
||||
"error_hint": "The PKCE code challenge did not match the code verifier.",
|
||||
"status_code": 400
|
||||
"error_description": "The provided authorization grant (e.g., authorization code, resource owner credentials) or refresh token is invalid, expired, revoked, does not match the redirection URI used in the authorization request, or was issued to another client. The PKCE code challenge did not match the code verifier."
|
||||
}
|
||||
`)
|
||||
|
||||
fositeTemporarilyUnavailableErrorBody = here.Doc(`
|
||||
{
|
||||
"error": "temporarily_unavailable",
|
||||
"error_description": "The authorization server is currently unable to handle the request due to a temporary overloading or maintenance of the server",
|
||||
"error_verbose": "The authorization server is currently unable to handle the request due to a temporary overloading or maintenance of the server",
|
||||
"status_code": 503
|
||||
"error_description": "The authorization server is currently unable to handle the request due to a temporary overloading or maintenance of the server."
|
||||
}
|
||||
`)
|
||||
|
||||
@ -749,7 +710,7 @@ func TestTokenExchange(t *testing.T) {
|
||||
},
|
||||
requestedAudience: "some-workload-cluster",
|
||||
wantStatus: http.StatusForbidden,
|
||||
wantResponseBodyContains: `missing the \"pinniped:request-audience\" scope`,
|
||||
wantResponseBodyContains: `missing the 'pinniped:request-audience' scope`,
|
||||
},
|
||||
{
|
||||
name: "access token missing openid scope",
|
||||
@ -766,7 +727,7 @@ func TestTokenExchange(t *testing.T) {
|
||||
},
|
||||
requestedAudience: "some-workload-cluster",
|
||||
wantStatus: http.StatusForbidden,
|
||||
wantResponseBodyContains: `missing the \"openid\" scope`,
|
||||
wantResponseBodyContains: `missing the 'openid' scope`,
|
||||
},
|
||||
{
|
||||
name: "token minting failure",
|
||||
|
@ -135,7 +135,7 @@ func (t *TokenExchangeHandler) validateAccessToken(ctx context.Context, requeste
|
||||
signature := t.accessTokenStrategy.AccessTokenSignature(accessToken)
|
||||
originalRequester, err := t.accessTokenStorage.GetAccessTokenSession(ctx, signature, requester.GetSession())
|
||||
if err != nil {
|
||||
return nil, fosite.ErrRequestUnauthorized.WithCause(err).WithHint("invalid subject_token")
|
||||
return nil, fosite.ErrRequestUnauthorized.WithWrap(err).WithHint("invalid subject_token")
|
||||
}
|
||||
return originalRequester, nil
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ storage:
|
||||
config:
|
||||
file: ":memory:"
|
||||
web:
|
||||
https: 0.0.0.0:443
|
||||
https: 0.0.0.0:8443
|
||||
tlsCert: /var/certs/dex.pem
|
||||
tlsKey: /var/certs/dex-key.pem
|
||||
oauth2:
|
||||
@ -20,7 +20,7 @@ oauth2:
|
||||
staticClients:
|
||||
- id: pinniped-cli
|
||||
name: 'Pinniped CLI'
|
||||
#! we can't have "public: true" until https://github.com/dexidp/dex/pull/1822 lands in Dex.
|
||||
public: true
|
||||
redirectURIs:
|
||||
- #@ "http://127.0.0.1:" + str(data.values.ports.cli) + "/callback"
|
||||
- #@ "http://[::1]:" + str(data.values.ports.cli) + "/callback"
|
||||
@ -76,7 +76,7 @@ spec:
|
||||
spec:
|
||||
containers:
|
||||
- name: dex
|
||||
image: quay.io/dexidp/dex:v2.10.0
|
||||
image: ghcr.io/dexidp/dex:v2.27.0
|
||||
imagePullPolicy: IfNotPresent
|
||||
command:
|
||||
- /usr/local/bin/dex
|
||||
@ -84,7 +84,7 @@ spec:
|
||||
- /etc/dex/cfg/config.yaml
|
||||
ports:
|
||||
- name: https
|
||||
containerPort: 443
|
||||
containerPort: 8443
|
||||
volumeMounts:
|
||||
- name: dex-config
|
||||
mountPath: /etc/dex/cfg
|
||||
@ -111,5 +111,7 @@ spec:
|
||||
selector:
|
||||
app: dex
|
||||
ports:
|
||||
- port: 443
|
||||
name: https
|
||||
- name: https
|
||||
port: 443
|
||||
targetPort: 8443
|
||||
|
||||
|
@ -25,7 +25,7 @@ spec:
|
||||
emptyDir: {}
|
||||
containers:
|
||||
- name: proxy
|
||||
image: docker.io/getpinniped/test-forward-proxy
|
||||
image: projects.registry.vmware.com/pinniped/test-forward-proxy
|
||||
imagePullPolicy: Always
|
||||
ports:
|
||||
- name: http
|
||||
|
@ -595,17 +595,17 @@ func requireStatus(t *testing.T, client pinnipedclientset.Interface, ns, name st
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
|
||||
defer cancel()
|
||||
|
||||
var opc *v1alpha1.FederationDomain
|
||||
var federationDomain *v1alpha1.FederationDomain
|
||||
var err error
|
||||
assert.Eventually(t, func() bool {
|
||||
opc, err = client.ConfigV1alpha1().FederationDomains(ns).Get(ctx, name, metav1.GetOptions{})
|
||||
federationDomain, err = client.ConfigV1alpha1().FederationDomains(ns).Get(ctx, name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
t.Logf("error trying to get FederationDomain: %s", err.Error())
|
||||
}
|
||||
return err == nil && opc.Status.Status == status
|
||||
return err == nil && federationDomain.Status.Status == status
|
||||
}, time.Minute, 200*time.Millisecond)
|
||||
require.NoError(t, err)
|
||||
require.Equalf(t, status, opc.Status.Status, "unexpected status (message = '%s')", opc.Status.Message)
|
||||
require.Equalf(t, status, federationDomain.Status.Status, "unexpected status (message = '%s')", federationDomain.Status.Message)
|
||||
}
|
||||
|
||||
func newHTTPClient(t *testing.T, caBundle string, dnsOverrides map[string]string) *http.Client {
|
||||
|
@ -27,71 +27,71 @@ func TestSupervisorSecrets(t *testing.T) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
|
||||
defer cancel()
|
||||
|
||||
// Create our OP under test.
|
||||
op := library.CreateTestFederationDomain(ctx, t, "", "", "")
|
||||
// Create our FederationDomain under test.
|
||||
federationDomain := library.CreateTestFederationDomain(ctx, t, "", "", "")
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
secretName func(op *configv1alpha1.FederationDomain) string
|
||||
secretName func(federationDomain *configv1alpha1.FederationDomain) string
|
||||
ensureValid func(t *testing.T, secret *corev1.Secret)
|
||||
}{
|
||||
{
|
||||
name: "csrf cookie signing key",
|
||||
secretName: func(op *configv1alpha1.FederationDomain) string {
|
||||
secretName: func(federationDomain *configv1alpha1.FederationDomain) string {
|
||||
return env.SupervisorAppName + "-key"
|
||||
},
|
||||
ensureValid: ensureValidSymmetricKey,
|
||||
ensureValid: ensureValidSymmetricSecretOfTypeFunc("secrets.pinniped.dev/supervisor-csrf-signing-key"),
|
||||
},
|
||||
{
|
||||
name: "jwks",
|
||||
secretName: func(op *configv1alpha1.FederationDomain) string {
|
||||
return op.Status.Secrets.JWKS.Name
|
||||
secretName: func(federationDomain *configv1alpha1.FederationDomain) string {
|
||||
return federationDomain.Status.Secrets.JWKS.Name
|
||||
},
|
||||
ensureValid: ensureValidJWKS,
|
||||
},
|
||||
{
|
||||
name: "hmac signing secret",
|
||||
secretName: func(op *configv1alpha1.FederationDomain) string {
|
||||
return op.Status.Secrets.TokenSigningKey.Name
|
||||
secretName: func(federationDomain *configv1alpha1.FederationDomain) string {
|
||||
return federationDomain.Status.Secrets.TokenSigningKey.Name
|
||||
},
|
||||
ensureValid: ensureValidSymmetricKey,
|
||||
ensureValid: ensureValidSymmetricSecretOfTypeFunc("secrets.pinniped.dev/federation-domain-token-signing-key"),
|
||||
},
|
||||
{
|
||||
name: "state signature secret",
|
||||
secretName: func(op *configv1alpha1.FederationDomain) string {
|
||||
return op.Status.Secrets.StateSigningKey.Name
|
||||
secretName: func(federationDomain *configv1alpha1.FederationDomain) string {
|
||||
return federationDomain.Status.Secrets.StateSigningKey.Name
|
||||
},
|
||||
ensureValid: ensureValidSymmetricKey,
|
||||
ensureValid: ensureValidSymmetricSecretOfTypeFunc("secrets.pinniped.dev/federation-domain-state-signing-key"),
|
||||
},
|
||||
{
|
||||
name: "state encryption secret",
|
||||
secretName: func(op *configv1alpha1.FederationDomain) string {
|
||||
return op.Status.Secrets.StateEncryptionKey.Name
|
||||
secretName: func(federationDomain *configv1alpha1.FederationDomain) string {
|
||||
return federationDomain.Status.Secrets.StateEncryptionKey.Name
|
||||
},
|
||||
ensureValid: ensureValidSymmetricKey,
|
||||
ensureValid: ensureValidSymmetricSecretOfTypeFunc("secrets.pinniped.dev/federation-domain-state-encryption-key"),
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
test := test
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
// Ensure a secret is created with the OP's JWKS.
|
||||
var updatedOP *configv1alpha1.FederationDomain
|
||||
// Ensure a secret is created with the FederationDomain's JWKS.
|
||||
var updatedFederationDomain *configv1alpha1.FederationDomain
|
||||
var err error
|
||||
assert.Eventually(t, func() bool {
|
||||
updatedOP, err = supervisorClient.
|
||||
updatedFederationDomain, err = supervisorClient.
|
||||
ConfigV1alpha1().
|
||||
FederationDomains(env.SupervisorNamespace).
|
||||
Get(ctx, op.Name, metav1.GetOptions{})
|
||||
return err == nil && test.secretName(updatedOP) != ""
|
||||
Get(ctx, federationDomain.Name, metav1.GetOptions{})
|
||||
return err == nil && test.secretName(updatedFederationDomain) != ""
|
||||
}, time.Second*10, time.Millisecond*500)
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, test.secretName(updatedOP))
|
||||
require.NotEmpty(t, test.secretName(updatedFederationDomain))
|
||||
|
||||
// Ensure the secret actually exists.
|
||||
secret, err := kubeClient.
|
||||
CoreV1().
|
||||
Secrets(env.SupervisorNamespace).
|
||||
Get(ctx, test.secretName(updatedOP), metav1.GetOptions{})
|
||||
Get(ctx, test.secretName(updatedFederationDomain), metav1.GetOptions{})
|
||||
require.NoError(t, err)
|
||||
|
||||
// Ensure that the secret was labelled.
|
||||
@ -107,13 +107,13 @@ func TestSupervisorSecrets(t *testing.T) {
|
||||
err = kubeClient.
|
||||
CoreV1().
|
||||
Secrets(env.SupervisorNamespace).
|
||||
Delete(ctx, test.secretName(updatedOP), metav1.DeleteOptions{})
|
||||
Delete(ctx, test.secretName(updatedFederationDomain), metav1.DeleteOptions{})
|
||||
require.NoError(t, err)
|
||||
assert.Eventually(t, func() bool {
|
||||
secret, err = kubeClient.
|
||||
CoreV1().
|
||||
Secrets(env.SupervisorNamespace).
|
||||
Get(ctx, test.secretName(updatedOP), metav1.GetOptions{})
|
||||
Get(ctx, test.secretName(updatedFederationDomain), metav1.GetOptions{})
|
||||
return err == nil
|
||||
}, time.Second*10, time.Millisecond*500)
|
||||
require.NoError(t, err)
|
||||
@ -123,12 +123,15 @@ func TestSupervisorSecrets(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
// Upon deleting the OP, the secret is deleted (we test this behavior in our uninstall tests).
|
||||
// Upon deleting the FederationDomain, the secret is deleted (we test this behavior in our uninstall tests).
|
||||
}
|
||||
|
||||
func ensureValidJWKS(t *testing.T, secret *corev1.Secret) {
|
||||
t.Helper()
|
||||
|
||||
// Ensure the secret has the right type.
|
||||
require.Equal(t, corev1.SecretType("secrets.pinniped.dev/federation-domain-jwks"), secret.Type)
|
||||
|
||||
// Ensure the secret has an active key.
|
||||
jwkData, ok := secret.Data["activeJWK"]
|
||||
require.True(t, ok, "secret is missing active jwk")
|
||||
@ -157,10 +160,12 @@ func ensureValidJWKS(t *testing.T, secret *corev1.Secret) {
|
||||
require.True(t, foundActiveJWK, "could not find active JWK in JWKS: %s", jwks)
|
||||
}
|
||||
|
||||
func ensureValidSymmetricKey(t *testing.T, secret *corev1.Secret) {
|
||||
t.Helper()
|
||||
require.Equal(t, corev1.SecretType("secrets.pinniped.dev/symmetric"), secret.Type)
|
||||
key, ok := secret.Data["key"]
|
||||
require.Truef(t, ok, "secret data does not contain 'key': %s", secret.Data)
|
||||
require.Equal(t, 32, len(key))
|
||||
func ensureValidSymmetricSecretOfTypeFunc(secretTypeValue string) func(*testing.T, *corev1.Secret) {
|
||||
return func(t *testing.T, secret *corev1.Secret) {
|
||||
t.Helper()
|
||||
require.Equal(t, corev1.SecretType(secretTypeValue), secret.Type)
|
||||
key, ok := secret.Data["key"]
|
||||
require.Truef(t, ok, "secret data does not contain 'key': %s", secret.Data)
|
||||
require.Equal(t, 32, len(key))
|
||||
}
|
||||
}
|
||||
|
@ -249,8 +249,8 @@ func CreateTestFederationDomain(ctx context.Context, t *testing.T, issuer string
|
||||
issuer = fmt.Sprintf("http://test-issuer-%s.pinniped.dev", RandHex(t, 8))
|
||||
}
|
||||
|
||||
opcs := NewSupervisorClientset(t).ConfigV1alpha1().FederationDomains(testEnv.SupervisorNamespace)
|
||||
opc, err := opcs.Create(createContext, &configv1alpha1.FederationDomain{
|
||||
federationDomains := NewSupervisorClientset(t).ConfigV1alpha1().FederationDomains(testEnv.SupervisorNamespace)
|
||||
federationDomain, err := federationDomains.Create(createContext, &configv1alpha1.FederationDomain{
|
||||
ObjectMeta: testObjectMeta(t, "oidc-provider"),
|
||||
Spec: configv1alpha1.FederationDomainSpec{
|
||||
Issuer: issuer,
|
||||
@ -258,31 +258,31 @@ func CreateTestFederationDomain(ctx context.Context, t *testing.T, issuer string
|
||||
},
|
||||
}, metav1.CreateOptions{})
|
||||
require.NoError(t, err, "could not create test FederationDomain")
|
||||
t.Logf("created test FederationDomain %s/%s", opc.Namespace, opc.Name)
|
||||
t.Logf("created test FederationDomain %s/%s", federationDomain.Namespace, federationDomain.Name)
|
||||
|
||||
t.Cleanup(func() {
|
||||
t.Helper()
|
||||
t.Logf("cleaning up test FederationDomain %s/%s", opc.Namespace, opc.Name)
|
||||
t.Logf("cleaning up test FederationDomain %s/%s", federationDomain.Namespace, federationDomain.Name)
|
||||
deleteCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
err := opcs.Delete(deleteCtx, opc.Name, metav1.DeleteOptions{})
|
||||
err := federationDomains.Delete(deleteCtx, federationDomain.Name, metav1.DeleteOptions{})
|
||||
notFound := k8serrors.IsNotFound(err)
|
||||
// It's okay if it is not found, because it might have been deleted by another part of this test.
|
||||
if !notFound {
|
||||
require.NoErrorf(t, err, "could not cleanup test FederationDomain %s/%s", opc.Namespace, opc.Name)
|
||||
require.NoErrorf(t, err, "could not cleanup test FederationDomain %s/%s", federationDomain.Namespace, federationDomain.Name)
|
||||
}
|
||||
})
|
||||
|
||||
// If we're not expecting any particular status, just return the new FederationDomain immediately.
|
||||
if expectStatus == "" {
|
||||
return opc
|
||||
return federationDomain
|
||||
}
|
||||
|
||||
// Wait for the FederationDomain to enter the expected phase (or time out).
|
||||
var result *configv1alpha1.FederationDomain
|
||||
assert.Eventuallyf(t, func() bool {
|
||||
var err error
|
||||
result, err = opcs.Get(ctx, opc.Name, metav1.GetOptions{})
|
||||
result, err = federationDomains.Get(ctx, federationDomain.Name, metav1.GetOptions{})
|
||||
require.NoError(t, err)
|
||||
return result.Status.Status == expectStatus
|
||||
}, 60*time.Second, 1*time.Second, "expected the FederationDomain to have status %q", expectStatus)
|
||||
@ -292,7 +292,7 @@ func CreateTestFederationDomain(ctx context.Context, t *testing.T, issuer string
|
||||
if result.Status.Status == configv1alpha1.SuccessFederationDomainStatusCondition {
|
||||
assert.Eventually(t, func() bool {
|
||||
var err error
|
||||
result, err = opcs.Get(ctx, opc.Name, metav1.GetOptions{})
|
||||
result, err = federationDomains.Get(ctx, federationDomain.Name, metav1.GetOptions{})
|
||||
require.NoError(t, err)
|
||||
return result.Status.Secrets.JWKS.Name != "" &&
|
||||
result.Status.Secrets.TokenSigningKey.Name != "" &&
|
||||
@ -304,7 +304,7 @@ func CreateTestFederationDomain(ctx context.Context, t *testing.T, issuer string
|
||||
require.NotEmpty(t, result.Status.Secrets.StateSigningKey.Name)
|
||||
require.NotEmpty(t, result.Status.Secrets.StateEncryptionKey.Name)
|
||||
}
|
||||
return opc
|
||||
return federationDomain
|
||||
}
|
||||
|
||||
func RandHex(t *testing.T, numBytes int) string {
|
||||
|
@ -1,12 +0,0 @@
|
||||
// +build tools
|
||||
|
||||
// Copyright 2020 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package tools exists to work around a Go modules oddity and depend on some tool versions.
|
||||
package tools
|
||||
|
||||
import (
|
||||
_ "github.com/golang/mock/mockgen"
|
||||
_ "github.com/golangci/golangci-lint/cmd/golangci-lint"
|
||||
)
|
Loading…
Reference in New Issue
Block a user