mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-06 10:43:56 +00:00
Enable service account signing key rotation
This commit is contained in:
parent
96fde0fe8d
commit
3c92eb75b3
@ -37,7 +37,7 @@ type APIServer struct {
|
|||||||
MaxConnectionBytesPerSec int64
|
MaxConnectionBytesPerSec int64
|
||||||
SSHKeyfile string
|
SSHKeyfile string
|
||||||
SSHUser string
|
SSHUser string
|
||||||
ServiceAccountKeyFile string
|
ServiceAccountKeyFiles []string
|
||||||
ServiceAccountLookup bool
|
ServiceAccountLookup bool
|
||||||
WebhookTokenAuthnConfigFile string
|
WebhookTokenAuthnConfigFile string
|
||||||
WebhookTokenAuthnCacheTTL time.Duration
|
WebhookTokenAuthnCacheTTL time.Duration
|
||||||
@ -70,9 +70,10 @@ func (s *APIServer) AddFlags(fs *pflag.FlagSet) {
|
|||||||
fs.DurationVar(&s.EventTTL, "event-ttl", s.EventTTL,
|
fs.DurationVar(&s.EventTTL, "event-ttl", s.EventTTL,
|
||||||
"Amount of time to retain events. Default is 1h.")
|
"Amount of time to retain events. Default is 1h.")
|
||||||
|
|
||||||
fs.StringVar(&s.ServiceAccountKeyFile, "service-account-key-file", s.ServiceAccountKeyFile, ""+
|
fs.StringArrayVar(&s.ServiceAccountKeyFiles, "service-account-key-file", s.ServiceAccountKeyFiles, ""+
|
||||||
"File containing PEM-encoded x509 RSA or ECDSA private or public key, used to verify "+
|
"File containing PEM-encoded x509 RSA or ECDSA private or public keys, used to verify "+
|
||||||
"ServiceAccount tokens. If unspecified, --tls-private-key-file is used.")
|
"ServiceAccount tokens. If unspecified, --tls-private-key-file is used. "+
|
||||||
|
"The specified file can contain multiple keys, and the flag can be specified multiple times with different files.")
|
||||||
|
|
||||||
fs.BoolVar(&s.ServiceAccountLookup, "service-account-lookup", s.ServiceAccountLookup,
|
fs.BoolVar(&s.ServiceAccountLookup, "service-account-lookup", s.ServiceAccountLookup,
|
||||||
"If true, validate ServiceAccount tokens exist in etcd as part of authentication.")
|
"If true, validate ServiceAccount tokens exist in etcd as part of authentication.")
|
||||||
|
@ -181,11 +181,11 @@ func Run(s *options.APIServer) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Default to the private server key for service account token signing
|
// Default to the private server key for service account token signing
|
||||||
if s.ServiceAccountKeyFile == "" && s.TLSPrivateKeyFile != "" {
|
if len(s.ServiceAccountKeyFiles) == 0 && s.TLSPrivateKeyFile != "" {
|
||||||
if authenticator.IsValidServiceAccountKeyFile(s.TLSPrivateKeyFile) {
|
if authenticator.IsValidServiceAccountKeyFile(s.TLSPrivateKeyFile) {
|
||||||
s.ServiceAccountKeyFile = s.TLSPrivateKeyFile
|
s.ServiceAccountKeyFiles = []string{s.TLSPrivateKeyFile}
|
||||||
} else {
|
} else {
|
||||||
glog.Warning("No RSA key provided, service account token authentication disabled")
|
glog.Warning("No TLS key provided, service account token authentication disabled")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -211,7 +211,7 @@ func Run(s *options.APIServer) error {
|
|||||||
OIDCCAFile: s.OIDCCAFile,
|
OIDCCAFile: s.OIDCCAFile,
|
||||||
OIDCUsernameClaim: s.OIDCUsernameClaim,
|
OIDCUsernameClaim: s.OIDCUsernameClaim,
|
||||||
OIDCGroupsClaim: s.OIDCGroupsClaim,
|
OIDCGroupsClaim: s.OIDCGroupsClaim,
|
||||||
ServiceAccountKeyFile: s.ServiceAccountKeyFile,
|
ServiceAccountKeyFiles: s.ServiceAccountKeyFiles,
|
||||||
ServiceAccountLookup: s.ServiceAccountLookup,
|
ServiceAccountLookup: s.ServiceAccountLookup,
|
||||||
ServiceAccountTokenGetter: serviceAccountGetter,
|
ServiceAccountTokenGetter: serviceAccountGetter,
|
||||||
KeystoneURL: s.KeystoneURL,
|
KeystoneURL: s.KeystoneURL,
|
||||||
|
@ -48,7 +48,7 @@ type AuthenticatorConfig struct {
|
|||||||
OIDCCAFile string
|
OIDCCAFile string
|
||||||
OIDCUsernameClaim string
|
OIDCUsernameClaim string
|
||||||
OIDCGroupsClaim string
|
OIDCGroupsClaim string
|
||||||
ServiceAccountKeyFile string
|
ServiceAccountKeyFiles []string
|
||||||
ServiceAccountLookup bool
|
ServiceAccountLookup bool
|
||||||
ServiceAccountTokenGetter serviceaccount.ServiceAccountTokenGetter
|
ServiceAccountTokenGetter serviceaccount.ServiceAccountTokenGetter
|
||||||
KeystoneURL string
|
KeystoneURL string
|
||||||
@ -94,8 +94,8 @@ func New(config AuthenticatorConfig) (authenticator.Request, error) {
|
|||||||
}
|
}
|
||||||
authenticators = append(authenticators, tokenAuth)
|
authenticators = append(authenticators, tokenAuth)
|
||||||
}
|
}
|
||||||
if len(config.ServiceAccountKeyFile) > 0 {
|
if len(config.ServiceAccountKeyFiles) > 0 {
|
||||||
serviceAccountAuth, err := newServiceAccountAuthenticator(config.ServiceAccountKeyFile, config.ServiceAccountLookup, config.ServiceAccountTokenGetter)
|
serviceAccountAuth, err := newServiceAccountAuthenticator(config.ServiceAccountKeyFiles, config.ServiceAccountLookup, config.ServiceAccountTokenGetter)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -152,7 +152,7 @@ func New(config AuthenticatorConfig) (authenticator.Request, error) {
|
|||||||
|
|
||||||
// IsValidServiceAccountKeyFile returns true if a valid public RSA key can be read from the given file
|
// IsValidServiceAccountKeyFile returns true if a valid public RSA key can be read from the given file
|
||||||
func IsValidServiceAccountKeyFile(file string) bool {
|
func IsValidServiceAccountKeyFile(file string) bool {
|
||||||
_, err := serviceaccount.ReadPublicKey(file)
|
_, err := serviceaccount.ReadPublicKeys(file)
|
||||||
return err == nil
|
return err == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -198,13 +198,17 @@ func newAuthenticatorFromOIDCIssuerURL(issuerURL, clientID, caFile, usernameClai
|
|||||||
}
|
}
|
||||||
|
|
||||||
// newServiceAccountAuthenticator returns an authenticator.Request or an error
|
// newServiceAccountAuthenticator returns an authenticator.Request or an error
|
||||||
func newServiceAccountAuthenticator(keyfile string, lookup bool, serviceAccountGetter serviceaccount.ServiceAccountTokenGetter) (authenticator.Request, error) {
|
func newServiceAccountAuthenticator(keyfiles []string, lookup bool, serviceAccountGetter serviceaccount.ServiceAccountTokenGetter) (authenticator.Request, error) {
|
||||||
publicKey, err := serviceaccount.ReadPublicKey(keyfile)
|
allPublicKeys := []interface{}{}
|
||||||
if err != nil {
|
for _, keyfile := range keyfiles {
|
||||||
return nil, err
|
publicKeys, err := serviceaccount.ReadPublicKeys(keyfile)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
allPublicKeys = append(allPublicKeys, publicKeys...)
|
||||||
}
|
}
|
||||||
|
|
||||||
tokenAuthenticator := serviceaccount.JWTTokenAuthenticator([]interface{}{publicKey}, lookup, serviceAccountGetter)
|
tokenAuthenticator := serviceaccount.JWTTokenAuthenticator(allPublicKeys, lookup, serviceAccountGetter)
|
||||||
return bearertoken.New(tokenAuthenticator), nil
|
return bearertoken.New(tokenAuthenticator), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,6 +21,7 @@ import (
|
|||||||
"crypto/ecdsa"
|
"crypto/ecdsa"
|
||||||
"crypto/elliptic"
|
"crypto/elliptic"
|
||||||
"crypto/rsa"
|
"crypto/rsa"
|
||||||
|
"encoding/pem"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
@ -80,37 +81,60 @@ func ReadPrivateKeyFromPEM(data []byte) (interface{}, error) {
|
|||||||
return nil, fmt.Errorf("data does not contain a valid RSA or ECDSA private key")
|
return nil, fmt.Errorf("data does not contain a valid RSA or ECDSA private key")
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadPublicKey is a helper function for reading an rsa.PublicKey or ecdsa.PublicKey from a PEM-encoded file.
|
// ReadPublicKeys is a helper function for reading an array of rsa.PublicKey or ecdsa.PublicKey from a PEM-encoded file.
|
||||||
// Reads public keys from both public and private key files.
|
// Reads public keys from both public and private key files.
|
||||||
func ReadPublicKey(file string) (interface{}, error) {
|
func ReadPublicKeys(file string) ([]interface{}, error) {
|
||||||
data, err := ioutil.ReadFile(file)
|
data, err := ioutil.ReadFile(file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
key, err := ReadPublicKeyFromPEM(data)
|
keys, err := ReadPublicKeysFromPEM(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error reading public key file %s: %v", file, err)
|
return nil, fmt.Errorf("error reading public key file %s: %v", file, err)
|
||||||
}
|
}
|
||||||
return key, nil
|
return keys, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadPublicKeyFromPEM is a helper function for reading an rsa.PublicKey or ecdsa.PublicKey from a PEM-encoded byte array.
|
// ReadPublicKeysFromPEM is a helper function for reading an array of rsa.PublicKey or ecdsa.PublicKey from a PEM-encoded byte array.
|
||||||
// Reads public keys from both public and private key files.
|
// Reads public keys from both public and private key files.
|
||||||
func ReadPublicKeyFromPEM(data []byte) (interface{}, error) {
|
func ReadPublicKeysFromPEM(data []byte) ([]interface{}, error) {
|
||||||
if privateKey, err := jwt.ParseRSAPrivateKeyFromPEM(data); err == nil {
|
var block *pem.Block
|
||||||
return &privateKey.PublicKey, nil
|
keys := []interface{}{}
|
||||||
}
|
for {
|
||||||
if publicKey, err := jwt.ParseRSAPublicKeyFromPEM(data); err == nil {
|
// read the next block
|
||||||
return publicKey, nil
|
block, data = pem.Decode(data)
|
||||||
|
if block == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// get PEM bytes for just this block
|
||||||
|
blockData := pem.EncodeToMemory(block)
|
||||||
|
if privateKey, err := jwt.ParseRSAPrivateKeyFromPEM(blockData); err == nil {
|
||||||
|
keys = append(keys, &privateKey.PublicKey)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if publicKey, err := jwt.ParseRSAPublicKeyFromPEM(blockData); err == nil {
|
||||||
|
keys = append(keys, publicKey)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if privateKey, err := jwt.ParseECPrivateKeyFromPEM(blockData); err == nil {
|
||||||
|
keys = append(keys, &privateKey.PublicKey)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if publicKey, err := jwt.ParseECPublicKeyFromPEM(blockData); err == nil {
|
||||||
|
keys = append(keys, publicKey)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// tolerate non-key PEM blocks for backwards compatibility
|
||||||
|
// originally, only the first PEM block was parsed and expected to be a key block
|
||||||
}
|
}
|
||||||
|
|
||||||
if privateKey, err := jwt.ParseECPrivateKeyFromPEM(data); err == nil {
|
if len(keys) == 0 {
|
||||||
return &privateKey.PublicKey, nil
|
return nil, fmt.Errorf("data does not contain a valid RSA or ECDSA key")
|
||||||
}
|
}
|
||||||
if publicKey, err := jwt.ParseECPublicKeyFromPEM(data); err == nil {
|
return keys, nil
|
||||||
return publicKey, nil
|
|
||||||
}
|
|
||||||
return nil, fmt.Errorf("data does not contain a valid RSA or ECDSA key")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// JWTTokenGenerator returns a TokenGenerator that generates signed JWT tokens, using the given privateKey.
|
// JWTTokenGenerator returns a TokenGenerator that generates signed JWT tokens, using the given privateKey.
|
||||||
|
@ -98,8 +98,8 @@ func getPrivateKey(data string) interface{} {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func getPublicKey(data string) interface{} {
|
func getPublicKey(data string) interface{} {
|
||||||
key, _ := serviceaccount.ReadPublicKeyFromPEM([]byte(data))
|
keys, _ := serviceaccount.ReadPublicKeysFromPEM([]byte(data))
|
||||||
return key
|
return keys[0]
|
||||||
}
|
}
|
||||||
func TestReadPrivateKey(t *testing.T) {
|
func TestReadPrivateKey(t *testing.T) {
|
||||||
f, err := ioutil.TempFile("", "")
|
f, err := ioutil.TempFile("", "")
|
||||||
@ -123,7 +123,7 @@ func TestReadPrivateKey(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestReadPublicKey(t *testing.T) {
|
func TestReadPublicKeys(t *testing.T) {
|
||||||
f, err := ioutil.TempFile("", "")
|
f, err := ioutil.TempFile("", "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("error creating tmpfile: %v", err)
|
t.Fatalf("error creating tmpfile: %v", err)
|
||||||
@ -133,16 +133,30 @@ func TestReadPublicKey(t *testing.T) {
|
|||||||
if err := ioutil.WriteFile(f.Name(), []byte(rsaPublicKey), os.FileMode(0600)); err != nil {
|
if err := ioutil.WriteFile(f.Name(), []byte(rsaPublicKey), os.FileMode(0600)); err != nil {
|
||||||
t.Fatalf("error writing public key to tmpfile: %v", err)
|
t.Fatalf("error writing public key to tmpfile: %v", err)
|
||||||
}
|
}
|
||||||
if _, err := serviceaccount.ReadPublicKey(f.Name()); err != nil {
|
if keys, err := serviceaccount.ReadPublicKeys(f.Name()); err != nil {
|
||||||
t.Fatalf("error reading RSA public key: %v", err)
|
t.Fatalf("error reading RSA public key: %v", err)
|
||||||
|
} else if len(keys) != 1 {
|
||||||
|
t.Fatalf("expected 1 key, got %d", len(keys))
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := ioutil.WriteFile(f.Name(), []byte(ecdsaPublicKey), os.FileMode(0600)); err != nil {
|
if err := ioutil.WriteFile(f.Name(), []byte(ecdsaPublicKey), os.FileMode(0600)); err != nil {
|
||||||
t.Fatalf("error writing public key to tmpfile: %v", err)
|
t.Fatalf("error writing public key to tmpfile: %v", err)
|
||||||
}
|
}
|
||||||
if _, err := serviceaccount.ReadPublicKey(f.Name()); err != nil {
|
if keys, err := serviceaccount.ReadPublicKeys(f.Name()); err != nil {
|
||||||
t.Fatalf("error reading ECDSA public key: %v", err)
|
t.Fatalf("error reading ECDSA public key: %v", err)
|
||||||
|
} else if len(keys) != 1 {
|
||||||
|
t.Fatalf("expected 1 key, got %d", len(keys))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := ioutil.WriteFile(f.Name(), []byte(rsaPublicKey+"\n"+ecdsaPublicKey), os.FileMode(0600)); err != nil {
|
||||||
|
t.Fatalf("error writing public key to tmpfile: %v", err)
|
||||||
|
}
|
||||||
|
if keys, err := serviceaccount.ReadPublicKeys(f.Name()); err != nil {
|
||||||
|
t.Fatalf("error reading combined RSA/ECDSA public key file: %v", err)
|
||||||
|
} else if len(keys) != 2 {
|
||||||
|
t.Fatalf("expected 2 keys, got %d", len(keys))
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTokenGenerateAndValidate(t *testing.T) {
|
func TestTokenGenerateAndValidate(t *testing.T) {
|
||||||
|
Loading…
Reference in New Issue
Block a user