mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-06 02:34:03 +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
|
||||
SSHKeyfile string
|
||||
SSHUser string
|
||||
ServiceAccountKeyFile string
|
||||
ServiceAccountKeyFiles []string
|
||||
ServiceAccountLookup bool
|
||||
WebhookTokenAuthnConfigFile string
|
||||
WebhookTokenAuthnCacheTTL time.Duration
|
||||
@ -70,9 +70,10 @@ func (s *APIServer) AddFlags(fs *pflag.FlagSet) {
|
||||
fs.DurationVar(&s.EventTTL, "event-ttl", s.EventTTL,
|
||||
"Amount of time to retain events. Default is 1h.")
|
||||
|
||||
fs.StringVar(&s.ServiceAccountKeyFile, "service-account-key-file", s.ServiceAccountKeyFile, ""+
|
||||
"File containing PEM-encoded x509 RSA or ECDSA private or public key, used to verify "+
|
||||
"ServiceAccount tokens. If unspecified, --tls-private-key-file is used.")
|
||||
fs.StringArrayVar(&s.ServiceAccountKeyFiles, "service-account-key-file", s.ServiceAccountKeyFiles, ""+
|
||||
"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. "+
|
||||
"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,
|
||||
"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
|
||||
if s.ServiceAccountKeyFile == "" && s.TLSPrivateKeyFile != "" {
|
||||
if len(s.ServiceAccountKeyFiles) == 0 && s.TLSPrivateKeyFile != "" {
|
||||
if authenticator.IsValidServiceAccountKeyFile(s.TLSPrivateKeyFile) {
|
||||
s.ServiceAccountKeyFile = s.TLSPrivateKeyFile
|
||||
s.ServiceAccountKeyFiles = []string{s.TLSPrivateKeyFile}
|
||||
} 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,
|
||||
OIDCUsernameClaim: s.OIDCUsernameClaim,
|
||||
OIDCGroupsClaim: s.OIDCGroupsClaim,
|
||||
ServiceAccountKeyFile: s.ServiceAccountKeyFile,
|
||||
ServiceAccountKeyFiles: s.ServiceAccountKeyFiles,
|
||||
ServiceAccountLookup: s.ServiceAccountLookup,
|
||||
ServiceAccountTokenGetter: serviceAccountGetter,
|
||||
KeystoneURL: s.KeystoneURL,
|
||||
|
@ -48,7 +48,7 @@ type AuthenticatorConfig struct {
|
||||
OIDCCAFile string
|
||||
OIDCUsernameClaim string
|
||||
OIDCGroupsClaim string
|
||||
ServiceAccountKeyFile string
|
||||
ServiceAccountKeyFiles []string
|
||||
ServiceAccountLookup bool
|
||||
ServiceAccountTokenGetter serviceaccount.ServiceAccountTokenGetter
|
||||
KeystoneURL string
|
||||
@ -94,8 +94,8 @@ func New(config AuthenticatorConfig) (authenticator.Request, error) {
|
||||
}
|
||||
authenticators = append(authenticators, tokenAuth)
|
||||
}
|
||||
if len(config.ServiceAccountKeyFile) > 0 {
|
||||
serviceAccountAuth, err := newServiceAccountAuthenticator(config.ServiceAccountKeyFile, config.ServiceAccountLookup, config.ServiceAccountTokenGetter)
|
||||
if len(config.ServiceAccountKeyFiles) > 0 {
|
||||
serviceAccountAuth, err := newServiceAccountAuthenticator(config.ServiceAccountKeyFiles, config.ServiceAccountLookup, config.ServiceAccountTokenGetter)
|
||||
if err != nil {
|
||||
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
|
||||
func IsValidServiceAccountKeyFile(file string) bool {
|
||||
_, err := serviceaccount.ReadPublicKey(file)
|
||||
_, err := serviceaccount.ReadPublicKeys(file)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
@ -198,13 +198,17 @@ func newAuthenticatorFromOIDCIssuerURL(issuerURL, clientID, caFile, usernameClai
|
||||
}
|
||||
|
||||
// newServiceAccountAuthenticator returns an authenticator.Request or an error
|
||||
func newServiceAccountAuthenticator(keyfile string, lookup bool, serviceAccountGetter serviceaccount.ServiceAccountTokenGetter) (authenticator.Request, error) {
|
||||
publicKey, err := serviceaccount.ReadPublicKey(keyfile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
func newServiceAccountAuthenticator(keyfiles []string, lookup bool, serviceAccountGetter serviceaccount.ServiceAccountTokenGetter) (authenticator.Request, error) {
|
||||
allPublicKeys := []interface{}{}
|
||||
for _, keyfile := range keyfiles {
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -21,6 +21,7 @@ import (
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rsa"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"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")
|
||||
}
|
||||
|
||||
// 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.
|
||||
func ReadPublicKey(file string) (interface{}, error) {
|
||||
func ReadPublicKeys(file string) ([]interface{}, error) {
|
||||
data, err := ioutil.ReadFile(file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
key, err := ReadPublicKeyFromPEM(data)
|
||||
keys, err := ReadPublicKeysFromPEM(data)
|
||||
if err != nil {
|
||||
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.
|
||||
func ReadPublicKeyFromPEM(data []byte) (interface{}, error) {
|
||||
if privateKey, err := jwt.ParseRSAPrivateKeyFromPEM(data); err == nil {
|
||||
return &privateKey.PublicKey, nil
|
||||
}
|
||||
if publicKey, err := jwt.ParseRSAPublicKeyFromPEM(data); err == nil {
|
||||
return publicKey, nil
|
||||
func ReadPublicKeysFromPEM(data []byte) ([]interface{}, error) {
|
||||
var block *pem.Block
|
||||
keys := []interface{}{}
|
||||
for {
|
||||
// read the next block
|
||||
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 {
|
||||
return &privateKey.PublicKey, nil
|
||||
if len(keys) == 0 {
|
||||
return nil, fmt.Errorf("data does not contain a valid RSA or ECDSA key")
|
||||
}
|
||||
if publicKey, err := jwt.ParseECPublicKeyFromPEM(data); err == nil {
|
||||
return publicKey, nil
|
||||
}
|
||||
return nil, fmt.Errorf("data does not contain a valid RSA or ECDSA key")
|
||||
return keys, nil
|
||||
}
|
||||
|
||||
// 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{} {
|
||||
key, _ := serviceaccount.ReadPublicKeyFromPEM([]byte(data))
|
||||
return key
|
||||
keys, _ := serviceaccount.ReadPublicKeysFromPEM([]byte(data))
|
||||
return keys[0]
|
||||
}
|
||||
func TestReadPrivateKey(t *testing.T) {
|
||||
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("", "")
|
||||
if err != nil {
|
||||
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 {
|
||||
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)
|
||||
} 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 {
|
||||
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)
|
||||
} 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) {
|
||||
|
Loading…
Reference in New Issue
Block a user