Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

present request header cert CA #40301

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
33 changes: 31 additions & 2 deletions pkg/genericapiserver/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ limitations under the License.
package genericapiserver

import (
"crypto/x509"
"fmt"
"io"
"net"
Expand Down Expand Up @@ -165,7 +166,7 @@ type SecureServingInfo struct {
// SNICerts are named CertKeys for serving secure traffic with SNI support.
SNICerts []NamedCertKey
// ClientCA is the certificate bundle for all the signers that you'll recognize for incoming client certificates
ClientCA string
ClientCA *x509.CertPool
}

type CertKey struct {
Expand Down Expand Up @@ -254,14 +255,42 @@ func (c *Config) ApplyOptions(options *options.ServerRunOptions) *Config {
},
},
SNICerts: []NamedCertKey{},
ClientCA: options.ClientCAFile,
}
if options.TLSCertFile == "" && options.TLSPrivateKeyFile == "" {
secureServingInfo.ServerCert.Generate = true
secureServingInfo.ServerCert.CertFile = path.Join(options.CertDirectory, "apiserver.crt")
secureServingInfo.ServerCert.KeyFile = path.Join(options.CertDirectory, "apiserver.key")
}

if len(options.ClientCAFile) > 0 {
clientCAs, err := certutil.CertsFromFile(options.ClientCAFile)
if err != nil {
// normally this would be no-no, but its the minimal change to backport to 1.5 and
// every caller is going to do this.
panic(fmt.Errorf("unable to load client CA file: %v", err))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you at least add this same check into ValidateRunOptions as well so they get a nice error under normal circumstances?

}
if secureServingInfo.ClientCA == nil {
secureServingInfo.ClientCA = x509.NewCertPool()
}
for _, cert := range clientCAs {
secureServingInfo.ClientCA.AddCert(cert)
}
}
if len(options.RequestHeaderClientCAFile) > 0 {
clientCAs, err := certutil.CertsFromFile(options.RequestHeaderClientCAFile)
if err != nil {
// normally this would be no-no, but its the minimal change to backport to 1.5 and
// every caller is going to do this.
panic(fmt.Errorf("unable to load requestheader client CA file: %v", err))
}
if secureServingInfo.ClientCA == nil {
secureServingInfo.ClientCA = x509.NewCertPool()
}
for _, cert := range clientCAs {
secureServingInfo.ClientCA.AddCert(cert)
}
}

secureServingInfo.SNICerts = nil
for _, nkc := range options.SNICertKeys {
secureServingInfo.SNICerts = append(secureServingInfo.SNICerts, NamedCertKey{
Expand Down
9 changes: 2 additions & 7 deletions pkg/genericapiserver/serve.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ import (
"sync"
"time"

certutil "k8s.io/kubernetes/pkg/util/cert"
utilruntime "k8s.io/kubernetes/pkg/util/runtime"
"k8s.io/kubernetes/pkg/util/validation"

Expand Down Expand Up @@ -78,16 +77,12 @@ func (s *GenericAPIServer) serveSecurely(stopCh <-chan struct{}) error {
secureServer.TLSConfig.Certificates = append(secureServer.TLSConfig.Certificates, *c)
}

if len(s.SecureServingInfo.ClientCA) > 0 {
clientCAs, err := certutil.NewPool(s.SecureServingInfo.ClientCA)
if err != nil {
return fmt.Errorf("unable to load client CA file: %v", err)
}
if s.SecureServingInfo.ClientCA != nil {
// 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
secureServer.TLSConfig.ClientCAs = s.SecureServingInfo.ClientCA
}

glog.Infof("Serving securely on %s", s.SecureServingInfo.BindAddress)
Expand Down
20 changes: 20 additions & 0 deletions pkg/genericapiserver/validation/universal_validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (

"github.com/golang/glog"
"k8s.io/kubernetes/pkg/genericapiserver/options"
certutil "k8s.io/kubernetes/pkg/util/cert"
utilerrors "k8s.io/kubernetes/pkg/util/errors"
)

Expand Down Expand Up @@ -69,6 +70,22 @@ func verifySecureAndInsecurePort(options *options.ServerRunOptions) []error {
return errors
}

func verifyClientCAs(options *options.ServerRunOptions) []error {
errors := []error{}
if len(options.ClientCAFile) > 0 {
if _, err := certutil.CertsFromFile(options.ClientCAFile); err != nil {
errors = append(errors, fmt.Errorf("unable to load client CA file: %v", err))
}
}
if len(options.RequestHeaderClientCAFile) > 0 {
if _, err := certutil.CertsFromFile(options.RequestHeaderClientCAFile); err != nil {
errors = append(errors, fmt.Errorf("unable to load requestheader client CA file: %v", err))
}
}

return errors
}

func ValidateRunOptions(options *options.ServerRunOptions) {
errors := []error{}
if errs := verifyClusterIPFlags(options); len(errs) > 0 {
Expand All @@ -80,6 +97,9 @@ func ValidateRunOptions(options *options.ServerRunOptions) {
if errs := verifySecureAndInsecurePort(options); len(errs) > 0 {
errors = append(errors, errs...)
}
if errs := verifyClientCAs(options); len(errs) > 0 {
errors = append(errors, errs...)
}
if err := utilerrors.NewAggregate(errors); err != nil {
glog.Fatalf("Validate server run options failed: %v", err)
}
Expand Down
6 changes: 3 additions & 3 deletions pkg/util/cert/io.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ func WriteKey(keyPath string, data []byte) error {
// NewPool 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 NewPool(filename string) (*x509.CertPool, error) {
certs, err := certsFromFile(filename)
certs, err := CertsFromFile(filename)
if err != nil {
return nil, err
}
Expand All @@ -90,9 +90,9 @@ func NewPool(filename string) (*x509.CertPool, error) {
return pool, nil
}

// certsFromFile returns the x509.Certificates contained in the given PEM-encoded file.
// CertsFromFile 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 certsFromFile(file string) ([]*x509.Certificate, error) {
func CertsFromFile(file string) ([]*x509.Certificate, error) {
if len(file) == 0 {
return nil, errors.New("error reading certificates from an empty filename")
}
Expand Down