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 pkg ./pkg
COPY internal ./internal
COPY tools ./tools
COPY hack ./hack
# Build the executable binary (CGO_ENABLED=0 means static linking)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -25,7 +25,7 @@ custom_labels: {} #! e.g. {myCustomLabelName: myCustomLabelValue, otherCustomLab
replicas: 2
#! Specify either an image_digest or an image_tag. If both are given, only image_digest will be used.
image_repo: docker.io/getpinniped/pinniped-server
image_repo: projects.registry.vmware.com/pinniped/pinniped-server
image_digest: #! e.g. sha256:f3c4fdfd3ef865d4b97a1fd295d94acc3f0c654c46b6f27ffad5cf80216903c8
image_tag: latest

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.
image_repo: docker.io/getpinniped/pinniped-server
image_repo: projects.registry.vmware.com/pinniped/pinniped-server
image_digest: #! e.g. sha256:f3c4fdfd3ef865d4b97a1fd295d94acc3f0c654c46b6f27ffad5cf80216903c8
image_tag: latest

View File

@ -25,7 +25,7 @@ custom_labels: {} #! e.g. {myCustomLabelName: myCustomLabelValue, otherCustomLab
replicas: 2
#! Specify either an image_digest or an image_tag. If both are given, only image_digest will be used.
image_repo: docker.io/getpinniped/pinniped-server
image_repo: projects.registry.vmware.com/pinniped/pinniped-server
image_digest: #! e.g. sha256:f3c4fdfd3ef865d4b97a1fd295d94acc3f0c654c46b6f27ffad5cf80216903c8
image_tag: latest

View File

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

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-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200707034311-ab3426394381 h1:VXak5I6aEWmAXeQjA+QSZzlgNrpq9mjcfDemuexIKsU=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b h1:uwuIcX0g4Yl1NC5XAz37xsr2lTtcqevgzYNVt49waME=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -113,8 +113,9 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200622214017-ed371f2e16b4 h1:5/PjkGUjvEU5Gl6BxmvKRPpqo2uNMv4rcHBMwzk/st8=
golang.org/x/sys v0.0.0-20200622214017-ed371f2e16b4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201112073958-5cba982894dd h1:5CtCZbICpIOFdgO940moixOPjc0178IU44m4EjOO5IY=
golang.org/x/sys v0.0.0-20201112073958-5cba982894dd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
@ -157,10 +158,10 @@ gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
k8s.io/api v0.19.2 h1:q+/krnHWKsL7OBZg/rxnycsl9569Pud76UJ77MvKXms=
k8s.io/api v0.19.2/go.mod h1:IQpK0zFQ1xc5iNIQPqzgoOwuFugaYHK4iCknlAQP9nI=
k8s.io/apimachinery v0.19.2 h1:5Gy9vQpAGTKHPVOh5c4plE274X8D/6cuEiTO2zve7tc=
k8s.io/apimachinery v0.19.2/go.mod h1:DnPGDnARWFvYa3pMHgSxtbZb7gpzzAZ1pTfaUNDVlmA=
k8s.io/api v0.19.5 h1:p0MRzyhokJ9Kn5jcJAHNup0s+COMBPfn1mTasls6mMg=
k8s.io/api v0.19.5/go.mod h1:yGZReuNa0vj56op6eT+NLrXJne0R0u9ktexZ8jdJzpc=
k8s.io/apimachinery v0.19.5 h1:Yvz6dOE0WbVE+FXBEFqc9lSvo87VPtq6mCSsrtC95HI=
k8s.io/apimachinery v0.19.5/go.mod h1:6sRbGRAVY5DOCuZwB5XkqguBqpqLU6q/kOaOdk29z6Q=
k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE=
k8s.io/klog/v2 v2.2.0 h1:XRvcwJozkgZ1UQJmfMGpvRthQHOvihEhYtDfAaxMz/A=

View File

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

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-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200707034311-ab3426394381 h1:VXak5I6aEWmAXeQjA+QSZzlgNrpq9mjcfDemuexIKsU=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b h1:uwuIcX0g4Yl1NC5XAz37xsr2lTtcqevgzYNVt49waME=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@ -235,8 +235,9 @@ golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200622214017-ed371f2e16b4 h1:5/PjkGUjvEU5Gl6BxmvKRPpqo2uNMv4rcHBMwzk/st8=
golang.org/x/sys v0.0.0-20200622214017-ed371f2e16b4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201112073958-5cba982894dd h1:5CtCZbICpIOFdgO940moixOPjc0178IU44m4EjOO5IY=
golang.org/x/sys v0.0.0-20201112073958-5cba982894dd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
@ -327,12 +328,12 @@ honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWh
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
k8s.io/api v0.19.2 h1:q+/krnHWKsL7OBZg/rxnycsl9569Pud76UJ77MvKXms=
k8s.io/api v0.19.2/go.mod h1:IQpK0zFQ1xc5iNIQPqzgoOwuFugaYHK4iCknlAQP9nI=
k8s.io/apimachinery v0.19.2 h1:5Gy9vQpAGTKHPVOh5c4plE274X8D/6cuEiTO2zve7tc=
k8s.io/apimachinery v0.19.2/go.mod h1:DnPGDnARWFvYa3pMHgSxtbZb7gpzzAZ1pTfaUNDVlmA=
k8s.io/client-go v0.19.2 h1:gMJuU3xJZs86L1oQ99R4EViAADUPMHHtS9jFshasHSc=
k8s.io/client-go v0.19.2/go.mod h1:S5wPhCqyDNAlzM9CnEdgTGV4OqhsW3jGO1UM1epwfJA=
k8s.io/api v0.19.5 h1:p0MRzyhokJ9Kn5jcJAHNup0s+COMBPfn1mTasls6mMg=
k8s.io/api v0.19.5/go.mod h1:yGZReuNa0vj56op6eT+NLrXJne0R0u9ktexZ8jdJzpc=
k8s.io/apimachinery v0.19.5 h1:Yvz6dOE0WbVE+FXBEFqc9lSvo87VPtq6mCSsrtC95HI=
k8s.io/apimachinery v0.19.5/go.mod h1:6sRbGRAVY5DOCuZwB5XkqguBqpqLU6q/kOaOdk29z6Q=
k8s.io/client-go v0.19.5 h1:Y7LsFwgbm9+5oVXER04KNCSPhY6TblYRgG1DQdVq+ig=
k8s.io/client-go v0.19.5/go.mod h1:BSG3iuxI40Bs0nNDLS1JRa/7ReBQDHzf0x8nZZrK0fo=
k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE=
k8s.io/klog/v2 v2.2.0 h1:XRvcwJozkgZ1UQJmfMGpvRthQHOvihEhYtDfAaxMz/A=

43
go.mod
View File

@ -3,40 +3,49 @@ module go.pinniped.dev
go 1.14
require (
cloud.google.com/go v0.60.0 // indirect
github.com/MakeNowJust/heredoc/v2 v2.0.1
github.com/blang/semver v3.5.1+incompatible // indirect
github.com/coreos/go-oidc v2.2.1+incompatible
github.com/davecgh/go-spew v1.1.1
github.com/go-logr/logr v0.2.1
github.com/go-logr/logr v0.3.0
github.com/go-logr/stdr v0.2.0
github.com/gofrs/flock v0.8.0
github.com/golang/mock v1.4.4
github.com/golangci/golangci-lint v1.31.0
github.com/google/go-cmp v0.5.2
github.com/google/gofuzz v1.1.0
github.com/google/go-cmp v0.5.4
github.com/google/gofuzz v1.2.0
github.com/gorilla/securecookie v1.1.1
github.com/ory/fosite v0.35.1
github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4
github.com/kr/text v0.2.0 // indirect
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
github.com/onsi/ginkgo v1.13.0 // indirect
github.com/ory/fosite v0.36.0
github.com/pkg/browser v0.0.0-20201207095918-0426ae3fba23
github.com/pkg/errors v0.9.1
github.com/sclevine/agouti v3.0.0+incompatible
github.com/sclevine/spec v1.4.0
github.com/spf13/cobra v1.0.0
github.com/spf13/cobra v1.1.1
github.com/spf13/pflag v1.0.5
github.com/stretchr/testify v1.6.1
go.pinniped.dev/generated/1.19/apis v0.0.0-00010101000000-000000000000
go.pinniped.dev/generated/1.19/client v0.0.0-00010101000000-000000000000
golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899
golang.org/x/crypto v0.0.0-20201217014255-9d1352758620
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987 // indirect
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
gopkg.in/square/go-jose.v2 v2.5.1
k8s.io/api v0.19.2
k8s.io/apimachinery v0.19.2
k8s.io/apiserver v0.19.2
k8s.io/client-go v0.19.2
k8s.io/component-base v0.19.2
k8s.io/klog/v2 v2.3.0
k8s.io/kube-aggregator v0.19.2
k8s.io/utils v0.0.0-20200729134348-d5654de09c73
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 // indirect
k8s.io/api v0.19.5
k8s.io/apimachinery v0.19.5
k8s.io/apiserver v0.19.5
k8s.io/client-go v0.19.5
k8s.io/component-base v0.19.5
k8s.io/klog/v2 v2.4.0
k8s.io/kube-aggregator v0.19.5
k8s.io/utils v0.0.0-20201110183641-67b214c5f920
sigs.k8s.io/yaml v1.2.0
)

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.17.11

View File

@ -13,7 +13,7 @@ export GO111MODULE="on"
if [[ -z "${CONTAINED:-}" ]]; then
for kubeVersion in "${KUBE_VERSIONS[@]}"; do
# CODEGEN_IMAGE is the container image to use when running
CODEGEN_IMAGE="docker.io/getpinniped/k8s-code-generator-$(echo "$kubeVersion" | cut -d"." -f1-2):latest"
CODEGEN_IMAGE="projects.registry.vmware.com/pinniped/k8s-code-generator-$(echo "$kubeVersion" | cut -d"." -f1-2):latest"
echo "generating code for ${kubeVersion} using ${CODEGEN_IMAGE}..."
docker run --rm \

View File

@ -12,12 +12,7 @@ function tidy_cmd() {
}
function lint_cmd() {
if [ -x "$(command -v golangci-lint)" ]; then
cmd='golangci-lint'
else
cmd='go run github.com/golangci/golangci-lint/cmd/golangci-lint'
fi
echo "${cmd} run --modules-download-mode=readonly --timeout=10m"
echo "golangci-lint run --modules-download-mode=readonly --timeout=10m"
}
function test_cmd() {

View File

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

View File

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

View File

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

View File

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

View File

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

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"
configv1alpha1 "go.pinniped.dev/generated/1.19/apis/supervisor/config/v1alpha1"
"go.pinniped.dev/internal/plog"
)
// SecretHelper describes an object that can Generate() a Secret and determine whether a Secret
@ -27,8 +26,18 @@ type SecretHelper interface {
}
const (
// symmetricSecretType is corev1.Secret.Type of all corev1.Secret's generated by this helper.
symmetricSecretType = "secrets.pinniped.dev/symmetric"
// SupervisorCSRFSigningKeySecretType for the Secret storing the CSRF signing key.
SupervisorCSRFSigningKeySecretType corev1.SecretType = "secrets.pinniped.dev/supervisor-csrf-signing-key"
// FederationDomainTokenSigningKeyType for the Secret storing the FederationDomain token signing key.
FederationDomainTokenSigningKeyType corev1.SecretType = "secrets.pinniped.dev/federation-domain-token-signing-key"
// FederationDomainStateSigningKeyType for the Secret storing the FederationDomain state signing key.
FederationDomainStateSigningKeyType corev1.SecretType = "secrets.pinniped.dev/federation-domain-state-signing-key"
// FederationDomainStateEncryptionKeyType for the Secret storing the FederationDomain state encryption key.
FederationDomainStateEncryptionKeyType corev1.SecretType = "secrets.pinniped.dev/federation-domain-state-encryption-key"
// symmetricSecretDataKey is the corev1.Secret.Data key for the symmetric key value generated by this helper.
symmetricSecretDataKey = "key"
@ -96,7 +105,7 @@ func (s *symmetricSecretHelper) Generate(parent *configv1alpha1.FederationDomain
}),
},
},
Type: symmetricSecretType,
Type: s.secretType(),
Data: map[string][]byte{
symmetricSecretDataKey: key,
},
@ -109,7 +118,7 @@ func (s *symmetricSecretHelper) IsValid(parent *configv1alpha1.FederationDomain,
return false
}
if secret.Type != symmetricSecretType {
if secret.Type != s.secretType() {
return false
}
@ -126,26 +135,34 @@ func (s *symmetricSecretHelper) IsValid(parent *configv1alpha1.FederationDomain,
// ObserveActiveSecretAndUpdateParentFederationDomain implements SecretHelper.ObserveActiveSecretAndUpdateParentFederationDomain().
func (s *symmetricSecretHelper) ObserveActiveSecretAndUpdateParentFederationDomain(
op *configv1alpha1.FederationDomain,
federationDomain *configv1alpha1.FederationDomain,
secret *corev1.Secret,
) *configv1alpha1.FederationDomain {
var cacheKey string
if op != nil {
cacheKey = op.Spec.Issuer
}
s.updateCacheFunc(cacheKey, secret.Data[symmetricSecretDataKey])
s.updateCacheFunc(federationDomain.Spec.Issuer, secret.Data[symmetricSecretDataKey])
switch s.secretUsage {
case SecretUsageTokenSigningKey:
op.Status.Secrets.TokenSigningKey.Name = secret.Name
federationDomain.Status.Secrets.TokenSigningKey.Name = secret.Name
case SecretUsageStateSigningKey:
op.Status.Secrets.StateSigningKey.Name = secret.Name
federationDomain.Status.Secrets.StateSigningKey.Name = secret.Name
case SecretUsageStateEncryptionKey:
op.Status.Secrets.StateEncryptionKey.Name = secret.Name
federationDomain.Status.Secrets.StateEncryptionKey.Name = secret.Name
default:
plog.Warning("unknown secret usage enum value: %d", s.secretUsage)
panic(fmt.Sprintf("unknown secret usage enum value: %d", s.secretUsage))
}
return op
return federationDomain
}
func (s *symmetricSecretHelper) secretType() corev1.SecretType {
switch s.secretUsage {
case SecretUsageTokenSigningKey:
return FederationDomainTokenSigningKeyType
case SecretUsageStateSigningKey:
return FederationDomainStateSigningKeyType
case SecretUsageStateEncryptionKey:
return FederationDomainStateEncryptionKeyType
default:
panic(fmt.Sprintf("unknown secret usage enum value: %d", s.secretUsage))
}
}

View File

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

View File

@ -6,12 +6,14 @@ package generator
import (
"context"
"crypto/rand"
"fmt"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
k8serrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
corev1informers "k8s.io/client-go/informers/core/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/util/retry"
@ -143,3 +145,80 @@ func (c *supervisorSecretsController) updateSecret(ctx context.Context, newSecre
return err
})
}
func generateSymmetricKey() ([]byte, error) {
b := make([]byte, symmetricKeySize)
if _, err := rand.Read(b); err != nil {
return nil, err
}
return b, nil
}
func isValid(secret *corev1.Secret, labels map[string]string) bool {
if secret.Type != SupervisorCSRFSigningKeySecretType {
return false
}
data, ok := secret.Data[symmetricSecretDataKey]
if !ok {
return false
}
if len(data) != symmetricKeySize {
return false
}
for key, value := range labels {
if secret.Labels[key] != value {
return false
}
}
return true
}
func secretDataFunc() (map[string][]byte, error) {
symmetricKey, err := generateKey()
if err != nil {
return nil, err
}
return map[string][]byte{
symmetricSecretDataKey: symmetricKey,
}, nil
}
func generateSecret(namespace, name string, labels map[string]string, secretDataFunc func() (map[string][]byte, error), owner metav1.Object) (*corev1.Secret, error) {
secretData, err := secretDataFunc()
if err != nil {
return nil, err
}
deploymentGVK := schema.GroupVersionKind{
Group: appsv1.SchemeGroupVersion.Group,
Version: appsv1.SchemeGroupVersion.Version,
Kind: "Deployment",
}
blockOwnerDeletion := true
isController := false
return &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: namespace,
OwnerReferences: []metav1.OwnerReference{
{
APIVersion: deploymentGVK.GroupVersion().String(),
Kind: deploymentGVK.Kind,
Name: owner.GetName(),
UID: owner.GetUID(),
BlockOwnerDeletion: &blockOwnerDeletion,
Controller: &isController,
},
},
Labels: labels,
},
Type: SupervisorCSRFSigningKeySecretType,
Data: secretData,
}, nil
}

View File

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

View File

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

View File

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

View File

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

View File

@ -86,7 +86,7 @@ func (a *accessTokenStorage) getSession(ctx context.Context, signature string) (
rv, err := a.storage.Get(ctx, signature, session)
if errors.IsNotFound(err) {
return nil, "", fosite.ErrNotFound.WithCause(err).WithDebug(err.Error())
return nil, "", fosite.ErrNotFound.WithWrap(err).WithDebug(err.Error())
}
if err != nil {

View File

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

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,
// if it adds a new field that can be fuzzed, this check will fail
// thus if AuthorizeRequest changes, we will detect it here (though we could possibly miss an omitempty field)
require.Equal(t, ExpectedAuthorizeCodeSessionJSONFromFuzzing, authorizeCodeSessionJSONFromFuzzing)
require.JSONEq(t, ExpectedAuthorizeCodeSessionJSONFromFuzzing, authorizeCodeSessionJSONFromFuzzing)
}

View File

@ -88,7 +88,7 @@ func (a *openIDConnectRequestStorage) getSession(ctx context.Context, signature
rv, err := a.storage.Get(ctx, signature, session)
if errors.IsNotFound(err) {
return nil, "", fosite.ErrNotFound.WithCause(err).WithDebug(err.Error())
return nil, "", fosite.ErrNotFound.WithWrap(err).WithDebug(err.Error())
}
if err != nil {

View File

@ -72,7 +72,7 @@ func (a *pkceStorage) getSession(ctx context.Context, signature string) (*sessio
rv, err := a.storage.Get(ctx, signature, session)
if errors.IsNotFound(err) {
return nil, "", fosite.ErrNotFound.WithCause(err).WithDebug(err.Error())
return nil, "", fosite.ErrNotFound.WithWrap(err).WithDebug(err.Error())
}
if err != nil {

View File

@ -86,7 +86,7 @@ func (a *refreshTokenStorage) getSession(ctx context.Context, signature string)
rv, err := a.storage.Get(ctx, signature, session)
if errors.IsNotFound(err) {
return nil, "", fosite.ErrNotFound.WithCause(err).WithDebug(err.Error())
return nil, "", fosite.ErrNotFound.WithWrap(err).WithDebug(err.Error())
}
if err != nil {

View File

@ -41,76 +41,62 @@ func TestAuthorizationEndpoint(t *testing.T) {
fositeInvalidClientErrorBody = here.Doc(`
{
"error": "invalid_client",
"error_verbose": "Client authentication failed (e.g., unknown client, no client authentication included, or unsupported authentication method)",
"error_description": "Client authentication failed (e.g., unknown client, no client authentication included, or unsupported authentication method)\n\nThe requested OAuth 2.0 Client does not exist.",
"error_hint": "The requested OAuth 2.0 Client does not exist.",
"status_code": 401
"error_description": "Client authentication failed (e.g., unknown client, no client authentication included, or unsupported authentication method). The requested OAuth 2.0 Client does not exist."
}
`)
fositeInvalidRedirectURIErrorBody = here.Doc(`
{
"error": "invalid_request",
"error_verbose": "The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed",
"error_description": "The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed\n\nThe \"redirect_uri\" parameter does not match any of the OAuth 2.0 Client's pre-registered redirect urls.",
"error_hint": "The \"redirect_uri\" parameter does not match any of the OAuth 2.0 Client's pre-registered redirect urls.",
"status_code": 400
"error_description": "The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed. The 'redirect_uri' parameter does not match any of the OAuth 2.0 Client's pre-registered redirect urls."
}
`)
fositePromptHasNoneAndOtherValueErrorQuery = map[string]string{
"error": "invalid_request",
"error_description": "The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed\n\nParameter \"prompt\" was set to \"none\", but contains other values as well which is not allowed.",
"error_hint": "Parameter \"prompt\" was set to \"none\", but contains other values as well which is not allowed.",
"error_description": "The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed. Parameter 'prompt' was set to 'none', but contains other values as well which is not allowed.",
"state": happyState,
}
fositeMissingCodeChallengeErrorQuery = map[string]string{
"error": "invalid_request",
"error_description": "The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed\n\nClients must include a code_challenge when performing the authorize code flow, but it is missing.",
"error_hint": "Clients must include a code_challenge when performing the authorize code flow, but it is missing.",
"error_description": "The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed. Clients must include a code_challenge when performing the authorize code flow, but it is missing.",
"state": happyState,
}
fositeMissingCodeChallengeMethodErrorQuery = map[string]string{
"error": "invalid_request",
"error_description": "The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed\n\nClients must use code_challenge_method=S256, plain is not allowed.",
"error_hint": "Clients must use code_challenge_method=S256, plain is not allowed.",
"error_description": "The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed. Clients must use code_challenge_method=S256, plain is not allowed.",
"state": happyState,
}
fositeInvalidCodeChallengeErrorQuery = map[string]string{
"error": "invalid_request",
"error_description": "The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed\n\nThe code_challenge_method is not supported, use S256 instead.",
"error_hint": "The code_challenge_method is not supported, use S256 instead.",
"error_description": "The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed. The code_challenge_method is not supported, use S256 instead.",
"state": happyState,
}
fositeUnsupportedResponseTypeErrorQuery = map[string]string{
"error": "unsupported_response_type",
"error_description": "The authorization server does not support obtaining a token using this method\n\nThe client is not allowed to request response_type \"unsupported\".",
"error_hint": `The client is not allowed to request response_type "unsupported".`,
"error_description": "The authorization server does not support obtaining a token using this method. The client is not allowed to request response_type 'unsupported'.",
"state": happyState,
}
fositeInvalidScopeErrorQuery = map[string]string{
"error": "invalid_scope",
"error_description": "The requested scope is invalid, unknown, or malformed\n\nThe OAuth 2.0 Client is not allowed to request scope \"tuna\".",
"error_hint": `The OAuth 2.0 Client is not allowed to request scope "tuna".`,
"error_description": "The requested scope is invalid, unknown, or malformed. The OAuth 2.0 Client is not allowed to request scope 'tuna'.",
"state": happyState,
}
fositeInvalidStateErrorQuery = map[string]string{
"error": "invalid_state",
"error_description": "The state is missing or does not have enough characters and is therefore considered too weak\n\nRequest parameter \"state\" must be at least be 8 characters long to ensure sufficient entropy.",
"error_hint": `Request parameter "state" must be at least be 8 characters long to ensure sufficient entropy.`,
"error_description": "The state is missing or does not have enough characters and is therefore considered too weak. Request parameter 'state' must be at least be 8 characters long to ensure sufficient entropy.",
"state": "short",
}
fositeMissingResponseTypeErrorQuery = map[string]string{
"error": "unsupported_response_type",
"error_description": "The authorization server does not support obtaining a token using this method\n\nThe request is missing the \"response_type\"\" parameter.",
"error_hint": `The request is missing the "response_type"" parameter.`,
"error_description": "The authorization server does not support obtaining a token using this method. `The request is missing the 'response_type' parameter.",
"state": happyState,
}
)

View File

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

View File

@ -50,7 +50,7 @@ func (s *dynamicOpenIDConnectECDSAStrategy) GenerateIDToken(
_, activeJwk := s.jwksProvider.GetJWKS(s.fositeConfig.IDTokenIssuer)
if activeJwk == nil {
plog.Debug("no JWK found for issuer", "issuer", s.fositeConfig.IDTokenIssuer)
return "", fosite.ErrTemporarilyUnavailable.WithCause(constable.Error("no JWK found for issuer"))
return "", fosite.ErrTemporarilyUnavailable.WithWrap(constable.Error("no JWK found for issuer"))
}
key, ok := activeJwk.Key.(*ecdsa.PrivateKey)
if !ok {
@ -65,7 +65,7 @@ func (s *dynamicOpenIDConnectECDSAStrategy) GenerateIDToken(
"actualType",
actualType,
)
return "", fosite.ErrServerError.WithCause(constable.Error("JWK must be of type ecdsa"))
return "", fosite.ErrServerError.WithWrap(constable.Error("JWK must be of type ecdsa"))
}
return compose.NewOpenIDConnectECDSAStrategy(s.fositeConfig, key).GenerateIDToken(ctx, requester)

View File

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

View File

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

View File

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

View File

@ -32,7 +32,7 @@ import (
// It is thread-safe.
type Manager struct {
mu sync.RWMutex
providers []*provider.FederationDomain
providers []*provider.FederationDomainIssuer
providerHandlers map[string]http.Handler // map of all routes for all providers
nextHandler http.Handler // the next handler in a chain, called when this manager didn't know how to handle a request
dynamicJWKSProvider jwks.DynamicJWKSProvider // in-memory cache of per-issuer JWKS data
@ -68,9 +68,9 @@ func NewManager(
// It also removes any providerHandlers that were previously added but were not passed in to
// the current invocation.
//
// This method assumes that all of the FederationDomain arguments have already been validated
// This method assumes that all of the FederationDomainIssuer arguments have already been validated
// by someone else before they are passed to this method.
func (m *Manager) SetProviders(federationDomains ...*provider.FederationDomain) {
func (m *Manager) SetProviders(federationDomains ...*provider.FederationDomainIssuer) {
m.mu.Lock()
defer m.mu.Unlock()

View File

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

View File

@ -78,138 +78,99 @@ var (
return here.Docf(`
{
"error": "invalid_request",
"error_verbose": "The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed",
"error_description": "The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed\n\nHTTP method is \"%s\", expected \"POST\".",
"error_hint": "HTTP method is \"%s\", expected \"POST\".",
"status_code": 400
"error_description": "The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed. HTTP method is '%s', expected 'POST'."
}
`, actual, actual)
`, actual)
}
fositeMissingGrantTypeErrorBody = here.Docf(`
{
"error": "invalid_request",
"error_verbose": "The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed",
"error_description": "The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed\n\nRequest parameter \"grant_type\"\" is missing",
"error_hint": "Request parameter \"grant_type\"\" is missing",
"status_code": 400
"error_description": "The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed. Request parameter 'grant_type' is missing"
}
`)
fositeEmptyPayloadErrorBody = here.Doc(`
{
"error": "invalid_request",
"error_verbose": "The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed",
"error_description": "The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed\n\nThe POST body can not be empty.",
"error_hint": "The POST body can not be empty.",
"status_code": 400
"error_description": "The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed. The POST body can not be empty."
}
`)
fositeInvalidPayloadErrorBody = here.Doc(`
{
"error": "invalid_request",
"error_verbose": "The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed",
"error_description": "The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed\n\nUnable to parse HTTP body, make sure to send a properly formatted form request body.",
"error_hint": "Unable to parse HTTP body, make sure to send a properly formatted form request body.",
"status_code": 400
"error_description": "The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed. Unable to parse HTTP body, make sure to send a properly formatted form request body."
}
`)
fositeInvalidRequestErrorBody = here.Doc(`
{
"error": "invalid_request",
"error_verbose": "The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed",
"error_description": "The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed\n\nMake sure that the various parameters are correct, be aware of case sensitivity and trim your parameters. Make sure that the client you are using has exactly whitelisted the redirect_uri you specified.",
"error_hint": "Make sure that the various parameters are correct, be aware of case sensitivity and trim your parameters. Make sure that the client you are using has exactly whitelisted the redirect_uri you specified.",
"status_code": 400
"error_description": "The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed. Make sure that the various parameters are correct, be aware of case sensitivity and trim your parameters. Make sure that the client you are using has exactly whitelisted the redirect_uri you specified."
}
`)
fositeInvalidRequestMissingGrantTypeErrorBody = here.Doc(`
{
"error": "invalid_request",
"error_description": "The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed\n\nRequest parameter \"grant_type\"\" is missing",
"error_hint": "Request parameter \"grant_type\"\" is missing",
"error_verbose": "The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed",
"status_code": 400
"error_description": "The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed. Request parameter 'grant_type' is missing"
}
`)
fositeMissingClientErrorBody = here.Doc(`
{
"error": "invalid_request",
"error_verbose": "The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed",
"error_description": "The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed\n\nClient credentials missing or malformed in both HTTP Authorization header and HTTP POST body.",
"error_hint": "Client credentials missing or malformed in both HTTP Authorization header and HTTP POST body.",
"status_code": 400
"error_description": "The request is missing a required parameter, includes an invalid parameter value, includes a parameter more than once, or is otherwise malformed. Client credentials missing or malformed in both HTTP Authorization header and HTTP POST body."
}
`)
fositeInvalidClientErrorBody = here.Doc(`
{
"error": "invalid_client",
"error_verbose": "Client authentication failed (e.g., unknown client, no client authentication included, or unsupported authentication method)",
"error_description": "Client authentication failed (e.g., unknown client, no client authentication included, or unsupported authentication method)",
"status_code": 401
"error_description": "Client authentication failed (e.g., unknown client, no client authentication included, or unsupported authentication method)."
}
`)
fositeInvalidAuthCodeErrorBody = here.Doc(`
{
"error": "invalid_grant",
"error_verbose": "The provided authorization grant (e.g., authorization code, resource owner credentials) or refresh token is invalid, expired, revoked, does not match the redirection URI used in the authorization request, or was issued to another client",
"error_description": "The provided authorization grant (e.g., authorization code, resource owner credentials) or refresh token is invalid, expired, revoked, does not match the redirection URI used in the authorization request, or was issued to another client",
"status_code": 400
"error_description": "The provided authorization grant (e.g., authorization code, resource owner credentials) or refresh token is invalid, expired, revoked, does not match the redirection URI used in the authorization request, or was issued to another client."
}
`)
fositeReusedAuthCodeErrorBody = here.Doc(`
{
"error": "invalid_grant",
"error_verbose": "The provided authorization grant (e.g., authorization code, resource owner credentials) or refresh token is invalid, expired, revoked, does not match the redirection URI used in the authorization request, or was issued to another client",
"error_description": "The provided authorization grant (e.g., authorization code, resource owner credentials) or refresh token is invalid, expired, revoked, does not match the redirection URI used in the authorization request, or was issued to another client\n\nThe authorization code has already been used.",
"error_hint": "The authorization code has already been used.",
"status_code": 400
"error_description": "The provided authorization grant (e.g., authorization code, resource owner credentials) or refresh token is invalid, expired, revoked, does not match the redirection URI used in the authorization request, or was issued to another client. The authorization code has already been used."
}
`)
fositeInvalidRedirectURIErrorBody = here.Doc(`
{
"error": "invalid_grant",
"error_verbose": "The provided authorization grant (e.g., authorization code, resource owner credentials) or refresh token is invalid, expired, revoked, does not match the redirection URI used in the authorization request, or was issued to another client",
"error_description": "The provided authorization grant (e.g., authorization code, resource owner credentials) or refresh token is invalid, expired, revoked, does not match the redirection URI used in the authorization request, or was issued to another client\n\nThe \"redirect_uri\" from this request does not match the one from the authorize request.",
"error_hint": "The \"redirect_uri\" from this request does not match the one from the authorize request.",
"status_code": 400
"error_description": "The provided authorization grant (e.g., authorization code, resource owner credentials) or refresh token is invalid, expired, revoked, does not match the redirection URI used in the authorization request, or was issued to another client. The 'redirect_uri' from this request does not match the one from the authorize request."
}
`)
fositeMissingPKCEVerifierErrorBody = here.Doc(`
{
"error": "invalid_grant",
"error_verbose": "The provided authorization grant (e.g., authorization code, resource owner credentials) or refresh token is invalid, expired, revoked, does not match the redirection URI used in the authorization request, or was issued to another client",
"error_description": "The provided authorization grant (e.g., authorization code, resource owner credentials) or refresh token is invalid, expired, revoked, does not match the redirection URI used in the authorization request, or was issued to another client\n\nThe PKCE code verifier must be at least 43 characters.",
"error_hint": "The PKCE code verifier must be at least 43 characters.",
"status_code": 400
"error_description": "The provided authorization grant (e.g., authorization code, resource owner credentials) or refresh token is invalid, expired, revoked, does not match the redirection URI used in the authorization request, or was issued to another client. The PKCE code verifier must be at least 43 characters."
}
`)
fositeWrongPKCEVerifierErrorBody = here.Doc(`
{
"error": "invalid_grant",
"error_verbose": "The provided authorization grant (e.g., authorization code, resource owner credentials) or refresh token is invalid, expired, revoked, does not match the redirection URI used in the authorization request, or was issued to another client",
"error_description": "The provided authorization grant (e.g., authorization code, resource owner credentials) or refresh token is invalid, expired, revoked, does not match the redirection URI used in the authorization request, or was issued to another client\n\nThe PKCE code challenge did not match the code verifier.",
"error_hint": "The PKCE code challenge did not match the code verifier.",
"status_code": 400
"error_description": "The provided authorization grant (e.g., authorization code, resource owner credentials) or refresh token is invalid, expired, revoked, does not match the redirection URI used in the authorization request, or was issued to another client. The PKCE code challenge did not match the code verifier."
}
`)
fositeTemporarilyUnavailableErrorBody = here.Doc(`
{
"error": "temporarily_unavailable",
"error_description": "The authorization server is currently unable to handle the request due to a temporary overloading or maintenance of the server",
"error_verbose": "The authorization server is currently unable to handle the request due to a temporary overloading or maintenance of the server",
"status_code": 503
"error_description": "The authorization server is currently unable to handle the request due to a temporary overloading or maintenance of the server."
}
`)
@ -749,7 +710,7 @@ func TestTokenExchange(t *testing.T) {
},
requestedAudience: "some-workload-cluster",
wantStatus: http.StatusForbidden,
wantResponseBodyContains: `missing the \"pinniped:request-audience\" scope`,
wantResponseBodyContains: `missing the 'pinniped:request-audience' scope`,
},
{
name: "access token missing openid scope",
@ -766,7 +727,7 @@ func TestTokenExchange(t *testing.T) {
},
requestedAudience: "some-workload-cluster",
wantStatus: http.StatusForbidden,
wantResponseBodyContains: `missing the \"openid\" scope`,
wantResponseBodyContains: `missing the 'openid' scope`,
},
{
name: "token minting failure",

View File

@ -135,7 +135,7 @@ func (t *TokenExchangeHandler) validateAccessToken(ctx context.Context, requeste
signature := t.accessTokenStrategy.AccessTokenSignature(accessToken)
originalRequester, err := t.accessTokenStorage.GetAccessTokenSession(ctx, signature, requester.GetSession())
if err != nil {
return nil, fosite.ErrRequestUnauthorized.WithCause(err).WithHint("invalid subject_token")
return nil, fosite.ErrRequestUnauthorized.WithWrap(err).WithHint("invalid subject_token")
}
return originalRequester, nil
}

View File

@ -12,7 +12,7 @@ storage:
config:
file: ":memory:"
web:
https: 0.0.0.0:443
https: 0.0.0.0:8443
tlsCert: /var/certs/dex.pem
tlsKey: /var/certs/dex-key.pem
oauth2:
@ -20,7 +20,7 @@ oauth2:
staticClients:
- id: pinniped-cli
name: 'Pinniped CLI'
#! we can't have "public: true" until https://github.com/dexidp/dex/pull/1822 lands in Dex.
public: true
redirectURIs:
- #@ "http://127.0.0.1:" + str(data.values.ports.cli) + "/callback"
- #@ "http://[::1]:" + str(data.values.ports.cli) + "/callback"
@ -76,7 +76,7 @@ spec:
spec:
containers:
- name: dex
image: quay.io/dexidp/dex:v2.10.0
image: ghcr.io/dexidp/dex:v2.27.0
imagePullPolicy: IfNotPresent
command:
- /usr/local/bin/dex
@ -84,7 +84,7 @@ spec:
- /etc/dex/cfg/config.yaml
ports:
- name: https
containerPort: 443
containerPort: 8443
volumeMounts:
- name: dex-config
mountPath: /etc/dex/cfg
@ -111,5 +111,7 @@ spec:
selector:
app: dex
ports:
- port: 443
name: https
- name: https
port: 443
targetPort: 8443

View File

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

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)
defer cancel()
var opc *v1alpha1.FederationDomain
var federationDomain *v1alpha1.FederationDomain
var err error
assert.Eventually(t, func() bool {
opc, err = client.ConfigV1alpha1().FederationDomains(ns).Get(ctx, name, metav1.GetOptions{})
federationDomain, err = client.ConfigV1alpha1().FederationDomains(ns).Get(ctx, name, metav1.GetOptions{})
if err != nil {
t.Logf("error trying to get FederationDomain: %s", err.Error())
}
return err == nil && opc.Status.Status == status
return err == nil && federationDomain.Status.Status == status
}, time.Minute, 200*time.Millisecond)
require.NoError(t, err)
require.Equalf(t, status, opc.Status.Status, "unexpected status (message = '%s')", opc.Status.Message)
require.Equalf(t, status, federationDomain.Status.Status, "unexpected status (message = '%s')", federationDomain.Status.Message)
}
func newHTTPClient(t *testing.T, caBundle string, dnsOverrides map[string]string) *http.Client {

View File

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

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

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