Merge pull request #6190 from liggitt/client_cert_auth

Add client cert authentication
This commit is contained in:
Robert Bailey 2015-04-01 14:11:29 -07:00
commit 22d9c67cb7
4 changed files with 137 additions and 13 deletions

View File

@ -60,6 +60,7 @@ type APIServer struct {
CloudProvider string CloudProvider string
CloudConfigFile string CloudConfigFile string
EventTTL time.Duration EventTTL time.Duration
ClientCAFile string
TokenAuthFile string TokenAuthFile string
AuthorizationMode string AuthorizationMode string
AuthorizationPolicyFile string AuthorizationPolicyFile string
@ -139,6 +140,7 @@ func (s *APIServer) AddFlags(fs *pflag.FlagSet) {
fs.StringVar(&s.CloudProvider, "cloud_provider", s.CloudProvider, "The provider for cloud services. Empty string for no provider.") fs.StringVar(&s.CloudProvider, "cloud_provider", s.CloudProvider, "The provider for cloud services. Empty string for no provider.")
fs.StringVar(&s.CloudConfigFile, "cloud_config", s.CloudConfigFile, "The path to the cloud provider configuration file. Empty string for no configuration file.") fs.StringVar(&s.CloudConfigFile, "cloud_config", s.CloudConfigFile, "The path to the cloud provider configuration file. Empty string for no configuration file.")
fs.DurationVar(&s.EventTTL, "event_ttl", s.EventTTL, "Amount of time to retain events. Default 1 hour.") fs.DurationVar(&s.EventTTL, "event_ttl", s.EventTTL, "Amount of time to retain events. Default 1 hour.")
fs.StringVar(&s.ClientCAFile, "client_ca_file", s.ClientCAFile, "If set, any request presenting a client certificate signed by one of the authorities in the client_ca_file is authenticated with an identity corresponding to the CommonName of the client certificate.")
fs.StringVar(&s.TokenAuthFile, "token_auth_file", s.TokenAuthFile, "If set, the file that will be used to secure the secure port of the API server via token authentication.") fs.StringVar(&s.TokenAuthFile, "token_auth_file", s.TokenAuthFile, "If set, the file that will be used to secure the secure port of the API server via token authentication.")
fs.StringVar(&s.AuthorizationMode, "authorization_mode", s.AuthorizationMode, "Selects how to do authorization on the secure port. One of: "+strings.Join(apiserver.AuthorizationModeChoices, ",")) fs.StringVar(&s.AuthorizationMode, "authorization_mode", s.AuthorizationMode, "Selects how to do authorization on the secure port. One of: "+strings.Join(apiserver.AuthorizationModeChoices, ","))
fs.StringVar(&s.AuthorizationPolicyFile, "authorization_policy_file", s.AuthorizationPolicyFile, "File with authorization policy in csv format, used with --authorization_mode=ABAC, on the secure port.") fs.StringVar(&s.AuthorizationPolicyFile, "authorization_policy_file", s.AuthorizationPolicyFile, "File with authorization policy in csv format, used with --authorization_mode=ABAC, on the secure port.")
@ -222,7 +224,7 @@ func (s *APIServer) Run(_ []string) error {
n := net.IPNet(s.PortalNet) n := net.IPNet(s.PortalNet)
authenticator, err := apiserver.NewAuthenticatorFromTokenFile(s.TokenAuthFile) authenticator, err := apiserver.NewAuthenticator(s.ClientCAFile, s.TokenAuthFile)
if err != nil { if err != nil {
glog.Fatalf("Invalid Authentication Config: %v", err) glog.Fatalf("Invalid Authentication Config: %v", err)
} }
@ -330,11 +332,21 @@ func (s *APIServer) Run(_ []string) error {
TLSConfig: &tls.Config{ TLSConfig: &tls.Config{
// Change default from SSLv3 to TLSv1.0 (because of POODLE vulnerability) // Change default from SSLv3 to TLSv1.0 (because of POODLE vulnerability)
MinVersion: tls.VersionTLS10, MinVersion: tls.VersionTLS10,
// Populate PeerCertificates in requests, but don't reject connections without certificates
// This allows certificates to be validated by authenticators, while still allowing other auth types
ClientAuth: tls.RequestClientCert,
}, },
} }
if len(s.ClientCAFile) > 0 {
clientCAs, err := util.CertPoolFromFile(s.ClientCAFile)
if err != nil {
glog.Fatalf("unable to load client CA file: %v", err)
}
// Populate PeerCertificates in requests, but don't reject connections without certificates
// This allows certificates to be validated by authenticators, while still allowing other auth types
secureServer.TLSConfig.ClientAuth = tls.RequestClientCert
// Specify allowed CAs for client certificates
secureServer.TLSConfig.ClientCAs = clientCAs
}
glog.Infof("Serving securely on %s", secureLocation) glog.Infof("Serving securely on %s", secureLocation)
go func() { go func() {
defer util.HandleCrash() defer util.HandleCrash()

View File

@ -1,8 +1,14 @@
# Authentication Plugins # Authentication Plugins
Kubernetes uses tokens to authenticate users for API calls. Kubernetes uses tokens or client certificates to authenticate users for API calls.
Authentication is enabled by passing the `--token_auth_file=SOMEFILE` option Client certificate authentication is enabled by passing the `--client_ca_file=SOMEFILE`
option to apiserver. The referenced file must contain one or more certificates authorities
to use to validate client certificates presented to the apiserver. If a client certificate
is presented and verified, the common name of the subject is used as the user name for the
request.
Token authentication is enabled by passing the `--token_auth_file=SOMEFILE` option
to apiserver. Currently, tokens last indefinitely, and the token list cannot to apiserver. Currently, tokens last indefinitely, and the token list cannot
be changed without restarting apiserver. We plan in the future for tokens to be changed without restarting apiserver. We plan in the future for tokens to
be short-lived, and to be generated as needed rather than stored in a file. be short-lived, and to be generated as needed rather than stored in a file.

View File

@ -19,18 +19,61 @@ package apiserver
import ( import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/auth/authenticator" "github.com/GoogleCloudPlatform/kubernetes/pkg/auth/authenticator"
"github.com/GoogleCloudPlatform/kubernetes/pkg/auth/authenticator/bearertoken" "github.com/GoogleCloudPlatform/kubernetes/pkg/auth/authenticator/bearertoken"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
"github.com/GoogleCloudPlatform/kubernetes/plugin/pkg/auth/authenticator/request/union"
"github.com/GoogleCloudPlatform/kubernetes/plugin/pkg/auth/authenticator/request/x509"
"github.com/GoogleCloudPlatform/kubernetes/plugin/pkg/auth/authenticator/token/tokenfile" "github.com/GoogleCloudPlatform/kubernetes/plugin/pkg/auth/authenticator/token/tokenfile"
) )
// NewAuthenticatorFromTokenFile returns an authenticator.Request or an error // NewAuthenticator returns an authenticator.Request or an error
func NewAuthenticatorFromTokenFile(tokenAuthFile string) (authenticator.Request, error) { func NewAuthenticator(clientCAFile string, tokenFile string) (authenticator.Request, error) {
var authenticator authenticator.Request authenticators := []authenticator.Request{}
if len(tokenAuthFile) != 0 {
if len(clientCAFile) > 0 {
certAuth, err := newAuthenticatorFromClientCAFile(clientCAFile)
if err != nil {
return nil, err
}
authenticators = append(authenticators, certAuth)
}
if len(tokenFile) > 0 {
tokenAuth, err := newAuthenticatorFromTokenFile(tokenFile)
if err != nil {
return nil, err
}
authenticators = append(authenticators, tokenAuth)
}
if len(authenticators) == 0 {
return nil, nil
}
if len(authenticators) == 1 {
return authenticators[1], nil
}
return union.New(authenticators...), nil
}
// newAuthenticatorFromTokenFile returns an authenticator.Request or an error
func newAuthenticatorFromTokenFile(tokenAuthFile string) (authenticator.Request, error) {
tokenAuthenticator, err := tokenfile.NewCSV(tokenAuthFile) tokenAuthenticator, err := tokenfile.NewCSV(tokenAuthFile)
if err != nil { if err != nil {
return nil, err return nil, err
} }
authenticator = bearertoken.New(tokenAuthenticator)
return bearertoken.New(tokenAuthenticator), nil
} }
return authenticator, nil
// newAuthenticatorFromClientCAFile returns an authenticator.Request or an error
func newAuthenticatorFromClientCAFile(clientCAFile string) (authenticator.Request, error) {
roots, err := util.CertPoolFromFile(clientCAFile)
if err != nil {
return nil, err
}
opts := x509.DefaultVerifyOptions()
opts.Roots = roots
return x509.New(opts, x509.CommonNameUserConversion), nil
} }

View File

@ -23,6 +23,7 @@ import (
"crypto/x509" "crypto/x509"
"crypto/x509/pkix" "crypto/x509/pkix"
"encoding/pem" "encoding/pem"
"errors"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"math/big" "math/big"
@ -97,3 +98,65 @@ func GenerateSelfSignedCert(host, certPath, keyPath string) error {
return nil return nil
} }
// CertPoolFromFile returns an x509.CertPool containing the certificates in the given PEM-encoded file.
// Returns an error if the file could not be read, a certificate could not be parsed, or if the file does not contain any certificates
func CertPoolFromFile(filename string) (*x509.CertPool, error) {
certs, err := certificatesFromFile(filename)
if err != nil {
return nil, err
}
pool := x509.NewCertPool()
for _, cert := range certs {
pool.AddCert(cert)
}
return pool, nil
}
// certificatesFromFile returns the x509.Certificates contained in the given PEM-encoded file.
// Returns an error if the file could not be read, a certificate could not be parsed, or if the file does not contain any certificates
func certificatesFromFile(file string) ([]*x509.Certificate, error) {
if len(file) == 0 {
return nil, errors.New("error reading certificates from an empty filename")
}
pemBlock, err := ioutil.ReadFile(file)
if err != nil {
return nil, err
}
certs, err := certsFromPEM(pemBlock)
if err != nil {
return nil, fmt.Errorf("error reading %s: %s", file, err)
}
return certs, nil
}
// certsFromPEM returns the x509.Certificates contained in the given PEM-encoded byte array
// Returns an error if a certificate could not be parsed, or if the data does not contain any certificates
func certsFromPEM(pemCerts []byte) ([]*x509.Certificate, error) {
ok := false
certs := []*x509.Certificate{}
for len(pemCerts) > 0 {
var block *pem.Block
block, pemCerts = pem.Decode(pemCerts)
if block == nil {
break
}
// Only use PEM "CERTIFICATE" blocks without extra headers
if block.Type != "CERTIFICATE" || len(block.Headers) != 0 {
continue
}
cert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
return certs, err
}
certs = append(certs, cert)
ok = true
}
if !ok {
return certs, errors.New("could not read any certificates")
}
return certs, nil
}