Merge branch 'main' of github.com:vmware-tanzu/pinniped into supervisor-docs

This commit is contained in:
Margo Crawford 2020-12-18 14:49:44 -08:00
commit 7c9f40b6d9
54 changed files with 1863 additions and 1239 deletions

View File

@ -15,7 +15,6 @@ COPY generated ./generated
COPY cmd ./cmd COPY cmd ./cmd
COPY pkg ./pkg COPY pkg ./pkg
COPY internal ./internal COPY internal ./internal
COPY tools ./tools
COPY hack ./hack COPY hack ./hack
# Build the executable binary (CGO_ENABLED=0 means static linking) # Build the executable binary (CGO_ENABLED=0 means static linking)

View File

@ -92,7 +92,7 @@ func startControllers(
kubeInformers kubeinformers.SharedInformerFactory, kubeInformers kubeinformers.SharedInformerFactory,
pinnipedInformers pinnipedinformers.SharedInformerFactory, pinnipedInformers pinnipedinformers.SharedInformerFactory,
) { ) {
opInformer := pinnipedInformers.Config().V1alpha1().FederationDomains() federationDomainInformer := pinnipedInformers.Config().V1alpha1().FederationDomains()
secretInformer := kubeInformers.Core().V1().Secrets() secretInformer := kubeInformers.Core().V1().Secrets()
// Create controller manager. // Create controller manager.
@ -102,7 +102,7 @@ func startControllers(
supervisorstorage.GarbageCollectorController( supervisorstorage.GarbageCollectorController(
clock.RealClock{}, clock.RealClock{},
kubeClient, kubeClient,
kubeInformers.Core().V1().Secrets(), secretInformer,
controllerlib.WithInformer, controllerlib.WithInformer,
), ),
singletonWorker, singletonWorker,
@ -112,7 +112,7 @@ func startControllers(
issuerManager, issuerManager,
clock.RealClock{}, clock.RealClock{},
pinnipedClient, pinnipedClient,
opInformer, federationDomainInformer,
controllerlib.WithInformer, controllerlib.WithInformer,
), ),
singletonWorker, singletonWorker,
@ -123,7 +123,7 @@ func startControllers(
kubeClient, kubeClient,
pinnipedClient, pinnipedClient,
secretInformer, secretInformer,
opInformer, federationDomainInformer,
controllerlib.WithInformer, controllerlib.WithInformer,
), ),
singletonWorker, singletonWorker,
@ -132,7 +132,7 @@ func startControllers(
supervisorconfig.NewJWKSObserverController( supervisorconfig.NewJWKSObserverController(
dynamicJWKSProvider, dynamicJWKSProvider,
secretInformer, secretInformer,
opInformer, federationDomainInformer,
controllerlib.WithInformer, controllerlib.WithInformer,
), ),
singletonWorker, singletonWorker,
@ -142,7 +142,7 @@ func startControllers(
dynamicTLSCertProvider, dynamicTLSCertProvider,
cfg.NamesConfig.DefaultTLSCertificateSecret, cfg.NamesConfig.DefaultTLSCertificateSecret,
secretInformer, secretInformer,
opInformer, federationDomainInformer,
controllerlib.WithInformer, controllerlib.WithInformer,
), ),
singletonWorker, singletonWorker,
@ -180,7 +180,7 @@ func startControllers(
kubeClient, kubeClient,
pinnipedClient, pinnipedClient,
secretInformer, secretInformer,
opInformer, federationDomainInformer,
controllerlib.WithInformer, controllerlib.WithInformer,
), ),
singletonWorker, singletonWorker,
@ -203,7 +203,7 @@ func startControllers(
kubeClient, kubeClient,
pinnipedClient, pinnipedClient,
secretInformer, secretInformer,
opInformer, federationDomainInformer,
controllerlib.WithInformer, controllerlib.WithInformer,
), ),
singletonWorker, singletonWorker,
@ -226,7 +226,7 @@ func startControllers(
kubeClient, kubeClient,
pinnipedClient, pinnipedClient,
secretInformer, secretInformer,
opInformer, federationDomainInformer,
controllerlib.WithInformer, controllerlib.WithInformer,
), ),
singletonWorker, singletonWorker,
@ -236,7 +236,7 @@ func startControllers(
dynamicUpstreamIDPProvider, dynamicUpstreamIDPProvider,
pinnipedClient, pinnipedClient,
pinnipedInformers.IDP().V1alpha1().OIDCIdentityProviders(), pinnipedInformers.IDP().V1alpha1().OIDCIdentityProviders(),
kubeInformers.Core().V1().Secrets(), secretInformer,
klogr.New()), klogr.New()),
singletonWorker) singletonWorker)

View File

@ -8,11 +8,7 @@ import (
) )
//nolint: gochecknoglobals //nolint: gochecknoglobals
var getCmd = &cobra.Command{ var getCmd = &cobra.Command{Use: "get", Short: "get"}
Use: "get",
Short: "get",
SilenceUsage: true, // do not print usage message when commands fail
}
//nolint: gochecknoinits //nolint: gochecknoinits
func init() { func init() {

View File

@ -81,7 +81,7 @@ func TestGetKubeconfig(t *testing.T) {
args: []string{}, args: []string{},
getPathToSelfErr: fmt.Errorf("some OS error"), getPathToSelfErr: fmt.Errorf("some OS error"),
wantError: true, wantError: true,
wantStdout: here.Doc(` wantStderr: here.Doc(`
Error: could not determine the Pinniped executable path: some OS error 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", "--oidc-ca-bundle", "./does/not/exist",
}, },
wantError: true, wantError: true,
wantStdout: here.Doc(` wantStderr: here.Doc(`
Error: could not read --oidc-ca-bundle: open ./does/not/exist: no such file or directory 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", "--kubeconfig", "./does/not/exist",
}, },
wantError: true, wantError: true,
wantStdout: here.Doc(` wantStderr: here.Doc(`
Error: could not load --kubeconfig: stat ./does/not/exist: no such file or directory 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", "--kubeconfig-context", "invalid",
}, },
wantError: true, wantError: true,
wantStdout: here.Doc(` wantStderr: here.Doc(`
Error: could not load --kubeconfig/--kubeconfig-context: no such context "invalid" 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"), getClientsetErr: fmt.Errorf("some kube error"),
wantError: true, wantError: true,
wantStdout: here.Doc(` wantStderr: here.Doc(`
Error: could not configure Kubernetes client: some kube error Error: could not configure Kubernetes client: some kube error
`), `),
}, },
@ -135,7 +135,7 @@ func TestGetKubeconfig(t *testing.T) {
"--concierge-authenticator-name", "test-authenticator", "--concierge-authenticator-name", "test-authenticator",
}, },
wantError: true, wantError: true,
wantStdout: here.Doc(` wantStderr: here.Doc(`
Error: webhookauthenticators.authentication.concierge.pinniped.dev "test-authenticator" not found Error: webhookauthenticators.authentication.concierge.pinniped.dev "test-authenticator" not found
`), `),
}, },
@ -147,7 +147,7 @@ func TestGetKubeconfig(t *testing.T) {
"--concierge-authenticator-name", "test-authenticator", "--concierge-authenticator-name", "test-authenticator",
}, },
wantError: true, wantError: true,
wantStdout: here.Doc(` wantStderr: here.Doc(`
Error: jwtauthenticators.authentication.concierge.pinniped.dev "test-authenticator" not found Error: jwtauthenticators.authentication.concierge.pinniped.dev "test-authenticator" not found
`), `),
}, },
@ -159,7 +159,7 @@ func TestGetKubeconfig(t *testing.T) {
"--concierge-authenticator-name", "test-authenticator", "--concierge-authenticator-name", "test-authenticator",
}, },
wantError: true, wantError: true,
wantStdout: here.Doc(` wantStderr: here.Doc(`
Error: invalid authenticator type "invalid", supported values are "webhook" and "jwt" Error: invalid authenticator type "invalid", supported values are "webhook" and "jwt"
`), `),
}, },
@ -178,7 +178,7 @@ func TestGetKubeconfig(t *testing.T) {
}, },
}, },
wantError: true, wantError: true,
wantStdout: here.Doc(` wantStderr: here.Doc(`
Error: failed to list JWTAuthenticator objects for autodiscovery: some list error Error: failed to list JWTAuthenticator objects for autodiscovery: some list error
`), `),
}, },
@ -197,7 +197,7 @@ func TestGetKubeconfig(t *testing.T) {
}, },
}, },
wantError: true, wantError: true,
wantStdout: here.Doc(` wantStderr: here.Doc(`
Error: failed to list WebhookAuthenticator objects for autodiscovery: some list error Error: failed to list WebhookAuthenticator objects for autodiscovery: some list error
`), `),
}, },
@ -207,7 +207,7 @@ func TestGetKubeconfig(t *testing.T) {
"--kubeconfig", "./testdata/kubeconfig.yaml", "--kubeconfig", "./testdata/kubeconfig.yaml",
}, },
wantError: true, wantError: true,
wantStdout: here.Doc(` wantStderr: here.Doc(`
Error: no authenticators were found in namespace "pinniped-concierge" (try setting --concierge-namespace) 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"}}, &conciergev1alpha1.WebhookAuthenticator{ObjectMeta: metav1.ObjectMeta{Name: "test-authenticator-4", Namespace: "test-namespace"}},
}, },
wantError: true, 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 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"}}, &conciergev1alpha1.WebhookAuthenticator{ObjectMeta: metav1.ObjectMeta{Name: "test-authenticator", Namespace: "test-namespace"}},
}, },
wantError: true, wantError: true,
wantStdout: here.Doc(` wantStderr: here.Doc(`
Error: could not autodiscover --oidc-issuer, and none was provided Error: could not autodiscover --oidc-issuer, and none was provided
`), `),
}, },
@ -259,7 +259,7 @@ func TestGetKubeconfig(t *testing.T) {
}, },
}, },
wantError: true, 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 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"}}, &conciergev1alpha1.WebhookAuthenticator{ObjectMeta: metav1.ObjectMeta{Name: "test-authenticator", Namespace: "test-namespace"}},
}, },
wantError: true, wantError: true,
wantStdout: here.Doc(` wantStderr: here.Doc(`
Error: only one of --static-token and --static-token-env can be specified Error: only one of --static-token and --static-token-env can be specified
`), `),
}, },

View File

@ -79,7 +79,7 @@ func TestLoginOIDCCommand(t *testing.T) {
name: "missing required flags", name: "missing required flags",
args: []string{}, args: []string{},
wantError: true, wantError: true,
wantStdout: here.Doc(` wantStderr: here.Doc(`
Error: required flag(s) "issuer" not set Error: required flag(s) "issuer" not set
`), `),
}, },
@ -91,7 +91,7 @@ func TestLoginOIDCCommand(t *testing.T) {
"--enable-concierge", "--enable-concierge",
}, },
wantError: true, wantError: true,
wantStdout: here.Doc(` wantStderr: here.Doc(`
Error: invalid concierge parameters: endpoint must not be empty Error: invalid concierge parameters: endpoint must not be empty
`), `),
}, },
@ -103,7 +103,7 @@ func TestLoginOIDCCommand(t *testing.T) {
"--ca-bundle", "./does/not/exist", "--ca-bundle", "./does/not/exist",
}, },
wantError: true, wantError: true,
wantStdout: here.Doc(` wantStderr: here.Doc(`
Error: could not read --ca-bundle: open ./does/not/exist: no such file or directory 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", "--ca-bundle-data", "invalid-base64",
}, },
wantError: true, wantError: true,
wantStdout: here.Doc(` wantStderr: here.Doc(`
Error: could not read --ca-bundle-data: illegal base64 data at input byte 7 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"), loginErr: fmt.Errorf("some login error"),
wantOptionsCount: 3, wantOptionsCount: 3,
wantError: true, wantError: true,
wantStdout: here.Doc(` wantStderr: here.Doc(`
Error: could not complete Pinniped login: some login error Error: could not complete Pinniped login: some login error
`), `),
}, },
@ -145,7 +145,7 @@ func TestLoginOIDCCommand(t *testing.T) {
conciergeErr: fmt.Errorf("some concierge error"), conciergeErr: fmt.Errorf("some concierge error"),
wantOptionsCount: 3, wantOptionsCount: 3,
wantError: true, wantError: true,
wantStdout: here.Doc(` wantStderr: here.Doc(`
Error: could not complete concierge credential exchange: some concierge error Error: could not complete concierge credential exchange: some concierge error
`), `),
}, },

View File

@ -66,7 +66,7 @@ func TestLoginStaticCommand(t *testing.T) {
name: "missing required flags", name: "missing required flags",
args: []string{}, args: []string{},
wantError: true, wantError: true,
wantStdout: here.Doc(` wantStderr: here.Doc(`
Error: one of --token or --token-env must be set Error: one of --token or --token-env must be set
`), `),
}, },
@ -77,7 +77,7 @@ func TestLoginStaticCommand(t *testing.T) {
"--enable-concierge", "--enable-concierge",
}, },
wantError: true, wantError: true,
wantStdout: here.Doc(` wantStderr: here.Doc(`
Error: invalid concierge parameters: endpoint must not be empty Error: invalid concierge parameters: endpoint must not be empty
`), `),
}, },
@ -87,7 +87,7 @@ func TestLoginStaticCommand(t *testing.T) {
"--token-env", "TEST_TOKEN_ENV", "--token-env", "TEST_TOKEN_ENV",
}, },
wantError: true, wantError: true,
wantStdout: here.Doc(` wantStderr: here.Doc(`
Error: --token-env variable "TEST_TOKEN_ENV" is not set Error: --token-env variable "TEST_TOKEN_ENV" is not set
`), `),
}, },
@ -100,7 +100,7 @@ func TestLoginStaticCommand(t *testing.T) {
"TEST_TOKEN_ENV": "", "TEST_TOKEN_ENV": "",
}, },
wantError: true, wantError: true,
wantStdout: here.Doc(` wantStderr: here.Doc(`
Error: --token-env variable "TEST_TOKEN_ENV" is empty Error: --token-env variable "TEST_TOKEN_ENV" is empty
`), `),
}, },
@ -125,7 +125,7 @@ func TestLoginStaticCommand(t *testing.T) {
}, },
conciergeErr: fmt.Errorf("some concierge error"), conciergeErr: fmt.Errorf("some concierge error"),
wantError: true, wantError: true,
wantStdout: here.Doc(` wantStderr: here.Doc(`
Error: could not complete concierge credential exchange: some concierge error Error: could not complete concierge credential exchange: some concierge error
`), `),
}, },

View File

@ -7,6 +7,7 @@ import (
"bytes" "bytes"
"testing" "testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"go.pinniped.dev/internal/here" "go.pinniped.dev/internal/here"
@ -41,7 +42,7 @@ func TestNewVersionCmd(t *testing.T) {
args []string args []string
wantError bool wantError bool
wantStdoutRegexp string wantStdoutRegexp string
wantStderr string wantStderrRegexp string
}{ }{
{ {
name: "no flags", name: "no flags",
@ -57,7 +58,8 @@ func TestNewVersionCmd(t *testing.T) {
name: "arg passed", name: "arg passed",
args: []string{"tuna"}, args: []string{"tuna"},
wantError: true, wantError: true,
wantStdoutRegexp: `Error: unknown command "tuna" for "version"` + "\n" + knownGoodUsageRegexpForVersion, wantStderrRegexp: `Error: unknown command "tuna" for "version"`,
wantStdoutRegexp: knownGoodUsageRegexpForVersion,
}, },
} }
for _, tt := range tests { for _, tt := range tests {
@ -76,8 +78,8 @@ func TestNewVersionCmd(t *testing.T) {
} else { } else {
require.NoError(t, err) require.NoError(t, err)
} }
require.Regexp(t, tt.wantStdoutRegexp, stdout.String(), "unexpected stdout") assert.Regexp(t, tt.wantStdoutRegexp, stdout.String(), "unexpected stdout")
require.Equal(t, tt.wantStderr, stderr.String(), "unexpected stderr") assert.Regexp(t, tt.wantStderrRegexp, stderr.String(), "unexpected stderr")
}) })
} }
} }

View File

@ -25,7 +25,7 @@ custom_labels: {} #! e.g. {myCustomLabelName: myCustomLabelValue, otherCustomLab
replicas: 2 replicas: 2
#! Specify either an image_digest or an image_tag. If both are given, only image_digest will be used. #! 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_digest: #! e.g. sha256:f3c4fdfd3ef865d4b97a1fd295d94acc3f0c654c46b6f27ffad5cf80216903c8
image_tag: latest image_tag: latest

View File

@ -5,7 +5,7 @@
--- ---
#! Specify either an image_digest or an image_tag. If both are given, only image_digest will be used. #! 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_digest: #! e.g. sha256:f3c4fdfd3ef865d4b97a1fd295d94acc3f0c654c46b6f27ffad5cf80216903c8
image_tag: latest image_tag: latest

View File

@ -25,7 +25,7 @@ custom_labels: {} #! e.g. {myCustomLabelName: myCustomLabelValue, otherCustomLab
replicas: 2 replicas: 2
#! Specify either an image_digest or an image_tag. If both are given, only image_digest will be used. #! 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_digest: #! e.g. sha256:f3c4fdfd3ef865d4b97a1fd295d94acc3f0c654c46b6f27ffad5cf80216903c8
image_tag: latest image_tag: latest

View File

@ -4,6 +4,6 @@ module go.pinniped.dev/generated/1.19/apis
go 1.13 go 1.13
require ( require (
k8s.io/api v0.19.2 k8s.io/api v0.19.5
k8s.io/apimachinery v0.19.2 k8s.io/apimachinery v0.19.5
) )

View File

@ -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-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-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-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-20201110031124-69a78807bb2b h1:uwuIcX0g4Yl1NC5XAz37xsr2lTtcqevgzYNVt49waME=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 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-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-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/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-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-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-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-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200622214017-ed371f2e16b4/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.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 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= 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-20190102054323-c2f93a96b099/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.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
k8s.io/api v0.19.2 h1:q+/krnHWKsL7OBZg/rxnycsl9569Pud76UJ77MvKXms= k8s.io/api v0.19.5 h1:p0MRzyhokJ9Kn5jcJAHNup0s+COMBPfn1mTasls6mMg=
k8s.io/api v0.19.2/go.mod h1:IQpK0zFQ1xc5iNIQPqzgoOwuFugaYHK4iCknlAQP9nI= k8s.io/api v0.19.5/go.mod h1:yGZReuNa0vj56op6eT+NLrXJne0R0u9ktexZ8jdJzpc=
k8s.io/apimachinery v0.19.2 h1:5Gy9vQpAGTKHPVOh5c4plE274X8D/6cuEiTO2zve7tc= k8s.io/apimachinery v0.19.5 h1:Yvz6dOE0WbVE+FXBEFqc9lSvo87VPtq6mCSsrtC95HI=
k8s.io/apimachinery v0.19.2/go.mod h1:DnPGDnARWFvYa3pMHgSxtbZb7gpzzAZ1pTfaUNDVlmA= 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/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.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE=
k8s.io/klog/v2 v2.2.0 h1:XRvcwJozkgZ1UQJmfMGpvRthQHOvihEhYtDfAaxMz/A= k8s.io/klog/v2 v2.2.0 h1:XRvcwJozkgZ1UQJmfMGpvRthQHOvihEhYtDfAaxMz/A=

View File

@ -6,8 +6,8 @@ go 1.13
require ( require (
github.com/go-openapi/spec v0.19.9 github.com/go-openapi/spec v0.19.9
go.pinniped.dev/generated/1.19/apis v0.0.0-00010101000000-000000000000 go.pinniped.dev/generated/1.19/apis v0.0.0-00010101000000-000000000000
k8s.io/apimachinery v0.19.2 k8s.io/apimachinery v0.19.5
k8s.io/client-go v0.19.2 k8s.io/client-go v0.19.5
k8s.io/kube-openapi v0.0.0-20200805222855-6aeccd4b50c6 k8s.io/kube-openapi v0.0.0-20200805222855-6aeccd4b50c6
) )

View File

@ -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-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-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-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-20201110031124-69a78807bb2b h1:uwuIcX0g4Yl1NC5XAz37xsr2lTtcqevgzYNVt49waME=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 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-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-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/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-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-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-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-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200622214017-ed371f2e16b4/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.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.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 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-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.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= 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.5 h1:p0MRzyhokJ9Kn5jcJAHNup0s+COMBPfn1mTasls6mMg=
k8s.io/api v0.19.2/go.mod h1:IQpK0zFQ1xc5iNIQPqzgoOwuFugaYHK4iCknlAQP9nI= k8s.io/api v0.19.5/go.mod h1:yGZReuNa0vj56op6eT+NLrXJne0R0u9ktexZ8jdJzpc=
k8s.io/apimachinery v0.19.2 h1:5Gy9vQpAGTKHPVOh5c4plE274X8D/6cuEiTO2zve7tc= k8s.io/apimachinery v0.19.5 h1:Yvz6dOE0WbVE+FXBEFqc9lSvo87VPtq6mCSsrtC95HI=
k8s.io/apimachinery v0.19.2/go.mod h1:DnPGDnARWFvYa3pMHgSxtbZb7gpzzAZ1pTfaUNDVlmA= k8s.io/apimachinery v0.19.5/go.mod h1:6sRbGRAVY5DOCuZwB5XkqguBqpqLU6q/kOaOdk29z6Q=
k8s.io/client-go v0.19.2 h1:gMJuU3xJZs86L1oQ99R4EViAADUPMHHtS9jFshasHSc= k8s.io/client-go v0.19.5 h1:Y7LsFwgbm9+5oVXER04KNCSPhY6TblYRgG1DQdVq+ig=
k8s.io/client-go v0.19.2/go.mod h1:S5wPhCqyDNAlzM9CnEdgTGV4OqhsW3jGO1UM1epwfJA= 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/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.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE=
k8s.io/klog/v2 v2.2.0 h1:XRvcwJozkgZ1UQJmfMGpvRthQHOvihEhYtDfAaxMz/A= k8s.io/klog/v2 v2.2.0 h1:XRvcwJozkgZ1UQJmfMGpvRthQHOvihEhYtDfAaxMz/A=

43
go.mod
View File

@ -3,40 +3,49 @@ module go.pinniped.dev
go 1.14 go 1.14
require ( require (
cloud.google.com/go v0.60.0 // indirect
github.com/MakeNowJust/heredoc/v2 v2.0.1 github.com/MakeNowJust/heredoc/v2 v2.0.1
github.com/blang/semver v3.5.1+incompatible // indirect github.com/blang/semver v3.5.1+incompatible // indirect
github.com/coreos/go-oidc v2.2.1+incompatible github.com/coreos/go-oidc v2.2.1+incompatible
github.com/davecgh/go-spew v1.1.1 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/go-logr/stdr v0.2.0
github.com/gofrs/flock v0.8.0 github.com/gofrs/flock v0.8.0
github.com/golang/mock v1.4.4 github.com/golang/mock v1.4.4
github.com/golangci/golangci-lint v1.31.0 github.com/google/go-cmp v0.5.4
github.com/google/go-cmp v0.5.2 github.com/google/gofuzz v1.2.0
github.com/google/gofuzz v1.1.0
github.com/gorilla/securecookie v1.1.1 github.com/gorilla/securecookie v1.1.1
github.com/ory/fosite v0.35.1 github.com/kr/text v0.2.0 // indirect
github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4 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/pkg/errors v0.9.1
github.com/sclevine/agouti v3.0.0+incompatible github.com/sclevine/agouti v3.0.0+incompatible
github.com/sclevine/spec v1.4.0 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/spf13/pflag v1.0.5
github.com/stretchr/testify v1.6.1 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/apis v0.0.0-00010101000000-000000000000
go.pinniped.dev/generated/1.19/client 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/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 gopkg.in/square/go-jose.v2 v2.5.1
k8s.io/api v0.19.2 gopkg.in/yaml.v2 v2.4.0 // indirect
k8s.io/apimachinery v0.19.2 gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 // indirect
k8s.io/apiserver v0.19.2 k8s.io/api v0.19.5
k8s.io/client-go v0.19.2 k8s.io/apimachinery v0.19.5
k8s.io/component-base v0.19.2 k8s.io/apiserver v0.19.5
k8s.io/klog/v2 v2.3.0 k8s.io/client-go v0.19.5
k8s.io/kube-aggregator v0.19.2 k8s.io/component-base v0.19.5
k8s.io/utils v0.0.0-20200729134348-d5654de09c73 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 sigs.k8s.io/yaml v1.2.0
) )

1012
go.sum

File diff suppressed because it is too large Load Diff

View File

@ -1,3 +1,3 @@
1.19.2 1.19.5
1.18.2 1.18.2
1.17.11 1.17.11

View File

@ -13,7 +13,7 @@ export GO111MODULE="on"
if [[ -z "${CONTAINED:-}" ]]; then if [[ -z "${CONTAINED:-}" ]]; then
for kubeVersion in "${KUBE_VERSIONS[@]}"; do for kubeVersion in "${KUBE_VERSIONS[@]}"; do
# CODEGEN_IMAGE is the container image to use when running # 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}..." echo "generating code for ${kubeVersion} using ${CODEGEN_IMAGE}..."
docker run --rm \ docker run --rm \

View File

@ -12,12 +12,7 @@ function tidy_cmd() {
} }
function lint_cmd() { function lint_cmd() {
if [ -x "$(command -v golangci-lint)" ]; then echo "golangci-lint run --modules-download-mode=readonly --timeout=10m"
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"
} }
function test_cmd() { function test_cmd() {

View File

@ -130,7 +130,7 @@ if ! tilt_mode; then
fi fi
fi fi
registry="docker.io" registry="pinniped.local"
repo="test/build" repo="test/build"
registry_repo="$registry/$repo" registry_repo="$registry/$repo"
tag=$(uuidgen) # always a new tag to force K8s to reload the image on redeploy tag=$(uuidgen) # always a new tag to force K8s to reload the image on redeploy

View File

@ -29,14 +29,14 @@ import (
// If there are no longer any valid issuers, then it can be called with no arguments. // 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. // Implementations of this type should be thread-safe to support calls from multiple goroutines.
type ProvidersSetter interface { type ProvidersSetter interface {
SetProviders(federationDomains ...*provider.FederationDomain) SetProviders(federationDomains ...*provider.FederationDomainIssuer)
} }
type federationDomainWatcherController struct { type federationDomainWatcherController struct {
providerSetter ProvidersSetter providerSetter ProvidersSetter
clock clock.Clock clock clock.Clock
client pinnipedclientset.Interface client pinnipedclientset.Interface
opcInformer configinformers.FederationDomainInformer federationDomainInformer configinformers.FederationDomainInformer
} }
// NewFederationDomainWatcherController creates a controllerlib.Controller that watches // NewFederationDomainWatcherController creates a controllerlib.Controller that watches
@ -45,21 +45,21 @@ func NewFederationDomainWatcherController(
providerSetter ProvidersSetter, providerSetter ProvidersSetter,
clock clock.Clock, clock clock.Clock,
client pinnipedclientset.Interface, client pinnipedclientset.Interface,
opcInformer configinformers.FederationDomainInformer, federationDomainInformer configinformers.FederationDomainInformer,
withInformer pinnipedcontroller.WithInformerOptionFunc, withInformer pinnipedcontroller.WithInformerOptionFunc,
) controllerlib.Controller { ) controllerlib.Controller {
return controllerlib.New( return controllerlib.New(
controllerlib.Config{ controllerlib.Config{
Name: "FederationDomainWatcherController", Name: "FederationDomainWatcherController",
Syncer: &federationDomainWatcherController{ Syncer: &federationDomainWatcherController{
providerSetter: providerSetter, providerSetter: providerSetter,
clock: clock, clock: clock,
client: client, client: client,
opcInformer: opcInformer, federationDomainInformer: federationDomainInformer,
}, },
}, },
withInformer( withInformer(
opcInformer, federationDomainInformer,
pinnipedcontroller.MatchAnythingFilter(pinnipedcontroller.SingletonQueue()), pinnipedcontroller.MatchAnythingFilter(pinnipedcontroller.SingletonQueue()),
controllerlib.InformerOption{}, controllerlib.InformerOption{},
), ),
@ -68,7 +68,7 @@ func NewFederationDomainWatcherController(
// Sync implements controllerlib.Syncer. // Sync implements controllerlib.Syncer.
func (c *federationDomainWatcherController) Sync(ctx controllerlib.Context) error { 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 { if err != nil {
return err return err
} }
@ -89,8 +89,8 @@ func (c *federationDomainWatcherController) Sync(ctx controllerlib.Context) erro
uniqueSecretNamesPerIssuerAddress := make(map[string]map[string]bool) uniqueSecretNamesPerIssuerAddress := make(map[string]map[string]bool)
issuerURLToHostnameKey := lowercaseHostWithoutPort issuerURLToHostnameKey := lowercaseHostWithoutPort
for _, opc := range all { for _, federationDomain := range federationDomains {
issuerURL, err := url.Parse(opc.Spec.Issuer) issuerURL, err := url.Parse(federationDomain.Spec.Issuer)
if err != nil { if err != nil {
continue // Skip url parse errors because they will be validated again below. 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) setOfSecretNames = make(map[string]bool)
uniqueSecretNamesPerIssuerAddress[issuerURLToHostnameKey(issuerURL)] = setOfSecretNames uniqueSecretNamesPerIssuerAddress[issuerURLToHostnameKey(issuerURL)] = setOfSecretNames
} }
if opc.Spec.TLS != nil { if federationDomain.Spec.TLS != nil {
setOfSecretNames[opc.Spec.TLS.SecretName] = true setOfSecretNames[federationDomain.Spec.TLS.SecretName] = true
} }
} }
errs := multierror.New() errs := multierror.New()
federationDomains := make([]*provider.FederationDomain, 0) federationDomainIssuers := make([]*provider.FederationDomainIssuer, 0)
for _, opc := range all { for _, federationDomain := range federationDomains {
issuerURL, urlParseErr := url.Parse(opc.Spec.Issuer) issuerURL, urlParseErr := url.Parse(federationDomain.Spec.Issuer)
// Skip url parse errors because they will be validated below. // Skip url parse errors because they will be validated below.
if urlParseErr == nil { if urlParseErr == nil {
if issuerCount := issuerCounts[issuerURLToIssuerKey(issuerURL)]; issuerCount > 1 { if issuerCount := issuerCounts[issuerURLToIssuerKey(issuerURL)]; issuerCount > 1 {
if err := c.updateStatus( if err := c.updateStatus(
ctx.Context, ctx.Context,
opc.Namespace, federationDomain.Namespace,
opc.Name, federationDomain.Name,
configv1alpha1.DuplicateFederationDomainStatusCondition, configv1alpha1.DuplicateFederationDomainStatusCondition,
"Duplicate issuer: "+opc.Spec.Issuer, "Duplicate issuer: "+federationDomain.Spec.Issuer,
); err != nil { ); err != nil {
errs.Add(fmt.Errorf("could not update status: %w", err)) 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 urlParseErr == nil && len(uniqueSecretNamesPerIssuerAddress[issuerURLToHostnameKey(issuerURL)]) > 1 {
if err := c.updateStatus( if err := c.updateStatus(
ctx.Context, ctx.Context,
opc.Namespace, federationDomain.Namespace,
opc.Name, federationDomain.Name,
configv1alpha1.SameIssuerHostMustUseSameSecretFederationDomainStatusCondition, configv1alpha1.SameIssuerHostMustUseSameSecretFederationDomainStatusCondition,
"Issuers with the same DNS hostname (address not including port) must use the same secretName: "+issuerURLToHostnameKey(issuerURL), "Issuers with the same DNS hostname (address not including port) must use the same secretName: "+issuerURLToHostnameKey(issuerURL),
); err != nil { ); err != nil {
@ -143,12 +143,12 @@ func (c *federationDomainWatcherController) Sync(ctx controllerlib.Context) erro
continue 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 != nil {
if err := c.updateStatus( if err := c.updateStatus(
ctx.Context, ctx.Context,
opc.Namespace, federationDomain.Namespace,
opc.Name, federationDomain.Name,
configv1alpha1.InvalidFederationDomainStatusCondition, configv1alpha1.InvalidFederationDomainStatusCondition,
"Invalid: "+err.Error(), "Invalid: "+err.Error(),
); err != nil { ); err != nil {
@ -159,18 +159,19 @@ func (c *federationDomainWatcherController) Sync(ctx controllerlib.Context) erro
if err := c.updateStatus( if err := c.updateStatus(
ctx.Context, ctx.Context,
opc.Namespace, federationDomain.Namespace,
opc.Name, federationDomain.Name,
configv1alpha1.SuccessFederationDomainStatusCondition, configv1alpha1.SuccessFederationDomainStatusCondition,
"Provider successfully created", "Provider successfully created",
); err != nil { ); err != nil {
errs.Add(fmt.Errorf("could not update status: %w", err)) errs.Add(fmt.Errorf("could not update status: %w", err))
continue continue
} }
federationDomains = append(federationDomains, federationDomain)
federationDomainIssuers = append(federationDomainIssuers, federationDomainIssuer)
} }
c.providerSetter.SetProviders(federationDomains...) c.providerSetter.SetProviders(federationDomainIssuers...)
return errs.ErrOrNil() return errs.ErrOrNil()
} }
@ -182,28 +183,28 @@ func (c *federationDomainWatcherController) updateStatus(
message string, message string,
) error { ) error {
return retry.RetryOnConflict(retry.DefaultRetry, func() 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 { if err != nil {
return fmt.Errorf("get failed: %w", err) 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 return nil
} }
plog.Debug( plog.Debug(
"attempting status update", "attempting status update",
"federationdomainconfig", "federationdomain",
klog.KRef(namespace, name), klog.KRef(namespace, name),
"status", "status",
status, status,
"message", "message",
message, message,
) )
opc.Status.Status = status federationDomain.Status.Status = status
opc.Status.Message = message federationDomain.Status.Message = message
opc.Status.LastUpdateTime = timePtr(metav1.NewTime(c.clock.Now())) federationDomain.Status.LastUpdateTime = timePtr(metav1.NewTime(c.clock.Now()))
_, err = c.client.ConfigV1alpha1().FederationDomains(namespace).Update(ctx, opc, metav1.UpdateOptions{}) _, err = c.client.ConfigV1alpha1().FederationDomains(namespace).Update(ctx, federationDomain, metav1.UpdateOptions{})
return err return err
}) })
} }

View File

@ -40,15 +40,15 @@ func TestInformerFilters(t *testing.T) {
it.Before(func() { it.Before(func() {
r = require.New(t) r = require.New(t)
observableWithInformerOption = testutil.NewObservableWithInformerOption() observableWithInformerOption = testutil.NewObservableWithInformerOption()
opcInformer := pinnipedinformers.NewSharedInformerFactoryWithOptions(nil, 0).Config().V1alpha1().FederationDomains() federationDomainInformer := pinnipedinformers.NewSharedInformerFactoryWithOptions(nil, 0).Config().V1alpha1().FederationDomains()
_ = NewFederationDomainWatcherController( _ = NewFederationDomainWatcherController(
nil, nil,
nil, nil,
nil, nil,
opcInformer, federationDomainInformer,
observableWithInformerOption.WithInformer, // make it possible to observe the behavior of the Filters 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() { when("watching FederationDomain objects", func() {
@ -84,10 +84,10 @@ func TestInformerFilters(t *testing.T) {
type fakeProvidersSetter struct { type fakeProvidersSetter struct {
SetProvidersWasCalled bool 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.SetProvidersWasCalled = true
f.FederationDomainsReceived = federationDomains f.FederationDomainsReceived = federationDomains
} }
@ -99,8 +99,8 @@ func TestSync(t *testing.T) {
var r *require.Assertions var r *require.Assertions
var subject controllerlib.Controller var subject controllerlib.Controller
var opcInformerClient *pinnipedfake.Clientset var federationDomainInformerClient *pinnipedfake.Clientset
var opcInformers pinnipedinformers.SharedInformerFactory var federationDomainInformers pinnipedinformers.SharedInformerFactory
var pinnipedAPIClient *pinnipedfake.Clientset var pinnipedAPIClient *pinnipedfake.Clientset
var timeoutContext context.Context var timeoutContext context.Context
var timeoutContextCancel context.CancelFunc var timeoutContextCancel context.CancelFunc
@ -117,7 +117,7 @@ func TestSync(t *testing.T) {
providersSetter, providersSetter,
clock.NewFakeClock(frozenNow), clock.NewFakeClock(frozenNow),
pinnipedAPIClient, pinnipedAPIClient,
opcInformers.Config().V1alpha1().FederationDomains(), federationDomainInformers.Config().V1alpha1().FederationDomains(),
controllerlib.WithInformer, controllerlib.WithInformer,
) )
@ -132,7 +132,7 @@ func TestSync(t *testing.T) {
} }
// Must start informers before calling TestRunSynchronously() // Must start informers before calling TestRunSynchronously()
opcInformers.Start(timeoutContext.Done()) federationDomainInformers.Start(timeoutContext.Done())
controllerlib.TestRunSynchronously(t, subject) controllerlib.TestRunSynchronously(t, subject)
} }
@ -144,8 +144,8 @@ func TestSync(t *testing.T) {
timeoutContext, timeoutContextCancel = context.WithTimeout(context.Background(), time.Second*3) timeoutContext, timeoutContextCancel = context.WithTimeout(context.Background(), time.Second*3)
opcInformerClient = pinnipedfake.NewSimpleClientset() federationDomainInformerClient = pinnipedfake.NewSimpleClientset()
opcInformers = pinnipedinformers.NewSharedInformerFactory(opcInformerClient, 0) federationDomainInformers = pinnipedinformers.NewSharedInformerFactory(federationDomainInformerClient, 0)
pinnipedAPIClient = pinnipedfake.NewSimpleClientset() pinnipedAPIClient = pinnipedfake.NewSimpleClientset()
federationDomainGVR = schema.GroupVersionResource{ federationDomainGVR = schema.GroupVersionResource{
@ -171,14 +171,14 @@ func TestSync(t *testing.T) {
Spec: v1alpha1.FederationDomainSpec{Issuer: "https://issuer1.com"}, Spec: v1alpha1.FederationDomainSpec{Issuer: "https://issuer1.com"},
} }
r.NoError(pinnipedAPIClient.Tracker().Add(federationDomain1)) r.NoError(pinnipedAPIClient.Tracker().Add(federationDomain1))
r.NoError(opcInformerClient.Tracker().Add(federationDomain1)) r.NoError(federationDomainInformerClient.Tracker().Add(federationDomain1))
federationDomain2 = &v1alpha1.FederationDomain{ federationDomain2 = &v1alpha1.FederationDomain{
ObjectMeta: metav1.ObjectMeta{Name: "config2", Namespace: namespace}, ObjectMeta: metav1.ObjectMeta{Name: "config2", Namespace: namespace},
Spec: v1alpha1.FederationDomainSpec{Issuer: "https://issuer2.com"}, Spec: v1alpha1.FederationDomainSpec{Issuer: "https://issuer2.com"},
} }
r.NoError(pinnipedAPIClient.Tracker().Add(federationDomain2)) r.NoError(pinnipedAPIClient.Tracker().Add(federationDomain2))
r.NoError(opcInformerClient.Tracker().Add(federationDomain2)) r.NoError(federationDomainInformerClient.Tracker().Add(federationDomain2))
}) })
it("calls the ProvidersSetter", func() { it("calls the ProvidersSetter", func() {
@ -186,15 +186,15 @@ func TestSync(t *testing.T) {
err := controllerlib.TestSync(t, subject, *syncContext) err := controllerlib.TestSync(t, subject, *syncContext)
r.NoError(err) r.NoError(err)
provider1, err := provider.NewFederationDomain(federationDomain1.Spec.Issuer) provider1, err := provider.NewFederationDomainIssuer(federationDomain1.Spec.Issuer)
r.NoError(err) r.NoError(err)
provider2, err := provider.NewFederationDomain(federationDomain2.Spec.Issuer) provider2, err := provider.NewFederationDomainIssuer(federationDomain2.Spec.Issuer)
r.NoError(err) r.NoError(err)
r.True(providersSetter.SetProvidersWasCalled) r.True(providersSetter.SetProvidersWasCalled)
r.ElementsMatch( r.ElementsMatch(
[]*provider.FederationDomain{ []*provider.FederationDomainIssuer{
provider1, provider1,
provider2, provider2,
}, },
@ -247,7 +247,7 @@ func TestSync(t *testing.T) {
federationDomain1.Status.LastUpdateTime = timePtr(metav1.NewTime(frozenNow)) federationDomain1.Status.LastUpdateTime = timePtr(metav1.NewTime(frozenNow))
r.NoError(pinnipedAPIClient.Tracker().Update(federationDomainGVR, federationDomain1, federationDomain1.Namespace)) 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() { it("only updates the out-of-date FederationDomain", func() {
@ -284,15 +284,15 @@ func TestSync(t *testing.T) {
err := controllerlib.TestSync(t, subject, *syncContext) err := controllerlib.TestSync(t, subject, *syncContext)
r.NoError(err) r.NoError(err)
provider1, err := provider.NewFederationDomain(federationDomain1.Spec.Issuer) provider1, err := provider.NewFederationDomainIssuer(federationDomain1.Spec.Issuer)
r.NoError(err) r.NoError(err)
provider2, err := provider.NewFederationDomain(federationDomain2.Spec.Issuer) provider2, err := provider.NewFederationDomainIssuer(federationDomain2.Spec.Issuer)
r.NoError(err) r.NoError(err)
r.True(providersSetter.SetProvidersWasCalled) r.True(providersSetter.SetProvidersWasCalled)
r.ElementsMatch( r.ElementsMatch(
[]*provider.FederationDomain{ []*provider.FederationDomainIssuer{
provider1, provider1,
provider2, provider2,
}, },
@ -322,10 +322,10 @@ func TestSync(t *testing.T) {
err := controllerlib.TestSync(t, subject, *syncContext) err := controllerlib.TestSync(t, subject, *syncContext)
r.EqualError(err, "1 error(s):\n- could not update status: some update error") 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) r.NoError(err)
provider2, err := provider.NewFederationDomain(federationDomain2.Spec.Issuer) provider2, err := provider.NewFederationDomainIssuer(federationDomain2.Spec.Issuer)
r.NoError(err) r.NoError(err)
r.True(providersSetter.SetProvidersWasCalled) r.True(providersSetter.SetProvidersWasCalled)
@ -387,7 +387,7 @@ func TestSync(t *testing.T) {
Spec: v1alpha1.FederationDomainSpec{Issuer: "https://issuer.com"}, Spec: v1alpha1.FederationDomainSpec{Issuer: "https://issuer.com"},
} }
r.NoError(pinnipedAPIClient.Tracker().Add(federationDomain)) 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() { 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"}, Spec: v1alpha1.FederationDomainSpec{Issuer: "https://valid-issuer.com"},
} }
r.NoError(pinnipedAPIClient.Tracker().Add(validFederationDomain)) r.NoError(pinnipedAPIClient.Tracker().Add(validFederationDomain))
r.NoError(opcInformerClient.Tracker().Add(validFederationDomain)) r.NoError(federationDomainInformerClient.Tracker().Add(validFederationDomain))
invalidFederationDomain = &v1alpha1.FederationDomain{ invalidFederationDomain = &v1alpha1.FederationDomain{
ObjectMeta: metav1.ObjectMeta{Name: "invalid-config", Namespace: namespace}, ObjectMeta: metav1.ObjectMeta{Name: "invalid-config", Namespace: namespace},
Spec: v1alpha1.FederationDomainSpec{Issuer: "https://invalid-issuer.com?some=query"}, Spec: v1alpha1.FederationDomainSpec{Issuer: "https://invalid-issuer.com?some=query"},
} }
r.NoError(pinnipedAPIClient.Tracker().Add(invalidFederationDomain)) 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() { it("calls the ProvidersSetter with the valid provider", func() {
@ -536,12 +536,12 @@ func TestSync(t *testing.T) {
err := controllerlib.TestSync(t, subject, *syncContext) err := controllerlib.TestSync(t, subject, *syncContext)
r.NoError(err) r.NoError(err)
validProvider, err := provider.NewFederationDomain(validFederationDomain.Spec.Issuer) validProvider, err := provider.NewFederationDomainIssuer(validFederationDomain.Spec.Issuer)
r.NoError(err) r.NoError(err)
r.True(providersSetter.SetProvidersWasCalled) r.True(providersSetter.SetProvidersWasCalled)
r.Equal( r.Equal(
[]*provider.FederationDomain{ []*provider.FederationDomainIssuer{
validProvider, validProvider,
}, },
providersSetter.FederationDomainsReceived, providersSetter.FederationDomainsReceived,
@ -593,8 +593,8 @@ func TestSync(t *testing.T) {
"federationdomains", "federationdomains",
func(action coretesting.Action) (bool, runtime.Object, error) { func(action coretesting.Action) (bool, runtime.Object, error) {
updateAction := action.(coretesting.UpdateActionImpl) updateAction := action.(coretesting.UpdateActionImpl)
opc := updateAction.Object.(*v1alpha1.FederationDomain) federationDomain := updateAction.Object.(*v1alpha1.FederationDomain)
if opc.Name == validFederationDomain.Name { if federationDomain.Name == validFederationDomain.Name {
return true, nil, nil return true, nil, nil
} }
@ -608,12 +608,12 @@ func TestSync(t *testing.T) {
err := controllerlib.TestSync(t, subject, *syncContext) err := controllerlib.TestSync(t, subject, *syncContext)
r.EqualError(err, "1 error(s):\n- could not update status: some update error") 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.NoError(err)
r.True(providersSetter.SetProvidersWasCalled) r.True(providersSetter.SetProvidersWasCalled)
r.Equal( r.Equal(
[]*provider.FederationDomain{ []*provider.FederationDomainIssuer{
validProvider, validProvider,
}, },
providersSetter.FederationDomainsReceived, providersSetter.FederationDomainsReceived,
@ -675,20 +675,20 @@ func TestSync(t *testing.T) {
Spec: v1alpha1.FederationDomainSpec{Issuer: "https://iSSueR-duPlicAte.cOm/a"}, Spec: v1alpha1.FederationDomainSpec{Issuer: "https://iSSueR-duPlicAte.cOm/a"},
} }
r.NoError(pinnipedAPIClient.Tracker().Add(federationDomainDuplicate1)) r.NoError(pinnipedAPIClient.Tracker().Add(federationDomainDuplicate1))
r.NoError(opcInformerClient.Tracker().Add(federationDomainDuplicate1)) r.NoError(federationDomainInformerClient.Tracker().Add(federationDomainDuplicate1))
federationDomainDuplicate2 = &v1alpha1.FederationDomain{ federationDomainDuplicate2 = &v1alpha1.FederationDomain{
ObjectMeta: metav1.ObjectMeta{Name: "duplicate2", Namespace: namespace}, ObjectMeta: metav1.ObjectMeta{Name: "duplicate2", Namespace: namespace},
Spec: v1alpha1.FederationDomainSpec{Issuer: "https://issuer-duplicate.com/a"}, Spec: v1alpha1.FederationDomainSpec{Issuer: "https://issuer-duplicate.com/a"},
} }
r.NoError(pinnipedAPIClient.Tracker().Add(federationDomainDuplicate2)) r.NoError(pinnipedAPIClient.Tracker().Add(federationDomainDuplicate2))
r.NoError(opcInformerClient.Tracker().Add(federationDomainDuplicate2)) r.NoError(federationDomainInformerClient.Tracker().Add(federationDomainDuplicate2))
federationDomain = &v1alpha1.FederationDomain{ federationDomain = &v1alpha1.FederationDomain{
ObjectMeta: metav1.ObjectMeta{Name: "not-duplicate", Namespace: namespace}, ObjectMeta: metav1.ObjectMeta{Name: "not-duplicate", Namespace: namespace},
Spec: v1alpha1.FederationDomainSpec{Issuer: "https://issuer-duplicate.com/A"}, // different path Spec: v1alpha1.FederationDomainSpec{Issuer: "https://issuer-duplicate.com/A"}, // different path
} }
r.NoError(pinnipedAPIClient.Tracker().Add(federationDomain)) 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() { it("calls the ProvidersSetter with the non-duplicate", func() {
@ -696,12 +696,12 @@ func TestSync(t *testing.T) {
err := controllerlib.TestSync(t, subject, *syncContext) err := controllerlib.TestSync(t, subject, *syncContext)
r.NoError(err) r.NoError(err)
nonDuplicateProvider, err := provider.NewFederationDomain(federationDomain.Spec.Issuer) nonDuplicateProvider, err := provider.NewFederationDomainIssuer(federationDomain.Spec.Issuer)
r.NoError(err) r.NoError(err)
r.True(providersSetter.SetProvidersWasCalled) r.True(providersSetter.SetProvidersWasCalled)
r.Equal( r.Equal(
[]*provider.FederationDomain{ []*provider.FederationDomainIssuer{
nonDuplicateProvider, nonDuplicateProvider,
}, },
providersSetter.FederationDomainsReceived, providersSetter.FederationDomainsReceived,
@ -824,7 +824,7 @@ func TestSync(t *testing.T) {
}, },
} }
r.NoError(pinnipedAPIClient.Tracker().Add(federationDomainSameIssuerAddress1)) r.NoError(pinnipedAPIClient.Tracker().Add(federationDomainSameIssuerAddress1))
r.NoError(opcInformerClient.Tracker().Add(federationDomainSameIssuerAddress1)) r.NoError(federationDomainInformerClient.Tracker().Add(federationDomainSameIssuerAddress1))
federationDomainSameIssuerAddress2 = &v1alpha1.FederationDomain{ federationDomainSameIssuerAddress2 = &v1alpha1.FederationDomain{
ObjectMeta: metav1.ObjectMeta{Name: "provider2", Namespace: namespace}, ObjectMeta: metav1.ObjectMeta{Name: "provider2", Namespace: namespace},
Spec: v1alpha1.FederationDomainSpec{ Spec: v1alpha1.FederationDomainSpec{
@ -835,7 +835,7 @@ func TestSync(t *testing.T) {
}, },
} }
r.NoError(pinnipedAPIClient.Tracker().Add(federationDomainSameIssuerAddress2)) r.NoError(pinnipedAPIClient.Tracker().Add(federationDomainSameIssuerAddress2))
r.NoError(opcInformerClient.Tracker().Add(federationDomainSameIssuerAddress2)) r.NoError(federationDomainInformerClient.Tracker().Add(federationDomainSameIssuerAddress2))
federationDomainDifferentIssuerAddress = &v1alpha1.FederationDomain{ federationDomainDifferentIssuerAddress = &v1alpha1.FederationDomain{
ObjectMeta: metav1.ObjectMeta{Name: "differentIssuerAddressProvider", Namespace: namespace}, ObjectMeta: metav1.ObjectMeta{Name: "differentIssuerAddressProvider", Namespace: namespace},
@ -845,7 +845,7 @@ func TestSync(t *testing.T) {
}, },
} }
r.NoError(pinnipedAPIClient.Tracker().Add(federationDomainDifferentIssuerAddress)) 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 // 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. // 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(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() { it("calls the ProvidersSetter with the non-duplicate", func() {
@ -868,12 +868,12 @@ func TestSync(t *testing.T) {
err := controllerlib.TestSync(t, subject, *syncContext) err := controllerlib.TestSync(t, subject, *syncContext)
r.NoError(err) r.NoError(err)
nonDuplicateProvider, err := provider.NewFederationDomain(federationDomainDifferentIssuerAddress.Spec.Issuer) nonDuplicateProvider, err := provider.NewFederationDomainIssuer(federationDomainDifferentIssuerAddress.Spec.Issuer)
r.NoError(err) r.NoError(err)
r.True(providersSetter.SetProvidersWasCalled) r.True(providersSetter.SetProvidersWasCalled)
r.Equal( r.Equal(
[]*provider.FederationDomain{ []*provider.FederationDomainIssuer{
nonDuplicateProvider, nonDuplicateProvider,
}, },
providersSetter.FederationDomainsReceived, providersSetter.FederationDomainsReceived,

View File

@ -24,13 +24,17 @@ import (
"go.pinniped.dev/internal/plog" "go.pinniped.dev/internal/plog"
) )
const (
federationDomainKind = "FederationDomain"
)
type federationDomainSecretsController struct { type federationDomainSecretsController struct {
secretHelper SecretHelper secretHelper SecretHelper
secretRefFunc func(domain *configv1alpha1.FederationDomain) *corev1.LocalObjectReference secretRefFunc func(domain *configv1alpha1.FederationDomain) *corev1.LocalObjectReference
kubeClient kubernetes.Interface kubeClient kubernetes.Interface
pinnipedClient pinnipedclientset.Interface pinnipedClient pinnipedclientset.Interface
opcInformer configinformers.FederationDomainInformer federationDomainInformer configinformers.FederationDomainInformer
secretInformer corev1informers.SecretInformer secretInformer corev1informers.SecretInformer
} }
// NewFederationDomainSecretsController returns a controllerlib.Controller that ensures a child Secret // NewFederationDomainSecretsController returns a controllerlib.Controller that ensures a child Secret
@ -42,27 +46,27 @@ func NewFederationDomainSecretsController(
kubeClient kubernetes.Interface, kubeClient kubernetes.Interface,
pinnipedClient pinnipedclientset.Interface, pinnipedClient pinnipedclientset.Interface,
secretInformer corev1informers.SecretInformer, secretInformer corev1informers.SecretInformer,
opcInformer configinformers.FederationDomainInformer, federationDomainInformer configinformers.FederationDomainInformer,
withInformer pinnipedcontroller.WithInformerOptionFunc, withInformer pinnipedcontroller.WithInformerOptionFunc,
) controllerlib.Controller { ) controllerlib.Controller {
return controllerlib.New( return controllerlib.New(
controllerlib.Config{ controllerlib.Config{
Name: fmt.Sprintf("%s%s", secretHelper.NamePrefix(), "controller"), Name: fmt.Sprintf("%s%s", secretHelper.NamePrefix(), "controller"),
Syncer: &federationDomainSecretsController{ Syncer: &federationDomainSecretsController{
secretHelper: secretHelper, secretHelper: secretHelper,
secretRefFunc: secretRefFunc, secretRefFunc: secretRefFunc,
kubeClient: kubeClient, kubeClient: kubeClient,
pinnipedClient: pinnipedClient, pinnipedClient: pinnipedClient,
secretInformer: secretInformer, secretInformer: secretInformer,
opcInformer: opcInformer, federationDomainInformer: federationDomainInformer,
}, },
}, },
// We want to be notified when a OPC's secret gets updated or deleted. When this happens, we // We want to be notified when a FederationDomain's secret gets updated or deleted. When this happens, we
// should get notified via the corresponding OPC key. // should get notified via the corresponding FederationDomain key.
withInformer( withInformer(
secretInformer, secretInformer,
pinnipedcontroller.SimpleFilter(isOPControllee, func(obj metav1.Object) controllerlib.Key { pinnipedcontroller.SimpleFilter(isFederationDomainControllee, func(obj metav1.Object) controllerlib.Key {
if isOPControllee(obj) { if isFederationDomainControllee(obj) {
controller := metav1.GetControllerOf(obj) controller := metav1.GetControllerOf(obj)
return controllerlib.Key{ return controllerlib.Key{
Name: controller.Name, Name: controller.Name,
@ -73,9 +77,9 @@ func NewFederationDomainSecretsController(
}), }),
controllerlib.InformerOption{}, 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( withInformer(
opcInformer, federationDomainInformer,
pinnipedcontroller.MatchAnythingFilter(nil), // nil parent func is fine because each event is distinct pinnipedcontroller.MatchAnythingFilter(nil), // nil parent func is fine because each event is distinct
controllerlib.InformerOption{}, controllerlib.InformerOption{},
), ),
@ -83,7 +87,7 @@ func NewFederationDomainSecretsController(
} }
func (c *federationDomainSecretsController) Sync(ctx controllerlib.Context) error { 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) notFound := k8serrors.IsNotFound(err)
if err != nil && !notFound { if err != nil && !notFound {
return fmt.Errorf( return fmt.Errorf(
@ -95,8 +99,8 @@ func (c *federationDomainSecretsController) Sync(ctx controllerlib.Context) erro
} }
if notFound { if notFound {
// The corresponding secret to this OP should have been garbage collected since it should have // The corresponding secret to this FederationDomain should have been garbage collected since it should have
// had this OP as its owner. // had this FederationDomain as its owner.
plog.Debug( plog.Debug(
"federationdomain deleted", "federationdomain deleted",
"federationdomain", "federationdomain",
@ -105,13 +109,13 @@ func (c *federationDomainSecretsController) Sync(ctx controllerlib.Context) erro
return nil return nil
} }
op = op.DeepCopy() federationDomain = federationDomain.DeepCopy()
newSecret, err := c.secretHelper.Generate(op) newSecret, err := c.secretHelper.Generate(federationDomain)
if err != nil { if err != nil {
return fmt.Errorf("failed to generate secret: %w", err) 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 { if err != nil {
return fmt.Errorf("failed to determine secret status: %w", err) return fmt.Errorf("failed to determine secret status: %w", err)
} }
@ -120,44 +124,44 @@ func (c *federationDomainSecretsController) Sync(ctx controllerlib.Context) erro
plog.Debug( plog.Debug(
"secret is up to date", "secret is up to date",
"federationdomain", "federationdomain",
klog.KObj(op), klog.KObj(federationDomain),
"secret", "secret",
klog.KObj(existingSecret), klog.KObj(existingSecret),
) )
op = c.secretHelper.ObserveActiveSecretAndUpdateParentFederationDomain(op, existingSecret) federationDomain = c.secretHelper.ObserveActiveSecretAndUpdateParentFederationDomain(federationDomain, existingSecret)
if err := c.updateFederationDomain(ctx.Context, op); err != nil { if err := c.updateFederationDomain(ctx.Context, federationDomain); err != nil {
return fmt.Errorf("failed to update federationdomain: %w", err) 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 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. // 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) 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) federationDomain = c.secretHelper.ObserveActiveSecretAndUpdateParentFederationDomain(federationDomain, newSecret)
if err := c.updateFederationDomain(ctx.Context, op); err != nil { if err := c.updateFederationDomain(ctx.Context, federationDomain); err != nil {
return fmt.Errorf("failed to update federationdomain: %w", err) 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 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. // needs to be updated. It returns the existing secret as its second argument.
func (c *federationDomainSecretsController) secretNeedsUpdate( func (c *federationDomainSecretsController) secretNeedsUpdate(
op *configv1alpha1.FederationDomain, federationDomain *configv1alpha1.FederationDomain,
secretName string, secretName string,
) (bool, *corev1.Secret, error) { ) (bool, *corev1.Secret, error) {
// This OPC says it has a secret associated with it. Let's try to get it from the cache. // 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(op.Namespace).Get(secretName) secret, err := c.secretInformer.Lister().Secrets(federationDomain.Namespace).Get(secretName)
notFound := k8serrors.IsNotFound(err) notFound := k8serrors.IsNotFound(err)
if err != nil && !notFound { if err != nil && !notFound {
return false, nil, fmt.Errorf("cannot get secret: %w", err) return false, nil, fmt.Errorf("cannot get secret: %w", err)
@ -167,7 +171,7 @@ func (c *federationDomainSecretsController) secretNeedsUpdate(
return true, nil, nil 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. // If this secret is invalid, we need to generate a new one.
return true, secret, nil return true, secret, nil
} }
@ -177,7 +181,7 @@ func (c *federationDomainSecretsController) secretNeedsUpdate(
func (c *federationDomainSecretsController) createOrUpdateSecret( func (c *federationDomainSecretsController) createOrUpdateSecret(
ctx context.Context, ctx context.Context,
op *configv1alpha1.FederationDomain, federationDomain *configv1alpha1.FederationDomain,
newSecret **corev1.Secret, newSecret **corev1.Secret,
) error { ) error {
secretClient := c.kubeClient.CoreV1().Secrets((*newSecret).Namespace) 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. // 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 // If the secret already has valid a valid Secret, then we are good to go and we don't need an
// update. // update.
*newSecret = oldSecret *newSecret = oldSecret
@ -216,23 +220,31 @@ func (c *federationDomainSecretsController) createOrUpdateSecret(
func (c *federationDomainSecretsController) updateFederationDomain( func (c *federationDomainSecretsController) updateFederationDomain(
ctx context.Context, ctx context.Context,
newOP *configv1alpha1.FederationDomain, newFederationDomain *configv1alpha1.FederationDomain,
) error { ) error {
opcClient := c.pinnipedClient.ConfigV1alpha1().FederationDomains(newOP.Namespace) federationDomainClient := c.pinnipedClient.ConfigV1alpha1().FederationDomains(newFederationDomain.Namespace)
return retry.RetryOnConflict(retry.DefaultRetry, func() error { 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 { 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) oldFederationDomainSecretRef := c.secretRefFunc(oldFederationDomain)
newOPSecretRef := c.secretRefFunc(newOP) newFederationDomainSecretRef := c.secretRefFunc(newFederationDomain)
if reflect.DeepEqual(oldOPSecretRef, newOPSecretRef) { if reflect.DeepEqual(oldFederationDomainSecretRef, newFederationDomainSecretRef) {
return nil return nil
} }
*oldOPSecretRef = *newOPSecretRef *oldFederationDomainSecretRef = *newFederationDomainSecretRef
_, err = opcClient.Update(ctx, oldOP, metav1.UpdateOptions{}) _, err = federationDomainClient.Update(ctx, oldFederationDomain, metav1.UpdateOptions{})
return err 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
}

View File

@ -150,7 +150,7 @@ func TestFederationDomainControllerFilterSecret(t *testing.T) {
kubernetesfake.NewSimpleClientset(), kubernetesfake.NewSimpleClientset(),
0, 0,
).Core().V1().Secrets() ).Core().V1().Secrets()
opcInformer := pinnipedinformers.NewSharedInformerFactory( federationDomainInformer := pinnipedinformers.NewSharedInformerFactory(
pinnipedfake.NewSimpleClientset(), pinnipedfake.NewSimpleClientset(),
0, 0,
).Config().V1alpha1().FederationDomains() ).Config().V1alpha1().FederationDomains()
@ -161,7 +161,7 @@ func TestFederationDomainControllerFilterSecret(t *testing.T) {
nil, // kubeClient, not needed nil, // kubeClient, not needed
nil, // pinnipedClient, not needed nil, // pinnipedClient, not needed
secretInformer, secretInformer,
opcInformer, federationDomainInformer,
withInformer.WithInformer, withInformer.WithInformer,
) )
@ -176,24 +176,24 @@ func TestFederationDomainControllerFilterSecret(t *testing.T) {
} }
} }
func TestNewFederationDomainSecretsControllerFilterOPC(t *testing.T) { func TestNewFederationDomainSecretsControllerFilterFederationDomain(t *testing.T) {
t.Parallel() t.Parallel()
tests := []struct { tests := []struct {
name string name string
opc configv1alpha1.FederationDomain federationDomain configv1alpha1.FederationDomain
wantAdd bool wantAdd bool
wantUpdate bool wantUpdate bool
wantDelete bool wantDelete bool
wantParent controllerlib.Key wantParent controllerlib.Key
}{ }{
{ {
name: "anything goes", name: "anything goes",
opc: configv1alpha1.FederationDomain{}, federationDomain: configv1alpha1.FederationDomain{},
wantAdd: true, wantAdd: true,
wantUpdate: true, wantUpdate: true,
wantDelete: true, wantDelete: true,
wantParent: controllerlib.Key{}, wantParent: controllerlib.Key{},
}, },
} }
for _, test := range tests { for _, test := range tests {
@ -210,7 +210,7 @@ func TestNewFederationDomainSecretsControllerFilterOPC(t *testing.T) {
kubernetesfake.NewSimpleClientset(), kubernetesfake.NewSimpleClientset(),
0, 0,
).Core().V1().Secrets() ).Core().V1().Secrets()
opcInformer := pinnipedinformers.NewSharedInformerFactory( federationDomainInformer := pinnipedinformers.NewSharedInformerFactory(
pinnipedfake.NewSimpleClientset(), pinnipedfake.NewSimpleClientset(),
0, 0,
).Config().V1alpha1().FederationDomains() ).Config().V1alpha1().FederationDomains()
@ -221,17 +221,17 @@ func TestNewFederationDomainSecretsControllerFilterOPC(t *testing.T) {
nil, // kubeClient, not needed nil, // kubeClient, not needed
nil, // pinnipedClient, not needed nil, // pinnipedClient, not needed
secretInformer, secretInformer,
opcInformer, federationDomainInformer,
withInformer.WithInformer, withInformer.WithInformer,
) )
unrelated := configv1alpha1.FederationDomain{} unrelated := configv1alpha1.FederationDomain{}
filter := withInformer.GetFilterForInformer(opcInformer) filter := withInformer.GetFilterForInformer(federationDomainInformer)
require.Equal(t, test.wantAdd, filter.Add(&test.opc)) require.Equal(t, test.wantAdd, filter.Add(&test.federationDomain))
require.Equal(t, test.wantUpdate, filter.Update(&unrelated, &test.opc)) require.Equal(t, test.wantUpdate, filter.Update(&unrelated, &test.federationDomain))
require.Equal(t, test.wantUpdate, filter.Update(&test.opc, &unrelated)) require.Equal(t, test.wantUpdate, filter.Update(&test.federationDomain, &unrelated))
require.Equal(t, test.wantDelete, filter.Delete(&test.opc)) require.Equal(t, test.wantDelete, filter.Delete(&test.federationDomain))
require.Equal(t, test.wantParent, filter.Parent(&test.opc)) require.Equal(t, test.wantParent, filter.Parent(&test.federationDomain))
}) })
} }
} }
@ -242,14 +242,14 @@ func TestFederationDomainSecretsControllerSync(t *testing.T) {
const ( const (
namespace = "some-namespace" namespace = "some-namespace"
opName = "op-name" federationDomainName = "federationDomain-name"
opUID = "op-uid" federationDomainUID = "federationDomain-uid"
secretName = "secret-name" secretName = "secret-name"
secretUID = "secret-uid" secretUID = "secret-uid"
) )
opGVR := schema.GroupVersionResource{ federationDomainGVR := schema.GroupVersionResource{
Group: configv1alpha1.SchemeGroupVersion.Group, Group: configv1alpha1.SchemeGroupVersion.Group,
Version: configv1alpha1.SchemeGroupVersion.Version, Version: configv1alpha1.SchemeGroupVersion.Version,
Resource: "federationdomains", Resource: "federationdomains",
@ -261,11 +261,11 @@ func TestFederationDomainSecretsControllerSync(t *testing.T) {
Resource: "secrets", Resource: "secrets",
} }
goodOP := &configv1alpha1.FederationDomain{ goodFederationDomain := &configv1alpha1.FederationDomain{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: opName, Name: federationDomainName,
Namespace: namespace, Namespace: namespace,
UID: opUID, UID: federationDomainUID,
}, },
} }
@ -276,10 +276,10 @@ func TestFederationDomainSecretsControllerSync(t *testing.T) {
UID: secretUID, UID: secretUID,
OwnerReferences: []metav1.OwnerReference{ OwnerReferences: []metav1.OwnerReference{
{ {
APIVersion: opGVR.GroupVersion().String(), APIVersion: federationDomainGVR.GroupVersion().String(),
Kind: "FederationDomain", Kind: "FederationDomain",
Name: opName, Name: federationDomainName,
UID: opUID, UID: federationDomainUID,
BlockOwnerDeletion: boolPtr(true), BlockOwnerDeletion: boolPtr(true),
Controller: boolPtr(true), Controller: boolPtr(true),
}, },
@ -295,14 +295,14 @@ func TestFederationDomainSecretsControllerSync(t *testing.T) {
}, },
} }
goodOPWithTokenSigningKey := goodOP.DeepCopy() goodFederationDomainWithTokenSigningKey := goodFederationDomain.DeepCopy()
goodOPWithTokenSigningKey.Status.Secrets.TokenSigningKey.Name = goodSecret.Name goodFederationDomainWithTokenSigningKey.Status.Secrets.TokenSigningKey.Name = goodSecret.Name
goodOPWithJWKS := goodOP.DeepCopy() goodFederationDomainWithJWKS := goodFederationDomain.DeepCopy()
goodOPWithJWKS.Status.Secrets.JWKS.Name = "some-jwks-key" goodFederationDomainWithJWKS.Status.Secrets.JWKS.Name = "some-jwks-key"
goodOPWithJWKSAndTokenSigningKey := goodOPWithJWKS.DeepCopy() goodFederationDomainWithJWKSAndTokenSigningKey := goodFederationDomainWithJWKS.DeepCopy()
goodOPWithJWKSAndTokenSigningKey.Status.Secrets.TokenSigningKey = goodOPWithTokenSigningKey.Status.Secrets.TokenSigningKey goodFederationDomainWithJWKSAndTokenSigningKey.Status.Secrets.TokenSigningKey = goodFederationDomainWithTokenSigningKey.Status.Secrets.TokenSigningKey
invalidSecret := &corev1.Secret{ invalidSecret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
@ -311,10 +311,10 @@ func TestFederationDomainSecretsControllerSync(t *testing.T) {
UID: secretUID, UID: secretUID,
OwnerReferences: []metav1.OwnerReference{ OwnerReferences: []metav1.OwnerReference{
{ {
APIVersion: opGVR.GroupVersion().String(), APIVersion: federationDomainGVR.GroupVersion().String(),
Kind: "FederationDomain", Kind: "FederationDomain",
Name: opName, Name: federationDomainName,
UID: opUID, UID: federationDomainUID,
BlockOwnerDeletion: boolPtr(true), BlockOwnerDeletion: boolPtr(true),
Controller: boolPtr(true), Controller: boolPtr(true),
}, },
@ -323,39 +323,39 @@ func TestFederationDomainSecretsControllerSync(t *testing.T) {
} }
tests := []struct { tests := []struct {
name string name string
storage func(**configv1alpha1.FederationDomain, **corev1.Secret) storage func(**configv1alpha1.FederationDomain, **corev1.Secret)
client func(*pinnipedfake.Clientset, *kubernetesfake.Clientset) client func(*pinnipedfake.Clientset, *kubernetesfake.Clientset)
secretHelper func(*mocksecrethelper.MockSecretHelper) secretHelper func(*mocksecrethelper.MockSecretHelper)
wantOPActions []kubetesting.Action wantFederationDomainActions []kubetesting.Action
wantSecretActions []kubetesting.Action wantSecretActions []kubetesting.Action
wantError string wantError string
}{ }{
{ {
name: "FederationDomain does not exist and secret does not exist", name: "FederationDomain does not exist and secret does not exist",
storage: func(op **configv1alpha1.FederationDomain, s **corev1.Secret) { storage: func(federationDomain **configv1alpha1.FederationDomain, s **corev1.Secret) {
*op = nil *federationDomain = nil
*s = nil *s = nil
}, },
}, },
{ {
name: "FederationDomain does not exist and secret exists", name: "FederationDomain does not exist and secret exists",
storage: func(op **configv1alpha1.FederationDomain, s **corev1.Secret) { storage: func(federationDomain **configv1alpha1.FederationDomain, s **corev1.Secret) {
*op = nil *federationDomain = nil
}, },
}, },
{ {
name: "FederationDomain exists and secret does not exist", 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 *s = nil
}, },
secretHelper: func(secretHelper *mocksecrethelper.MockSecretHelper) { secretHelper: func(secretHelper *mocksecrethelper.MockSecretHelper) {
secretHelper.EXPECT().Generate(goodOP).Times(1).Return(goodSecret, nil) secretHelper.EXPECT().Generate(goodFederationDomain).Times(1).Return(goodSecret, nil)
secretHelper.EXPECT().ObserveActiveSecretAndUpdateParentFederationDomain(goodOP, goodSecret).Times(1).Return(goodOPWithTokenSigningKey) secretHelper.EXPECT().ObserveActiveSecretAndUpdateParentFederationDomain(goodFederationDomain, goodSecret).Times(1).Return(goodFederationDomainWithTokenSigningKey)
}, },
wantOPActions: []kubetesting.Action{ wantFederationDomainActions: []kubetesting.Action{
kubetesting.NewGetAction(opGVR, namespace, goodOP.Name), kubetesting.NewGetAction(federationDomainGVR, namespace, goodFederationDomain.Name),
kubetesting.NewUpdateAction(opGVR, namespace, goodOPWithTokenSigningKey), kubetesting.NewUpdateAction(federationDomainGVR, namespace, goodFederationDomainWithTokenSigningKey),
}, },
wantSecretActions: []kubetesting.Action{ wantSecretActions: []kubetesting.Action{
kubetesting.NewGetAction(secretGVR, namespace, goodSecret.Name), 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", 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 *s = nil
}, },
secretHelper: func(secretHelper *mocksecrethelper.MockSecretHelper) { secretHelper: func(secretHelper *mocksecrethelper.MockSecretHelper) {
secretHelper.EXPECT().Generate(goodOP).Times(1).Return(goodSecret, nil) secretHelper.EXPECT().Generate(goodFederationDomain).Times(1).Return(goodSecret, nil)
secretHelper.EXPECT().ObserveActiveSecretAndUpdateParentFederationDomain(goodOP, goodSecret).Times(1).Return(goodOPWithTokenSigningKey) secretHelper.EXPECT().ObserveActiveSecretAndUpdateParentFederationDomain(goodFederationDomain, goodSecret).Times(1).Return(goodFederationDomainWithTokenSigningKey)
}, },
client: func(c *pinnipedfake.Clientset, _ *kubernetesfake.Clientset) { client: func(c *pinnipedfake.Clientset, _ *kubernetesfake.Clientset) {
c.PrependReactor("get", "federationdomains", func(_ kubetesting.Action) (bool, runtime.Object, error) { c.PrependReactor("get", "federationdomains", func(_ kubetesting.Action) (bool, runtime.Object, error) {
return true, goodOPWithJWKS, nil return true, goodFederationDomainWithJWKS, nil
}) })
}, },
wantOPActions: []kubetesting.Action{ wantFederationDomainActions: []kubetesting.Action{
kubetesting.NewGetAction(opGVR, namespace, goodOP.Name), kubetesting.NewGetAction(federationDomainGVR, namespace, goodFederationDomain.Name),
kubetesting.NewUpdateAction(opGVR, namespace, goodOPWithJWKSAndTokenSigningKey), kubetesting.NewUpdateAction(federationDomainGVR, namespace, goodFederationDomainWithJWKSAndTokenSigningKey),
}, },
wantSecretActions: []kubetesting.Action{ wantSecretActions: []kubetesting.Action{
kubetesting.NewGetAction(secretGVR, namespace, goodSecret.Name), 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", 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 *s = nil
}, },
secretHelper: func(secretHelper *mocksecrethelper.MockSecretHelper) { secretHelper: func(secretHelper *mocksecrethelper.MockSecretHelper) {
secretHelper.EXPECT().Generate(goodOP).Times(1).Return(goodSecret, nil) secretHelper.EXPECT().Generate(goodFederationDomain).Times(1).Return(goodSecret, nil)
secretHelper.EXPECT().ObserveActiveSecretAndUpdateParentFederationDomain(goodOP, goodSecret).Times(1).Return(goodOPWithTokenSigningKey) secretHelper.EXPECT().ObserveActiveSecretAndUpdateParentFederationDomain(goodFederationDomain, goodSecret).Times(1).Return(goodFederationDomainWithTokenSigningKey)
}, },
client: func(c *pinnipedfake.Clientset, _ *kubernetesfake.Clientset) { client: func(c *pinnipedfake.Clientset, _ *kubernetesfake.Clientset) {
c.PrependReactor("get", "federationdomains", func(_ kubetesting.Action) (bool, runtime.Object, error) { c.PrependReactor("get", "federationdomains", func(_ kubetesting.Action) (bool, runtime.Object, error) {
return true, goodOPWithJWKSAndTokenSigningKey, nil return true, goodFederationDomainWithJWKSAndTokenSigningKey, nil
}) })
}, },
wantOPActions: []kubetesting.Action{ wantFederationDomainActions: []kubetesting.Action{
kubetesting.NewGetAction(opGVR, namespace, goodOP.Name), kubetesting.NewGetAction(federationDomainGVR, namespace, goodFederationDomain.Name),
}, },
wantSecretActions: []kubetesting.Action{ wantSecretActions: []kubetesting.Action{
kubetesting.NewGetAction(secretGVR, namespace, goodSecret.Name), kubetesting.NewGetAction(secretGVR, namespace, goodSecret.Name),
@ -409,17 +409,17 @@ func TestFederationDomainSecretsControllerSync(t *testing.T) {
}, },
{ {
name: "FederationDomain exists and invalid secret exists", 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() *s = invalidSecret.DeepCopy()
}, },
secretHelper: func(secretHelper *mocksecrethelper.MockSecretHelper) { secretHelper: func(secretHelper *mocksecrethelper.MockSecretHelper) {
secretHelper.EXPECT().Generate(goodOP).Times(1).Return(goodSecret, nil) secretHelper.EXPECT().Generate(goodFederationDomain).Times(1).Return(goodSecret, nil)
secretHelper.EXPECT().IsValid(goodOP, invalidSecret).Times(2).Return(false) secretHelper.EXPECT().IsValid(goodFederationDomain, invalidSecret).Times(2).Return(false)
secretHelper.EXPECT().ObserveActiveSecretAndUpdateParentFederationDomain(goodOP, goodSecret).Times(1).Return(goodOPWithTokenSigningKey) secretHelper.EXPECT().ObserveActiveSecretAndUpdateParentFederationDomain(goodFederationDomain, goodSecret).Times(1).Return(goodFederationDomainWithTokenSigningKey)
}, },
wantOPActions: []kubetesting.Action{ wantFederationDomainActions: []kubetesting.Action{
kubetesting.NewGetAction(opGVR, namespace, goodOP.Name), kubetesting.NewGetAction(federationDomainGVR, namespace, goodFederationDomain.Name),
kubetesting.NewUpdateAction(opGVR, namespace, goodOPWithTokenSigningKey), kubetesting.NewUpdateAction(federationDomainGVR, namespace, goodFederationDomainWithTokenSigningKey),
}, },
wantSecretActions: []kubetesting.Action{ wantSecretActions: []kubetesting.Action{
kubetesting.NewGetAction(secretGVR, namespace, goodSecret.Name), kubetesting.NewGetAction(secretGVR, namespace, goodSecret.Name),
@ -429,7 +429,7 @@ func TestFederationDomainSecretsControllerSync(t *testing.T) {
{ {
name: "FederationDomain exists and generating a secret fails", name: "FederationDomain exists and generating a secret fails",
secretHelper: func(secretHelper *mocksecrethelper.MockSecretHelper) { 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", wantError: "failed to generate secret: some generate error",
}, },
@ -439,14 +439,14 @@ func TestFederationDomainSecretsControllerSync(t *testing.T) {
otherSecret := goodSecret.DeepCopy() otherSecret := goodSecret.DeepCopy()
otherSecret.UID = "other-secret-uid" otherSecret.UID = "other-secret-uid"
secretHelper.EXPECT().Generate(goodOP).Times(1).Return(otherSecret, nil) secretHelper.EXPECT().Generate(goodFederationDomain).Times(1).Return(otherSecret, nil)
secretHelper.EXPECT().IsValid(goodOP, goodSecret).Times(1).Return(false) secretHelper.EXPECT().IsValid(goodFederationDomain, goodSecret).Times(1).Return(false)
secretHelper.EXPECT().IsValid(goodOP, goodSecret).Times(1).Return(true) secretHelper.EXPECT().IsValid(goodFederationDomain, goodSecret).Times(1).Return(true)
secretHelper.EXPECT().ObserveActiveSecretAndUpdateParentFederationDomain(goodOP, goodSecret).Times(1).Return(goodOPWithTokenSigningKey) secretHelper.EXPECT().ObserveActiveSecretAndUpdateParentFederationDomain(goodFederationDomain, goodSecret).Times(1).Return(goodFederationDomainWithTokenSigningKey)
}, },
wantOPActions: []kubetesting.Action{ wantFederationDomainActions: []kubetesting.Action{
kubetesting.NewGetAction(opGVR, namespace, goodOP.Name), kubetesting.NewGetAction(federationDomainGVR, namespace, goodFederationDomain.Name),
kubetesting.NewUpdateAction(opGVR, namespace, goodOPWithTokenSigningKey), kubetesting.NewUpdateAction(federationDomainGVR, namespace, goodFederationDomainWithTokenSigningKey),
}, },
wantSecretActions: []kubetesting.Action{ wantSecretActions: []kubetesting.Action{
kubetesting.NewGetAction(secretGVR, namespace, goodSecret.Name), 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", name: "FederationDomain exists and invalid secret exists and getting secret fails",
secretHelper: func(secretHelper *mocksecrethelper.MockSecretHelper) { secretHelper: func(secretHelper *mocksecrethelper.MockSecretHelper) {
secretHelper.EXPECT().Generate(goodOP).Times(1).Return(goodSecret, nil) secretHelper.EXPECT().Generate(goodFederationDomain).Times(1).Return(goodSecret, nil)
secretHelper.EXPECT().IsValid(goodOP, goodSecret).Times(1).Return(false) secretHelper.EXPECT().IsValid(goodFederationDomain, goodSecret).Times(1).Return(false)
}, },
client: func(_ *pinnipedfake.Clientset, c *kubernetesfake.Clientset) { client: func(_ *pinnipedfake.Clientset, c *kubernetesfake.Clientset) {
c.PrependReactor("get", "secrets", func(_ kubetesting.Action) (bool, runtime.Object, error) { 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", 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 *s = nil
}, },
secretHelper: func(secretHelper *mocksecrethelper.MockSecretHelper) { 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) { client: func(_ *pinnipedfake.Clientset, c *kubernetesfake.Clientset) {
c.PrependReactor("create", "secrets", func(_ kubetesting.Action) (bool, runtime.Object, error) { 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", name: "FederationDomain exists and invalid secret exists and updating secret fails",
secretHelper: func(secretHelper *mocksecrethelper.MockSecretHelper) { secretHelper: func(secretHelper *mocksecrethelper.MockSecretHelper) {
secretHelper.EXPECT().Generate(goodOP).Times(1).Return(goodSecret, nil) secretHelper.EXPECT().Generate(goodFederationDomain).Times(1).Return(goodSecret, nil)
secretHelper.EXPECT().IsValid(goodOP, goodSecret).Times(2).Return(false) secretHelper.EXPECT().IsValid(goodFederationDomain, goodSecret).Times(2).Return(false)
}, },
client: func(_ *pinnipedfake.Clientset, c *kubernetesfake.Clientset) { client: func(_ *pinnipedfake.Clientset, c *kubernetesfake.Clientset) {
c.PrependReactor("update", "secrets", func(_ kubetesting.Action) (bool, runtime.Object, error) { 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", 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() *s = invalidSecret.DeepCopy()
}, },
secretHelper: func(secretHelper *mocksecrethelper.MockSecretHelper) { secretHelper: func(secretHelper *mocksecrethelper.MockSecretHelper) {
secretHelper.EXPECT().Generate(goodOP).Times(1).Return(goodSecret, nil) secretHelper.EXPECT().Generate(goodFederationDomain).Times(1).Return(goodSecret, nil)
secretHelper.EXPECT().IsValid(goodOP, invalidSecret).Times(3).Return(false) secretHelper.EXPECT().IsValid(goodFederationDomain, invalidSecret).Times(3).Return(false)
secretHelper.EXPECT().ObserveActiveSecretAndUpdateParentFederationDomain(goodOP, goodSecret).Times(1).Return(goodOPWithTokenSigningKey) secretHelper.EXPECT().ObserveActiveSecretAndUpdateParentFederationDomain(goodFederationDomain, goodSecret).Times(1).Return(goodFederationDomainWithTokenSigningKey)
}, },
client: func(_ *pinnipedfake.Clientset, c *kubernetesfake.Clientset) { client: func(_ *pinnipedfake.Clientset, c *kubernetesfake.Clientset) {
once := sync.Once{} once := sync.Once{}
@ -522,9 +522,9 @@ func TestFederationDomainSecretsControllerSync(t *testing.T) {
return true, nil, err return true, nil, err
}) })
}, },
wantOPActions: []kubetesting.Action{ wantFederationDomainActions: []kubetesting.Action{
kubetesting.NewGetAction(opGVR, namespace, goodOP.Name), kubetesting.NewGetAction(federationDomainGVR, namespace, goodFederationDomain.Name),
kubetesting.NewUpdateAction(opGVR, namespace, goodOPWithTokenSigningKey), kubetesting.NewUpdateAction(federationDomainGVR, namespace, goodFederationDomainWithTokenSigningKey),
}, },
wantSecretActions: []kubetesting.Action{ wantSecretActions: []kubetesting.Action{
kubetesting.NewGetAction(secretGVR, namespace, goodSecret.Name), 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", 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() *s = invalidSecret.DeepCopy()
}, },
secretHelper: func(secretHelper *mocksecrethelper.MockSecretHelper) { secretHelper: func(secretHelper *mocksecrethelper.MockSecretHelper) {
secretHelper.EXPECT().Generate(goodOP).Times(1).Return(goodSecret, nil) secretHelper.EXPECT().Generate(goodFederationDomain).Times(1).Return(goodSecret, nil)
secretHelper.EXPECT().IsValid(goodOP, invalidSecret).Times(2).Return(false) secretHelper.EXPECT().IsValid(goodFederationDomain, invalidSecret).Times(2).Return(false)
secretHelper.EXPECT().ObserveActiveSecretAndUpdateParentFederationDomain(goodOP, goodSecret).Times(1).Return(goodOPWithTokenSigningKey) secretHelper.EXPECT().ObserveActiveSecretAndUpdateParentFederationDomain(goodFederationDomain, goodSecret).Times(1).Return(goodFederationDomainWithTokenSigningKey)
}, },
client: func(c *pinnipedfake.Clientset, _ *kubernetesfake.Clientset) { client: func(c *pinnipedfake.Clientset, _ *kubernetesfake.Clientset) {
c.PrependReactor("get", "federationdomains", func(_ kubetesting.Action) (bool, runtime.Object, error) { c.PrependReactor("get", "federationdomains", func(_ kubetesting.Action) (bool, runtime.Object, error) {
return true, nil, errors.New("some get error") return true, nil, errors.New("some get error")
}) })
}, },
wantOPActions: []kubetesting.Action{ wantFederationDomainActions: []kubetesting.Action{
kubetesting.NewGetAction(opGVR, namespace, goodOP.Name), kubetesting.NewGetAction(federationDomainGVR, namespace, goodFederationDomain.Name),
}, },
wantSecretActions: []kubetesting.Action{ wantSecretActions: []kubetesting.Action{
kubetesting.NewGetAction(secretGVR, namespace, goodSecret.Name), kubetesting.NewGetAction(secretGVR, namespace, goodSecret.Name),
kubetesting.NewUpdateAction(secretGVR, namespace, goodSecret), 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", 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() *s = invalidSecret.DeepCopy()
}, },
secretHelper: func(secretHelper *mocksecrethelper.MockSecretHelper) { secretHelper: func(secretHelper *mocksecrethelper.MockSecretHelper) {
secretHelper.EXPECT().Generate(goodOP).Times(1).Return(goodSecret, nil) secretHelper.EXPECT().Generate(goodFederationDomain).Times(1).Return(goodSecret, nil)
secretHelper.EXPECT().IsValid(goodOP, invalidSecret).Times(2).Return(false) secretHelper.EXPECT().IsValid(goodFederationDomain, invalidSecret).Times(2).Return(false)
secretHelper.EXPECT().ObserveActiveSecretAndUpdateParentFederationDomain(goodOP, goodSecret).Times(1).Return(goodOPWithTokenSigningKey) secretHelper.EXPECT().ObserveActiveSecretAndUpdateParentFederationDomain(goodFederationDomain, goodSecret).Times(1).Return(goodFederationDomainWithTokenSigningKey)
}, },
client: func(c *pinnipedfake.Clientset, _ *kubernetesfake.Clientset) { client: func(c *pinnipedfake.Clientset, _ *kubernetesfake.Clientset) {
once := sync.Once{} once := sync.Once{}
@ -575,11 +575,11 @@ func TestFederationDomainSecretsControllerSync(t *testing.T) {
return true, nil, err return true, nil, err
}) })
}, },
wantOPActions: []kubetesting.Action{ wantFederationDomainActions: []kubetesting.Action{
kubetesting.NewGetAction(opGVR, namespace, goodOP.Name), kubetesting.NewGetAction(federationDomainGVR, namespace, goodFederationDomain.Name),
kubetesting.NewUpdateAction(opGVR, namespace, goodOPWithTokenSigningKey), kubetesting.NewUpdateAction(federationDomainGVR, namespace, goodFederationDomainWithTokenSigningKey),
kubetesting.NewGetAction(opGVR, namespace, goodOP.Name), kubetesting.NewGetAction(federationDomainGVR, namespace, goodFederationDomain.Name),
kubetesting.NewUpdateAction(opGVR, namespace, goodOPWithTokenSigningKey), kubetesting.NewUpdateAction(federationDomainGVR, namespace, goodFederationDomainWithTokenSigningKey),
}, },
wantSecretActions: []kubetesting.Action{ wantSecretActions: []kubetesting.Action{
kubetesting.NewGetAction(secretGVR, namespace, goodSecret.Name), kubetesting.NewGetAction(secretGVR, namespace, goodSecret.Name),
@ -601,14 +601,14 @@ func TestFederationDomainSecretsControllerSync(t *testing.T) {
kubeAPIClient := kubernetesfake.NewSimpleClientset() kubeAPIClient := kubernetesfake.NewSimpleClientset()
kubeInformerClient := kubernetesfake.NewSimpleClientset() kubeInformerClient := kubernetesfake.NewSimpleClientset()
op := goodOP.DeepCopy() federationDomain := goodFederationDomain.DeepCopy()
secret := goodSecret.DeepCopy() secret := goodSecret.DeepCopy()
if test.storage != nil { if test.storage != nil {
test.storage(&op, &secret) test.storage(&federationDomain, &secret)
} }
if op != nil { if federationDomain != nil {
require.NoError(t, pinnipedAPIClient.Tracker().Add(op)) require.NoError(t, pinnipedAPIClient.Tracker().Add(federationDomain))
require.NoError(t, pinnipedInformerClient.Tracker().Add(op)) require.NoError(t, pinnipedInformerClient.Tracker().Add(federationDomain))
} }
if secret != nil { if secret != nil {
require.NoError(t, kubeAPIClient.Tracker().Add(secret)) require.NoError(t, kubeAPIClient.Tracker().Add(secret))
@ -657,7 +657,7 @@ func TestFederationDomainSecretsControllerSync(t *testing.T) {
Context: ctx, Context: ctx,
Key: controllerlib.Key{ Key: controllerlib.Key{
Namespace: namespace, Namespace: namespace,
Name: opName, Name: federationDomainName,
}, },
}) })
if test.wantError != "" { if test.wantError != "" {
@ -666,10 +666,10 @@ func TestFederationDomainSecretsControllerSync(t *testing.T) {
} }
require.NoError(t, err) require.NoError(t, err)
if test.wantOPActions == nil { if test.wantFederationDomainActions == nil {
test.wantOPActions = []kubetesting.Action{} test.wantFederationDomainActions = []kubetesting.Action{}
} }
require.Equal(t, test.wantOPActions, pinnipedAPIClient.Actions()) require.Equal(t, test.wantFederationDomainActions, pinnipedAPIClient.Actions())
if test.wantSecretActions == nil { if test.wantSecretActions == nil {
test.wantSecretActions = []kubetesting.Action{} test.wantSecretActions = []kubetesting.Action{}
} }

View File

@ -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
}

View File

@ -12,7 +12,6 @@ import (
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
configv1alpha1 "go.pinniped.dev/generated/1.19/apis/supervisor/config/v1alpha1" 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 // SecretHelper describes an object that can Generate() a Secret and determine whether a Secret
@ -27,8 +26,18 @@ type SecretHelper interface {
} }
const ( const (
// symmetricSecretType is corev1.Secret.Type of all corev1.Secret's generated by this helper. // SupervisorCSRFSigningKeySecretType for the Secret storing the CSRF signing key.
symmetricSecretType = "secrets.pinniped.dev/symmetric" 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 is the corev1.Secret.Data key for the symmetric key value generated by this helper.
symmetricSecretDataKey = "key" symmetricSecretDataKey = "key"
@ -96,7 +105,7 @@ func (s *symmetricSecretHelper) Generate(parent *configv1alpha1.FederationDomain
}), }),
}, },
}, },
Type: symmetricSecretType, Type: s.secretType(),
Data: map[string][]byte{ Data: map[string][]byte{
symmetricSecretDataKey: key, symmetricSecretDataKey: key,
}, },
@ -109,7 +118,7 @@ func (s *symmetricSecretHelper) IsValid(parent *configv1alpha1.FederationDomain,
return false return false
} }
if secret.Type != symmetricSecretType { if secret.Type != s.secretType() {
return false return false
} }
@ -126,26 +135,34 @@ func (s *symmetricSecretHelper) IsValid(parent *configv1alpha1.FederationDomain,
// ObserveActiveSecretAndUpdateParentFederationDomain implements SecretHelper.ObserveActiveSecretAndUpdateParentFederationDomain(). // ObserveActiveSecretAndUpdateParentFederationDomain implements SecretHelper.ObserveActiveSecretAndUpdateParentFederationDomain().
func (s *symmetricSecretHelper) ObserveActiveSecretAndUpdateParentFederationDomain( func (s *symmetricSecretHelper) ObserveActiveSecretAndUpdateParentFederationDomain(
op *configv1alpha1.FederationDomain, federationDomain *configv1alpha1.FederationDomain,
secret *corev1.Secret, secret *corev1.Secret,
) *configv1alpha1.FederationDomain { ) *configv1alpha1.FederationDomain {
var cacheKey string s.updateCacheFunc(federationDomain.Spec.Issuer, secret.Data[symmetricSecretDataKey])
if op != nil {
cacheKey = op.Spec.Issuer
}
s.updateCacheFunc(cacheKey, secret.Data[symmetricSecretDataKey])
switch s.secretUsage { switch s.secretUsage {
case SecretUsageTokenSigningKey: case SecretUsageTokenSigningKey:
op.Status.Secrets.TokenSigningKey.Name = secret.Name federationDomain.Status.Secrets.TokenSigningKey.Name = secret.Name
case SecretUsageStateSigningKey: case SecretUsageStateSigningKey:
op.Status.Secrets.StateSigningKey.Name = secret.Name federationDomain.Status.Secrets.StateSigningKey.Name = secret.Name
case SecretUsageStateEncryptionKey: case SecretUsageStateEncryptionKey:
op.Status.Secrets.StateEncryptionKey.Name = secret.Name federationDomain.Status.Secrets.StateEncryptionKey.Name = secret.Name
default: 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))
}
} }

View File

@ -23,27 +23,31 @@ func TestSymmetricSecretHelper(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
secretUsage SecretUsage secretUsage SecretUsage
wantSecretType corev1.SecretType
wantSetFederationDomainField func(*configv1alpha1.FederationDomain) string wantSetFederationDomainField func(*configv1alpha1.FederationDomain) string
}{ }{
{ {
name: "token signing key", name: "token signing key",
secretUsage: SecretUsageTokenSigningKey, secretUsage: SecretUsageTokenSigningKey,
wantSetFederationDomainField: func(op *configv1alpha1.FederationDomain) string { wantSecretType: "secrets.pinniped.dev/federation-domain-token-signing-key",
return op.Status.Secrets.TokenSigningKey.Name wantSetFederationDomainField: func(federationDomain *configv1alpha1.FederationDomain) string {
return federationDomain.Status.Secrets.TokenSigningKey.Name
}, },
}, },
{ {
name: "state signing key", name: "state signing key",
secretUsage: SecretUsageStateSigningKey, secretUsage: SecretUsageStateSigningKey,
wantSetFederationDomainField: func(op *configv1alpha1.FederationDomain) string { wantSecretType: "secrets.pinniped.dev/federation-domain-state-signing-key",
return op.Status.Secrets.StateSigningKey.Name wantSetFederationDomainField: func(federationDomain *configv1alpha1.FederationDomain) string {
return federationDomain.Status.Secrets.StateSigningKey.Name
}, },
}, },
{ {
name: "state encryption key", name: "state encryption key",
secretUsage: SecretUsageStateEncryptionKey, secretUsage: SecretUsageStateEncryptionKey,
wantSetFederationDomainField: func(op *configv1alpha1.FederationDomain) string { wantSecretType: "secrets.pinniped.dev/federation-domain-state-encryption-key",
return op.Status.Secrets.StateEncryptionKey.Name 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{ Data: map[string][]byte{
"key": []byte(keyWith32Bytes), "key": []byte(keyWith32Bytes),
}, },
@ -110,55 +114,69 @@ func TestSymmetricSecretHelper(t *testing.T) {
func TestSymmetricSecretHelperIsValid(t *testing.T) { func TestSymmetricSecretHelperIsValid(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
child func(*corev1.Secret) secretUsage SecretUsage
parent func(*configv1alpha1.FederationDomain) child func(*corev1.Secret)
want bool parent func(*configv1alpha1.FederationDomain)
want bool
}{ }{
{ {
name: "wrong type", name: "wrong type",
secretUsage: SecretUsageTokenSigningKey,
child: func(s *corev1.Secret) { child: func(s *corev1.Secret) {
s.Type = "wrong" s.Type = "wrong"
}, },
want: false, want: false,
}, },
{ {
name: "empty type", name: "empty type",
secretUsage: SecretUsageTokenSigningKey,
child: func(s *corev1.Secret) { child: func(s *corev1.Secret) {
s.Type = "" s.Type = ""
}, },
want: false, want: false,
}, },
{ {
name: "data key is too short", name: "data key is too short",
secretUsage: SecretUsageTokenSigningKey,
child: func(s *corev1.Secret) { child: func(s *corev1.Secret) {
s.Type = FederationDomainTokenSigningKeyType
s.Data["key"] = []byte("short") s.Data["key"] = []byte("short")
}, },
want: false, want: false,
}, },
{ {
name: "data key does not exist", name: "data key does not exist",
secretUsage: SecretUsageTokenSigningKey,
child: func(s *corev1.Secret) { child: func(s *corev1.Secret) {
s.Type = FederationDomainTokenSigningKeyType
delete(s.Data, "key") delete(s.Data, "key")
}, },
want: false, want: false,
}, },
{ {
name: "child not owned by parent", name: "child not owned by parent",
parent: func(op *configv1alpha1.FederationDomain) { secretUsage: SecretUsageTokenSigningKey,
op.UID = "wrong" child: func(s *corev1.Secret) {
s.Type = FederationDomainTokenSigningKeyType
},
parent: func(federationDomain *configv1alpha1.FederationDomain) {
federationDomain.UID = "wrong"
}, },
want: false, want: false,
}, },
{ {
name: "happy path", name: "happy path",
want: true, secretUsage: SecretUsageTokenSigningKey,
child: func(s *corev1.Secret) {
s.Type = FederationDomainTokenSigningKeyType
}, want: true,
}, },
} }
for _, test := range tests { for _, test := range tests {
test := test test := test
t.Run(test.name, func(t *testing.T) { 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{ parent := &configv1alpha1.FederationDomain{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
@ -179,7 +197,7 @@ func TestSymmetricSecretHelperIsValid(t *testing.T) {
}), }),
}, },
}, },
Type: "secrets.pinniped.dev/symmetric", Type: "invalid default",
Data: map[string][]byte{ Data: map[string][]byte{
"key": []byte(keyWith32Bytes), "key": []byte(keyWith32Bytes),
}, },

View File

@ -6,12 +6,14 @@ package generator
import ( import (
"context" "context"
"crypto/rand"
"fmt" "fmt"
appsv1 "k8s.io/api/apps/v1" appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
k8serrors "k8s.io/apimachinery/pkg/api/errors" k8serrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
corev1informers "k8s.io/client-go/informers/core/v1" corev1informers "k8s.io/client-go/informers/core/v1"
"k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes"
"k8s.io/client-go/util/retry" "k8s.io/client-go/util/retry"
@ -143,3 +145,80 @@ func (c *supervisorSecretsController) updateSecret(ctx context.Context, newSecre
return err 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
}

View File

@ -258,7 +258,7 @@ func TestSupervisorSecretsControllerSync(t *testing.T) {
}, },
Labels: labels, Labels: labels,
}, },
Type: "secrets.pinniped.dev/symmetric", Type: "secrets.pinniped.dev/supervisor-csrf-signing-key",
Data: map[string][]byte{ Data: map[string][]byte{
"key": generatedSymmetricKey, "key": generatedSymmetricKey,
}, },
@ -280,7 +280,7 @@ func TestSupervisorSecretsControllerSync(t *testing.T) {
}, },
Labels: labels, Labels: labels,
}, },
Type: "secrets.pinniped.dev/symmetric", Type: "secrets.pinniped.dev/supervisor-csrf-signing-key",
Data: map[string][]byte{ Data: map[string][]byte{
"key": otherGeneratedSymmetricKey, "key": otherGeneratedSymmetricKey,
}, },

View File

@ -30,7 +30,7 @@ import (
"go.pinniped.dev/internal/plog" "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 ( const (
// activeJWKKey points to the current private key used for signing tokens. // 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! // Note! The value for this key will contain only public key material!
jwksKey = "jwks" jwksKey = "jwks"
jwksSecretTypeValue corev1.SecretType = "secrets.pinniped.dev/federation-domain-jwks"
) )
const ( const (
opcKind = "FederationDomain" federationDomainKind = "FederationDomain"
) )
// generateKey is stubbed out for the purpose of testing. The default behavior is to generate an EC key. // 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) 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. // secrets, both via a cache and via the API.
type jwksWriterController struct { type jwksWriterController struct {
jwksSecretLabels map[string]string jwksSecretLabels map[string]string
pinnipedClient pinnipedclientset.Interface pinnipedClient pinnipedclientset.Interface
kubeClient kubernetes.Interface kubeClient kubernetes.Interface
opcInformer configinformers.FederationDomainInformer federationDomainInformer configinformers.FederationDomainInformer
secretInformer corev1informers.SecretInformer 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. // Secret that contains a valid active JWK and JWKS.
func NewJWKSWriterController( func NewJWKSWriterController(
jwksSecretLabels map[string]string, jwksSecretLabels map[string]string,
kubeClient kubernetes.Interface, kubeClient kubernetes.Interface,
pinnipedClient pinnipedclientset.Interface, pinnipedClient pinnipedclientset.Interface,
secretInformer corev1informers.SecretInformer, secretInformer corev1informers.SecretInformer,
opcInformer configinformers.FederationDomainInformer, federationDomainInformer configinformers.FederationDomainInformer,
withInformer pinnipedcontroller.WithInformerOptionFunc, withInformer pinnipedcontroller.WithInformerOptionFunc,
) controllerlib.Controller { ) controllerlib.Controller {
return controllerlib.New( return controllerlib.New(
controllerlib.Config{ controllerlib.Config{
Name: "JWKSController", Name: "JWKSController",
Syncer: &jwksWriterController{ Syncer: &jwksWriterController{
jwksSecretLabels: jwksSecretLabels, jwksSecretLabels: jwksSecretLabels,
kubeClient: kubeClient, kubeClient: kubeClient,
pinnipedClient: pinnipedClient, pinnipedClient: pinnipedClient,
secretInformer: secretInformer, secretInformer: secretInformer,
opcInformer: opcInformer, federationDomainInformer: federationDomainInformer,
}, },
}, },
// We want to be notified when a OPC's secret gets updated or deleted. When this happens, we // We want to be notified when a FederationDomain's secret gets updated or deleted. When this happens, we
// should get notified via the corresponding OPC key. // should get notified via the corresponding FederationDomain key.
withInformer( withInformer(
secretInformer, secretInformer,
controllerlib.FilterFuncs{ controllerlib.FilterFuncs{
ParentFunc: func(obj metav1.Object) controllerlib.Key { ParentFunc: func(obj metav1.Object) controllerlib.Key {
if isOPCControllee(obj) { if isFederationDomainControllee(obj) {
controller := metav1.GetControllerOf(obj) controller := metav1.GetControllerOf(obj)
return controllerlib.Key{ return controllerlib.Key{
Name: controller.Name, Name: controller.Name,
@ -100,17 +102,17 @@ func NewJWKSWriterController(
} }
return controllerlib.Key{} return controllerlib.Key{}
}, },
AddFunc: isOPCControllee, AddFunc: isFederationDomainControllee,
UpdateFunc: func(oldObj, newObj metav1.Object) bool { UpdateFunc: func(oldObj, newObj metav1.Object) bool {
return isOPCControllee(oldObj) || isOPCControllee(newObj) return isFederationDomainControllee(oldObj) || isFederationDomainControllee(newObj)
}, },
DeleteFunc: isOPCControllee, DeleteFunc: isFederationDomainControllee,
}, },
controllerlib.InformerOption{}, 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( withInformer(
opcInformer, federationDomainInformer,
pinnipedcontroller.MatchAnythingFilter(nil), // nil parent func is fine because each event is distinct pinnipedcontroller.MatchAnythingFilter(nil), // nil parent func is fine because each event is distinct
controllerlib.InformerOption{}, controllerlib.InformerOption{},
), ),
@ -119,7 +121,7 @@ func NewJWKSWriterController(
// Sync implements controllerlib.Syncer. // Sync implements controllerlib.Syncer.
func (c *jwksWriterController) Sync(ctx controllerlib.Context) error { 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) notFound := k8serrors.IsNotFound(err)
if err != nil && !notFound { if err != nil && !notFound {
return fmt.Errorf( return fmt.Errorf(
@ -131,17 +133,17 @@ func (c *jwksWriterController) Sync(ctx controllerlib.Context) error {
} }
if notFound { if notFound {
// The corresponding secret to this OPC should have been garbage collected since it should have // The corresponding secret to this FederationDomain should have been garbage collected since it should have
// had this OPC as its owner. // had this FederationDomain as its owner.
plog.Debug( plog.Debug(
"federationdomain deleted", "FederationDomain deleted",
"federationdomain", "federationdomain",
klog.KRef(ctx.Key.Namespace, ctx.Key.Name), klog.KRef(ctx.Key.Namespace, ctx.Key.Name),
) )
return nil return nil
} }
secretNeedsUpdate, err := c.secretNeedsUpdate(opc) secretNeedsUpdate, err := c.secretNeedsUpdate(federationDomain)
if err != nil { if err != nil {
return fmt.Errorf("cannot determine secret status: %w", err) return fmt.Errorf("cannot determine secret status: %w", err)
} }
@ -155,9 +157,9 @@ func (c *jwksWriterController) Sync(ctx controllerlib.Context) error {
return nil 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). // 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 { if err != nil {
return fmt.Errorf("cannot generate secret: %w", err) 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)) plog.Debug("created/updated secret", "secret", klog.KObj(secret))
// Ensure that the OPC points to the secret. // Ensure that the FederationDomain points to the secret.
newOPC := opc.DeepCopy() newFederationDomain := federationDomain.DeepCopy()
newOPC.Status.Secrets.JWKS.Name = secret.Name newFederationDomain.Status.Secrets.JWKS.Name = secret.Name
if err := c.updateOPC(ctx.Context, newOPC); err != nil { if err := c.updateFederationDomain(ctx.Context, newFederationDomain); err != nil {
return fmt.Errorf("cannot update opc: %w", err) 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 return nil
} }
func (c *jwksWriterController) secretNeedsUpdate(opc *configv1alpha1.FederationDomain) (bool, error) { func (c *jwksWriterController) secretNeedsUpdate(federationDomain *configv1alpha1.FederationDomain) (bool, error) {
if opc.Status.Secrets.JWKS.Name == "" { if federationDomain.Status.Secrets.JWKS.Name == "" {
// If the OPC says it doesn't have a secret associated with it, then let's create one. // If the FederationDomain says it doesn't have a secret associated with it, then let's create one.
return true, nil return true, nil
} }
// This OPC says it has a secret associated with it. Let's try to get it from the cache. // 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(opc.Namespace).Get(opc.Status.Secrets.JWKS.Name) secret, err := c.secretInformer.Lister().Secrets(federationDomain.Namespace).Get(federationDomain.Status.Secrets.JWKS.Name)
notFound := k8serrors.IsNotFound(err) notFound := k8serrors.IsNotFound(err)
if err != nil && !notFound { if err != nil && !notFound {
return false, fmt.Errorf("cannot get secret: %w", err) return false, fmt.Errorf("cannot get secret: %w", err)
@ -203,9 +205,9 @@ func (c *jwksWriterController) secretNeedsUpdate(opc *configv1alpha1.FederationD
return false, nil return false, nil
} }
func (c *jwksWriterController) generateSecret(opc *configv1alpha1.FederationDomain) (*corev1.Secret, error) { func (c *jwksWriterController) generateSecret(federationDomain *configv1alpha1.FederationDomain) (*corev1.Secret, error) {
// Note! This is where we could potentially add more handling of OPC spec fields which tell us how // Note! This is where we could potentially add more handling of FederationDomain spec fields which tell us how
// this OIDC provider should sign and verify ID tokens (e.g., hardcoded token secret, gRPC // this FederationDomain should sign and verify ID tokens (e.g., hardcoded token secret, gRPC
// connection to KMS, etc). // connection to KMS, etc).
// //
// For now, we just generate an new RSA keypair and put that in the secret. // 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{ s := corev1.Secret{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: opc.Name + "-jwks", Name: federationDomain.Name + "-jwks",
Namespace: opc.Namespace, Namespace: federationDomain.Namespace,
Labels: c.jwksSecretLabels, Labels: c.jwksSecretLabels,
OwnerReferences: []metav1.OwnerReference{ OwnerReferences: []metav1.OwnerReference{
*metav1.NewControllerRef(opc, schema.GroupVersionKind{ *metav1.NewControllerRef(federationDomain, schema.GroupVersionKind{
Group: configv1alpha1.SchemeGroupVersion.Group, Group: configv1alpha1.SchemeGroupVersion.Group,
Version: configv1alpha1.SchemeGroupVersion.Version, Version: configv1alpha1.SchemeGroupVersion.Version,
Kind: opcKind, Kind: federationDomainKind,
}), }),
}, },
}, },
@ -251,6 +253,7 @@ func (c *jwksWriterController) generateSecret(opc *configv1alpha1.FederationDoma
activeJWKKey: jwkData, activeJWKKey: jwkData,
jwksKey: jwksData, jwksKey: jwksData,
}, },
Type: jwksSecretTypeValue,
} }
return &s, nil return &s, nil
@ -285,43 +288,49 @@ func (c *jwksWriterController) createOrUpdateSecret(
} }
oldSecret.Data = newSecret.Data oldSecret.Data = newSecret.Data
oldSecret.Type = jwksSecretTypeValue
_, err = secretClient.Update(ctx, oldSecret, metav1.UpdateOptions{}) _, err = secretClient.Update(ctx, oldSecret, metav1.UpdateOptions{})
return err return err
}) })
} }
func (c *jwksWriterController) updateOPC( func (c *jwksWriterController) updateFederationDomain(
ctx context.Context, ctx context.Context,
newOPC *configv1alpha1.FederationDomain, newFederationDomain *configv1alpha1.FederationDomain,
) error { ) error {
opcClient := c.pinnipedClient.ConfigV1alpha1().FederationDomains(newOPC.Namespace) federationDomainClient := c.pinnipedClient.ConfigV1alpha1().FederationDomains(newFederationDomain.Namespace)
return retry.RetryOnConflict(retry.DefaultRetry, func() error { 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 { 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 newFederationDomain.Status.Secrets.JWKS.Name == oldFederationDomain.Status.Secrets.JWKS.Name {
// If the existing OPC is up to date, we don't need to update it. // If the existing FederationDomain is up to date, we don't need to update it.
return nil return nil
} }
oldOPC.Status.Secrets.JWKS.Name = newOPC.Status.Secrets.JWKS.Name oldFederationDomain.Status.Secrets.JWKS.Name = newFederationDomain.Status.Secrets.JWKS.Name
_, err = opcClient.Update(ctx, oldOPC, metav1.UpdateOptions{}) _, err = federationDomainClient.Update(ctx, oldFederationDomain, metav1.UpdateOptions{})
return err return err
}) })
} }
// isOPCControlle returns whether the provided obj is controlled by an OPC. // isFederationDomainControlle returns whether the provided obj is controlled by a FederationDomain.
func isOPCControllee(obj metav1.Object) bool { func isFederationDomainControllee(obj metav1.Object) bool {
controller := metav1.GetControllerOf(obj) controller := metav1.GetControllerOf(obj)
return controller != nil && return controller != nil &&
controller.APIVersion == configv1alpha1.SchemeGroupVersion.String() && 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. // isValid returns whether the provided secret contains a valid active JWK and verification JWKS.
func isValid(secret *corev1.Secret) bool { 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] jwkData, ok := secret.Data[activeJWKKey]
if !ok { if !ok {
plog.Debug("secret does not contain active jwk") plog.Debug("secret does not contain active jwk")

View File

@ -145,7 +145,7 @@ func TestJWKSWriterControllerFilterSecret(t *testing.T) {
kubernetesfake.NewSimpleClientset(), kubernetesfake.NewSimpleClientset(),
0, 0,
).Core().V1().Secrets() ).Core().V1().Secrets()
opcInformer := pinnipedinformers.NewSharedInformerFactory( federationDomainInformer := pinnipedinformers.NewSharedInformerFactory(
pinnipedfake.NewSimpleClientset(), pinnipedfake.NewSimpleClientset(),
0, 0,
).Config().V1alpha1().FederationDomains() ).Config().V1alpha1().FederationDomains()
@ -155,7 +155,7 @@ func TestJWKSWriterControllerFilterSecret(t *testing.T) {
nil, // kubeClient, not needed nil, // kubeClient, not needed
nil, // pinnipedClient, not needed nil, // pinnipedClient, not needed
secretInformer, secretInformer,
opcInformer, federationDomainInformer,
withInformer.WithInformer, withInformer.WithInformer,
) )
@ -170,24 +170,24 @@ func TestJWKSWriterControllerFilterSecret(t *testing.T) {
} }
} }
func TestJWKSWriterControllerFilterOPC(t *testing.T) { func TestJWKSWriterControllerFilterFederationDomain(t *testing.T) {
t.Parallel() t.Parallel()
tests := []struct { tests := []struct {
name string name string
opc configv1alpha1.FederationDomain federationDomain configv1alpha1.FederationDomain
wantAdd bool wantAdd bool
wantUpdate bool wantUpdate bool
wantDelete bool wantDelete bool
wantParent controllerlib.Key wantParent controllerlib.Key
}{ }{
{ {
name: "anything goes", name: "anything goes",
opc: configv1alpha1.FederationDomain{}, federationDomain: configv1alpha1.FederationDomain{},
wantAdd: true, wantAdd: true,
wantUpdate: true, wantUpdate: true,
wantDelete: true, wantDelete: true,
wantParent: controllerlib.Key{}, wantParent: controllerlib.Key{},
}, },
} }
for _, test := range tests { for _, test := range tests {
@ -199,7 +199,7 @@ func TestJWKSWriterControllerFilterOPC(t *testing.T) {
kubernetesfake.NewSimpleClientset(), kubernetesfake.NewSimpleClientset(),
0, 0,
).Core().V1().Secrets() ).Core().V1().Secrets()
opcInformer := pinnipedinformers.NewSharedInformerFactory( federationDomainInformer := pinnipedinformers.NewSharedInformerFactory(
pinnipedfake.NewSimpleClientset(), pinnipedfake.NewSimpleClientset(),
0, 0,
).Config().V1alpha1().FederationDomains() ).Config().V1alpha1().FederationDomains()
@ -209,17 +209,17 @@ func TestJWKSWriterControllerFilterOPC(t *testing.T) {
nil, // kubeClient, not needed nil, // kubeClient, not needed
nil, // pinnipedClient, not needed nil, // pinnipedClient, not needed
secretInformer, secretInformer,
opcInformer, federationDomainInformer,
withInformer.WithInformer, withInformer.WithInformer,
) )
unrelated := configv1alpha1.FederationDomain{} unrelated := configv1alpha1.FederationDomain{}
filter := withInformer.GetFilterForInformer(opcInformer) filter := withInformer.GetFilterForInformer(federationDomainInformer)
require.Equal(t, test.wantAdd, filter.Add(&test.opc)) require.Equal(t, test.wantAdd, filter.Add(&test.federationDomain))
require.Equal(t, test.wantUpdate, filter.Update(&unrelated, &test.opc)) require.Equal(t, test.wantUpdate, filter.Update(&unrelated, &test.federationDomain))
require.Equal(t, test.wantUpdate, filter.Update(&test.opc, &unrelated)) require.Equal(t, test.wantUpdate, filter.Update(&test.federationDomain, &unrelated))
require.Equal(t, test.wantDelete, filter.Delete(&test.opc)) require.Equal(t, test.wantDelete, filter.Delete(&test.federationDomain))
require.Equal(t, test.wantParent, filter.Parent(&test.opc)) require.Equal(t, test.wantParent, filter.Parent(&test.federationDomain))
}) })
} }
} }
@ -236,24 +236,24 @@ func TestJWKSWriterControllerSync(t *testing.T) {
goodKey, err := x509.ParseECPrivateKey(block.Bytes) goodKey, err := x509.ParseECPrivateKey(block.Bytes)
require.NoError(t, err) require.NoError(t, err)
opcGVR := schema.GroupVersionResource{ federationDomainGVR := schema.GroupVersionResource{
Group: configv1alpha1.SchemeGroupVersion.Group, Group: configv1alpha1.SchemeGroupVersion.Group,
Version: configv1alpha1.SchemeGroupVersion.Version, Version: configv1alpha1.SchemeGroupVersion.Version,
Resource: "federationdomains", Resource: "federationdomains",
} }
goodOPC := &configv1alpha1.FederationDomain{ goodFederationDomain := &configv1alpha1.FederationDomain{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: "good-opc", Name: "good-federationDomain",
Namespace: namespace, Namespace: namespace,
UID: "good-opc-uid", UID: "good-federationDomain-uid",
}, },
Spec: configv1alpha1.FederationDomainSpec{ Spec: configv1alpha1.FederationDomainSpec{
Issuer: "https://some-issuer.com", Issuer: "https://some-issuer.com",
}, },
} }
goodOPCWithStatus := goodOPC.DeepCopy() goodFederationDomainWithStatus := goodFederationDomain.DeepCopy()
goodOPCWithStatus.Status.Secrets.JWKS.Name = goodOPCWithStatus.Name + "-jwks" goodFederationDomainWithStatus.Status.Secrets.JWKS.Name = goodFederationDomainWithStatus.Name + "-jwks"
secretGVR := schema.GroupVersionResource{ secretGVR := schema.GroupVersionResource{
Group: corev1.SchemeGroupVersion.Group, Group: corev1.SchemeGroupVersion.Group,
@ -264,7 +264,7 @@ func TestJWKSWriterControllerSync(t *testing.T) {
newSecret := func(activeJWKPath, jwksPath string) *corev1.Secret { newSecret := func(activeJWKPath, jwksPath string) *corev1.Secret {
s := corev1.Secret{ s := corev1.Secret{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: goodOPCWithStatus.Status.Secrets.JWKS.Name, Name: goodFederationDomainWithStatus.Status.Secrets.JWKS.Name,
Namespace: namespace, Namespace: namespace,
Labels: map[string]string{ Labels: map[string]string{
"myLabelKey1": "myLabelValue1", "myLabelKey1": "myLabelValue1",
@ -272,15 +272,16 @@ func TestJWKSWriterControllerSync(t *testing.T) {
}, },
OwnerReferences: []metav1.OwnerReference{ OwnerReferences: []metav1.OwnerReference{
{ {
APIVersion: opcGVR.GroupVersion().String(), APIVersion: federationDomainGVR.GroupVersion().String(),
Kind: "FederationDomain", Kind: "FederationDomain",
Name: goodOPC.Name, Name: goodFederationDomain.Name,
UID: goodOPC.UID, UID: goodFederationDomain.UID,
BlockOwnerDeletion: boolPtr(true), BlockOwnerDeletion: boolPtr(true),
Controller: boolPtr(true), Controller: boolPtr(true),
}, },
}, },
}, },
Type: "secrets.pinniped.dev/federation-domain-jwks",
} }
s.Data = make(map[string][]byte) s.Data = make(map[string][]byte)
if activeJWKPath != "" { if activeJWKPath != "" {
@ -294,40 +295,43 @@ func TestJWKSWriterControllerSync(t *testing.T) {
goodSecret := newSecret("testdata/good-jwk.json", "testdata/good-jwks.json") 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 { tests := []struct {
name string name string
key controllerlib.Key key controllerlib.Key
secrets []*corev1.Secret secrets []*corev1.Secret
configKubeClient func(*kubernetesfake.Clientset) configKubeClient func(*kubernetesfake.Clientset)
configPinnipedClient func(*pinnipedfake.Clientset) configPinnipedClient func(*pinnipedfake.Clientset)
opcs []*configv1alpha1.FederationDomain federationDomains []*configv1alpha1.FederationDomain
generateKeyErr error generateKeyErr error
wantGenerateKeyCount int wantGenerateKeyCount int
wantSecretActions []kubetesting.Action wantSecretActions []kubetesting.Action
wantOPCActions []kubetesting.Action wantFederationDomainActions []kubetesting.Action
wantError string wantError string
}{ }{
{ {
name: "new opc with no secret", name: "new federationDomain with no secret",
key: controllerlib.Key{Namespace: goodOPC.Namespace, Name: goodOPC.Name}, key: controllerlib.Key{Namespace: goodFederationDomain.Namespace, Name: goodFederationDomain.Name},
opcs: []*configv1alpha1.FederationDomain{ federationDomains: []*configv1alpha1.FederationDomain{
goodOPC, goodFederationDomain,
}, },
wantGenerateKeyCount: 1, wantGenerateKeyCount: 1,
wantSecretActions: []kubetesting.Action{ wantSecretActions: []kubetesting.Action{
kubetesting.NewGetAction(secretGVR, namespace, goodSecret.Name), kubetesting.NewGetAction(secretGVR, namespace, goodSecret.Name),
kubetesting.NewCreateAction(secretGVR, namespace, goodSecret), kubetesting.NewCreateAction(secretGVR, namespace, goodSecret),
}, },
wantOPCActions: []kubetesting.Action{ wantFederationDomainActions: []kubetesting.Action{
kubetesting.NewGetAction(opcGVR, namespace, goodOPC.Name), kubetesting.NewGetAction(federationDomainGVR, namespace, goodFederationDomain.Name),
kubetesting.NewUpdateAction(opcGVR, namespace, goodOPCWithStatus), kubetesting.NewUpdateAction(federationDomainGVR, namespace, goodFederationDomainWithStatus),
}, },
}, },
{ {
name: "opc without status with existing secret", name: "federationDomain without status with existing secret",
key: controllerlib.Key{Namespace: goodOPC.Namespace, Name: goodOPC.Name}, key: controllerlib.Key{Namespace: goodFederationDomain.Namespace, Name: goodFederationDomain.Name},
opcs: []*configv1alpha1.FederationDomain{ federationDomains: []*configv1alpha1.FederationDomain{
goodOPC, goodFederationDomain,
}, },
secrets: []*corev1.Secret{ secrets: []*corev1.Secret{
goodSecret, goodSecret,
@ -336,46 +340,46 @@ func TestJWKSWriterControllerSync(t *testing.T) {
wantSecretActions: []kubetesting.Action{ wantSecretActions: []kubetesting.Action{
kubetesting.NewGetAction(secretGVR, namespace, goodSecret.Name), kubetesting.NewGetAction(secretGVR, namespace, goodSecret.Name),
}, },
wantOPCActions: []kubetesting.Action{ wantFederationDomainActions: []kubetesting.Action{
kubetesting.NewGetAction(opcGVR, namespace, goodOPC.Name), kubetesting.NewGetAction(federationDomainGVR, namespace, goodFederationDomain.Name),
kubetesting.NewUpdateAction(opcGVR, namespace, goodOPCWithStatus), kubetesting.NewUpdateAction(federationDomainGVR, namespace, goodFederationDomainWithStatus),
}, },
}, },
{ {
name: "existing opc with no secret", name: "existing federationDomain with no secret",
key: controllerlib.Key{Namespace: goodOPC.Namespace, Name: goodOPC.Name}, key: controllerlib.Key{Namespace: goodFederationDomain.Namespace, Name: goodFederationDomain.Name},
opcs: []*configv1alpha1.FederationDomain{ federationDomains: []*configv1alpha1.FederationDomain{
goodOPCWithStatus, goodFederationDomainWithStatus,
}, },
wantGenerateKeyCount: 1, wantGenerateKeyCount: 1,
wantSecretActions: []kubetesting.Action{ wantSecretActions: []kubetesting.Action{
kubetesting.NewGetAction(secretGVR, namespace, goodSecret.Name), kubetesting.NewGetAction(secretGVR, namespace, goodSecret.Name),
kubetesting.NewCreateAction(secretGVR, namespace, goodSecret), kubetesting.NewCreateAction(secretGVR, namespace, goodSecret),
}, },
wantOPCActions: []kubetesting.Action{ wantFederationDomainActions: []kubetesting.Action{
kubetesting.NewGetAction(opcGVR, namespace, goodOPC.Name), kubetesting.NewGetAction(federationDomainGVR, namespace, goodFederationDomain.Name),
}, },
}, },
{ {
name: "existing opc with existing secret", name: "existing federationDomain with existing secret",
key: controllerlib.Key{Namespace: goodOPC.Namespace, Name: goodOPC.Name}, key: controllerlib.Key{Namespace: goodFederationDomain.Namespace, Name: goodFederationDomain.Name},
opcs: []*configv1alpha1.FederationDomain{ federationDomains: []*configv1alpha1.FederationDomain{
goodOPCWithStatus, goodFederationDomainWithStatus,
}, },
secrets: []*corev1.Secret{ secrets: []*corev1.Secret{
goodSecret, goodSecret,
}, },
}, },
{ {
name: "deleted opc", name: "deleted federationDomain",
key: controllerlib.Key{Namespace: goodOPC.Namespace, Name: goodOPC.Name}, key: controllerlib.Key{Namespace: goodFederationDomain.Namespace, Name: goodFederationDomain.Name},
// Nothing to do here since Kube will garbage collect our child secret via its OwnerReference. // Nothing to do here since Kube will garbage collect our child secret via its OwnerReference.
}, },
{ {
name: "missing jwk in secret", name: "missing jwk in secret",
key: controllerlib.Key{Namespace: goodOPC.Namespace, Name: goodOPC.Name}, key: controllerlib.Key{Namespace: goodFederationDomain.Namespace, Name: goodFederationDomain.Name},
opcs: []*configv1alpha1.FederationDomain{ federationDomains: []*configv1alpha1.FederationDomain{
goodOPCWithStatus, goodFederationDomainWithStatus,
}, },
secrets: []*corev1.Secret{ secrets: []*corev1.Secret{
newSecret("", "testdata/good-jwks.json"), newSecret("", "testdata/good-jwks.json"),
@ -385,15 +389,15 @@ func TestJWKSWriterControllerSync(t *testing.T) {
kubetesting.NewGetAction(secretGVR, namespace, goodSecret.Name), kubetesting.NewGetAction(secretGVR, namespace, goodSecret.Name),
kubetesting.NewUpdateAction(secretGVR, namespace, goodSecret), kubetesting.NewUpdateAction(secretGVR, namespace, goodSecret),
}, },
wantOPCActions: []kubetesting.Action{ wantFederationDomainActions: []kubetesting.Action{
kubetesting.NewGetAction(opcGVR, namespace, goodOPC.Name), kubetesting.NewGetAction(federationDomainGVR, namespace, goodFederationDomain.Name),
}, },
}, },
{ {
name: "missing jwks in secret", name: "missing jwks in secret",
key: controllerlib.Key{Namespace: goodOPC.Namespace, Name: goodOPC.Name}, key: controllerlib.Key{Namespace: goodFederationDomain.Namespace, Name: goodFederationDomain.Name},
opcs: []*configv1alpha1.FederationDomain{ federationDomains: []*configv1alpha1.FederationDomain{
goodOPCWithStatus, goodFederationDomainWithStatus,
}, },
secrets: []*corev1.Secret{ secrets: []*corev1.Secret{
newSecret("testdata/good-jwk.json", ""), newSecret("testdata/good-jwk.json", ""),
@ -403,15 +407,33 @@ func TestJWKSWriterControllerSync(t *testing.T) {
kubetesting.NewGetAction(secretGVR, namespace, goodSecret.Name), kubetesting.NewGetAction(secretGVR, namespace, goodSecret.Name),
kubetesting.NewUpdateAction(secretGVR, namespace, goodSecret), kubetesting.NewUpdateAction(secretGVR, namespace, goodSecret),
}, },
wantOPCActions: []kubetesting.Action{ wantFederationDomainActions: []kubetesting.Action{
kubetesting.NewGetAction(opcGVR, namespace, goodOPC.Name), 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", name: "invalid jwk JSON in secret",
key: controllerlib.Key{Namespace: goodOPC.Namespace, Name: goodOPC.Name}, key: controllerlib.Key{Namespace: goodFederationDomain.Namespace, Name: goodFederationDomain.Name},
opcs: []*configv1alpha1.FederationDomain{ federationDomains: []*configv1alpha1.FederationDomain{
goodOPCWithStatus, goodFederationDomainWithStatus,
}, },
secrets: []*corev1.Secret{ secrets: []*corev1.Secret{
newSecret("testdata/not-json.txt", "testdata/good-jwks.json"), 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.NewGetAction(secretGVR, namespace, goodSecret.Name),
kubetesting.NewUpdateAction(secretGVR, namespace, goodSecret), kubetesting.NewUpdateAction(secretGVR, namespace, goodSecret),
}, },
wantOPCActions: []kubetesting.Action{ wantFederationDomainActions: []kubetesting.Action{
kubetesting.NewGetAction(opcGVR, namespace, goodOPC.Name), kubetesting.NewGetAction(federationDomainGVR, namespace, goodFederationDomain.Name),
}, },
}, },
{ {
name: "invalid jwks JSON in secret", name: "invalid jwks JSON in secret",
key: controllerlib.Key{Namespace: goodOPC.Namespace, Name: goodOPC.Name}, key: controllerlib.Key{Namespace: goodFederationDomain.Namespace, Name: goodFederationDomain.Name},
opcs: []*configv1alpha1.FederationDomain{ federationDomains: []*configv1alpha1.FederationDomain{
goodOPCWithStatus, goodFederationDomainWithStatus,
}, },
secrets: []*corev1.Secret{ secrets: []*corev1.Secret{
newSecret("testdata/good-jwk.json", "testdata/not-json.txt"), 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.NewGetAction(secretGVR, namespace, goodSecret.Name),
kubetesting.NewUpdateAction(secretGVR, namespace, goodSecret), kubetesting.NewUpdateAction(secretGVR, namespace, goodSecret),
}, },
wantOPCActions: []kubetesting.Action{ wantFederationDomainActions: []kubetesting.Action{
kubetesting.NewGetAction(opcGVR, namespace, goodOPC.Name), kubetesting.NewGetAction(federationDomainGVR, namespace, goodFederationDomain.Name),
}, },
}, },
{ {
name: "public jwk in secret", name: "public jwk in secret",
key: controllerlib.Key{Namespace: goodOPC.Namespace, Name: goodOPC.Name}, key: controllerlib.Key{Namespace: goodFederationDomain.Namespace, Name: goodFederationDomain.Name},
opcs: []*configv1alpha1.FederationDomain{ federationDomains: []*configv1alpha1.FederationDomain{
goodOPCWithStatus, goodFederationDomainWithStatus,
}, },
secrets: []*corev1.Secret{ secrets: []*corev1.Secret{
newSecret("testdata/public-jwk.json", "testdata/good-jwks.json"), 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.NewGetAction(secretGVR, namespace, goodSecret.Name),
kubetesting.NewUpdateAction(secretGVR, namespace, goodSecret), kubetesting.NewUpdateAction(secretGVR, namespace, goodSecret),
}, },
wantOPCActions: []kubetesting.Action{ wantFederationDomainActions: []kubetesting.Action{
kubetesting.NewGetAction(opcGVR, namespace, goodOPC.Name), kubetesting.NewGetAction(federationDomainGVR, namespace, goodFederationDomain.Name),
}, },
}, },
{ {
name: "private jwks in secret", name: "private jwks in secret",
key: controllerlib.Key{Namespace: goodOPC.Namespace, Name: goodOPC.Name}, key: controllerlib.Key{Namespace: goodFederationDomain.Namespace, Name: goodFederationDomain.Name},
opcs: []*configv1alpha1.FederationDomain{ federationDomains: []*configv1alpha1.FederationDomain{
goodOPCWithStatus, goodFederationDomainWithStatus,
}, },
secrets: []*corev1.Secret{ secrets: []*corev1.Secret{
newSecret("testdata/good-jwk.json", "testdata/private-jwks.json"), 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.NewGetAction(secretGVR, namespace, goodSecret.Name),
kubetesting.NewUpdateAction(secretGVR, namespace, goodSecret), kubetesting.NewUpdateAction(secretGVR, namespace, goodSecret),
}, },
wantOPCActions: []kubetesting.Action{ wantFederationDomainActions: []kubetesting.Action{
kubetesting.NewGetAction(opcGVR, namespace, goodOPC.Name), kubetesting.NewGetAction(federationDomainGVR, namespace, goodFederationDomain.Name),
}, },
}, },
{ {
name: "invalid jwk key in secret", name: "invalid jwk key in secret",
key: controllerlib.Key{Namespace: goodOPC.Namespace, Name: goodOPC.Name}, key: controllerlib.Key{Namespace: goodFederationDomain.Namespace, Name: goodFederationDomain.Name},
opcs: []*configv1alpha1.FederationDomain{ federationDomains: []*configv1alpha1.FederationDomain{
goodOPCWithStatus, goodFederationDomainWithStatus,
}, },
secrets: []*corev1.Secret{ secrets: []*corev1.Secret{
newSecret("testdata/invalid-key-jwk.json", "testdata/good-jwks.json"), 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.NewGetAction(secretGVR, namespace, goodSecret.Name),
kubetesting.NewUpdateAction(secretGVR, namespace, goodSecret), kubetesting.NewUpdateAction(secretGVR, namespace, goodSecret),
}, },
wantOPCActions: []kubetesting.Action{ wantFederationDomainActions: []kubetesting.Action{
kubetesting.NewGetAction(opcGVR, namespace, goodOPC.Name), kubetesting.NewGetAction(federationDomainGVR, namespace, goodFederationDomain.Name),
}, },
}, },
{ {
name: "invalid jwks key in secret", name: "invalid jwks key in secret",
key: controllerlib.Key{Namespace: goodOPC.Namespace, Name: goodOPC.Name}, key: controllerlib.Key{Namespace: goodFederationDomain.Namespace, Name: goodFederationDomain.Name},
opcs: []*configv1alpha1.FederationDomain{ federationDomains: []*configv1alpha1.FederationDomain{
goodOPCWithStatus, goodFederationDomainWithStatus,
}, },
secrets: []*corev1.Secret{ secrets: []*corev1.Secret{
newSecret("testdata/good-jwk.json", "testdata/invalid-key-jwks.json"), 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.NewGetAction(secretGVR, namespace, goodSecret.Name),
kubetesting.NewUpdateAction(secretGVR, namespace, goodSecret), kubetesting.NewUpdateAction(secretGVR, namespace, goodSecret),
}, },
wantOPCActions: []kubetesting.Action{ wantFederationDomainActions: []kubetesting.Action{
kubetesting.NewGetAction(opcGVR, namespace, goodOPC.Name), kubetesting.NewGetAction(federationDomainGVR, namespace, goodFederationDomain.Name),
}, },
}, },
{ {
name: "missing active jwks in secret", name: "missing active jwks in secret",
key: controllerlib.Key{Namespace: goodOPC.Namespace, Name: goodOPC.Name}, key: controllerlib.Key{Namespace: goodFederationDomain.Namespace, Name: goodFederationDomain.Name},
opcs: []*configv1alpha1.FederationDomain{ federationDomains: []*configv1alpha1.FederationDomain{
goodOPCWithStatus, goodFederationDomainWithStatus,
}, },
secrets: []*corev1.Secret{ secrets: []*corev1.Secret{
newSecret("testdata/good-jwk.json", "testdata/missing-active-jwks.json"), 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.NewGetAction(secretGVR, namespace, goodSecret.Name),
kubetesting.NewUpdateAction(secretGVR, namespace, goodSecret), kubetesting.NewUpdateAction(secretGVR, namespace, goodSecret),
}, },
wantOPCActions: []kubetesting.Action{ wantFederationDomainActions: []kubetesting.Action{
kubetesting.NewGetAction(opcGVR, namespace, goodOPC.Name), kubetesting.NewGetAction(federationDomainGVR, namespace, goodFederationDomain.Name),
}, },
}, },
{ {
name: "generate key fails", name: "generate key fails",
key: controllerlib.Key{Namespace: goodOPC.Namespace, Name: goodOPC.Name}, key: controllerlib.Key{Namespace: goodFederationDomain.Namespace, Name: goodFederationDomain.Name},
opcs: []*configv1alpha1.FederationDomain{ federationDomains: []*configv1alpha1.FederationDomain{
goodOPCWithStatus, goodFederationDomainWithStatus,
}, },
generateKeyErr: errors.New("some generate error"), generateKeyErr: errors.New("some generate error"),
wantError: "cannot generate secret: cannot generate key: some generate error", wantError: "cannot generate secret: cannot generate key: some generate error",
}, },
{ {
name: "get secret fails", name: "get secret fails",
key: controllerlib.Key{Namespace: goodOPC.Namespace, Name: goodOPC.Name}, key: controllerlib.Key{Namespace: goodFederationDomain.Namespace, Name: goodFederationDomain.Name},
opcs: []*configv1alpha1.FederationDomain{ federationDomains: []*configv1alpha1.FederationDomain{
goodOPC, goodFederationDomain,
}, },
configKubeClient: func(client *kubernetesfake.Clientset) { configKubeClient: func(client *kubernetesfake.Clientset) {
client.PrependReactor("get", "secrets", func(_ kubetesting.Action) (bool, runtime.Object, error) { client.PrependReactor("get", "secrets", func(_ kubetesting.Action) (bool, runtime.Object, error) {
@ -557,9 +579,9 @@ func TestJWKSWriterControllerSync(t *testing.T) {
}, },
{ {
name: "create secret fails", name: "create secret fails",
key: controllerlib.Key{Namespace: goodOPC.Namespace, Name: goodOPC.Name}, key: controllerlib.Key{Namespace: goodFederationDomain.Namespace, Name: goodFederationDomain.Name},
opcs: []*configv1alpha1.FederationDomain{ federationDomains: []*configv1alpha1.FederationDomain{
goodOPC, goodFederationDomain,
}, },
configKubeClient: func(client *kubernetesfake.Clientset) { configKubeClient: func(client *kubernetesfake.Clientset) {
client.PrependReactor("create", "secrets", func(_ kubetesting.Action) (bool, runtime.Object, error) { client.PrependReactor("create", "secrets", func(_ kubetesting.Action) (bool, runtime.Object, error) {
@ -570,9 +592,9 @@ func TestJWKSWriterControllerSync(t *testing.T) {
}, },
{ {
name: "update secret fails", name: "update secret fails",
key: controllerlib.Key{Namespace: goodOPC.Namespace, Name: goodOPC.Name}, key: controllerlib.Key{Namespace: goodFederationDomain.Namespace, Name: goodFederationDomain.Name},
opcs: []*configv1alpha1.FederationDomain{ federationDomains: []*configv1alpha1.FederationDomain{
goodOPC, goodFederationDomain,
}, },
secrets: []*corev1.Secret{ secrets: []*corev1.Secret{
newSecret("", ""), newSecret("", ""),
@ -585,30 +607,30 @@ func TestJWKSWriterControllerSync(t *testing.T) {
wantError: "cannot create or update secret: some update error", wantError: "cannot create or update secret: some update error",
}, },
{ {
name: "get opc fails", name: "get FederationDomain fails",
key: controllerlib.Key{Namespace: goodOPC.Namespace, Name: goodOPC.Name}, key: controllerlib.Key{Namespace: goodFederationDomain.Namespace, Name: goodFederationDomain.Name},
opcs: []*configv1alpha1.FederationDomain{ federationDomains: []*configv1alpha1.FederationDomain{
goodOPC, goodFederationDomain,
}, },
configPinnipedClient: func(client *pinnipedfake.Clientset) { configPinnipedClient: func(client *pinnipedfake.Clientset) {
client.PrependReactor("get", "federationdomains", func(_ kubetesting.Action) (bool, runtime.Object, error) { client.PrependReactor("get", "federationdomains", func(_ kubetesting.Action) (bool, runtime.Object, error) {
return true, nil, errors.New("some get 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", name: "update federationDomain fails",
key: controllerlib.Key{Namespace: goodOPC.Namespace, Name: goodOPC.Name}, key: controllerlib.Key{Namespace: goodFederationDomain.Namespace, Name: goodFederationDomain.Name},
opcs: []*configv1alpha1.FederationDomain{ federationDomains: []*configv1alpha1.FederationDomain{
goodOPC, goodFederationDomain,
}, },
configPinnipedClient: func(client *pinnipedfake.Clientset) { configPinnipedClient: func(client *pinnipedfake.Clientset) {
client.PrependReactor("update", "federationdomains", func(_ kubetesting.Action) (bool, runtime.Object, error) { client.PrependReactor("update", "federationdomains", func(_ kubetesting.Action) (bool, runtime.Object, error) {
return true, nil, errors.New("some update 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 { for _, test := range tests {
@ -636,9 +658,9 @@ func TestJWKSWriterControllerSync(t *testing.T) {
pinnipedAPIClient := pinnipedfake.NewSimpleClientset() pinnipedAPIClient := pinnipedfake.NewSimpleClientset()
pinnipedInformerClient := pinnipedfake.NewSimpleClientset() pinnipedInformerClient := pinnipedfake.NewSimpleClientset()
for _, opc := range test.opcs { for _, federationDomain := range test.federationDomains {
require.NoError(t, pinnipedAPIClient.Tracker().Add(opc)) require.NoError(t, pinnipedAPIClient.Tracker().Add(federationDomain))
require.NoError(t, pinnipedInformerClient.Tracker().Add(opc)) require.NoError(t, pinnipedInformerClient.Tracker().Add(federationDomain))
} }
if test.configPinnipedClient != nil { if test.configPinnipedClient != nil {
test.configPinnipedClient(pinnipedAPIClient) test.configPinnipedClient(pinnipedAPIClient)
@ -685,8 +707,8 @@ func TestJWKSWriterControllerSync(t *testing.T) {
if test.wantSecretActions != nil { if test.wantSecretActions != nil {
require.Equal(t, test.wantSecretActions, kubeAPIClient.Actions()) require.Equal(t, test.wantSecretActions, kubeAPIClient.Actions())
} }
if test.wantOPCActions != nil { if test.wantFederationDomainActions != nil {
require.Equal(t, test.wantOPCActions, pinnipedAPIClient.Actions()) require.Equal(t, test.wantFederationDomainActions, pinnipedAPIClient.Actions())
} }
}) })
} }

View File

@ -18,6 +18,7 @@ import (
"github.com/coreos/go-oidc" "github.com/coreos/go-oidc"
"github.com/go-logr/logr" "github.com/go-logr/logr"
"golang.org/x/oauth2" "golang.org/x/oauth2"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/equality" "k8s.io/apimachinery/pkg/api/equality"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/labels"
@ -39,9 +40,10 @@ const (
controllerName = "upstream-observer" controllerName = "upstream-observer"
// Constants related to the client credentials Secret. // Constants related to the client credentials Secret.
oidcClientSecretType = "secrets.pinniped.dev/oidc-client" oidcClientSecretType corev1.SecretType = "secrets.pinniped.dev/oidc-client"
clientIDDataKey = "clientID"
clientSecretDataKey = "clientSecret" clientIDDataKey = "clientID"
clientSecretDataKey = "clientSecret"
// Constants related to the OIDC provider discovery cache. These do not affect the cache of JWKS. // Constants related to the OIDC provider discovery cache. These do not affect the cache of JWKS.
validatorCacheTTL = 15 * time.Minute validatorCacheTTL = 15 * time.Minute
@ -97,12 +99,12 @@ func (c *lruValidatorCache) cacheKey(spec *v1alpha1.OIDCIdentityProviderSpec) in
} }
type controller struct { type controller struct {
cache IDPCache cache IDPCache
log logr.Logger log logr.Logger
client pinnipedclientset.Interface client pinnipedclientset.Interface
providers idpinformers.OIDCIdentityProviderInformer oidcIdentityProviderInformer idpinformers.OIDCIdentityProviderInformer
secrets corev1informers.SecretInformer secretInformer corev1informers.SecretInformer
validatorCache interface { validatorCache interface {
getProvider(*v1alpha1.OIDCIdentityProviderSpec) (*oidc.Provider, *http.Client) getProvider(*v1alpha1.OIDCIdentityProviderSpec) (*oidc.Provider, *http.Client)
putProvider(*v1alpha1.OIDCIdentityProviderSpec, *oidc.Provider, *http.Client) putProvider(*v1alpha1.OIDCIdentityProviderSpec, *oidc.Provider, *http.Client)
} }
@ -112,29 +114,29 @@ type controller struct {
func New( func New(
idpCache IDPCache, idpCache IDPCache,
client pinnipedclientset.Interface, client pinnipedclientset.Interface,
providers idpinformers.OIDCIdentityProviderInformer, oidcIdentityProviderInformer idpinformers.OIDCIdentityProviderInformer,
secrets corev1informers.SecretInformer, secretInformer corev1informers.SecretInformer,
log logr.Logger, log logr.Logger,
) controllerlib.Controller { ) controllerlib.Controller {
c := controller{ c := controller{
cache: idpCache, cache: idpCache,
log: log.WithName(controllerName), log: log.WithName(controllerName),
client: client, client: client,
providers: providers, oidcIdentityProviderInformer: oidcIdentityProviderInformer,
secrets: secrets, secretInformer: secretInformer,
validatorCache: &lruValidatorCache{cache: cache.NewExpiring()}, validatorCache: &lruValidatorCache{cache: cache.NewExpiring()},
} }
filter := pinnipedcontroller.MatchAnythingFilter(pinnipedcontroller.SingletonQueue()) filter := pinnipedcontroller.MatchAnythingFilter(pinnipedcontroller.SingletonQueue())
return controllerlib.New( return controllerlib.New(
controllerlib.Config{Name: controllerName, Syncer: &c}, controllerlib.Config{Name: controllerName, Syncer: &c},
controllerlib.WithInformer(providers, filter, controllerlib.InformerOption{}), controllerlib.WithInformer(oidcIdentityProviderInformer, filter, controllerlib.InformerOption{}),
controllerlib.WithInformer(secrets, filter, controllerlib.InformerOption{}), controllerlib.WithInformer(secretInformer, filter, controllerlib.InformerOption{}),
) )
} }
// Sync implements controllerlib.Syncer. // Sync implements controllerlib.Syncer.
func (c *controller) Sync(ctx controllerlib.Context) error { 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 { if err != nil {
return fmt.Errorf("failed to list OIDCIdentityProviders: %w", err) 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 secretName := upstream.Spec.Client.SecretName
// Fetch the Secret from informer cache. // 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 { if err != nil {
return &v1alpha1.Condition{ return &v1alpha1.Condition{
Type: typeClientCredsValid, Type: typeClientCredsValid,

View File

@ -86,7 +86,7 @@ func (a *accessTokenStorage) getSession(ctx context.Context, signature string) (
rv, err := a.storage.Get(ctx, signature, session) rv, err := a.storage.Get(ctx, signature, session)
if errors.IsNotFound(err) { 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 { if err != nil {

View File

@ -110,7 +110,7 @@ func (a *authorizeCodeStorage) getSession(ctx context.Context, signature string)
rv, err := a.storage.Get(ctx, signature, session) rv, err := a.storage.Get(ctx, signature, session)
if errors.IsNotFound(err) { 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 { if err != nil {
@ -168,161 +168,161 @@ func (e *errSerializationFailureWithCause) Error() string {
// ExpectedAuthorizeCodeSessionJSONFromFuzzing is used for round tripping tests. // ExpectedAuthorizeCodeSessionJSONFromFuzzing is used for round tripping tests.
// It is exported to allow integration tests to use it. // It is exported to allow integration tests to use it.
const ExpectedAuthorizeCodeSessionJSONFromFuzzing = `{ const ExpectedAuthorizeCodeSessionJSONFromFuzzing = `{
"active": true, "active": true,
"request": { "request": {
"id": "嫎l蟲aƖ啘艿", "id": "曑x螠Gæ鄋楨",
"requestedAt": "2082-11-10T18:36:11.627253638Z", "requestedAt": "2082-11-10T18:36:11.627253638Z",
"client": { "client": {
"id": "!ſɄĈp[述齛ʘUȻ.5ȿE", "id": ":NJ¸Ɣ8(黋馛ÄRɴJa¶z",
"client_secret": "UQ==", "client_secret": "UQ==",
"redirect_uris": [ "redirect_uris": [
"ǣ珑 ʑ飶畛Ȳ螤Yɫüeɯ紤邥翔勋\\", "ǖ枭kʍ切厦ȳ箦;¥ʊXĝ奨誷傥祩d",
"Bʒ;", "zŇZ",
"鿃攴Ųęʍ鎾ʦ©cÏN,Ġ/_" "優蒼ĊɌț訫DŽǽeʀO2ƚ&N"
], ],
"grant_types": [ "grant_types": [
"憉sHĒ尥窘挼Ŀʼn" "唐W6ɻ橩斚薛ɑƐ"
], ],
"response_types": [ "response_types": [
"4", "w",
"ʄÔ@}i{絧遗Ū^ȝĸ谋Vʋ鱴閇T" "ǔŭe[u@阽羂ŷ-Ĵ½輢OÅ濲喾H"
], ],
"scopes": [ "scopes": [
"R鴝順諲ŮŚ节ȭŀȋc剠鏯ɽÿ¸" "G螩歐湡ƙı唡ɸğƎ&胢輢Ƈĵƚ"
], ],
"audience": [ "audience": [
" "
], ],
"public": true, "public": false,
"jwks_uri": "BA瘪囷ɫCʄɢ雐譄uée'", "jwks_uri": "o*泞羅ʘ Ⱦķ瀊垰7ã\")",
"jwks": { "jwks": {
"keys": [ "keys": [
{ {
"kty": "OKP", "kty": "OKP",
"crv": "Ed25519", "crv": "Ed25519",
"x": "nK9xgX_iN7u3u_i8YOO7ZRT_WK028Vd_nhtsUu7Eo6E", "x": "nK9xgX_iN7u3u_i8YOO7ZRT_WK028Vd_nhtsUu7Eo6E",
"x5u": { "x5u": {
"Scheme": "", "Scheme": "",
"Opaque": "", "Opaque": "",
"User": null, "User": null,
"Host": "", "Host": "",
"Path": "", "Path": "",
"RawPath": "", "RawPath": "",
"ForceQuery": false, "ForceQuery": false,
"RawQuery": "", "RawQuery": "",
"Fragment": "", "Fragment": "",
"RawFragment": "" "RawFragment": ""
} }
}, },
{ {
"kty": "OKP", "kty": "OKP",
"crv": "Ed25519", "crv": "Ed25519",
"x": "UbbswQgzWhfGCRlwQmMp6fw_HoIoqkIaKT-2XN2fuYU", "x": "UbbswQgzWhfGCRlwQmMp6fw_HoIoqkIaKT-2XN2fuYU",
"x5u": { "x5u": {
"Scheme": "", "Scheme": "",
"Opaque": "", "Opaque": "",
"User": null, "User": null,
"Host": "", "Host": "",
"Path": "", "Path": "",
"RawPath": "", "RawPath": "",
"ForceQuery": false, "ForceQuery": false,
"RawQuery": "", "RawQuery": "",
"Fragment": "", "Fragment": "",
"RawFragment": "" "RawFragment": ""
} }
} }
] ]
}, },
"token_endpoint_auth_method": "ŚǗƳȕ暭Q0ņP羾,塐", "token_endpoint_auth_method": "ƿʥǟȒ伉<x¹T鼓c吏",
"request_uris": [ "request_uris": [
"lj翻LH^俤µDzɹ@©|\u003eɃ", "Ć捘j]=谅ʑɑɮ$Ól4Ȟ",
"[:c顎疻紵D" ",Q7钎漡臧n"
], ],
"request_object_signing_alg": "m1Ì恣S@T嵇LJV,Æ櫔袆鋹奘", "request_object_signing_alg": "3@¡廜+v,淬Ʋ4Dʧ呩锏緍场",
"token_endpoint_auth_signing_alg": "Fãƻʚ肈ą8O+a駣" "token_endpoint_auth_signing_alg": "(ưƓǴ罷ǹ~]ea胠"
}, },
"scopes": [ "scopes": [
"ɼk瘸'鴵yſǮŁ±\u003eFA曎餄FxD溪", "ĩv絹b垇IŕĩǀŻQ'k頂箨J-a稆",
"綻N镪p赌h%桙dĽ" "啶#昏Q遐*\\髎bŸ"
], ],
"grantedScopes": [ "grantedScopes": [
"癗E]Ņʘʟ車s" "慂UFƼĮǡ鑻Z"
], ],
"form": { "form": {
"蹬器ķ8ŷ萒寎廭#疶昄Ą-Ƃƞ轵": [ "褾攚ŝlĆ厦駳骪l拁乖¡J¿Ƈ妔": [
"熞ĝƌĆ1ȇyǴ濎=Tʉȼʁŀ\u003c", "懧¥ɂĵ~Čyʊ恀c\"NJřðȿ/",
"耡q戨稞R÷mȵg釽[ƞ@", "裢?霃谥vƘ:ƿ/濔Aʉ<",
"đ[嬧鱒Ȁ彆媚杨嶒ĤGÀ吧Lŷ" "ȭ$奍囀Dž悷鵱民撲ʓeŘ嬀j¤"
], ],
"餟": [ "诞": [
"蒍z\u0026(K鵢Kj ŏ9Q韉Ķ%", "狲N<Cq罉ZPſĝEK郊©l",
"輫ǘ(¨Ƞ亱6ě#嫀^xz ", "餚LJ/ɷȑ潠[ĝU噤'pX ",
"@耢ɝ^¡!犃ĹĐJí¿ō擫" "Y妶ǵ!ȁu狍ɶȳsčɦƦ"
] ]
}, },
"session": { "session": {
"Claims": { "Claims": {
"JTI": "懫砰¿C筽娴ƓaPu镈賆ŗɰ", "JTI": "攬林Ñz焁糳¿o>Q鱙翑ȲŻ",
"Issuer": "皶竇瞍涘¹焕iǢǽɽĺŧ", "Issuer": "锰劝旣樎Ȱ鍌#ȳńƩŴȭ",
"Subject": "矠M6ɡǜg炾ʙ$%o6肿Ȫ", "Subject": "绝TFNJĆw宵ɚeY48珎²",
"Audience": [ "Audience": [
"ƌÙ鯆GQơ鮫R嫁ɍUƞ9+u!Ȱ踾$" "éã越|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", "\\BRë_g\"ʎ啴SƇMǃļ": {
"IssuedAt": "1976-02-22T09:57:20.479850437Z", "ʦ4": false
"RequestedAt": "2016-04-13T04:18:53.648949323Z", },
"AuthTime": "2098-07-12T04:38:54.034043015Z", "鶡萷ɵ啜s攦": null
"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
} }
}, },
"Headers": { "曓蓳n匟鯘磹*金爃鶴滱ůĮǐ_c3#": 2520197933
"Extra": { }
"熒ɘȏıȒ諃龟ŴŠ'耐Ƭ扵ƹ玄ɕwL": { },
"ýÏʥZq7烱藌\\捀¿őŧQ": { "Headers": {
"微'X焌襱ǭɕņ殥!_": null, "Extra": {
"荇届UȚ?戋璖$9\u00269舋": { "寱ĊƑ÷Ƒ螞费Ďğ~劰û橸ɽ銐ƭ?}": {
"ɕ餦ÑEǰ哤癨浦浏1Rk頓ć§蚲6": true "ȜʁɁ;Bd謺錳4帳ŅǃĊd": {
} "翢砜Fȏl鐉诳DT=3骜": {
}, "ų厷ɁOƪ穋嶿鳈恱va|载ǰɱ汶C": false
"鲒鿮禗O暒aJP鐜?ĮV嫎h譭ȉ]DĘ": [ },
954647573 "鸨EJ毕懴řĬń戹%c": null
] },
}, "室癑勦e骲v0H晦XŘO溪V蔓Ȍ+~ē": [
"皩Ƭ}Ɇ.雬Ɨ´唁": 1572524915 954647573
} ]
}, },
"ExpiresAt": { "麈ƵDǀ\\郂üţ垂": 1572524915
"\u003cqċ譈8ŪɎP绿MÅ": "2031-10-18T22:07:34.950803105Z", }
"ȸěaʜD捛?½ʀ+Ċ偢镳ʬÍɷȓ\u003c": "2049-05-13T15:27:20.968432454Z" },
}, "ExpiresAt": {
"Username": "1藍殙菥趏酱Nʎ\u0026^横懋ƶ峦Fïȫƅw", "'=ĸ闒NȢȰ.醋fʜ": "2031-10-18T22:07:34.950803105Z",
"Subject": "檾ĩĆ爨4犹|v炩f柏ʒ鴙*鸆偡" "ɦüHêQ仏1őƖ2Ė暮唍ǞʜƢú4": "2049-05-13T15:27:20.968432454Z"
},
"Username": "+韁臯氃妪婝rȤ\"h丬鎒ơ娻}ɼƟȥE",
"Subject": "龳ǽÙ龦O亾EW莛8嘶×姮c恭企"
}, },
"requestedAudience": [ "requestedAudience": [
"肯Ûx穞Ƀ", "邖ɐ5檄¬",
"ź蕴3ǐ薝Ƅ腲=ʐ诂鱰屾Ê窢ɋ鄊qɠ谫" "Ĭ葜SŦ餧Ĭ倏4ĵ嶼仒篻ɥ闣ʬ橳(ý綃"
], ],
"grantedAudience": [ "grantedAudience": [
"ǵƕ牀1鞊\\ȹ)}鉍商OɄƣ圔,xĪ", "ʚƟ覣k眐4ĈtC嵽痊w©Ź榨Q|ô",
"悾xn冏裻摼0Ʈ蚵Ȼ塕»£#稏扟X" "猊Ia瓕巈環_ɑƍ蛊ʚ£:設虝2"
] ]
}, },
"version": "1" "version": "1"
}` }`

View File

@ -391,5 +391,5 @@ func TestFuzzAndJSONNewValidEmptyAuthorizeCodeSession(t *testing.T) {
// while the fuzzer will panic if AuthorizeRequest changes in a way that cannot be fuzzed, // 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 // 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) // 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)
} }

View File

@ -88,7 +88,7 @@ func (a *openIDConnectRequestStorage) getSession(ctx context.Context, signature
rv, err := a.storage.Get(ctx, signature, session) rv, err := a.storage.Get(ctx, signature, session)
if errors.IsNotFound(err) { 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 { if err != nil {

View File

@ -72,7 +72,7 @@ func (a *pkceStorage) getSession(ctx context.Context, signature string) (*sessio
rv, err := a.storage.Get(ctx, signature, session) rv, err := a.storage.Get(ctx, signature, session)
if errors.IsNotFound(err) { 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 { if err != nil {

View File

@ -86,7 +86,7 @@ func (a *refreshTokenStorage) getSession(ctx context.Context, signature string)
rv, err := a.storage.Get(ctx, signature, session) rv, err := a.storage.Get(ctx, signature, session)
if errors.IsNotFound(err) { 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 { if err != nil {

View File

@ -41,76 +41,62 @@ func TestAuthorizationEndpoint(t *testing.T) {
fositeInvalidClientErrorBody = here.Doc(` fositeInvalidClientErrorBody = here.Doc(`
{ {
"error": "invalid_client", "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). The requested OAuth 2.0 Client does not exist."
"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
} }
`) `)
fositeInvalidRedirectURIErrorBody = here.Doc(` fositeInvalidRedirectURIErrorBody = here.Doc(`
{ {
"error": "invalid_request", "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. The 'redirect_uri' parameter does not match any of the OAuth 2.0 Client's pre-registered redirect urls."
"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
} }
`) `)
fositePromptHasNoneAndOtherValueErrorQuery = map[string]string{ fositePromptHasNoneAndOtherValueErrorQuery = map[string]string{
"error": "invalid_request", "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_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.",
"error_hint": "Parameter \"prompt\" was set to \"none\", but contains other values as well which is not allowed.",
"state": happyState, "state": happyState,
} }
fositeMissingCodeChallengeErrorQuery = map[string]string{ fositeMissingCodeChallengeErrorQuery = map[string]string{
"error": "invalid_request", "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_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.",
"error_hint": "Clients must include a code_challenge when performing the authorize code flow, but it is missing.",
"state": happyState, "state": happyState,
} }
fositeMissingCodeChallengeMethodErrorQuery = map[string]string{ fositeMissingCodeChallengeMethodErrorQuery = map[string]string{
"error": "invalid_request", "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_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.",
"error_hint": "Clients must use code_challenge_method=S256, plain is not allowed.",
"state": happyState, "state": happyState,
} }
fositeInvalidCodeChallengeErrorQuery = map[string]string{ fositeInvalidCodeChallengeErrorQuery = map[string]string{
"error": "invalid_request", "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_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.",
"error_hint": "The code_challenge_method is not supported, use S256 instead.",
"state": happyState, "state": happyState,
} }
fositeUnsupportedResponseTypeErrorQuery = map[string]string{ fositeUnsupportedResponseTypeErrorQuery = map[string]string{
"error": "unsupported_response_type", "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_description": "The authorization server does not support obtaining a token using this method. The client is not allowed to request response_type 'unsupported'.",
"error_hint": `The client is not allowed to request response_type "unsupported".`,
"state": happyState, "state": happyState,
} }
fositeInvalidScopeErrorQuery = map[string]string{ fositeInvalidScopeErrorQuery = map[string]string{
"error": "invalid_scope", "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_description": "The requested scope is invalid, unknown, or malformed. The 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".`,
"state": happyState, "state": happyState,
} }
fositeInvalidStateErrorQuery = map[string]string{ fositeInvalidStateErrorQuery = map[string]string{
"error": "invalid_state", "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_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.",
"error_hint": `Request parameter "state" must be at least be 8 characters long to ensure sufficient entropy.`,
"state": "short", "state": "short",
} }
fositeMissingResponseTypeErrorQuery = map[string]string{ fositeMissingResponseTypeErrorQuery = map[string]string{
"error": "unsupported_response_type", "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_description": "The authorization server does not support obtaining a token using this method. `The request is missing the 'response_type' parameter.",
"error_hint": `The request is missing the "response_type"" parameter.`,
"state": happyState, "state": happyState,
} }
) )

View File

@ -356,7 +356,7 @@ func TestCallbackEndpoint(t *testing.T) {
).String(), ).String(),
csrfCookie: happyCSRFCookie, csrfCookie: happyCSRFCookie,
wantStatus: http.StatusFound, wantStatus: http.StatusFound,
wantRedirectLocationRegexp: downstreamRedirectURI + `\?code=([^&]+)&scope=openid%20offline_access&state=` + happyDownstreamState, wantRedirectLocationRegexp: downstreamRedirectURI + `\?code=([^&]+)&scope=openid\+offline_access&state=` + happyDownstreamState,
wantDownstreamIDTokenUsername: upstreamUsername, wantDownstreamIDTokenUsername: upstreamUsername,
wantDownstreamIDTokenSubject: upstreamIssuer + "?sub=" + upstreamSubject, wantDownstreamIDTokenSubject: upstreamIssuer + "?sub=" + upstreamSubject,
wantDownstreamRequestedScopes: []string{"openid", "offline_access"}, wantDownstreamRequestedScopes: []string{"openid", "offline_access"},

View File

@ -50,7 +50,7 @@ func (s *dynamicOpenIDConnectECDSAStrategy) GenerateIDToken(
_, activeJwk := s.jwksProvider.GetJWKS(s.fositeConfig.IDTokenIssuer) _, activeJwk := s.jwksProvider.GetJWKS(s.fositeConfig.IDTokenIssuer)
if activeJwk == nil { if activeJwk == nil {
plog.Debug("no JWK found for issuer", "issuer", s.fositeConfig.IDTokenIssuer) 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) key, ok := activeJwk.Key.(*ecdsa.PrivateKey)
if !ok { if !ok {
@ -65,7 +65,7 @@ func (s *dynamicOpenIDConnectECDSAStrategy) GenerateIDToken(
"actualType", "actualType",
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) return compose.NewOpenIDConnectECDSAStrategy(s.fositeConfig, key).GenerateIDToken(ctx, requester)

View File

@ -266,11 +266,11 @@ func FositeErrorForLog(err error) []interface{} {
rfc6749Error := fosite.ErrorToRFC6749Error(err) rfc6749Error := fosite.ErrorToRFC6749Error(err)
keysAndValues := make([]interface{}, 0) keysAndValues := make([]interface{}, 0)
keysAndValues = append(keysAndValues, "name") keysAndValues = append(keysAndValues, "name")
keysAndValues = append(keysAndValues, rfc6749Error.Name) keysAndValues = append(keysAndValues, rfc6749Error.ErrorField)
keysAndValues = append(keysAndValues, "status") keysAndValues = append(keysAndValues, "status")
keysAndValues = append(keysAndValues, rfc6749Error.Status()) keysAndValues = append(keysAndValues, rfc6749Error.Status())
keysAndValues = append(keysAndValues, "description") keysAndValues = append(keysAndValues, "description")
keysAndValues = append(keysAndValues, rfc6749Error.Description) keysAndValues = append(keysAndValues, rfc6749Error.DescriptionField)
return keysAndValues return keysAndValues
} }

View File

@ -11,15 +11,16 @@ import (
"go.pinniped.dev/internal/constable" "go.pinniped.dev/internal/constable"
) )
// FederationDomain represents all of the settings and state for an OIDC provider. // FederationDomainIssuer represents all of the settings and state for a downstream OIDC provider
type FederationDomain struct { // as defined by a FederationDomain.
type FederationDomainIssuer struct {
issuer string issuer string
issuerHost string issuerHost string
issuerPath string issuerPath string
} }
func NewFederationDomain(issuer string) (*FederationDomain, error) { func NewFederationDomainIssuer(issuer string) (*FederationDomainIssuer, error) {
p := FederationDomain{issuer: issuer} p := FederationDomainIssuer{issuer: issuer}
err := p.validate() err := p.validate()
if err != nil { if err != nil {
return nil, err return nil, err
@ -27,9 +28,9 @@ func NewFederationDomain(issuer string) (*FederationDomain, error) {
return &p, nil return &p, nil
} }
func (p *FederationDomain) validate() error { func (p *FederationDomainIssuer) validate() error {
if p.issuer == "" { 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) issuerURL, err := url.Parse(p.issuer)
@ -63,14 +64,14 @@ func (p *FederationDomain) validate() error {
return nil return nil
} }
func (p *FederationDomain) Issuer() string { func (p *FederationDomainIssuer) Issuer() string {
return p.issuer return p.issuer
} }
func (p *FederationDomain) IssuerHost() string { func (p *FederationDomainIssuer) IssuerHost() string {
return p.issuerHost return p.issuerHost
} }
func (p *FederationDomain) IssuerPath() string { func (p *FederationDomainIssuer) IssuerPath() string {
return p.issuerPath return p.issuerPath
} }

View File

@ -9,16 +9,16 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
func TestFederationDomainValidations(t *testing.T) { func TestFederationDomainIssuerValidations(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
issuer string issuer string
wantError string wantError string
}{ }{
{ {
name: "provider must have an issuer", name: "must have an issuer",
issuer: "", issuer: "",
wantError: "provider must have an issuer", wantError: "federation domain must have an issuer",
}, },
{ {
name: "no scheme", name: "no scheme",
@ -72,7 +72,7 @@ func TestFederationDomainValidations(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
tt := tt tt := tt
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
_, err := NewFederationDomain(tt.issuer) _, err := NewFederationDomainIssuer(tt.issuer)
if tt.wantError != "" { if tt.wantError != "" {
require.EqualError(t, err, tt.wantError) require.EqualError(t, err, tt.wantError)
} else { } else {

View File

@ -32,7 +32,7 @@ import (
// It is thread-safe. // It is thread-safe.
type Manager struct { type Manager struct {
mu sync.RWMutex mu sync.RWMutex
providers []*provider.FederationDomain providers []*provider.FederationDomainIssuer
providerHandlers map[string]http.Handler // map of all routes for all providers 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 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 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 // It also removes any providerHandlers that were previously added but were not passed in to
// the current invocation. // 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. // 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() m.mu.Lock()
defer m.mu.Unlock() defer m.mu.Unlock()

View File

@ -348,9 +348,9 @@ func TestManager(t *testing.T) {
when("given some valid providers via SetProviders()", func() { when("given some valid providers via SetProviders()", func() {
it.Before(func() { it.Before(func() {
p1, err := provider.NewFederationDomain(issuer1) p1, err := provider.NewFederationDomainIssuer(issuer1)
r.NoError(err) r.NoError(err)
p2, err := provider.NewFederationDomain(issuer2) p2, err := provider.NewFederationDomainIssuer(issuer2)
r.NoError(err) r.NoError(err)
subject.SetProviders(p1, p2) 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() { when("given the same valid providers as arguments to SetProviders() in reverse order", func() {
it.Before(func() { it.Before(func() {
p1, err := provider.NewFederationDomain(issuer1) p1, err := provider.NewFederationDomainIssuer(issuer1)
r.NoError(err) r.NoError(err)
p2, err := provider.NewFederationDomain(issuer2) p2, err := provider.NewFederationDomainIssuer(issuer2)
r.NoError(err) r.NoError(err)
subject.SetProviders(p2, p1) subject.SetProviders(p2, p1)

View File

@ -78,138 +78,99 @@ var (
return here.Docf(` return here.Docf(`
{ {
"error": "invalid_request", "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. HTTP method is '%s', expected 'POST'."
"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
} }
`, actual, actual) `, actual)
} }
fositeMissingGrantTypeErrorBody = here.Docf(` fositeMissingGrantTypeErrorBody = here.Docf(`
{ {
"error": "invalid_request", "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. Request parameter 'grant_type' 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\n\nRequest parameter \"grant_type\"\" is missing",
"error_hint": "Request parameter \"grant_type\"\" is missing",
"status_code": 400
} }
`) `)
fositeEmptyPayloadErrorBody = here.Doc(` fositeEmptyPayloadErrorBody = here.Doc(`
{ {
"error": "invalid_request", "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. The POST body can not be empty."
"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
} }
`) `)
fositeInvalidPayloadErrorBody = here.Doc(` fositeInvalidPayloadErrorBody = here.Doc(`
{ {
"error": "invalid_request", "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. Unable to parse HTTP body, make sure to send a properly formatted form request body."
"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
} }
`) `)
fositeInvalidRequestErrorBody = here.Doc(` fositeInvalidRequestErrorBody = here.Doc(`
{ {
"error": "invalid_request", "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. 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."
"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
} }
`) `)
fositeInvalidRequestMissingGrantTypeErrorBody = here.Doc(` fositeInvalidRequestMissingGrantTypeErrorBody = here.Doc(`
{ {
"error": "invalid_request", "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_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"
"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
} }
`) `)
fositeMissingClientErrorBody = here.Doc(` fositeMissingClientErrorBody = here.Doc(`
{ {
"error": "invalid_request", "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. Client credentials missing or malformed in both HTTP Authorization header and HTTP POST body."
"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
} }
`) `)
fositeInvalidClientErrorBody = here.Doc(` fositeInvalidClientErrorBody = here.Doc(`
{ {
"error": "invalid_client", "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)."
"error_description": "Client authentication failed (e.g., unknown client, no client authentication included, or unsupported authentication method)",
"status_code": 401
} }
`) `)
fositeInvalidAuthCodeErrorBody = here.Doc(` fositeInvalidAuthCodeErrorBody = here.Doc(`
{ {
"error": "invalid_grant", "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."
"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
} }
`) `)
fositeReusedAuthCodeErrorBody = here.Doc(` fositeReusedAuthCodeErrorBody = here.Doc(`
{ {
"error": "invalid_grant", "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. The authorization code has already been used."
"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
} }
`) `)
fositeInvalidRedirectURIErrorBody = here.Doc(` fositeInvalidRedirectURIErrorBody = here.Doc(`
{ {
"error": "invalid_grant", "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. The 'redirect_uri' from this request does not match the one from the authorize request."
"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
} }
`) `)
fositeMissingPKCEVerifierErrorBody = here.Doc(` fositeMissingPKCEVerifierErrorBody = here.Doc(`
{ {
"error": "invalid_grant", "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. The PKCE code verifier must be at least 43 characters."
"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
} }
`) `)
fositeWrongPKCEVerifierErrorBody = here.Doc(` fositeWrongPKCEVerifierErrorBody = here.Doc(`
{ {
"error": "invalid_grant", "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. The PKCE code challenge did not match the code verifier."
"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
} }
`) `)
fositeTemporarilyUnavailableErrorBody = here.Doc(` fositeTemporarilyUnavailableErrorBody = here.Doc(`
{ {
"error": "temporarily_unavailable", "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_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
} }
`) `)
@ -749,7 +710,7 @@ func TestTokenExchange(t *testing.T) {
}, },
requestedAudience: "some-workload-cluster", requestedAudience: "some-workload-cluster",
wantStatus: http.StatusForbidden, wantStatus: http.StatusForbidden,
wantResponseBodyContains: `missing the \"pinniped:request-audience\" scope`, wantResponseBodyContains: `missing the 'pinniped:request-audience' scope`,
}, },
{ {
name: "access token missing openid scope", name: "access token missing openid scope",
@ -766,7 +727,7 @@ func TestTokenExchange(t *testing.T) {
}, },
requestedAudience: "some-workload-cluster", requestedAudience: "some-workload-cluster",
wantStatus: http.StatusForbidden, wantStatus: http.StatusForbidden,
wantResponseBodyContains: `missing the \"openid\" scope`, wantResponseBodyContains: `missing the 'openid' scope`,
}, },
{ {
name: "token minting failure", name: "token minting failure",

View File

@ -135,7 +135,7 @@ func (t *TokenExchangeHandler) validateAccessToken(ctx context.Context, requeste
signature := t.accessTokenStrategy.AccessTokenSignature(accessToken) signature := t.accessTokenStrategy.AccessTokenSignature(accessToken)
originalRequester, err := t.accessTokenStorage.GetAccessTokenSession(ctx, signature, requester.GetSession()) originalRequester, err := t.accessTokenStorage.GetAccessTokenSession(ctx, signature, requester.GetSession())
if err != nil { 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 return originalRequester, nil
} }

View File

@ -12,7 +12,7 @@ storage:
config: config:
file: ":memory:" file: ":memory:"
web: web:
https: 0.0.0.0:443 https: 0.0.0.0:8443
tlsCert: /var/certs/dex.pem tlsCert: /var/certs/dex.pem
tlsKey: /var/certs/dex-key.pem tlsKey: /var/certs/dex-key.pem
oauth2: oauth2:
@ -20,7 +20,7 @@ oauth2:
staticClients: staticClients:
- id: pinniped-cli - id: pinniped-cli
name: '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: redirectURIs:
- #@ "http://127.0.0.1:" + str(data.values.ports.cli) + "/callback" - #@ "http://127.0.0.1:" + str(data.values.ports.cli) + "/callback"
- #@ "http://[::1]:" + str(data.values.ports.cli) + "/callback" - #@ "http://[::1]:" + str(data.values.ports.cli) + "/callback"
@ -76,7 +76,7 @@ spec:
spec: spec:
containers: containers:
- name: dex - name: dex
image: quay.io/dexidp/dex:v2.10.0 image: ghcr.io/dexidp/dex:v2.27.0
imagePullPolicy: IfNotPresent imagePullPolicy: IfNotPresent
command: command:
- /usr/local/bin/dex - /usr/local/bin/dex
@ -84,7 +84,7 @@ spec:
- /etc/dex/cfg/config.yaml - /etc/dex/cfg/config.yaml
ports: ports:
- name: https - name: https
containerPort: 443 containerPort: 8443
volumeMounts: volumeMounts:
- name: dex-config - name: dex-config
mountPath: /etc/dex/cfg mountPath: /etc/dex/cfg
@ -111,5 +111,7 @@ spec:
selector: selector:
app: dex app: dex
ports: ports:
- port: 443 - name: https
name: https port: 443
targetPort: 8443

View File

@ -25,7 +25,7 @@ spec:
emptyDir: {} emptyDir: {}
containers: containers:
- name: proxy - name: proxy
image: docker.io/getpinniped/test-forward-proxy image: projects.registry.vmware.com/pinniped/test-forward-proxy
imagePullPolicy: Always imagePullPolicy: Always
ports: ports:
- name: http - name: http

View File

@ -595,17 +595,17 @@ func requireStatus(t *testing.T, client pinnipedclientset.Interface, ns, name st
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute) ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
defer cancel() defer cancel()
var opc *v1alpha1.FederationDomain var federationDomain *v1alpha1.FederationDomain
var err error var err error
assert.Eventually(t, func() bool { 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 { if err != nil {
t.Logf("error trying to get FederationDomain: %s", err.Error()) 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) }, time.Minute, 200*time.Millisecond)
require.NoError(t, err) 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 { func newHTTPClient(t *testing.T, caBundle string, dnsOverrides map[string]string) *http.Client {

View File

@ -27,71 +27,71 @@ func TestSupervisorSecrets(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute) ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
defer cancel() defer cancel()
// Create our OP under test. // Create our FederationDomain under test.
op := library.CreateTestFederationDomain(ctx, t, "", "", "") federationDomain := library.CreateTestFederationDomain(ctx, t, "", "", "")
tests := []struct { tests := []struct {
name string name string
secretName func(op *configv1alpha1.FederationDomain) string secretName func(federationDomain *configv1alpha1.FederationDomain) string
ensureValid func(t *testing.T, secret *corev1.Secret) ensureValid func(t *testing.T, secret *corev1.Secret)
}{ }{
{ {
name: "csrf cookie signing key", name: "csrf cookie signing key",
secretName: func(op *configv1alpha1.FederationDomain) string { secretName: func(federationDomain *configv1alpha1.FederationDomain) string {
return env.SupervisorAppName + "-key" return env.SupervisorAppName + "-key"
}, },
ensureValid: ensureValidSymmetricKey, ensureValid: ensureValidSymmetricSecretOfTypeFunc("secrets.pinniped.dev/supervisor-csrf-signing-key"),
}, },
{ {
name: "jwks", name: "jwks",
secretName: func(op *configv1alpha1.FederationDomain) string { secretName: func(federationDomain *configv1alpha1.FederationDomain) string {
return op.Status.Secrets.JWKS.Name return federationDomain.Status.Secrets.JWKS.Name
}, },
ensureValid: ensureValidJWKS, ensureValid: ensureValidJWKS,
}, },
{ {
name: "hmac signing secret", name: "hmac signing secret",
secretName: func(op *configv1alpha1.FederationDomain) string { secretName: func(federationDomain *configv1alpha1.FederationDomain) string {
return op.Status.Secrets.TokenSigningKey.Name return federationDomain.Status.Secrets.TokenSigningKey.Name
}, },
ensureValid: ensureValidSymmetricKey, ensureValid: ensureValidSymmetricSecretOfTypeFunc("secrets.pinniped.dev/federation-domain-token-signing-key"),
}, },
{ {
name: "state signature secret", name: "state signature secret",
secretName: func(op *configv1alpha1.FederationDomain) string { secretName: func(federationDomain *configv1alpha1.FederationDomain) string {
return op.Status.Secrets.StateSigningKey.Name return federationDomain.Status.Secrets.StateSigningKey.Name
}, },
ensureValid: ensureValidSymmetricKey, ensureValid: ensureValidSymmetricSecretOfTypeFunc("secrets.pinniped.dev/federation-domain-state-signing-key"),
}, },
{ {
name: "state encryption secret", name: "state encryption secret",
secretName: func(op *configv1alpha1.FederationDomain) string { secretName: func(federationDomain *configv1alpha1.FederationDomain) string {
return op.Status.Secrets.StateEncryptionKey.Name return federationDomain.Status.Secrets.StateEncryptionKey.Name
}, },
ensureValid: ensureValidSymmetricKey, ensureValid: ensureValidSymmetricSecretOfTypeFunc("secrets.pinniped.dev/federation-domain-state-encryption-key"),
}, },
} }
for _, test := range tests { for _, test := range tests {
test := test test := test
t.Run(test.name, func(t *testing.T) { t.Run(test.name, func(t *testing.T) {
// Ensure a secret is created with the OP's JWKS. // Ensure a secret is created with the FederationDomain's JWKS.
var updatedOP *configv1alpha1.FederationDomain var updatedFederationDomain *configv1alpha1.FederationDomain
var err error var err error
assert.Eventually(t, func() bool { assert.Eventually(t, func() bool {
updatedOP, err = supervisorClient. updatedFederationDomain, err = supervisorClient.
ConfigV1alpha1(). ConfigV1alpha1().
FederationDomains(env.SupervisorNamespace). FederationDomains(env.SupervisorNamespace).
Get(ctx, op.Name, metav1.GetOptions{}) Get(ctx, federationDomain.Name, metav1.GetOptions{})
return err == nil && test.secretName(updatedOP) != "" return err == nil && test.secretName(updatedFederationDomain) != ""
}, time.Second*10, time.Millisecond*500) }, time.Second*10, time.Millisecond*500)
require.NoError(t, err) require.NoError(t, err)
require.NotEmpty(t, test.secretName(updatedOP)) require.NotEmpty(t, test.secretName(updatedFederationDomain))
// Ensure the secret actually exists. // Ensure the secret actually exists.
secret, err := kubeClient. secret, err := kubeClient.
CoreV1(). CoreV1().
Secrets(env.SupervisorNamespace). Secrets(env.SupervisorNamespace).
Get(ctx, test.secretName(updatedOP), metav1.GetOptions{}) Get(ctx, test.secretName(updatedFederationDomain), metav1.GetOptions{})
require.NoError(t, err) require.NoError(t, err)
// Ensure that the secret was labelled. // Ensure that the secret was labelled.
@ -107,13 +107,13 @@ func TestSupervisorSecrets(t *testing.T) {
err = kubeClient. err = kubeClient.
CoreV1(). CoreV1().
Secrets(env.SupervisorNamespace). Secrets(env.SupervisorNamespace).
Delete(ctx, test.secretName(updatedOP), metav1.DeleteOptions{}) Delete(ctx, test.secretName(updatedFederationDomain), metav1.DeleteOptions{})
require.NoError(t, err) require.NoError(t, err)
assert.Eventually(t, func() bool { assert.Eventually(t, func() bool {
secret, err = kubeClient. secret, err = kubeClient.
CoreV1(). CoreV1().
Secrets(env.SupervisorNamespace). Secrets(env.SupervisorNamespace).
Get(ctx, test.secretName(updatedOP), metav1.GetOptions{}) Get(ctx, test.secretName(updatedFederationDomain), metav1.GetOptions{})
return err == nil return err == nil
}, time.Second*10, time.Millisecond*500) }, time.Second*10, time.Millisecond*500)
require.NoError(t, err) 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) { func ensureValidJWKS(t *testing.T, secret *corev1.Secret) {
t.Helper() 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. // Ensure the secret has an active key.
jwkData, ok := secret.Data["activeJWK"] jwkData, ok := secret.Data["activeJWK"]
require.True(t, ok, "secret is missing active jwk") 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) require.True(t, foundActiveJWK, "could not find active JWK in JWKS: %s", jwks)
} }
func ensureValidSymmetricKey(t *testing.T, secret *corev1.Secret) { func ensureValidSymmetricSecretOfTypeFunc(secretTypeValue string) func(*testing.T, *corev1.Secret) {
t.Helper() return func(t *testing.T, secret *corev1.Secret) {
require.Equal(t, corev1.SecretType("secrets.pinniped.dev/symmetric"), secret.Type) t.Helper()
key, ok := secret.Data["key"] require.Equal(t, corev1.SecretType(secretTypeValue), secret.Type)
require.Truef(t, ok, "secret data does not contain 'key': %s", secret.Data) key, ok := secret.Data["key"]
require.Equal(t, 32, len(key)) require.Truef(t, ok, "secret data does not contain 'key': %s", secret.Data)
require.Equal(t, 32, len(key))
}
} }

View File

@ -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)) issuer = fmt.Sprintf("http://test-issuer-%s.pinniped.dev", RandHex(t, 8))
} }
opcs := NewSupervisorClientset(t).ConfigV1alpha1().FederationDomains(testEnv.SupervisorNamespace) federationDomains := NewSupervisorClientset(t).ConfigV1alpha1().FederationDomains(testEnv.SupervisorNamespace)
opc, err := opcs.Create(createContext, &configv1alpha1.FederationDomain{ federationDomain, err := federationDomains.Create(createContext, &configv1alpha1.FederationDomain{
ObjectMeta: testObjectMeta(t, "oidc-provider"), ObjectMeta: testObjectMeta(t, "oidc-provider"),
Spec: configv1alpha1.FederationDomainSpec{ Spec: configv1alpha1.FederationDomainSpec{
Issuer: issuer, Issuer: issuer,
@ -258,31 +258,31 @@ func CreateTestFederationDomain(ctx context.Context, t *testing.T, issuer string
}, },
}, metav1.CreateOptions{}) }, metav1.CreateOptions{})
require.NoError(t, err, "could not create test FederationDomain") 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.Cleanup(func() {
t.Helper() 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) deleteCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel() defer cancel()
err := opcs.Delete(deleteCtx, opc.Name, metav1.DeleteOptions{}) err := federationDomains.Delete(deleteCtx, federationDomain.Name, metav1.DeleteOptions{})
notFound := k8serrors.IsNotFound(err) notFound := k8serrors.IsNotFound(err)
// It's okay if it is not found, because it might have been deleted by another part of this test. // It's okay if it is not found, because it might have been deleted by another part of this test.
if !notFound { 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 we're not expecting any particular status, just return the new FederationDomain immediately.
if expectStatus == "" { if expectStatus == "" {
return opc return federationDomain
} }
// Wait for the FederationDomain to enter the expected phase (or time out). // Wait for the FederationDomain to enter the expected phase (or time out).
var result *configv1alpha1.FederationDomain var result *configv1alpha1.FederationDomain
assert.Eventuallyf(t, func() bool { assert.Eventuallyf(t, func() bool {
var err error 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) require.NoError(t, err)
return result.Status.Status == expectStatus return result.Status.Status == expectStatus
}, 60*time.Second, 1*time.Second, "expected the FederationDomain to have status %q", 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 { if result.Status.Status == configv1alpha1.SuccessFederationDomainStatusCondition {
assert.Eventually(t, func() bool { assert.Eventually(t, func() bool {
var err error 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) require.NoError(t, err)
return result.Status.Secrets.JWKS.Name != "" && return result.Status.Secrets.JWKS.Name != "" &&
result.Status.Secrets.TokenSigningKey.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.StateSigningKey.Name)
require.NotEmpty(t, result.Status.Secrets.StateEncryptionKey.Name) require.NotEmpty(t, result.Status.Secrets.StateEncryptionKey.Name)
} }
return opc return federationDomain
} }
func RandHex(t *testing.T, numBytes int) string { func RandHex(t *testing.T, numBytes int) string {

View File

@ -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"
)