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:
Ricardo Pchevuzinske Katz 2017-02-06 16:16:36 -02:00
parent f5e005f84f
commit a342c0bce3
12 changed files with 349 additions and 52 deletions

View file

@ -44,6 +44,8 @@ The following annotations are supported:
|[ingress.kubernetes.io/auth-secret](#authentication)|string| |[ingress.kubernetes.io/auth-secret](#authentication)|string|
|[ingress.kubernetes.io/auth-type](#authentication)|basic or digest| |[ingress.kubernetes.io/auth-type](#authentication)|basic or digest|
|[ingress.kubernetes.io/auth-url](#external-authentication)|string| |[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/enable-cors](#enable-cors)|true or false|
|[ingress.kubernetes.io/limit-connections](#rate-limiting)|number| |[ingress.kubernetes.io/limit-connections](#rate-limiting)|number|
|[ingress.kubernetes.io/limit-rps](#rate-limiting)|number| |[ingress.kubernetes.io/limit-rps](#rate-limiting)|number|
@ -126,6 +128,27 @@ ingress.kubernetes.io/auth-realm: "realm string"
Please check the [auth](examples/auth/README.md) example. 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 ### 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. 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,10 +225,11 @@ http {
{{ $path := buildLocation $location }} {{ $path := buildLocation $location }}
{{ $authPath := buildAuthLocation $location }} {{ $authPath := buildAuthLocation $location }}
{{ if not (empty $location.CertificateAuth.CertFileName) }} {{ if not (empty $location.CertificateAuth.AuthSSLCert.CAFileName) }}
# PEM sha: {{ $location.CertificateAuth.PemSHA }} # PEM sha: {{ $location.CertificateAuth.AuthSSLCert.PemSHA }}
ssl_client_certificate {{ $location.CertificateAuth.CAFileName }}; ssl_client_certificate {{ $location.CertificateAuth.AuthSSLCert.CAFileName }};
ssl_verify_client on; ssl_verify_client on;
ssl_verify_depth {{ $location.CertificateAuth.ValidationDepth }};
{{ end }} {{ end }}
{{ if not (empty $authPath) }} {{ if not (empty $authPath) }}
@ -295,6 +296,11 @@ http {
proxy_set_header Host $host; proxy_set_header Host $host;
# Pass the extracted client certificate to the backend
{{ if not (empty $location.CertificateAuth.AuthSSLCert.CAFileName) }}
proxy_set_header ssl-client-cert $ssl_client_cert;
{{ end }}
# Pass Real IP # Pass Real IP
proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Real-IP $remote_addr;

View file

@ -28,11 +28,16 @@ import (
const ( const (
// name of the secret // 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 { // AuthSSLConfig contains the AuthSSLCert used for muthual autentication
certResolver resolver.AuthCertificate // and the configured ValidationDepth
type AuthSSLConfig struct {
AuthSSLCert resolver.AuthSSLCert
ValidationDepth int `json:"validationDepth"`
} }
// NewParser creates a new TLS authentication annotation parser // NewParser creates a new TLS authentication annotation parser
@ -40,29 +45,42 @@ func NewParser(resolver resolver.AuthCertificate) parser.IngressAnnotation {
return authTLS{resolver} return authTLS{resolver}
} }
// ParseAnnotations parses the annotations contained in the ingress type authTLS struct {
// rule used to use an external URL as source for authentication 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) { func (a authTLS) Parse(ing *extensions.Ingress) (interface{}, error) {
str, err := parser.GetStringAnnotation(authTLSSecret, ing)
tlsauthsecret, err := parser.GetStringAnnotation(annotationAuthTLSSecret, ing)
if err != nil { if err != nil {
return nil, err return &AuthSSLConfig{}, err
} }
if str == "" { if tlsauthsecret == "" {
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")
} }
_, _, err = k8s.ParseNameNS(str) _, _, err = k8s.ParseNameNS(tlsauthsecret)
if err != nil { 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 { if err != nil {
return nil, ing_errors.LocationDenied{ return &AuthSSLConfig{}, ing_errors.LocationDenied{
Reason: errors.Wrap(err, "error obtaining certificate"), Reason: errors.Wrap(err, "error obtaining certificate"),
} }
} }
return authCert, nil return &AuthSSLConfig{
AuthSSLCert: *authCert,
ValidationDepth: tlsdepth,
}, nil
} }

View file

@ -98,6 +98,8 @@ func (ic *GenericController) syncSecret(k interface{}) error {
return nil 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) { func (ic *GenericController) getPemCertificate(secretName string) (*ingress.SSLCert, error) {
secretInterface, exists, err := ic.secrLister.Store.GetByKey(secretName) secretInterface, exists, err := ic.secrLister.Store.GetByKey(secretName)
if err != nil { if err != nil {
@ -108,19 +110,24 @@ func (ic *GenericController) getPemCertificate(secretName string) (*ingress.SSLC
} }
secret := secretInterface.(*api.Secret) secret := secretInterface.(*api.Secret)
cert, ok := secret.Data[api.TLSCertKey] cert, okcert := secret.Data[api.TLSCertKey]
if !ok { key, okkey := secret.Data[api.TLSPrivateKeyKey]
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)
}
ca := secret.Data["ca.crt"] ca := secret.Data["ca.crt"]
nsSecName := strings.Replace(secretName, "/", "-", -1) 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 { if err != nil {
return nil, err return nil, err
} }

View file

@ -680,16 +680,23 @@ func (ic *GenericController) getBackendServers() ([]*ingress.Backend, []*ingress
// GetAuthCertificate ... // GetAuthCertificate ...
func (ic GenericController) GetAuthCertificate(secretName string) (*resolver.AuthSSLCert, error) { 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) bc, exists := ic.sslCertTracker.Get(secretName)
if !exists { if !exists {
return &resolver.AuthSSLCert{}, fmt.Errorf("secret %v does not exists", secretName) return &resolver.AuthSSLCert{}, fmt.Errorf("secret %v does not exists", secretName)
} }
cert := bc.(*ingress.SSLCert) cert := bc.(*ingress.SSLCert)
return &resolver.AuthSSLCert{ return &resolver.AuthSSLCert{
Secret: secretName, Secret: secretName,
CertFileName: cert.PemFileName, CAFileName: cert.CAFileName,
CAFileName: cert.CAFileName, PemSHA: cert.PemSHA,
PemSHA: cert.PemSHA,
}, nil }, nil
} }

View file

@ -24,11 +24,11 @@ import (
"k8s.io/ingress/core/pkg/ingress" "k8s.io/ingress/core/pkg/ingress"
"k8s.io/ingress/core/pkg/ingress/annotations/auth" "k8s.io/ingress/core/pkg/ingress/annotations/auth"
"k8s.io/ingress/core/pkg/ingress/annotations/authreq" "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/ipwhitelist"
"k8s.io/ingress/core/pkg/ingress/annotations/proxy" "k8s.io/ingress/core/pkg/ingress/annotations/proxy"
"k8s.io/ingress/core/pkg/ingress/annotations/ratelimit" "k8s.io/ingress/core/pkg/ingress/annotations/ratelimit"
"k8s.io/ingress/core/pkg/ingress/annotations/rewrite" "k8s.io/ingress/core/pkg/ingress/annotations/rewrite"
"k8s.io/ingress/core/pkg/ingress/resolver"
"k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/apis/extensions" "k8s.io/kubernetes/pkg/apis/extensions"
) )
@ -136,7 +136,7 @@ func TestMergeLocationAnnotations(t *testing.T) {
"Redirect": rewrite.Redirect{}, "Redirect": rewrite.Redirect{},
"Whitelist": ipwhitelist.SourceRange{}, "Whitelist": ipwhitelist.SourceRange{},
"Proxy": proxy.Configuration{}, "Proxy": proxy.Configuration{},
"CertificateAuth": resolver.AuthSSLCert{}, "CertificateAuth": authtls.AuthSSLConfig{},
"UsePortInRedirects": true, "UsePortInRedirects": true,
} }

View file

@ -37,8 +37,6 @@ type Secret interface {
// AuthCertificate resolves a given secret name into an SSL certificate. // AuthCertificate resolves a given secret name into an SSL certificate.
// The secret must contain 3 keys named: // The secret must contain 3 keys named:
// ca.crt: contains the certificate chain used for authentication // 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 { type AuthCertificate interface {
GetAuthCertificate(string) (*AuthSSLCert, error) GetAuthCertificate(string) (*AuthSSLCert, error)
} }
@ -48,10 +46,6 @@ type AuthCertificate interface {
type AuthSSLCert struct { type AuthSSLCert struct {
// Secret contains the name of the secret this was fetched from // Secret contains the name of the secret this was fetched from
Secret string `json:"secret"` 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 contains the path to the secrets 'ca.crt'
CAFileName string `json:"caFilename"` CAFileName string `json:"caFilename"`
// PemSHA contains the SHA1 hash of the 'tls.crt' value // PemSHA contains the SHA1 hash of the 'tls.crt' value

View file

@ -27,12 +27,12 @@ import (
cache_store "k8s.io/ingress/core/pkg/cache" cache_store "k8s.io/ingress/core/pkg/cache"
"k8s.io/ingress/core/pkg/ingress/annotations/auth" "k8s.io/ingress/core/pkg/ingress/annotations/auth"
"k8s.io/ingress/core/pkg/ingress/annotations/authreq" "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/ipwhitelist"
"k8s.io/ingress/core/pkg/ingress/annotations/proxy" "k8s.io/ingress/core/pkg/ingress/annotations/proxy"
"k8s.io/ingress/core/pkg/ingress/annotations/ratelimit" "k8s.io/ingress/core/pkg/ingress/annotations/ratelimit"
"k8s.io/ingress/core/pkg/ingress/annotations/rewrite" "k8s.io/ingress/core/pkg/ingress/annotations/rewrite"
"k8s.io/ingress/core/pkg/ingress/defaults" "k8s.io/ingress/core/pkg/ingress/defaults"
"k8s.io/ingress/core/pkg/ingress/resolver"
) )
var ( var (
@ -274,7 +274,7 @@ type Location struct {
// CertificateAuth indicates the access to this location requires // CertificateAuth indicates the access to this location requires
// external authentication // external authentication
// +optional // +optional
CertificateAuth resolver.AuthSSLCert `json:"certificateAuth,omitempty"` CertificateAuth authtls.AuthSSLConfig `json:"certificateAuth,omitempty"`
// UsePortInRedirects indicates if redirects must specify the port // UsePortInRedirects indicates if redirects must specify the port
// +optional // +optional
UsePortInRedirects bool `json:"use-port-in-redirects"` UsePortInRedirects bool `json:"use-port-in-redirects"`

View file

@ -37,6 +37,8 @@ func AddOrUpdateCertAndKey(name string, cert, key, ca []byte) (*ingress.SSLCert,
pemFileName := fmt.Sprintf("%v/%v", ingress.DefaultSSLDirectory, pemName) pemFileName := fmt.Sprintf("%v/%v", ingress.DefaultSSLDirectory, pemName)
tempPemFile, err := ioutil.TempFile(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 { if err != nil {
return nil, fmt.Errorf("could not create temp pem file %v: %v", pemFileName, err) 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 return nil, err
} }
pembBock, _ := pem.Decode(pemCerts) pemBlock, _ := pem.Decode(pemCerts)
if pembBock == nil { if pemBlock == nil {
return nil, fmt.Errorf("No valid PEM formatted block found") 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 { if err != nil {
return nil, err return nil, err
} }
@ -97,21 +99,21 @@ func AddOrUpdateCertAndKey(name string, cert, key, ca []byte) (*ingress.SSLCert,
return nil, errors.New(oe) return nil, errors.New(oe)
} }
caName := fmt.Sprintf("ca-%v.pem", name) caFile, err := os.OpenFile(pemFileName, os.O_RDWR|os.O_APPEND, 0600)
caFileName := fmt.Sprintf("%v/%v", ingress.DefaultSSLDirectory, caName)
f, err := os.Create(caFileName)
if err != nil { 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 { 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{ return &ingress.SSLCert{
CAFileName: caFileName, CAFileName: pemFileName,
PemFileName: pemFileName, PemFileName: pemFileName,
PemSHA: pemSHA1(pemFileName), PemSHA: pemSHA1(pemFileName),
CN: cn, CN: cn,
@ -125,6 +127,36 @@ func AddOrUpdateCertAndKey(name string, cert, key, ca []byte) (*ingress.SSLCert,
}, nil }, 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 // 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 // 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 // returns the path. If not it just returns an empty string

View file

@ -36,6 +36,105 @@ $ kubectl create secret tls tls-secret --key tls.key --cert tls.crt
secret "tls-secret" created secret "tls-secret" created
``` ```
## CA Authentication
You can act as your very own CA, or use an existing one. As an exercise / learning, we're going to generate our
own CA, and also generate a client certificate.
These instructions are based in CoreOS OpenSSL [instructions](https://coreos.com/kubernetes/docs/latest/openssl.html)
### Generating a CA
First of all, you've to generate a CA. This is going to be the one who will sign your client certificates.
In real production world, you may face CAs with intermediate certificates, as the following:
```console
$ openssl s_client -connect www.google.com:443
[...]
---
Certificate chain
0 s:/C=US/ST=California/L=Mountain View/O=Google Inc/CN=www.google.com
i:/C=US/O=Google Inc/CN=Google Internet Authority G2
1 s:/C=US/O=Google Inc/CN=Google Internet Authority G2
i:/C=US/O=GeoTrust Inc./CN=GeoTrust Global CA
2 s:/C=US/O=GeoTrust Inc./CN=GeoTrust Global CA
i:/C=US/O=Equifax/OU=Equifax Secure Certificate Authority
```
To generate our CA Certificate, we've to run the following commands:
```console
$ openssl genrsa -out ca.key 2048
$ openssl req -x509 -new -nodes -key ca.key -days 10000 -out ca.crt -subj "/CN=example-ca"
```
This will generate two files: A private key (ca.key) and a public key (ca.crt). This CA is valid for 10000 days.
The ca.crt can be used later in the step of creation of CA authentication secret.
### Generating the client certificate
The following steps generates a client certificate signed by the CA generated above. This client can be
used to authenticate in a tls-auth configured ingress.
First, we need to generate an 'openssl.cnf' file that will be used while signing the keys:
```
[req]
req_extensions = v3_req
distinguished_name = req_distinguished_name
[req_distinguished_name]
[ v3_req ]
basicConstraints = CA:FALSE
keyUsage = nonRepudiation, digitalSignature, keyEncipherment
```
Then, a user generates his very own private key (that he needs to keep secret)
and a CSR (Certificate Signing Request) that will be sent to the CA to sign and generate a certificate.
```console
$ openssl genrsa -out client1.key 2048
$ openssl req -new -key client1.key -out client1.csr -subj "/CN=client1" -config openssl.cnf
```
As the CA receives the generated 'client1.csr' file, it signs it and generates a client.crt certificate:
```console
$ openssl x509 -req -in client1.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out client1.crt -days 365 -extensions v3_req -extfile openssl.cnf
```
Then, you'll have 3 files: the client.key (user's private key), client.crt (user's public key) and client.csr (disposable CSR).
### Creating the CA Authentication secret
If you're using the CA Authentication feature, you need to generate a secret containing
all the authorized CAs. You must download them from your CA site in PEM format (like the following):
```
-----BEGIN CERTIFICATE-----
[....]
-----END CERTIFICATE-----
```
You can have as many certificates as you want. If they're in the binary DER format,
you can convert them as the following:
```console
$ openssl x509 -in certificate.der -inform der -out certificate.crt -outform pem
```
Then, you've to concatenate them all in only one file, named 'ca.crt' as the following:
```console
$ cat certificate1.crt certificate2.crt certificate3.crt >> ca.crt
```
The final step is to create a secret with the content of this file. This secret is going to be used in
the TLS Auth directive:
```console
$ kubectl create secret generic caingress --namespace=default --from-file=ca.crt
```
## Test HTTP Service ## Test HTTP Service
All examples that require a test HTTP Service use the standard http-svc pod, All examples that require a test HTTP Service use the standard http-svc pod,

View file

@ -0,0 +1,86 @@
# TLS authentication
This example demonstrates how to enable the TLS Authentication through the nginx Ingress controller.
## Terminology
* CA: Certificate authority signing the client cert, in this example we will play the role of a CA.
You can generate a CA cert as show in this doc.
* CA Certificate(s) - Certificate Authority public key. Client certs must chain back to this cert,
meaning the Issuer field of some certificate in the chain leading up to the client cert must contain
the name of this CA. For purposes of this example, this is a self signed certificate.
* CA chains: A chain of certificates where the parent has a Subject field matching the Issuer field of
the child, except for the root, which has Issuer == Subject.
* Client Cert: Certificate used by the clients to authenticate themselves with the loadbalancer/backends.
## Prerequisites
You need a valid CA File, composed of a group of valid enabled CAs. This MUST be in PEM Format.
The instructions are described [here](../../../PREREQUISITES.md#ca-authentication)
Also your ingress must be configured as a HTTPs/TLS Ingress.
## Deployment
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
$ kubectl create -f nginx-tls-auth.yaml
```
## Validation
You can confirm that the Ingress works.
```console
$ kubectl describe ing nginx-test
Name: nginx-test
Namespace: default
Address: 104.198.183.6
Default backend: default-http-backend:80 (10.180.0.4:8080,10.240.0.2:8080)
TLS:
tls-secret terminates ingress.test.com
Rules:
Host Path Backends
---- ---- --------
*
http-svc:80 (<none>)
Annotations:
auth-tls-secret: default/caingress
auth-tls-verify-depth: 3
Events:
FirstSeen LastSeen Count From SubObjectPath Type Reason Message
--------- -------- ----- ---- ------------- -------- ------ -------
7s 7s 1 {nginx-ingress-controller } Normal CREATE default/nginx-test
7s 7s 1 {nginx-ingress-controller } Normal UPDATE default/nginx-test
7s 7s 1 {nginx-ingress-controller } Normal CREATE ip: 104.198.183.6
7s 7s 1 {nginx-ingress-controller } Warning MAPPING Ingress rule 'default/nginx-test' contains no path definition. Assuming /
$ curl -k https://ingress.test.com
HTTP/1.1 400 Bad Request
Server: nginx/1.11.9
$ curl -I -k --key ~/user.key --cert ~/user.cer https://ingress.test.com
HTTP/1.1 200 OK
Server: nginx/1.11.9
```
You must use the full DNS name while testing, as NGINX relies on the Server Name (SNI) to select the correct Ingress to be used.
The curl version used here was ``curl 7.47.0``

View file

@ -0,0 +1,25 @@
apiVersion: extensions/v1beta1
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-verify-depth: "3"
kubernetes.io/ingress.class: "nginx"
name: nginx-test
namespace: default
spec:
rules:
- host: ingress.test.com
http:
paths:
- backend:
serviceName: http-svc:80
servicePort: 80
path: /
tls:
- hosts:
- ingress.test.com
# Create this cert as described in 'multi-tls' example
secretName: cert