Adds correct support for TLS Muthual autentication and depth verification
modified: controllers/nginx/configuration.md modified: controllers/nginx/rootfs/etc/nginx/template/nginx.tmpl modified: core/pkg/ingress/annotations/authtls/main.go modified: core/pkg/ingress/controller/backend_ssl.go modified: core/pkg/ingress/controller/controller.go modified: core/pkg/ingress/controller/util_test.go modified: core/pkg/ingress/resolver/main.go modified: core/pkg/ingress/types.go modified: core/pkg/net/ssl/ssl.go modified: examples/PREREQUISITES.md new file: examples/auth/client-certs/nginx/README.md new file: examples/auth/client-certs/nginx/nginx-tls-auth.yaml
This commit is contained in:
parent
f5e005f84f
commit
a342c0bce3
12 changed files with 349 additions and 52 deletions
|
|
@ -28,11 +28,16 @@ import (
|
|||
|
||||
const (
|
||||
// name of the secret
|
||||
authTLSSecret = "ingress.kubernetes.io/auth-tls-secret"
|
||||
annotationAuthTLSSecret = "ingress.kubernetes.io/auth-tls-secret"
|
||||
annotationAuthTLSDepth = "ingress.kubernetes.io/auth-tls-verify-depth"
|
||||
defaultAuthTLSDepth = 1
|
||||
)
|
||||
|
||||
type authTLS struct {
|
||||
certResolver resolver.AuthCertificate
|
||||
// AuthSSLConfig contains the AuthSSLCert used for muthual autentication
|
||||
// and the configured ValidationDepth
|
||||
type AuthSSLConfig struct {
|
||||
AuthSSLCert resolver.AuthSSLCert
|
||||
ValidationDepth int `json:"validationDepth"`
|
||||
}
|
||||
|
||||
// NewParser creates a new TLS authentication annotation parser
|
||||
|
|
@ -40,29 +45,42 @@ func NewParser(resolver resolver.AuthCertificate) parser.IngressAnnotation {
|
|||
return authTLS{resolver}
|
||||
}
|
||||
|
||||
// ParseAnnotations parses the annotations contained in the ingress
|
||||
// rule used to use an external URL as source for authentication
|
||||
type authTLS struct {
|
||||
certResolver resolver.AuthCertificate
|
||||
}
|
||||
|
||||
// Parse parses the annotations contained in the ingress
|
||||
// rule used to use a Certificate as authentication method
|
||||
func (a authTLS) Parse(ing *extensions.Ingress) (interface{}, error) {
|
||||
str, err := parser.GetStringAnnotation(authTLSSecret, ing)
|
||||
|
||||
tlsauthsecret, err := parser.GetStringAnnotation(annotationAuthTLSSecret, ing)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return &AuthSSLConfig{}, err
|
||||
}
|
||||
|
||||
if str == "" {
|
||||
return nil, ing_errors.NewLocationDenied("an empty string is not a valid secret name")
|
||||
if tlsauthsecret == "" {
|
||||
return &AuthSSLConfig{}, ing_errors.NewLocationDenied("an empty string is not a valid secret name")
|
||||
}
|
||||
|
||||
_, _, err = k8s.ParseNameNS(str)
|
||||
_, _, err = k8s.ParseNameNS(tlsauthsecret)
|
||||
if err != nil {
|
||||
return nil, ing_errors.NewLocationDenied("an empty string is not a valid secret name")
|
||||
return &AuthSSLConfig{}, ing_errors.NewLocationDenied("an empty string is not a valid secret name")
|
||||
}
|
||||
|
||||
authCert, err := a.certResolver.GetAuthCertificate(str)
|
||||
tlsdepth, err := parser.GetIntAnnotation(annotationAuthTLSDepth, ing)
|
||||
if err != nil || tlsdepth == 0 {
|
||||
tlsdepth = defaultAuthTLSDepth
|
||||
}
|
||||
|
||||
authCert, err := a.certResolver.GetAuthCertificate(tlsauthsecret)
|
||||
if err != nil {
|
||||
return nil, ing_errors.LocationDenied{
|
||||
return &AuthSSLConfig{}, ing_errors.LocationDenied{
|
||||
Reason: errors.Wrap(err, "error obtaining certificate"),
|
||||
}
|
||||
}
|
||||
|
||||
return authCert, nil
|
||||
return &AuthSSLConfig{
|
||||
AuthSSLCert: *authCert,
|
||||
ValidationDepth: tlsdepth,
|
||||
}, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -98,6 +98,8 @@ func (ic *GenericController) syncSecret(k interface{}) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// getPemCertificate receives a secret, and creates a ingress.SSLCert as return.
|
||||
// It parses the secret and verifies if it's a keypair, or a 'ca.crt' secret only.
|
||||
func (ic *GenericController) getPemCertificate(secretName string) (*ingress.SSLCert, error) {
|
||||
secretInterface, exists, err := ic.secrLister.Store.GetByKey(secretName)
|
||||
if err != nil {
|
||||
|
|
@ -108,19 +110,24 @@ func (ic *GenericController) getPemCertificate(secretName string) (*ingress.SSLC
|
|||
}
|
||||
|
||||
secret := secretInterface.(*api.Secret)
|
||||
cert, ok := secret.Data[api.TLSCertKey]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("secret named %v has no private key", secretName)
|
||||
}
|
||||
key, ok := secret.Data[api.TLSPrivateKeyKey]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("secret named %v has no cert", secretName)
|
||||
}
|
||||
cert, okcert := secret.Data[api.TLSCertKey]
|
||||
key, okkey := secret.Data[api.TLSPrivateKeyKey]
|
||||
|
||||
ca := secret.Data["ca.crt"]
|
||||
|
||||
nsSecName := strings.Replace(secretName, "/", "-", -1)
|
||||
s, err := ssl.AddOrUpdateCertAndKey(nsSecName, cert, key, ca)
|
||||
|
||||
var s *ingress.SSLCert
|
||||
if okcert && okkey {
|
||||
glog.V(3).Infof("Found certificate and private key, configuring %v as a TLS Secret", secretName)
|
||||
s, err = ssl.AddOrUpdateCertAndKey(nsSecName, cert, key, ca)
|
||||
} else if ca != nil {
|
||||
glog.V(3).Infof("Found only ca.crt, configuring %v as an Certificate Authentication secret", secretName)
|
||||
s, err = ssl.AddCertAuth(nsSecName, ca)
|
||||
} else {
|
||||
return nil, fmt.Errorf("No keypair or CA cert could be found in %v", secretName)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -680,16 +680,23 @@ func (ic *GenericController) getBackendServers() ([]*ingress.Backend, []*ingress
|
|||
|
||||
// GetAuthCertificate ...
|
||||
func (ic GenericController) GetAuthCertificate(secretName string) (*resolver.AuthSSLCert, error) {
|
||||
key, err := ic.GetSecret(secretName)
|
||||
if err != nil {
|
||||
return &resolver.AuthSSLCert{}, fmt.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
if key != nil {
|
||||
ic.secretQueue.Enqueue(key)
|
||||
}
|
||||
|
||||
bc, exists := ic.sslCertTracker.Get(secretName)
|
||||
if !exists {
|
||||
return &resolver.AuthSSLCert{}, fmt.Errorf("secret %v does not exists", secretName)
|
||||
}
|
||||
cert := bc.(*ingress.SSLCert)
|
||||
return &resolver.AuthSSLCert{
|
||||
Secret: secretName,
|
||||
CertFileName: cert.PemFileName,
|
||||
CAFileName: cert.CAFileName,
|
||||
PemSHA: cert.PemSHA,
|
||||
Secret: secretName,
|
||||
CAFileName: cert.CAFileName,
|
||||
PemSHA: cert.PemSHA,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -24,11 +24,11 @@ import (
|
|||
"k8s.io/ingress/core/pkg/ingress"
|
||||
"k8s.io/ingress/core/pkg/ingress/annotations/auth"
|
||||
"k8s.io/ingress/core/pkg/ingress/annotations/authreq"
|
||||
"k8s.io/ingress/core/pkg/ingress/annotations/authtls"
|
||||
"k8s.io/ingress/core/pkg/ingress/annotations/ipwhitelist"
|
||||
"k8s.io/ingress/core/pkg/ingress/annotations/proxy"
|
||||
"k8s.io/ingress/core/pkg/ingress/annotations/ratelimit"
|
||||
"k8s.io/ingress/core/pkg/ingress/annotations/rewrite"
|
||||
"k8s.io/ingress/core/pkg/ingress/resolver"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/apis/extensions"
|
||||
)
|
||||
|
|
@ -136,7 +136,7 @@ func TestMergeLocationAnnotations(t *testing.T) {
|
|||
"Redirect": rewrite.Redirect{},
|
||||
"Whitelist": ipwhitelist.SourceRange{},
|
||||
"Proxy": proxy.Configuration{},
|
||||
"CertificateAuth": resolver.AuthSSLCert{},
|
||||
"CertificateAuth": authtls.AuthSSLConfig{},
|
||||
"UsePortInRedirects": true,
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -37,8 +37,6 @@ type Secret interface {
|
|||
// AuthCertificate resolves a given secret name into an SSL certificate.
|
||||
// The secret must contain 3 keys named:
|
||||
// ca.crt: contains the certificate chain used for authentication
|
||||
// tls.crt: (ignored) contains the tls certificate chain, or any other valid base64 data
|
||||
// tls.key: (ignored) contains the tls secret key, or any other valid base64 data
|
||||
type AuthCertificate interface {
|
||||
GetAuthCertificate(string) (*AuthSSLCert, error)
|
||||
}
|
||||
|
|
@ -48,10 +46,6 @@ type AuthCertificate interface {
|
|||
type AuthSSLCert struct {
|
||||
// Secret contains the name of the secret this was fetched from
|
||||
Secret string `json:"secret"`
|
||||
// CertFileName contains the filename the secret's 'tls.crt' was saved to
|
||||
CertFileName string `json:"certFilename"`
|
||||
// KeyFileName contains the path the secret's 'tls.key'
|
||||
KeyFileName string `json:"keyFilename"`
|
||||
// CAFileName contains the path to the secrets 'ca.crt'
|
||||
CAFileName string `json:"caFilename"`
|
||||
// PemSHA contains the SHA1 hash of the 'tls.crt' value
|
||||
|
|
|
|||
|
|
@ -27,12 +27,12 @@ import (
|
|||
cache_store "k8s.io/ingress/core/pkg/cache"
|
||||
"k8s.io/ingress/core/pkg/ingress/annotations/auth"
|
||||
"k8s.io/ingress/core/pkg/ingress/annotations/authreq"
|
||||
"k8s.io/ingress/core/pkg/ingress/annotations/authtls"
|
||||
"k8s.io/ingress/core/pkg/ingress/annotations/ipwhitelist"
|
||||
"k8s.io/ingress/core/pkg/ingress/annotations/proxy"
|
||||
"k8s.io/ingress/core/pkg/ingress/annotations/ratelimit"
|
||||
"k8s.io/ingress/core/pkg/ingress/annotations/rewrite"
|
||||
"k8s.io/ingress/core/pkg/ingress/defaults"
|
||||
"k8s.io/ingress/core/pkg/ingress/resolver"
|
||||
)
|
||||
|
||||
var (
|
||||
|
|
@ -274,7 +274,7 @@ type Location struct {
|
|||
// CertificateAuth indicates the access to this location requires
|
||||
// external authentication
|
||||
// +optional
|
||||
CertificateAuth resolver.AuthSSLCert `json:"certificateAuth,omitempty"`
|
||||
CertificateAuth authtls.AuthSSLConfig `json:"certificateAuth,omitempty"`
|
||||
// UsePortInRedirects indicates if redirects must specify the port
|
||||
// +optional
|
||||
UsePortInRedirects bool `json:"use-port-in-redirects"`
|
||||
|
|
|
|||
|
|
@ -37,6 +37,8 @@ func AddOrUpdateCertAndKey(name string, cert, key, ca []byte) (*ingress.SSLCert,
|
|||
pemFileName := fmt.Sprintf("%v/%v", ingress.DefaultSSLDirectory, pemName)
|
||||
|
||||
tempPemFile, err := ioutil.TempFile(ingress.DefaultSSLDirectory, pemName)
|
||||
|
||||
glog.V(3).Infof("Creating temp file %v for Keypair: %v", tempPemFile.Name(), pemName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not create temp pem file %v: %v", pemFileName, err)
|
||||
}
|
||||
|
|
@ -64,12 +66,12 @@ func AddOrUpdateCertAndKey(name string, cert, key, ca []byte) (*ingress.SSLCert,
|
|||
return nil, err
|
||||
}
|
||||
|
||||
pembBock, _ := pem.Decode(pemCerts)
|
||||
if pembBock == nil {
|
||||
pemBlock, _ := pem.Decode(pemCerts)
|
||||
if pemBlock == nil {
|
||||
return nil, fmt.Errorf("No valid PEM formatted block found")
|
||||
}
|
||||
|
||||
pemCert, err := x509.ParseCertificate(pembBock.Bytes)
|
||||
pemCert, err := x509.ParseCertificate(pemBlock.Bytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -97,21 +99,21 @@ func AddOrUpdateCertAndKey(name string, cert, key, ca []byte) (*ingress.SSLCert,
|
|||
return nil, errors.New(oe)
|
||||
}
|
||||
|
||||
caName := fmt.Sprintf("ca-%v.pem", name)
|
||||
caFileName := fmt.Sprintf("%v/%v", ingress.DefaultSSLDirectory, caName)
|
||||
f, err := os.Create(caFileName)
|
||||
caFile, err := os.OpenFile(pemFileName, os.O_RDWR|os.O_APPEND, 0600)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not create ca pem file %v: %v", caFileName, err)
|
||||
return nil, fmt.Errorf("Could not open file %v for writing additional CA chains: %v", pemFileName, err)
|
||||
}
|
||||
defer f.Close()
|
||||
_, err = f.Write(ca)
|
||||
|
||||
defer caFile.Close()
|
||||
_, err = caFile.Write([]byte("\n"))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not create ca pem file %v: %v", caFileName, err)
|
||||
return nil, fmt.Errorf("could not append CA to cert file %v: %v", pemFileName, err)
|
||||
}
|
||||
f.Write([]byte("\n"))
|
||||
caFile.Write(ca)
|
||||
caFile.Write([]byte("\n"))
|
||||
|
||||
return &ingress.SSLCert{
|
||||
CAFileName: caFileName,
|
||||
CAFileName: pemFileName,
|
||||
PemFileName: pemFileName,
|
||||
PemSHA: pemSHA1(pemFileName),
|
||||
CN: cn,
|
||||
|
|
@ -125,6 +127,36 @@ func AddOrUpdateCertAndKey(name string, cert, key, ca []byte) (*ingress.SSLCert,
|
|||
}, nil
|
||||
}
|
||||
|
||||
// AddCertAuth creates a .pem file with the specified CAs to be used in Cert Authentication
|
||||
// If it's already exists, it's clobbered.
|
||||
func AddCertAuth(name string, ca []byte) (*ingress.SSLCert, error) {
|
||||
|
||||
caName := fmt.Sprintf("ca-%v.pem", name)
|
||||
caFileName := fmt.Sprintf("%v/%v", ingress.DefaultSSLDirectory, caName)
|
||||
|
||||
pemCABlock, _ := pem.Decode(ca)
|
||||
if pemCABlock == nil {
|
||||
return nil, fmt.Errorf("No valid PEM formatted block found")
|
||||
}
|
||||
|
||||
_, err := x509.ParseCertificate(pemCABlock.Bytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = ioutil.WriteFile(caFileName, ca, 0644)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not write CA file %v: %v", caFileName, err)
|
||||
}
|
||||
|
||||
glog.V(3).Infof("Created CA Certificate for authentication: %v", caFileName)
|
||||
return &ingress.SSLCert{
|
||||
CAFileName: caFileName,
|
||||
PemFileName: caFileName,
|
||||
PemSHA: pemSHA1(caFileName),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// SearchDHParamFile iterates all the secrets mounted inside the /etc/nginx-ssl directory
|
||||
// in order to find a file with the name dhparam.pem. If such file exists it will
|
||||
// returns the path. If not it just returns an empty string
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue