/* Copyright 2014 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package serviceaccount_test import ( "context" "reflect" "testing" "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apiserver/pkg/authentication/authenticator" clientset "k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes/fake" certutil "k8s.io/client-go/util/cert" serviceaccountcontroller "k8s.io/kubernetes/pkg/controller/serviceaccount" "k8s.io/kubernetes/pkg/serviceaccount" ) const otherPublicKey = `-----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArXz0QkIG1B5Bj2/W69GH rsm5e+RC3kE+VTgocge0atqlLBek35tRqLgUi3AcIrBZ/0YctMSWDVcRt5fkhWwe Lqjj6qvAyNyOkrkBi1NFDpJBjYJtuKHgRhNxXbOzTSNpdSKXTfOkzqv56MwHOP25 yP/NNAODUtr92D5ySI5QX8RbXW+uDn+ixul286PBW/BCrE4tuS88dA0tYJPf8LCu sqQOwlXYH/rNUg4Pyl9xxhR5DIJR0OzNNfChjw60zieRIt2LfM83fXhwk8IxRGkc gPZm7ZsipmfbZK2Tkhnpsa4QxDg7zHJPMsB5kxRXW0cQipXcC3baDyN9KBApNXa0 PwIDAQAB -----END PUBLIC KEY-----` const rsaPublicKey = `-----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA249XwEo9k4tM8fMxV7zx OhcrP+WvXn917koM5Qr2ZXs4vo26e4ytdlrV0bQ9SlcLpQVSYjIxNfhTZdDt+ecI zshKuv1gKIxbbLQMOuK1eA/4HALyEkFgmS/tleLJrhc65tKPMGD+pKQ/xhmzRuCG 51RoiMgbQxaCyYxGfNLpLAZK9L0Tctv9a0mJmGIYnIOQM4kC1A1I1n3EsXMWmeJU j7OTh/AjjCnMnkgvKT2tpKxYQ59PgDgU8Ssc7RDSmSkLxnrv+OrN80j6xrw0OjEi B4Ycr0PqfzZcvy8efTtFQ/Jnc4Bp1zUtFXt7+QeevePtQ2EcyELXE0i63T1CujRM WwIDAQAB -----END PUBLIC KEY----- ` const rsaPrivateKey = `-----BEGIN RSA PRIVATE KEY----- MIIEowIBAAKCAQEA249XwEo9k4tM8fMxV7zxOhcrP+WvXn917koM5Qr2ZXs4vo26 e4ytdlrV0bQ9SlcLpQVSYjIxNfhTZdDt+ecIzshKuv1gKIxbbLQMOuK1eA/4HALy EkFgmS/tleLJrhc65tKPMGD+pKQ/xhmzRuCG51RoiMgbQxaCyYxGfNLpLAZK9L0T ctv9a0mJmGIYnIOQM4kC1A1I1n3EsXMWmeJUj7OTh/AjjCnMnkgvKT2tpKxYQ59P gDgU8Ssc7RDSmSkLxnrv+OrN80j6xrw0OjEiB4Ycr0PqfzZcvy8efTtFQ/Jnc4Bp 1zUtFXt7+QeevePtQ2EcyELXE0i63T1CujRMWwIDAQABAoIBAHJx8GqyCBDNbqk7 e7/hI9iE1S10Wwol5GH2RWxqX28cYMKq+8aE2LI1vPiXO89xOgelk4DN6urX6xjK ZBF8RRIMQy/e/O2F4+3wl+Nl4vOXV1u6iVXMsD6JRg137mqJf1Fr9elg1bsaRofL Q7CxPoB8dhS+Qb+hj0DhlqhgA9zG345CQCAds0ZYAZe8fP7bkwrLqZpMn7Dz9WVm ++YgYYKjuE95kPuup/LtWfA9rJyE/Fws8/jGvRSpVn1XglMLSMKhLd27sE8ZUSV0 2KUzbfRGE0+AnRULRrjpYaPu0XQ2JjdNvtkjBnv27RB89W9Gklxq821eH1Y8got8 FZodjxECgYEA93pz7AQZ2xDs67d1XLCzpX84GxKzttirmyj3OIlxgzVHjEMsvw8v sjFiBU5xEEQDosrBdSknnlJqyiq1YwWG/WDckr13d8G2RQWoySN7JVmTQfXcLoTu YGRiiTuoEi3ab3ZqrgGrFgX7T/cHuasbYvzCvhM2b4VIR3aSxU2DTUMCgYEA4x7J T/ErP6GkU5nKstu/mIXwNzayEO1BJvPYsy7i7EsxTm3xe/b8/6cYOz5fvJLGH5mT Q8YvuLqBcMwZardrYcwokD55UvNLOyfADDFZ6l3WntIqbA640Ok2g1X4U8J09xIq ZLIWK1yWbbvi4QCeN5hvWq47e8sIj5QHjIIjRwkCgYEAyNqjltxFN9zmzPDa2d24 EAvOt3pYTYBQ1t9KtqImdL0bUqV6fZ6PsWoPCgt+DBuHb+prVPGP7Bkr/uTmznU/ +AlTO+12NsYLbr2HHagkXE31DEXE7CSLa8RNjN/UKtz4Ohq7vnowJvG35FCz/mb3 FUHbtHTXa2+bGBUOTf/5Hw0CgYBxw0r9EwUhw1qnUYJ5op7OzFAtp+T7m4ul8kCa SCL8TxGsgl+SQ34opE775dtYfoBk9a0RJqVit3D8yg71KFjOTNAIqHJm/Vyyjc+h i9rJDSXiuczsAVfLtPVMRfS0J9QkqeG4PIfkQmVLI/CZ2ZBmsqEcX+eFs4ZfPLun Qsxe2QKBgGuPilIbLeIBDIaPiUI0FwU8v2j8CEQBYvoQn34c95hVQsig/o5z7zlo UsO0wlTngXKlWdOcCs1kqEhTLrstf48djDxAYAxkw40nzeJOt7q52ib/fvf4/UBy X024wzbiw1q07jFCyfQmODzURAx1VNT7QVUMdz/N8vy47/H40AZJ -----END RSA PRIVATE KEY----- ` // openssl ecparam -name prime256v1 -genkey -noout -out ecdsa256.pem const ecdsaPrivateKey = `-----BEGIN EC PRIVATE KEY----- MHcCAQEEIEZmTmUhuanLjPA2CLquXivuwBDHTt5XYwgIr/kA1LtRoAoGCCqGSM49 AwEHoUQDQgAEH6cuzP8XuD5wal6wf9M6xDljTOPLX2i8uIp/C/ASqiIGUeeKQtX0 /IR3qCXyThP/dbCiHrF3v1cuhBOHY8CLVg== -----END EC PRIVATE KEY-----` // openssl ec -in ecdsa256.pem -pubout -out ecdsa256pub.pem const ecdsaPublicKey = `-----BEGIN PUBLIC KEY----- MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEH6cuzP8XuD5wal6wf9M6xDljTOPL X2i8uIp/C/ASqiIGUeeKQtX0/IR3qCXyThP/dbCiHrF3v1cuhBOHY8CLVg== -----END PUBLIC KEY-----` func getPrivateKey(data string) interface{} { key, _ := certutil.ParsePrivateKeyPEM([]byte(data)) return key } func getPublicKey(data string) interface{} { keys, _ := certutil.ParsePublicKeysPEM([]byte(data)) return keys[0] } func TestTokenGenerateAndValidate(t *testing.T) { expectedUserName := "system:serviceaccount:test:my-service-account" expectedUserUID := "12345" // Related API objects serviceAccount := &v1.ServiceAccount{ ObjectMeta: metav1.ObjectMeta{ Name: "my-service-account", UID: "12345", Namespace: "test", }, } rsaSecret := &v1.Secret{ ObjectMeta: metav1.ObjectMeta{ Name: "my-rsa-secret", Namespace: "test", }, } ecdsaSecret := &v1.Secret{ ObjectMeta: metav1.ObjectMeta{ Name: "my-ecdsa-secret", Namespace: "test", }, } // Generate the RSA token rsaGenerator, err := serviceaccount.JWTTokenGenerator(serviceaccount.LegacyIssuer, getPrivateKey(rsaPrivateKey)) if err != nil { t.Fatalf("error making generator: %v", err) } rsaToken, err := rsaGenerator.GenerateToken(serviceaccount.LegacyClaims(*serviceAccount, *rsaSecret)) if err != nil { t.Fatalf("error generating token: %v", err) } if len(rsaToken) == 0 { t.Fatalf("no token generated") } rsaSecret.Data = map[string][]byte{ "token": []byte(rsaToken), } // Generate the ECDSA token ecdsaGenerator, err := serviceaccount.JWTTokenGenerator(serviceaccount.LegacyIssuer, getPrivateKey(ecdsaPrivateKey)) if err != nil { t.Fatalf("error making generator: %v", err) } ecdsaToken, err := ecdsaGenerator.GenerateToken(serviceaccount.LegacyClaims(*serviceAccount, *ecdsaSecret)) if err != nil { t.Fatalf("error generating token: %v", err) } if len(ecdsaToken) == 0 { t.Fatalf("no token generated") } ecdsaSecret.Data = map[string][]byte{ "token": []byte(ecdsaToken), } // Generate signer with same keys as RSA signer but different issuer badIssuerGenerator, err := serviceaccount.JWTTokenGenerator("foo", getPrivateKey(rsaPrivateKey)) if err != nil { t.Fatalf("error making generator: %v", err) } badIssuerToken, err := badIssuerGenerator.GenerateToken(serviceaccount.LegacyClaims(*serviceAccount, *rsaSecret)) if err != nil { t.Fatalf("error generating token: %v", err) } testCases := map[string]struct { Client clientset.Interface Keys []interface{} Token string ExpectedErr bool ExpectedOK bool ExpectedUserName string ExpectedUserUID string ExpectedGroups []string }{ "no keys": { Token: rsaToken, Client: nil, Keys: []interface{}{}, ExpectedErr: false, ExpectedOK: false, }, "invalid keys (rsa)": { Token: rsaToken, Client: nil, Keys: []interface{}{getPublicKey(otherPublicKey), getPublicKey(ecdsaPublicKey)}, ExpectedErr: true, ExpectedOK: false, }, "invalid keys (ecdsa)": { Token: ecdsaToken, Client: nil, Keys: []interface{}{getPublicKey(otherPublicKey), getPublicKey(rsaPublicKey)}, ExpectedErr: true, ExpectedOK: false, }, "valid key (rsa)": { Token: rsaToken, Client: nil, Keys: []interface{}{getPublicKey(rsaPublicKey)}, ExpectedErr: false, ExpectedOK: true, ExpectedUserName: expectedUserName, ExpectedUserUID: expectedUserUID, ExpectedGroups: []string{"system:serviceaccounts", "system:serviceaccounts:test"}, }, "valid key, invalid issuer (rsa)": { Token: badIssuerToken, Client: nil, Keys: []interface{}{getPublicKey(rsaPublicKey)}, ExpectedErr: false, ExpectedOK: false, }, "valid key (ecdsa)": { Token: ecdsaToken, Client: nil, Keys: []interface{}{getPublicKey(ecdsaPublicKey)}, ExpectedErr: false, ExpectedOK: true, ExpectedUserName: expectedUserName, ExpectedUserUID: expectedUserUID, ExpectedGroups: []string{"system:serviceaccounts", "system:serviceaccounts:test"}, }, "rotated keys (rsa)": { Token: rsaToken, Client: nil, Keys: []interface{}{getPublicKey(otherPublicKey), getPublicKey(ecdsaPublicKey), getPublicKey(rsaPublicKey)}, ExpectedErr: false, ExpectedOK: true, ExpectedUserName: expectedUserName, ExpectedUserUID: expectedUserUID, ExpectedGroups: []string{"system:serviceaccounts", "system:serviceaccounts:test"}, }, "rotated keys (ecdsa)": { Token: ecdsaToken, Client: nil, Keys: []interface{}{getPublicKey(otherPublicKey), getPublicKey(rsaPublicKey), getPublicKey(ecdsaPublicKey)}, ExpectedErr: false, ExpectedOK: true, ExpectedUserName: expectedUserName, ExpectedUserUID: expectedUserUID, ExpectedGroups: []string{"system:serviceaccounts", "system:serviceaccounts:test"}, }, "valid lookup": { Token: rsaToken, Client: fake.NewSimpleClientset(serviceAccount, rsaSecret, ecdsaSecret), Keys: []interface{}{getPublicKey(rsaPublicKey)}, ExpectedErr: false, ExpectedOK: true, ExpectedUserName: expectedUserName, ExpectedUserUID: expectedUserUID, ExpectedGroups: []string{"system:serviceaccounts", "system:serviceaccounts:test"}, }, "invalid secret lookup": { Token: rsaToken, Client: fake.NewSimpleClientset(serviceAccount), Keys: []interface{}{getPublicKey(rsaPublicKey)}, ExpectedErr: true, ExpectedOK: false, }, "invalid serviceaccount lookup": { Token: rsaToken, Client: fake.NewSimpleClientset(rsaSecret, ecdsaSecret), Keys: []interface{}{getPublicKey(rsaPublicKey)}, ExpectedErr: true, ExpectedOK: false, }, } for k, tc := range testCases { auds := authenticator.Audiences{"api"} getter := serviceaccountcontroller.NewGetterFromClient(tc.Client) authn := serviceaccount.JWTTokenAuthenticator(serviceaccount.LegacyIssuer, tc.Keys, auds, serviceaccount.NewLegacyValidator(tc.Client != nil, getter)) // An invalid, non-JWT token should always fail ctx := authenticator.WithAudiences(context.Background(), auds) if _, ok, err := authn.AuthenticateToken(ctx, "invalid token"); err != nil || ok { t.Errorf("%s: Expected err=nil, ok=false for non-JWT token", k) continue } resp, ok, err := authn.AuthenticateToken(ctx, tc.Token) if (err != nil) != tc.ExpectedErr { t.Errorf("%s: Expected error=%v, got %v", k, tc.ExpectedErr, err) continue } if ok != tc.ExpectedOK { t.Errorf("%s: Expected ok=%v, got %v", k, tc.ExpectedOK, ok) continue } if err != nil || !ok { continue } if resp.User.GetName() != tc.ExpectedUserName { t.Errorf("%s: Expected username=%v, got %v", k, tc.ExpectedUserName, resp.User.GetName()) continue } if resp.User.GetUID() != tc.ExpectedUserUID { t.Errorf("%s: Expected userUID=%v, got %v", k, tc.ExpectedUserUID, resp.User.GetUID()) continue } if !reflect.DeepEqual(resp.User.GetGroups(), tc.ExpectedGroups) { t.Errorf("%s: Expected groups=%v, got %v", k, tc.ExpectedGroups, resp.User.GetGroups()) continue } } }