jwtcachefiller_test.go: refactor and remove "if short skip" check
- Refactor the test to avoid testing a private method and instead always test the results of running the controller. - Also remove the `if testing.Short()` check because it will always be short when running unit tests. This prevented the unit test from ever running, both locally and in CI. Signed-off-by: Ryan Richard <richardry@vmware.com>
This commit is contained in:
parent
0e60c93cef
commit
720bc7ae42
@ -41,225 +41,10 @@ import (
|
|||||||
func TestController(t *testing.T) {
|
func TestController(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
someJWTAuthenticatorSpec := &auth1alpha1.JWTAuthenticatorSpec{
|
|
||||||
Issuer: "https://some-issuer.com",
|
|
||||||
Audience: "some-audience",
|
|
||||||
TLS: &auth1alpha1.TLSSpec{CertificateAuthorityData: "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURVVENDQWptZ0F3SUJBZ0lWQUpzNStTbVRtaTJXeUI0bGJJRXBXaUs5a1RkUE1BMEdDU3FHU0liM0RRRUIKQ3dVQU1COHhDekFKQmdOVkJBWVRBbFZUTVJBd0RnWURWUVFLREFkUWFYWnZkR0ZzTUI0WERUSXdNRFV3TkRFMgpNamMxT0ZvWERUSTBNRFV3TlRFMk1qYzFPRm93SHpFTE1Ba0dBMVVFQmhNQ1ZWTXhFREFPQmdOVkJBb01CMUJwCmRtOTBZV3d3Z2dFaU1BMEdDU3FHU0liM0RRRUJBUVVBQTRJQkR3QXdnZ0VLQW9JQkFRRERZWmZvWGR4Z2NXTEMKZEJtbHB5a0tBaG9JMlBuUWtsVFNXMno1cGcwaXJjOGFRL1E3MXZzMTRZYStmdWtFTGlvOTRZYWw4R01DdVFrbApMZ3AvUEE5N1VYelhQNDBpK25iNXcwRGpwWWd2dU9KQXJXMno2MFRnWE5NSFh3VHk4ME1SZEhpUFVWZ0VZd0JpCmtkNThzdEFVS1Y1MnBQTU1reTJjNy9BcFhJNmRXR2xjalUvaFBsNmtpRzZ5dEw2REtGYjJQRWV3MmdJM3pHZ2IKOFVVbnA1V05DZDd2WjNVY0ZHNXlsZEd3aGc3cnZ4U1ZLWi9WOEhCMGJmbjlxamlrSVcxWFM4dzdpUUNlQmdQMApYZWhKZmVITlZJaTJtZlczNlVQbWpMdnVKaGpqNDIrdFBQWndvdDkzdWtlcEgvbWpHcFJEVm9wamJyWGlpTUYrCkYxdnlPNGMxQWdNQkFBR2pnWU13Z1lBd0hRWURWUjBPQkJZRUZNTWJpSXFhdVkwajRVWWphWDl0bDJzby9LQ1IKTUI4R0ExVWRJd1FZTUJhQUZNTWJpSXFhdVkwajRVWWphWDl0bDJzby9LQ1JNQjBHQTFVZEpRUVdNQlFHQ0NzRwpBUVVGQndNQ0JnZ3JCZ0VGQlFjREFUQVBCZ05WSFJNQkFmOEVCVEFEQVFIL01BNEdBMVVkRHdFQi93UUVBd0lCCkJqQU5CZ2txaGtpRzl3MEJBUXNGQUFPQ0FRRUFYbEh4M2tIMDZwY2NDTDlEVE5qTnBCYnlVSytGd2R6T2IwWFYKcmpNaGtxdHVmdEpUUnR5T3hKZ0ZKNXhUR3pCdEtKamcrVU1pczBOV0t0VDBNWThVMU45U2c5SDl0RFpHRHBjVQpxMlVRU0Y4dXRQMVR3dnJIUzIrdzB2MUoxdHgrTEFiU0lmWmJCV0xXQ21EODUzRlVoWlFZekkvYXpFM28vd0p1CmlPUklMdUpNUk5vNlBXY3VLZmRFVkhaS1RTWnk3a25FcHNidGtsN3EwRE91eUFWdG9HVnlkb3VUR0FOdFhXK2YKczNUSTJjKzErZXg3L2RZOEJGQTFzNWFUOG5vZnU3T1RTTzdiS1kzSkRBUHZOeFQzKzVZUXJwNGR1Nmh0YUFMbAppOHNaRkhidmxpd2EzdlhxL3p1Y2JEaHEzQzBhZnAzV2ZwRGxwSlpvLy9QUUFKaTZLQT09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K"},
|
|
||||||
}
|
|
||||||
otherJWTAuthenticatorSpec := &auth1alpha1.JWTAuthenticatorSpec{
|
|
||||||
Issuer: "https://some-other-issuer.com",
|
|
||||||
Audience: "some-audience",
|
|
||||||
TLS: &auth1alpha1.TLSSpec{CertificateAuthorityData: "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURVVENDQWptZ0F3SUJBZ0lWQUpzNStTbVRtaTJXeUI0bGJJRXBXaUs5a1RkUE1BMEdDU3FHU0liM0RRRUIKQ3dVQU1COHhDekFKQmdOVkJBWVRBbFZUTVJBd0RnWURWUVFLREFkUWFYWnZkR0ZzTUI0WERUSXdNRFV3TkRFMgpNamMxT0ZvWERUSTBNRFV3TlRFMk1qYzFPRm93SHpFTE1Ba0dBMVVFQmhNQ1ZWTXhFREFPQmdOVkJBb01CMUJwCmRtOTBZV3d3Z2dFaU1BMEdDU3FHU0liM0RRRUJBUVVBQTRJQkR3QXdnZ0VLQW9JQkFRRERZWmZvWGR4Z2NXTEMKZEJtbHB5a0tBaG9JMlBuUWtsVFNXMno1cGcwaXJjOGFRL1E3MXZzMTRZYStmdWtFTGlvOTRZYWw4R01DdVFrbApMZ3AvUEE5N1VYelhQNDBpK25iNXcwRGpwWWd2dU9KQXJXMno2MFRnWE5NSFh3VHk4ME1SZEhpUFVWZ0VZd0JpCmtkNThzdEFVS1Y1MnBQTU1reTJjNy9BcFhJNmRXR2xjalUvaFBsNmtpRzZ5dEw2REtGYjJQRWV3MmdJM3pHZ2IKOFVVbnA1V05DZDd2WjNVY0ZHNXlsZEd3aGc3cnZ4U1ZLWi9WOEhCMGJmbjlxamlrSVcxWFM4dzdpUUNlQmdQMApYZWhKZmVITlZJaTJtZlczNlVQbWpMdnVKaGpqNDIrdFBQWndvdDkzdWtlcEgvbWpHcFJEVm9wamJyWGlpTUYrCkYxdnlPNGMxQWdNQkFBR2pnWU13Z1lBd0hRWURWUjBPQkJZRUZNTWJpSXFhdVkwajRVWWphWDl0bDJzby9LQ1IKTUI4R0ExVWRJd1FZTUJhQUZNTWJpSXFhdVkwajRVWWphWDl0bDJzby9LQ1JNQjBHQTFVZEpRUVdNQlFHQ0NzRwpBUVVGQndNQ0JnZ3JCZ0VGQlFjREFUQVBCZ05WSFJNQkFmOEVCVEFEQVFIL01BNEdBMVVkRHdFQi93UUVBd0lCCkJqQU5CZ2txaGtpRzl3MEJBUXNGQUFPQ0FRRUFYbEh4M2tIMDZwY2NDTDlEVE5qTnBCYnlVSytGd2R6T2IwWFYKcmpNaGtxdHVmdEpUUnR5T3hKZ0ZKNXhUR3pCdEtKamcrVU1pczBOV0t0VDBNWThVMU45U2c5SDl0RFpHRHBjVQpxMlVRU0Y4dXRQMVR3dnJIUzIrdzB2MUoxdHgrTEFiU0lmWmJCV0xXQ21EODUzRlVoWlFZekkvYXpFM28vd0p1CmlPUklMdUpNUk5vNlBXY3VLZmRFVkhaS1RTWnk3a25FcHNidGtsN3EwRE91eUFWdG9HVnlkb3VUR0FOdFhXK2YKczNUSTJjKzErZXg3L2RZOEJGQTFzNWFUOG5vZnU3T1RTTzdiS1kzSkRBUHZOeFQzKzVZUXJwNGR1Nmh0YUFMbAppOHNaRkhidmxpd2EzdlhxL3p1Y2JEaHEzQzBhZnAzV2ZwRGxwSlpvLy9QUUFKaTZLQT09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K"},
|
|
||||||
}
|
|
||||||
missingTLSJWTAuthenticatorSpec := &auth1alpha1.JWTAuthenticatorSpec{
|
|
||||||
Issuer: "https://some-issuer.com",
|
|
||||||
Audience: "some-audience",
|
|
||||||
}
|
|
||||||
invalidTLSJWTAuthenticatorSpec := &auth1alpha1.JWTAuthenticatorSpec{
|
|
||||||
Issuer: "https://some-other-issuer.com",
|
|
||||||
Audience: "some-audience",
|
|
||||||
TLS: &auth1alpha1.TLSSpec{CertificateAuthorityData: "invalid base64-encoded data"},
|
|
||||||
}
|
|
||||||
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
cache func(*testing.T, *authncache.Cache, bool)
|
|
||||||
wantClose bool
|
|
||||||
syncKey controllerlib.Key
|
|
||||||
jwtAuthenticators []runtime.Object
|
|
||||||
wantErr string
|
|
||||||
wantLogs []string
|
|
||||||
wantCacheEntries int
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "not found",
|
|
||||||
syncKey: controllerlib.Key{Namespace: "test-namespace", Name: "test-name"},
|
|
||||||
wantLogs: []string{
|
|
||||||
`jwtcachefiller-controller "level"=0 "msg"="Sync() found that the JWTAuthenticator does not exist yet or was deleted"`,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "valid jwt authenticator with CA",
|
|
||||||
syncKey: controllerlib.Key{Namespace: "test-namespace", Name: "test-name"},
|
|
||||||
jwtAuthenticators: []runtime.Object{
|
|
||||||
&auth1alpha1.JWTAuthenticator{
|
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
|
||||||
Namespace: "test-namespace",
|
|
||||||
Name: "test-name",
|
|
||||||
},
|
|
||||||
Spec: *someJWTAuthenticatorSpec,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
wantLogs: []string{
|
|
||||||
`jwtcachefiller-controller "level"=0 "msg"="added new jwt authenticator" "issuer"="https://some-issuer.com" "jwtAuthenticator"={"name":"test-name","namespace":"test-namespace"}`,
|
|
||||||
},
|
|
||||||
wantCacheEntries: 1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "updating jwt authenticator with new fields closes previous instance",
|
|
||||||
cache: func(t *testing.T, cache *authncache.Cache, wantClose bool) {
|
|
||||||
cache.Store(
|
|
||||||
authncache.Key{
|
|
||||||
Name: "test-name",
|
|
||||||
Namespace: "test-namespace",
|
|
||||||
Kind: "JWTAuthenticator",
|
|
||||||
APIGroup: auth1alpha1.SchemeGroupVersion.Group,
|
|
||||||
},
|
|
||||||
newCacheValue(t, *otherJWTAuthenticatorSpec, wantClose),
|
|
||||||
)
|
|
||||||
},
|
|
||||||
wantClose: true,
|
|
||||||
syncKey: controllerlib.Key{Namespace: "test-namespace", Name: "test-name"},
|
|
||||||
jwtAuthenticators: []runtime.Object{
|
|
||||||
&auth1alpha1.JWTAuthenticator{
|
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
|
||||||
Namespace: "test-namespace",
|
|
||||||
Name: "test-name",
|
|
||||||
},
|
|
||||||
Spec: *someJWTAuthenticatorSpec,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
wantLogs: []string{
|
|
||||||
`jwtcachefiller-controller "level"=0 "msg"="added new jwt authenticator" "issuer"="https://some-issuer.com" "jwtAuthenticator"={"name":"test-name","namespace":"test-namespace"}`,
|
|
||||||
},
|
|
||||||
wantCacheEntries: 1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "updating jwt authenticator with the same value does nothing",
|
|
||||||
cache: func(t *testing.T, cache *authncache.Cache, wantClose bool) {
|
|
||||||
cache.Store(
|
|
||||||
authncache.Key{
|
|
||||||
Name: "test-name",
|
|
||||||
Namespace: "test-namespace",
|
|
||||||
Kind: "JWTAuthenticator",
|
|
||||||
APIGroup: auth1alpha1.SchemeGroupVersion.Group,
|
|
||||||
},
|
|
||||||
newCacheValue(t, *someJWTAuthenticatorSpec, wantClose),
|
|
||||||
)
|
|
||||||
},
|
|
||||||
wantClose: false,
|
|
||||||
syncKey: controllerlib.Key{Namespace: "test-namespace", Name: "test-name"},
|
|
||||||
jwtAuthenticators: []runtime.Object{
|
|
||||||
&auth1alpha1.JWTAuthenticator{
|
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
|
||||||
Namespace: "test-namespace",
|
|
||||||
Name: "test-name",
|
|
||||||
},
|
|
||||||
Spec: *someJWTAuthenticatorSpec,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
wantLogs: []string{
|
|
||||||
`jwtcachefiller-controller "level"=0 "msg"="actual jwt authenticator and desired jwt authenticator are the same" "issuer"="https://some-issuer.com" "jwtAuthenticator"={"name":"test-name","namespace":"test-namespace"}`,
|
|
||||||
},
|
|
||||||
wantCacheEntries: 1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "updating jwt authenticator when cache value is wrong type",
|
|
||||||
cache: func(t *testing.T, cache *authncache.Cache, wantClose bool) {
|
|
||||||
cache.Store(
|
|
||||||
authncache.Key{
|
|
||||||
Name: "test-name",
|
|
||||||
Namespace: "test-namespace",
|
|
||||||
Kind: "JWTAuthenticator",
|
|
||||||
APIGroup: auth1alpha1.SchemeGroupVersion.Group,
|
|
||||||
},
|
|
||||||
struct{ authenticator.Token }{},
|
|
||||||
)
|
|
||||||
},
|
|
||||||
syncKey: controllerlib.Key{Namespace: "test-namespace", Name: "test-name"},
|
|
||||||
jwtAuthenticators: []runtime.Object{
|
|
||||||
&auth1alpha1.JWTAuthenticator{
|
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
|
||||||
Namespace: "test-namespace",
|
|
||||||
Name: "test-name",
|
|
||||||
},
|
|
||||||
Spec: *someJWTAuthenticatorSpec,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
wantLogs: []string{
|
|
||||||
`jwtcachefiller-controller "level"=0 "msg"="wrong JWT authenticator type in cache" "actualType"="struct { authenticator.Token }"`,
|
|
||||||
`jwtcachefiller-controller "level"=0 "msg"="added new jwt authenticator" "issuer"="https://some-issuer.com" "jwtAuthenticator"={"name":"test-name","namespace":"test-namespace"}`,
|
|
||||||
},
|
|
||||||
wantCacheEntries: 1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "valid jwt authenticator without CA",
|
|
||||||
syncKey: controllerlib.Key{Namespace: "test-namespace", Name: "test-name"},
|
|
||||||
jwtAuthenticators: []runtime.Object{
|
|
||||||
&auth1alpha1.JWTAuthenticator{
|
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
|
||||||
Namespace: "test-namespace",
|
|
||||||
Name: "test-name",
|
|
||||||
},
|
|
||||||
Spec: *missingTLSJWTAuthenticatorSpec,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
wantLogs: []string{
|
|
||||||
`jwtcachefiller-controller "level"=0 "msg"="added new jwt authenticator" "issuer"="https://some-issuer.com" "jwtAuthenticator"={"name":"test-name","namespace":"test-namespace"}`,
|
|
||||||
},
|
|
||||||
wantCacheEntries: 1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "invalid jwt authenticator CA",
|
|
||||||
syncKey: controllerlib.Key{Namespace: "test-namespace", Name: "test-name"},
|
|
||||||
jwtAuthenticators: []runtime.Object{
|
|
||||||
&auth1alpha1.JWTAuthenticator{
|
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
|
||||||
Namespace: "test-namespace",
|
|
||||||
Name: "test-name",
|
|
||||||
},
|
|
||||||
Spec: *invalidTLSJWTAuthenticatorSpec,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
wantErr: "failed to build jwt authenticator: invalid TLS configuration: illegal base64 data at input byte 7",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
tt := tt
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
fakeClient := pinnipedfake.NewSimpleClientset(tt.jwtAuthenticators...)
|
|
||||||
informers := pinnipedinformers.NewSharedInformerFactory(fakeClient, 0)
|
|
||||||
cache := authncache.New()
|
|
||||||
testLog := testlogger.New(t)
|
|
||||||
|
|
||||||
if tt.cache != nil {
|
|
||||||
tt.cache(t, cache, tt.wantClose)
|
|
||||||
}
|
|
||||||
|
|
||||||
controller := New(cache, informers.Authentication().V1alpha1().JWTAuthenticators(), testLog)
|
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
informers.Start(ctx.Done())
|
|
||||||
controllerlib.TestRunSynchronously(t, controller)
|
|
||||||
|
|
||||||
syncCtx := controllerlib.Context{Context: ctx, Key: tt.syncKey}
|
|
||||||
|
|
||||||
if err := controllerlib.TestSync(t, controller, syncCtx); tt.wantErr != "" {
|
|
||||||
require.EqualError(t, err, tt.wantErr)
|
|
||||||
} else {
|
|
||||||
require.NoError(t, err)
|
|
||||||
}
|
|
||||||
require.Equal(t, tt.wantLogs, testLog.Lines())
|
|
||||||
require.Equal(t, tt.wantCacheEntries, len(cache.Keys()))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNewJWTAuthenticator(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
goodSubject = "some-subject"
|
|
||||||
goodAudience = "some-audience"
|
|
||||||
group0 = "some-group-0"
|
|
||||||
group1 = "some-group-1"
|
|
||||||
|
|
||||||
goodECSigningKeyID = "some-ec-key-id"
|
goodECSigningKeyID = "some-ec-key-id"
|
||||||
goodRSASigningKeyID = "some-rsa-key-id"
|
goodRSASigningKeyID = "some-rsa-key-id"
|
||||||
|
goodAudience = "some-audience"
|
||||||
)
|
)
|
||||||
|
|
||||||
goodECSigningKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
goodECSigningKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||||
@ -299,27 +84,317 @@ func TestNewJWTAuthenticator(t *testing.T) {
|
|||||||
}))
|
}))
|
||||||
|
|
||||||
goodIssuer := server.URL
|
goodIssuer := server.URL
|
||||||
a, err := newJWTAuthenticator(&auth1alpha1.JWTAuthenticatorSpec{
|
|
||||||
|
someJWTAuthenticatorSpec := &auth1alpha1.JWTAuthenticatorSpec{
|
||||||
Issuer: goodIssuer,
|
Issuer: goodIssuer,
|
||||||
Audience: goodAudience,
|
Audience: goodAudience,
|
||||||
TLS: tlsSpecFromTLSConfig(server.TLS),
|
TLS: tlsSpecFromTLSConfig(server.TLS),
|
||||||
})
|
|
||||||
require.NoError(t, err)
|
|
||||||
t.Cleanup(a.Close)
|
|
||||||
|
|
||||||
// The implementation of AuthenticateToken() that we use waits 10 seconds after creation to
|
|
||||||
// perform OIDC discovery. Therefore, the JWTAuthenticator is not functional for the first 10
|
|
||||||
// seconds. We sleep for 13 seconds in this unit test to give a little bit of cushion to that 10
|
|
||||||
// second delay.
|
|
||||||
//
|
|
||||||
// We should get rid of this 10 second delay. See
|
|
||||||
// https://github.com/vmware-tanzu/pinniped/issues/260.
|
|
||||||
if testing.Short() {
|
|
||||||
t.Skip("skipping this test when '-short' flag is passed to avoid necessary 13 second sleep")
|
|
||||||
}
|
}
|
||||||
time.Sleep(time.Second * 13)
|
otherJWTAuthenticatorSpec := &auth1alpha1.JWTAuthenticatorSpec{
|
||||||
|
Issuer: "https://some-other-issuer.com",
|
||||||
|
Audience: goodAudience,
|
||||||
|
TLS: &auth1alpha1.TLSSpec{CertificateAuthorityData: "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURVVENDQWptZ0F3SUJBZ0lWQUpzNStTbVRtaTJXeUI0bGJJRXBXaUs5a1RkUE1BMEdDU3FHU0liM0RRRUIKQ3dVQU1COHhDekFKQmdOVkJBWVRBbFZUTVJBd0RnWURWUVFLREFkUWFYWnZkR0ZzTUI0WERUSXdNRFV3TkRFMgpNamMxT0ZvWERUSTBNRFV3TlRFMk1qYzFPRm93SHpFTE1Ba0dBMVVFQmhNQ1ZWTXhFREFPQmdOVkJBb01CMUJwCmRtOTBZV3d3Z2dFaU1BMEdDU3FHU0liM0RRRUJBUVVBQTRJQkR3QXdnZ0VLQW9JQkFRRERZWmZvWGR4Z2NXTEMKZEJtbHB5a0tBaG9JMlBuUWtsVFNXMno1cGcwaXJjOGFRL1E3MXZzMTRZYStmdWtFTGlvOTRZYWw4R01DdVFrbApMZ3AvUEE5N1VYelhQNDBpK25iNXcwRGpwWWd2dU9KQXJXMno2MFRnWE5NSFh3VHk4ME1SZEhpUFVWZ0VZd0JpCmtkNThzdEFVS1Y1MnBQTU1reTJjNy9BcFhJNmRXR2xjalUvaFBsNmtpRzZ5dEw2REtGYjJQRWV3MmdJM3pHZ2IKOFVVbnA1V05DZDd2WjNVY0ZHNXlsZEd3aGc3cnZ4U1ZLWi9WOEhCMGJmbjlxamlrSVcxWFM4dzdpUUNlQmdQMApYZWhKZmVITlZJaTJtZlczNlVQbWpMdnVKaGpqNDIrdFBQWndvdDkzdWtlcEgvbWpHcFJEVm9wamJyWGlpTUYrCkYxdnlPNGMxQWdNQkFBR2pnWU13Z1lBd0hRWURWUjBPQkJZRUZNTWJpSXFhdVkwajRVWWphWDl0bDJzby9LQ1IKTUI4R0ExVWRJd1FZTUJhQUZNTWJpSXFhdVkwajRVWWphWDl0bDJzby9LQ1JNQjBHQTFVZEpRUVdNQlFHQ0NzRwpBUVVGQndNQ0JnZ3JCZ0VGQlFjREFUQVBCZ05WSFJNQkFmOEVCVEFEQVFIL01BNEdBMVVkRHdFQi93UUVBd0lCCkJqQU5CZ2txaGtpRzl3MEJBUXNGQUFPQ0FRRUFYbEh4M2tIMDZwY2NDTDlEVE5qTnBCYnlVSytGd2R6T2IwWFYKcmpNaGtxdHVmdEpUUnR5T3hKZ0ZKNXhUR3pCdEtKamcrVU1pczBOV0t0VDBNWThVMU45U2c5SDl0RFpHRHBjVQpxMlVRU0Y4dXRQMVR3dnJIUzIrdzB2MUoxdHgrTEFiU0lmWmJCV0xXQ21EODUzRlVoWlFZekkvYXpFM28vd0p1CmlPUklMdUpNUk5vNlBXY3VLZmRFVkhaS1RTWnk3a25FcHNidGtsN3EwRE91eUFWdG9HVnlkb3VUR0FOdFhXK2YKczNUSTJjKzErZXg3L2RZOEJGQTFzNWFUOG5vZnU3T1RTTzdiS1kzSkRBUHZOeFQzKzVZUXJwNGR1Nmh0YUFMbAppOHNaRkhidmxpd2EzdlhxL3p1Y2JEaHEzQzBhZnAzV2ZwRGxwSlpvLy9QUUFKaTZLQT09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K"},
|
||||||
|
}
|
||||||
|
missingTLSJWTAuthenticatorSpec := &auth1alpha1.JWTAuthenticatorSpec{
|
||||||
|
Issuer: goodIssuer,
|
||||||
|
Audience: goodAudience,
|
||||||
|
}
|
||||||
|
invalidTLSJWTAuthenticatorSpec := &auth1alpha1.JWTAuthenticatorSpec{
|
||||||
|
Issuer: "https://some-other-issuer.com",
|
||||||
|
Audience: goodAudience,
|
||||||
|
TLS: &auth1alpha1.TLSSpec{CertificateAuthorityData: "invalid base64-encoded data"},
|
||||||
|
}
|
||||||
|
|
||||||
var tests = []struct {
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
cache func(*testing.T, *authncache.Cache, bool)
|
||||||
|
syncKey controllerlib.Key
|
||||||
|
jwtAuthenticators []runtime.Object
|
||||||
|
wantClose bool
|
||||||
|
wantErr string
|
||||||
|
wantLogs []string
|
||||||
|
wantCacheEntries int
|
||||||
|
runTestsOnResultingAuthenticator bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "not found",
|
||||||
|
syncKey: controllerlib.Key{Namespace: "test-namespace", Name: "test-name"},
|
||||||
|
wantLogs: []string{
|
||||||
|
`jwtcachefiller-controller "level"=0 "msg"="Sync() found that the JWTAuthenticator does not exist yet or was deleted"`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "valid jwt authenticator with CA",
|
||||||
|
syncKey: controllerlib.Key{Namespace: "test-namespace", Name: "test-name"},
|
||||||
|
jwtAuthenticators: []runtime.Object{
|
||||||
|
&auth1alpha1.JWTAuthenticator{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Namespace: "test-namespace",
|
||||||
|
Name: "test-name",
|
||||||
|
},
|
||||||
|
Spec: *someJWTAuthenticatorSpec,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantLogs: []string{
|
||||||
|
`jwtcachefiller-controller "level"=0 "msg"="added new jwt authenticator" "issuer"="` + goodIssuer + `" "jwtAuthenticator"={"name":"test-name","namespace":"test-namespace"}`,
|
||||||
|
},
|
||||||
|
wantCacheEntries: 1,
|
||||||
|
runTestsOnResultingAuthenticator: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "updating jwt authenticator with new fields closes previous instance",
|
||||||
|
cache: func(t *testing.T, cache *authncache.Cache, wantClose bool) {
|
||||||
|
cache.Store(
|
||||||
|
authncache.Key{
|
||||||
|
Name: "test-name",
|
||||||
|
Namespace: "test-namespace",
|
||||||
|
Kind: "JWTAuthenticator",
|
||||||
|
APIGroup: auth1alpha1.SchemeGroupVersion.Group,
|
||||||
|
},
|
||||||
|
newCacheValue(t, *otherJWTAuthenticatorSpec, wantClose),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
wantClose: true,
|
||||||
|
syncKey: controllerlib.Key{Namespace: "test-namespace", Name: "test-name"},
|
||||||
|
jwtAuthenticators: []runtime.Object{
|
||||||
|
&auth1alpha1.JWTAuthenticator{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Namespace: "test-namespace",
|
||||||
|
Name: "test-name",
|
||||||
|
},
|
||||||
|
Spec: *someJWTAuthenticatorSpec,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantLogs: []string{
|
||||||
|
`jwtcachefiller-controller "level"=0 "msg"="added new jwt authenticator" "issuer"="` + goodIssuer + `" "jwtAuthenticator"={"name":"test-name","namespace":"test-namespace"}`,
|
||||||
|
},
|
||||||
|
wantCacheEntries: 1,
|
||||||
|
runTestsOnResultingAuthenticator: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "updating jwt authenticator with the same value does nothing",
|
||||||
|
cache: func(t *testing.T, cache *authncache.Cache, wantClose bool) {
|
||||||
|
cache.Store(
|
||||||
|
authncache.Key{
|
||||||
|
Name: "test-name",
|
||||||
|
Namespace: "test-namespace",
|
||||||
|
Kind: "JWTAuthenticator",
|
||||||
|
APIGroup: auth1alpha1.SchemeGroupVersion.Group,
|
||||||
|
},
|
||||||
|
newCacheValue(t, *someJWTAuthenticatorSpec, wantClose),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
wantClose: false,
|
||||||
|
syncKey: controllerlib.Key{Namespace: "test-namespace", Name: "test-name"},
|
||||||
|
jwtAuthenticators: []runtime.Object{
|
||||||
|
&auth1alpha1.JWTAuthenticator{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Namespace: "test-namespace",
|
||||||
|
Name: "test-name",
|
||||||
|
},
|
||||||
|
Spec: *someJWTAuthenticatorSpec,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantLogs: []string{
|
||||||
|
`jwtcachefiller-controller "level"=0 "msg"="actual jwt authenticator and desired jwt authenticator are the same" "issuer"="` + goodIssuer + `" "jwtAuthenticator"={"name":"test-name","namespace":"test-namespace"}`,
|
||||||
|
},
|
||||||
|
wantCacheEntries: 1,
|
||||||
|
runTestsOnResultingAuthenticator: false, // skip the tests because the authenticator left in the cache is the mock version that was added above
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "updating jwt authenticator when cache value is wrong type",
|
||||||
|
cache: func(t *testing.T, cache *authncache.Cache, wantClose bool) {
|
||||||
|
cache.Store(
|
||||||
|
authncache.Key{
|
||||||
|
Name: "test-name",
|
||||||
|
Namespace: "test-namespace",
|
||||||
|
Kind: "JWTAuthenticator",
|
||||||
|
APIGroup: auth1alpha1.SchemeGroupVersion.Group,
|
||||||
|
},
|
||||||
|
struct{ authenticator.Token }{},
|
||||||
|
)
|
||||||
|
},
|
||||||
|
syncKey: controllerlib.Key{Namespace: "test-namespace", Name: "test-name"},
|
||||||
|
jwtAuthenticators: []runtime.Object{
|
||||||
|
&auth1alpha1.JWTAuthenticator{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Namespace: "test-namespace",
|
||||||
|
Name: "test-name",
|
||||||
|
},
|
||||||
|
Spec: *someJWTAuthenticatorSpec,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantLogs: []string{
|
||||||
|
`jwtcachefiller-controller "level"=0 "msg"="wrong JWT authenticator type in cache" "actualType"="struct { authenticator.Token }"`,
|
||||||
|
`jwtcachefiller-controller "level"=0 "msg"="added new jwt authenticator" "issuer"="` + goodIssuer + `" "jwtAuthenticator"={"name":"test-name","namespace":"test-namespace"}`,
|
||||||
|
},
|
||||||
|
wantCacheEntries: 1,
|
||||||
|
runTestsOnResultingAuthenticator: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "valid jwt authenticator without CA",
|
||||||
|
syncKey: controllerlib.Key{Namespace: "test-namespace", Name: "test-name"},
|
||||||
|
jwtAuthenticators: []runtime.Object{
|
||||||
|
&auth1alpha1.JWTAuthenticator{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Namespace: "test-namespace",
|
||||||
|
Name: "test-name",
|
||||||
|
},
|
||||||
|
Spec: *missingTLSJWTAuthenticatorSpec,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantLogs: []string{
|
||||||
|
`jwtcachefiller-controller "level"=0 "msg"="added new jwt authenticator" "issuer"="` + goodIssuer + `" "jwtAuthenticator"={"name":"test-name","namespace":"test-namespace"}`,
|
||||||
|
},
|
||||||
|
wantCacheEntries: 1,
|
||||||
|
runTestsOnResultingAuthenticator: false, // skip the tests because the authenticator left in the cache doesn't have the CA for our test discovery server
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid jwt authenticator CA",
|
||||||
|
syncKey: controllerlib.Key{Namespace: "test-namespace", Name: "test-name"},
|
||||||
|
jwtAuthenticators: []runtime.Object{
|
||||||
|
&auth1alpha1.JWTAuthenticator{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Namespace: "test-namespace",
|
||||||
|
Name: "test-name",
|
||||||
|
},
|
||||||
|
Spec: *invalidTLSJWTAuthenticatorSpec,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantErr: "failed to build jwt authenticator: invalid TLS configuration: illegal base64 data at input byte 7",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
tt := tt
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
fakeClient := pinnipedfake.NewSimpleClientset(tt.jwtAuthenticators...)
|
||||||
|
informers := pinnipedinformers.NewSharedInformerFactory(fakeClient, 0)
|
||||||
|
cache := authncache.New()
|
||||||
|
testLog := testlogger.New(t)
|
||||||
|
|
||||||
|
if tt.cache != nil {
|
||||||
|
tt.cache(t, cache, tt.wantClose)
|
||||||
|
}
|
||||||
|
|
||||||
|
controller := New(cache, informers.Authentication().V1alpha1().JWTAuthenticators(), testLog)
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
informers.Start(ctx.Done())
|
||||||
|
controllerlib.TestRunSynchronously(t, controller)
|
||||||
|
|
||||||
|
syncCtx := controllerlib.Context{Context: ctx, Key: tt.syncKey}
|
||||||
|
|
||||||
|
if err := controllerlib.TestSync(t, controller, syncCtx); tt.wantErr != "" {
|
||||||
|
require.EqualError(t, err, tt.wantErr)
|
||||||
|
} else {
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
require.Equal(t, tt.wantLogs, testLog.Lines())
|
||||||
|
require.Equal(t, tt.wantCacheEntries, len(cache.Keys()))
|
||||||
|
|
||||||
|
if !tt.runTestsOnResultingAuthenticator {
|
||||||
|
return // end of test unless we wanted to run tests on the resulting authenticator from the cache
|
||||||
|
}
|
||||||
|
|
||||||
|
// The implementation of AuthenticateToken() that we use waits 10 seconds after creation to
|
||||||
|
// perform OIDC discovery. Therefore, the JWTAuthenticator is not functional for the first 10
|
||||||
|
// seconds. We sleep for 13 seconds in this unit test to give a little bit of cushion to that 10
|
||||||
|
// second delay.
|
||||||
|
//
|
||||||
|
// We should get rid of this 10 second delay. See
|
||||||
|
// https://github.com/vmware-tanzu/pinniped/issues/260.
|
||||||
|
time.Sleep(time.Second * 13)
|
||||||
|
|
||||||
|
// We expected the cache to have an entry, so pull that entry from the cache and test it.
|
||||||
|
expectedCacheKey := authncache.Key{
|
||||||
|
APIGroup: auth1alpha1.GroupName,
|
||||||
|
Kind: "JWTAuthenticator",
|
||||||
|
Namespace: syncCtx.Key.Namespace,
|
||||||
|
Name: syncCtx.Key.Name,
|
||||||
|
}
|
||||||
|
cachedAuthenticator := cache.Get(expectedCacheKey)
|
||||||
|
require.NotNil(t, cachedAuthenticator)
|
||||||
|
|
||||||
|
// Schedule it to be closed at the end of the test.
|
||||||
|
t.Cleanup(cachedAuthenticator.(*jwtAuthenticator).Close)
|
||||||
|
|
||||||
|
const (
|
||||||
|
goodSubject = "some-subject"
|
||||||
|
group0 = "some-group-0"
|
||||||
|
group1 = "some-group-1"
|
||||||
|
)
|
||||||
|
|
||||||
|
for _, test := range testTableForAuthenticateTokenTests(
|
||||||
|
t,
|
||||||
|
goodSubject,
|
||||||
|
goodRSASigningKey,
|
||||||
|
goodRSASigningAlgo,
|
||||||
|
goodRSASigningKeyID,
|
||||||
|
group0,
|
||||||
|
group1,
|
||||||
|
) {
|
||||||
|
test := test
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
wellKnownClaims := jwt.Claims{
|
||||||
|
Issuer: goodIssuer,
|
||||||
|
Subject: goodSubject,
|
||||||
|
Audience: []string{goodAudience},
|
||||||
|
Expiry: jwt.NewNumericDate(time.Now().Add(time.Hour)),
|
||||||
|
NotBefore: jwt.NewNumericDate(time.Now().Add(-time.Hour)),
|
||||||
|
IssuedAt: jwt.NewNumericDate(time.Now().Add(-time.Hour)),
|
||||||
|
}
|
||||||
|
var groups interface{}
|
||||||
|
if test.jwtClaims != nil {
|
||||||
|
test.jwtClaims(&wellKnownClaims, &groups)
|
||||||
|
}
|
||||||
|
|
||||||
|
var signingKey interface{} = goodECSigningKey
|
||||||
|
signingAlgo := goodECSigningAlgo
|
||||||
|
signingKID := goodECSigningKeyID
|
||||||
|
if test.jwtSignature != nil {
|
||||||
|
test.jwtSignature(&signingKey, &signingAlgo, &signingKID)
|
||||||
|
}
|
||||||
|
|
||||||
|
jwt := createJWT(t, signingKey, signingAlgo, signingKID, &wellKnownClaims, groups)
|
||||||
|
rsp, authenticated, err := cachedAuthenticator.AuthenticateToken(context.Background(), jwt)
|
||||||
|
if test.wantErrorRegexp != "" {
|
||||||
|
require.Error(t, err)
|
||||||
|
require.Regexp(t, test.wantErrorRegexp, err.Error())
|
||||||
|
} else {
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, test.wantResponse, rsp)
|
||||||
|
require.Equal(t, test.wantAuthenticated, authenticated)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testTableForAuthenticateTokenTests(
|
||||||
|
t *testing.T,
|
||||||
|
goodSubject string,
|
||||||
|
goodRSASigningKey *rsa.PrivateKey,
|
||||||
|
goodRSASigningAlgo jose.SignatureAlgorithm,
|
||||||
|
goodRSASigningKeyID string,
|
||||||
|
group0 string,
|
||||||
|
group1 string,
|
||||||
|
) []struct {
|
||||||
|
name string
|
||||||
|
jwtClaims func(wellKnownClaims *jwt.Claims, groups *interface{})
|
||||||
|
jwtSignature func(key *interface{}, algo *jose.SignatureAlgorithm, kid *string)
|
||||||
|
wantResponse *authenticator.Response
|
||||||
|
wantAuthenticated bool
|
||||||
|
wantErrorRegexp string
|
||||||
|
} {
|
||||||
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
jwtClaims func(wellKnownClaims *jwt.Claims, groups *interface{})
|
jwtClaims func(wellKnownClaims *jwt.Claims, groups *interface{})
|
||||||
jwtSignature func(key *interface{}, algo *jose.SignatureAlgorithm, kid *string)
|
jwtSignature func(key *interface{}, algo *jose.SignatureAlgorithm, kid *string)
|
||||||
@ -429,7 +504,7 @@ func TestNewJWTAuthenticator(t *testing.T) {
|
|||||||
jwtClaims: func(claims *jwt.Claims, _ *interface{}) {
|
jwtClaims: func(claims *jwt.Claims, _ *interface{}) {
|
||||||
claims.Expiry = jwt.NewNumericDate(time.Date(1, 2, 3, 4, 5, 6, 7, time.UTC))
|
claims.Expiry = jwt.NewNumericDate(time.Date(1, 2, 3, 4, 5, 6, 7, time.UTC))
|
||||||
},
|
},
|
||||||
wantErrorRegexp: `oidc: verify token: oidc: token is expired \(Token Expiry: 0001-02-02 23:09:04 -0456 LMT\)`,
|
wantErrorRegexp: `oidc: verify token: oidc: token is expired \(Token Expiry: 0001-02-02 20:12:08 -0752 LMT\)`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "bad token without exp",
|
name: "bad token without exp",
|
||||||
@ -459,43 +534,8 @@ func TestNewJWTAuthenticator(t *testing.T) {
|
|||||||
wantErrorRegexp: `oidc: verify token: oidc: id token signed with unsupported algorithm, expected \["RS256" "ES256"\] got "ES384"`,
|
wantErrorRegexp: `oidc: verify token: oidc: id token signed with unsupported algorithm, expected \["RS256" "ES256"\] got "ES384"`,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, test := range tests {
|
|
||||||
test := test
|
|
||||||
t.Run(test.name, func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
wellKnownClaims := jwt.Claims{
|
return tests
|
||||||
Issuer: goodIssuer,
|
|
||||||
Subject: goodSubject,
|
|
||||||
Audience: []string{goodAudience},
|
|
||||||
Expiry: jwt.NewNumericDate(time.Now().Add(time.Hour)),
|
|
||||||
NotBefore: jwt.NewNumericDate(time.Now().Add(-time.Hour)),
|
|
||||||
IssuedAt: jwt.NewNumericDate(time.Now().Add(-time.Hour)),
|
|
||||||
}
|
|
||||||
var groups interface{}
|
|
||||||
if test.jwtClaims != nil {
|
|
||||||
test.jwtClaims(&wellKnownClaims, &groups)
|
|
||||||
}
|
|
||||||
|
|
||||||
var signingKey interface{} = goodECSigningKey
|
|
||||||
signingAlgo := goodECSigningAlgo
|
|
||||||
signingKID := goodECSigningKeyID
|
|
||||||
if test.jwtSignature != nil {
|
|
||||||
test.jwtSignature(&signingKey, &signingAlgo, &signingKID)
|
|
||||||
}
|
|
||||||
|
|
||||||
jwt := createJWT(t, signingKey, signingAlgo, signingKID, &wellKnownClaims, groups)
|
|
||||||
rsp, authenticated, err := a.AuthenticateToken(context.Background(), jwt)
|
|
||||||
if test.wantErrorRegexp != "" {
|
|
||||||
require.Error(t, err)
|
|
||||||
require.Regexp(t, test.wantErrorRegexp, err.Error())
|
|
||||||
} else {
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, test.wantResponse, rsp)
|
|
||||||
require.Equal(t, test.wantAuthenticated, authenticated)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func tlsSpecFromTLSConfig(tls *tls.Config) *auth1alpha1.TLSSpec {
|
func tlsSpecFromTLSConfig(tls *tls.Config) *auth1alpha1.TLSSpec {
|
||||||
|
Loading…
Reference in New Issue
Block a user