Improve the Certificate Authentication and verify depth
This commit is contained in:
parent
a9ad2fe0b3
commit
679c0ec368
10 changed files with 91 additions and 68 deletions
|
@ -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.
|
||||
|
|
|
@ -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 }}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"`
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue