Improve the Certificate Authentication and verify depth

This commit is contained in:
Ricardo Pchevuzinske Katz 2017-02-17 16:54:03 -02:00
parent a9ad2fe0b3
commit 679c0ec368
10 changed files with 91 additions and 68 deletions

View file

@ -43,6 +43,8 @@ The following annotations are supported:
|[ingress.kubernetes.io/auth-secret](#authentication)|string|
|[ingress.kubernetes.io/auth-type](#authentication)|basic or digest|
|[ingress.kubernetes.io/auth-url](#external-authentication)|string|
|[ingress.kubernetes.io/auth-tls-secret](#Certificate Authentication)|string|
|[ingress.kubernetes.io/auth-tls-verify-depth](#Certificate Authentication)|number|
|[ingress.kubernetes.io/enable-cors](#enable-cors)|true or false|
|[ingress.kubernetes.io/limit-connections](#rate-limiting)|number|
|[ingress.kubernetes.io/limit-rps](#rate-limiting)|number|
@ -124,6 +126,27 @@ ingress.kubernetes.io/auth-realm: "realm string"
Please check the [auth](examples/auth/README.md) example.
### Certificate Authentication
It's possible to enable Certificate based authentication using additional annotations in Ingres Rule.
The annotations are:
```
ingress.kubernetes.io/auth-tls-secret: secretName
```
The name of the secret that contains the full Certificate Authority chain that is enabled to authenticate against this ingress. It's composed of namespace/secretName
```
ingress.kubernetes.io/auth-tls-verify-depth
```
The validation depth between the provided client certificate and the Certification Authority chain.
Please check the [tls-auth](examples/auth/client-certs/README.md) example.
### Enable CORS
To enable Cross-Origin Resource Sharing (CORS) in an Ingress rule add the annotation `ingress.kubernetes.io/enable-cors: "true"`. This will add a section in the server location enabling this functionality.

View file

@ -225,11 +225,11 @@ http {
{{ $path := buildLocation $location }}
{{ $authPath := buildAuthLocation $location }}
{{ if not (empty $location.CertificateAuth.CAFileName) }}
# PEM sha: {{ $location.CertificateAuth.PemSHA }}
ssl_client_certificate {{ $location.CertificateAuth.CAFileName }};
{{ if not (empty $location.CertificateAuth.AuthSSLCert.CAFileName) }}
# PEM sha: {{ $location.CertificateAuth.AuthSSLCert.PemSHA }}
ssl_client_certificate {{ $location.CertificateAuth.AuthSSLCert.CAFileName }};
ssl_verify_client on;
ssl_verify_depth 10;
ssl_verify_depth {{ $location.CertificateAuth.ValidationDepth }};
{{ end }}
{{ if not (empty $authPath) }}
@ -297,7 +297,7 @@ http {
proxy_set_header Host $host;
# Pass the extracted client certificate to the backend
{{ if not (empty $location.CertificateAuth.CAFileName) }}
{{ if not (empty $location.CertificateAuth.AuthSSLCert.CAFileName) }}
proxy_set_header ssl-client-cert $ssl_client_cert;
{{ end }}

View file

@ -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
}

View file

@ -123,7 +123,7 @@ func (ic *GenericController) getPemCertificate(secretName string) (*ingress.SSLC
s, err = ssl.AddOrUpdateCertAndKey(nsSecName, cert, key, ca)
} else if ca != nil {
glog.V(3).Infof("Found only ca.crt, configuring %v as an AuthCert secret", secretName)
s, err = ssl.AddOrUpdateCertAuth(nsSecName, ca)
s, err = ssl.AddCertAuth(nsSecName, ca)
} else {
return nil, fmt.Errorf("No keypair or CA cert could be found in %v", secretName)
}

View file

@ -702,18 +702,16 @@ func (ic GenericController) GetAuthCertificate(secretName string) (*resolver.Aut
if key != nil {
ic.secretQueue.Enqueue(key)
}
// Enough time to enqueue the new certificate
time.Sleep(5 * time.Second)
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
}

View file

@ -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

View file

@ -26,12 +26,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 (
@ -273,7 +273,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"`

View file

@ -38,7 +38,7 @@ func AddOrUpdateCertAndKey(name string, cert, key, ca []byte) (*ingress.SSLCert,
tempPemFile, err := ioutil.TempFile(ingress.DefaultSSLDirectory, pemName)
glog.V(3).Infof("AddOrUpdateCertAndKey: Creating temp file %v for Keypair: %v", tempPemFile.Name(), 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)
}
@ -66,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
}
@ -127,49 +127,29 @@ func AddOrUpdateCertAndKey(name string, cert, key, ca []byte) (*ingress.SSLCert,
}, nil
}
// AddOrUpdateCertAuth creates a .pem file with the specified CAs to be used in Cert Authentication
func AddOrUpdateCertAuth(name string, ca []byte) (*ingress.SSLCert, error) {
// 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)
tempCAPemFile, err := ioutil.TempFile(ingress.DefaultSSLDirectory, caName)
glog.V(3).Infof("AddOrUpdateCertAuth: Creating temp file %v for CA: %v", tempCAPemFile.Name(), caName)
if err != nil {
return nil, fmt.Errorf("could not create temp CA pem file %v: %v", tempCAPemFile.Name(), err)
}
_, err = tempCAPemFile.Write(ca)
if err != nil {
return nil, fmt.Errorf("could not write to CA pem file %v: %v", tempCAPemFile.Name(), err)
}
err = tempCAPemFile.Close()
if err != nil {
return nil, fmt.Errorf("could not close CA temp pem file %v: %v", tempCAPemFile.Name(), err)
}
pemCACerts, err := ioutil.ReadFile(tempCAPemFile.Name())
if err != nil {
return nil, err
}
pemCABlock, _ := pem.Decode(pemCACerts)
pemCABlock, _ := pem.Decode(ca)
if pemCABlock == nil {
return nil, fmt.Errorf("No valid PEM formatted block found")
}
_, err = x509.ParseCertificate(pemCABlock.Bytes)
_, err := x509.ParseCertificate(pemCABlock.Bytes)
if err != nil {
return nil, err
}
err = os.Rename(tempCAPemFile.Name(), caFileName)
err = ioutil.WriteFile(caFileName, ca, 0644)
if err != nil {
return nil, fmt.Errorf("could not move temp pem file %v to destination %v: %v", tempCAPemFile.Name(), caFileName, err)
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,

View file

@ -26,7 +26,15 @@ Also your ingress must be configured as a HTTPs/TLS Ingress.
## Deployment
The following command instructs the controller to enalbe TLS authentication using the secret from the ``ingress.kubernetes.io/auth-tls-secret``
Certificate Authentication is achieved through 2 annotations on the Ingress, as shown in the [example](nginx-tls-auth.yaml).
|Name|Description|Values|
| --- | --- | --- |
|ingress.kubernetes.io/auth-tls-secret|Sets the secret that contains the authorized CA Chain|string|
|ingress.kubernetes.io/auth-tls-verify-depth|The verification depth Certificate Authentication will make|number (default to 1)|
The following command instructs the controller to enable TLS authentication using the secret from the ``ingress.kubernetes.io/auth-tls-secret``
annotation on the Ingress. Clients must present this cert to the loadbalancer, or they will receive a HTTP 400 response
```console
@ -52,6 +60,7 @@ Rules:
http-svc:80 (<none>)
Annotations:
auth-tls-secret: default/caingress
auth-tls-verify-depth: 3
Events:
FirstSeen LastSeen Count From SubObjectPath Type Reason Message

View file

@ -3,7 +3,8 @@ kind: Ingress
metadata:
annotations:
# Create this with kubectl create secret generic caingress --from-file=ca.crt --namespace=default
ingress.kubernetes.io/auth-tls-secret: default/caingress
ingress.kubernetes.io/auth-tls-secret: "default/caingress"
ingress.kubernetes.io/auth-tls-verify-depth: "3"
kubernetes.io/ingress.class: "nginx"
name: nginx-test
namespace: default