Additional AuthTLS assertions and doc change to demonstrate auth-tls-secret enables the other AuthTLS annotations (#7202)
* Fix indentation of nested list in AuthTLS annotations Also, put `<annotation>`: <description text>` on a single line in Markdown markup, which will match what gets rendered eventually. On the other hand, for the line on auth-tls-secret (This annotation expects the Secret name in the form "namespace/secretName"), its Markdown markup suggests that the author wanted the line to start on its own line, but currently this gets rendered on the same line. It's nice for this to be on its own line, since it's kind of a "note" about the annotation syntax. Format/indent the markup appropriately so that it shows up on its line. * Fix indentation of nested list in CORS annotations Also, put `<annotation>`: <description text>` on a single line in Markdown markup, which will match what gets rendered eventually. On the other hand, for lines noting the allowed characters (This is a multi-valued field...), its Markdown markup suggests that the author wanted the line to start on its own line, but currently this gets rendered on the same line. It's nice for this to be on its own line, since it's kind of a "note" about the annotation syntax. Format/indent the markup appropriately so that it shows up on its line. * Replace f.HTTPTestClientWithTLSConfig() in AuthTLS E2E, the odd one out for requests without client certs * Demonstrate and document that auth-tls-secret enables the other AuthTLS annotations like verify client, depth * Split E2E for auth-tls-error-page and *-pass-certificate-to-upstream
This commit is contained in:
parent
e7791941ba
commit
cf9ae96d72
3 changed files with 143 additions and 62 deletions
|
@ -244,23 +244,18 @@ It is possible to enable Client Certificate Authentication using additional anno
|
|||
|
||||
Client Certificate Authentication is applied per host and it is not possible to specify rules that differ for individual paths.
|
||||
|
||||
The annotations are:
|
||||
To enable, add the annotation `nginx.ingress.kubernetes.io/auth-tls-secret: namespace/secretName`. This secret must have a file named `ca.crt` containing the full Certificate Authority chain `ca.crt` that is enabled to authenticate against this Ingress.
|
||||
|
||||
* `nginx.ingress.kubernetes.io/auth-tls-secret: secretName`:
|
||||
The name of the Secret that contains the full Certificate Authority chain `ca.crt` that is enabled to authenticate against this Ingress.
|
||||
This annotation expects the Secret name in the form "namespace/secretName".
|
||||
* `nginx.ingress.kubernetes.io/auth-tls-verify-depth`:
|
||||
The validation depth between the provided client certificate and the Certification Authority chain.
|
||||
* `nginx.ingress.kubernetes.io/auth-tls-verify-client`:
|
||||
Enables verification of client certificates. Possible values are:
|
||||
* `off`: Don't request client certificates and don't do client certificate verification. (default)
|
||||
* `on`: Request a client certificate that must be signed by a certificate that is included in the secret key `ca.crt` of the secret specified by `nginx.ingress.kubernetes.io/auth-tls-secret: secretName`. Failed certificate verification will result in a status code 400 (Bad Request).
|
||||
* `optional`: Do optional client certificate validation against the CAs from `auth-tls-secret`. The request fails with status code 400 (Bad Request) when a certificate is provided that is not signed by the CA. When no or an otherwise invalid certificate is provided, the request does not fail, but instead the verification result is sent to the upstream service.
|
||||
* `optional_no_ca`: Do optional client certificate validation, but do not fail the request when the client certificate is not signed by the CAs from `auth-tls-secret`. Certificate verification result is sent to the upstream service.
|
||||
* `nginx.ingress.kubernetes.io/auth-tls-error-page`:
|
||||
The URL/Page that user should be redirected in case of a Certificate Authentication Error
|
||||
* `nginx.ingress.kubernetes.io/auth-tls-pass-certificate-to-upstream`:
|
||||
Indicates if the received certificates should be passed or not to the upstream server in the header `ssl-client-cert`. Possible values are "true" or "false" (default).
|
||||
You can further customize client certificate authentication and behaviour with these annotations:
|
||||
|
||||
* `nginx.ingress.kubernetes.io/auth-tls-verify-depth`: The validation depth between the provided client certificate and the Certification Authority chain. (default: 1)
|
||||
* `nginx.ingress.kubernetes.io/auth-tls-verify-client`: Enables verification of client certificates. Possible values are:
|
||||
* `on`: Request a client certificate that must be signed by a certificate that is included in the secret key `ca.crt` of the secret specified by `nginx.ingress.kubernetes.io/auth-tls-secret: namespace/secretName`. Failed certificate verification will result in a status code 400 (Bad Request) (default)
|
||||
* `off`: Don't request client certificates and don't do client certificate verification.
|
||||
* `optional`: Do optional client certificate validation against the CAs from `auth-tls-secret`. The request fails with status code 400 (Bad Request) when a certificate is provided that is not signed by the CA. When no or an otherwise invalid certificate is provided, the request does not fail, but instead the verification result is sent to the upstream service.
|
||||
* `optional_no_ca`: Do optional client certificate validation, but do not fail the request when the client certificate is not signed by the CAs from `auth-tls-secret`. Certificate verification result is sent to the upstream service.
|
||||
* `nginx.ingress.kubernetes.io/auth-tls-error-page`: The URL/Page that user should be redirected in case of a Certificate Authentication Error
|
||||
* `nginx.ingress.kubernetes.io/auth-tls-pass-certificate-to-upstream`: Indicates if the received certificates should be passed or not to the upstream server in the header `ssl-client-cert`. Possible values are "true" or "false" (default).
|
||||
|
||||
The following headers are sent to the upstream service according to the `auth-tls-*` annotations:
|
||||
|
||||
|
@ -333,39 +328,43 @@ location enabling this functionality.
|
|||
|
||||
CORS can be controlled with the following annotations:
|
||||
|
||||
* `nginx.ingress.kubernetes.io/cors-allow-methods`
|
||||
controls which methods are accepted. This is a multi-valued field, separated by ',' and
|
||||
accepts only letters (upper and lower case).
|
||||
- Default: `GET, PUT, POST, DELETE, PATCH, OPTIONS`
|
||||
- Example: `nginx.ingress.kubernetes.io/cors-allow-methods: "PUT, GET, POST, OPTIONS"`
|
||||
* `nginx.ingress.kubernetes.io/cors-allow-methods`: Controls which methods are accepted.
|
||||
|
||||
* `nginx.ingress.kubernetes.io/cors-allow-headers`
|
||||
controls which headers are accepted. This is a multi-valued field, separated by ',' and accepts letters,
|
||||
numbers, _ and -.
|
||||
- Default: `DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization`
|
||||
- Example: `nginx.ingress.kubernetes.io/cors-allow-headers: "X-Forwarded-For, X-app123-XPTO"`
|
||||
This is a multi-valued field, separated by ',' and accepts only letters (upper and lower case).
|
||||
|
||||
* `nginx.ingress.kubernetes.io/cors-expose-headers`
|
||||
controls which headers are exposed to response. This is a multi-valued field, separated by ',' and accepts
|
||||
letters, numbers, _, - and *.
|
||||
- Default: *empty*
|
||||
- Example: `nginx.ingress.kubernetes.io/cors-expose-headers: "*, X-CustomResponseHeader"`
|
||||
- Default: `GET, PUT, POST, DELETE, PATCH, OPTIONS`
|
||||
- Example: `nginx.ingress.kubernetes.io/cors-allow-methods: "PUT, GET, POST, OPTIONS"`
|
||||
|
||||
* `nginx.ingress.kubernetes.io/cors-allow-origin`
|
||||
controls what's the accepted Origin for CORS.
|
||||
This is a single field value, with the following format: `http(s)://origin-site.com` or `http(s)://origin-site.com:port`
|
||||
- Default: `*`
|
||||
- Example: `nginx.ingress.kubernetes.io/cors-allow-origin: "https://origin-site.com:4443"`
|
||||
* `nginx.ingress.kubernetes.io/cors-allow-headers`: Controls which headers are accepted.
|
||||
|
||||
* `nginx.ingress.kubernetes.io/cors-allow-credentials`
|
||||
controls if credentials can be passed during CORS operations.
|
||||
- Default: `true`
|
||||
- Example: `nginx.ingress.kubernetes.io/cors-allow-credentials: "false"`
|
||||
This is a multi-valued field, separated by ',' and accepts letters, numbers, _ and -.
|
||||
|
||||
* `nginx.ingress.kubernetes.io/cors-max-age`
|
||||
controls how long preflight requests can be cached.
|
||||
Default: `1728000`
|
||||
Example: `nginx.ingress.kubernetes.io/cors-max-age: 600`
|
||||
- Default: `DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization`
|
||||
- Example: `nginx.ingress.kubernetes.io/cors-allow-headers: "X-Forwarded-For, X-app123-XPTO"`
|
||||
|
||||
* `nginx.ingress.kubernetes.io/cors-expose-headers`: Controls which headers are exposed to response.
|
||||
|
||||
This is a multi-valued field, separated by ',' and accepts letters, numbers, _, - and *.
|
||||
|
||||
- Default: *empty*
|
||||
- Example: `nginx.ingress.kubernetes.io/cors-expose-headers: "*, X-CustomResponseHeader"`
|
||||
|
||||
* `nginx.ingress.kubernetes.io/cors-allow-origin`: Controls what's the accepted Origin for CORS.
|
||||
|
||||
This is a single field value, with the following format: `http(s)://origin-site.com` or `http(s)://origin-site.com:port`
|
||||
|
||||
- Default: `*`
|
||||
- Example: `nginx.ingress.kubernetes.io/cors-allow-origin: "https://origin-site.com:4443"`
|
||||
|
||||
* `nginx.ingress.kubernetes.io/cors-allow-credentials`: Controls if credentials can be passed during CORS operations.
|
||||
|
||||
- Default: `true`
|
||||
- Example: `nginx.ingress.kubernetes.io/cors-allow-credentials: "false"`
|
||||
|
||||
* `nginx.ingress.kubernetes.io/cors-max-age`: Controls how long preflight requests can be cached.
|
||||
|
||||
- Default: `1728000`
|
||||
- Example: `nginx.ingress.kubernetes.io/cors-max-age: 600`
|
||||
|
||||
!!! note
|
||||
For more information please see [https://enable-cors.org](https://enable-cors.org/server_nginx.html)
|
||||
|
|
|
@ -94,10 +94,6 @@ func TestAnnotations(t *testing.T) {
|
|||
data := map[string]string{}
|
||||
|
||||
data[parser.GetAnnotationWithPrefix("auth-tls-secret")] = "default/demo-secret"
|
||||
data[parser.GetAnnotationWithPrefix("auth-tls-verify-client")] = "off"
|
||||
data[parser.GetAnnotationWithPrefix("auth-tls-verify-depth")] = "1"
|
||||
data[parser.GetAnnotationWithPrefix("auth-tls-error-page")] = "ok.com/error"
|
||||
data[parser.GetAnnotationWithPrefix("auth-tls-pass-certificate-to-upstream")] = "true"
|
||||
|
||||
ing.SetAnnotations(data)
|
||||
|
||||
|
@ -120,12 +116,45 @@ func TestAnnotations(t *testing.T) {
|
|||
if u.AuthSSLCert.Secret != secret.Secret {
|
||||
t.Errorf("expected %v but got %v", secret.Secret, u.AuthSSLCert.Secret)
|
||||
}
|
||||
if u.VerifyClient != "off" {
|
||||
t.Errorf("expected %v but got %v", "off", u.VerifyClient)
|
||||
if u.VerifyClient != "on" {
|
||||
t.Errorf("expected %v but got %v", "on", u.VerifyClient)
|
||||
}
|
||||
if u.ValidationDepth != 1 {
|
||||
t.Errorf("expected %v but got %v", 1, u.ValidationDepth)
|
||||
}
|
||||
if u.ErrorPage != "" {
|
||||
t.Errorf("expected %v but got %v", "", u.ErrorPage)
|
||||
}
|
||||
if u.PassCertToUpstream != false {
|
||||
t.Errorf("expected %v but got %v", false, u.PassCertToUpstream)
|
||||
}
|
||||
|
||||
data[parser.GetAnnotationWithPrefix("auth-tls-verify-client")] = "off"
|
||||
data[parser.GetAnnotationWithPrefix("auth-tls-verify-depth")] = "2"
|
||||
data[parser.GetAnnotationWithPrefix("auth-tls-error-page")] = "ok.com/error"
|
||||
data[parser.GetAnnotationWithPrefix("auth-tls-pass-certificate-to-upstream")] = "true"
|
||||
|
||||
ing.SetAnnotations(data)
|
||||
|
||||
i, err = NewParser(fakeSecret).Parse(ing)
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error with ingress: %v", err)
|
||||
}
|
||||
|
||||
u, ok = i.(*Config)
|
||||
if !ok {
|
||||
t.Errorf("expected *Config but got %v", u)
|
||||
}
|
||||
|
||||
if u.AuthSSLCert.Secret != secret.Secret {
|
||||
t.Errorf("expected %v but got %v", secret.Secret, u.AuthSSLCert.Secret)
|
||||
}
|
||||
if u.VerifyClient != "off" {
|
||||
t.Errorf("expected %v but got %v", "off", u.VerifyClient)
|
||||
}
|
||||
if u.ValidationDepth != 2 {
|
||||
t.Errorf("expected %v but got %v", 2, u.ValidationDepth)
|
||||
}
|
||||
if u.ErrorPage != "ok.com/error" {
|
||||
t.Errorf("expected %v but got %v", "ok.com/error", u.ErrorPage)
|
||||
}
|
||||
|
|
|
@ -17,7 +17,6 @@ limitations under the License.
|
|||
package annotations
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
@ -34,7 +33,7 @@ var _ = framework.DescribeAnnotation("auth-tls-*", func() {
|
|||
f.NewEchoDeploymentWithReplicas(2)
|
||||
})
|
||||
|
||||
ginkgo.It("should set valid auth-tls-secret", func() {
|
||||
ginkgo.It("should set sslClientCertificate, sslVerifyClient and sslVerifyDepth with auth-tls-secret", func() {
|
||||
host := "authtls.foo.com"
|
||||
nameSpace := f.Namespace
|
||||
|
||||
|
@ -45,16 +44,28 @@ var _ = framework.DescribeAnnotation("auth-tls-*", func() {
|
|||
nameSpace)
|
||||
assert.Nil(ginkgo.GinkgoT(), err)
|
||||
|
||||
annotations := map[string]string{
|
||||
annotations := map[string]string{}
|
||||
|
||||
ing := f.EnsureIngress(framework.NewSingleIngressWithTLS(host, "/", host, []string{host}, nameSpace, framework.EchoService, 80, annotations))
|
||||
|
||||
f.WaitForNginxServer(host,
|
||||
func(server string) bool {
|
||||
return !strings.Contains(server, "ssl_client_certificate") &&
|
||||
!strings.Contains(server, "ssl_verify_client") &&
|
||||
!strings.Contains(server, "ssl_verify_depth")
|
||||
})
|
||||
|
||||
annotations = map[string]string{
|
||||
"nginx.ingress.kubernetes.io/auth-tls-secret": nameSpace + "/" + host,
|
||||
}
|
||||
|
||||
f.EnsureIngress(framework.NewSingleIngressWithTLS(host, "/", host, []string{host}, nameSpace, framework.EchoService, 80, annotations))
|
||||
ing.SetAnnotations(annotations)
|
||||
f.UpdateIngress(ing)
|
||||
|
||||
assertSslClientCertificateConfig(f, host, "on", "1")
|
||||
|
||||
// Send Request without Client Certs
|
||||
f.HTTPTestClientWithTLSConfig(&tls.Config{ServerName: host, InsecureSkipVerify: true}).
|
||||
f.HTTPTestClient().
|
||||
GET("/").
|
||||
WithURL(f.GetURL(framework.HTTPS)).
|
||||
WithHeader("Host", host).
|
||||
|
@ -100,7 +111,7 @@ var _ = framework.DescribeAnnotation("auth-tls-*", func() {
|
|||
Status(http.StatusOK)
|
||||
})
|
||||
|
||||
ginkgo.It("should set valid auth-tls-secret, pass certificate to upstream, and error page", func() {
|
||||
ginkgo.It("should 302 redirect to error page instead of 400 when auth-tls-error-page is set", func() {
|
||||
host := "authtls.foo.com"
|
||||
nameSpace := f.Namespace
|
||||
|
||||
|
@ -114,9 +125,8 @@ var _ = framework.DescribeAnnotation("auth-tls-*", func() {
|
|||
assert.Nil(ginkgo.GinkgoT(), err)
|
||||
|
||||
annotations := map[string]string{
|
||||
"nginx.ingress.kubernetes.io/auth-tls-secret": nameSpace + "/" + host,
|
||||
"nginx.ingress.kubernetes.io/auth-tls-error-page": f.GetURL(framework.HTTP) + errorPath,
|
||||
"nginx.ingress.kubernetes.io/auth-tls-pass-certificate-to-upstream": "true",
|
||||
"nginx.ingress.kubernetes.io/auth-tls-secret": nameSpace + "/" + host,
|
||||
"nginx.ingress.kubernetes.io/auth-tls-error-page": f.GetURL(framework.HTTP) + errorPath,
|
||||
}
|
||||
|
||||
f.EnsureIngress(framework.NewSingleIngressWithTLS(host, "/", host, []string{host}, nameSpace, framework.EchoService, 80, annotations))
|
||||
|
@ -124,12 +134,10 @@ var _ = framework.DescribeAnnotation("auth-tls-*", func() {
|
|||
assertSslClientCertificateConfig(f, host, "on", "1")
|
||||
|
||||
sslErrorPage := fmt.Sprintf("error_page 495 496 = %s;", f.GetURL(framework.HTTP)+errorPath)
|
||||
sslUpstreamClientCert := "proxy_set_header ssl-client-cert $ssl_client_escaped_cert;"
|
||||
|
||||
f.WaitForNginxServer(host,
|
||||
func(server string) bool {
|
||||
return strings.Contains(server, sslErrorPage) &&
|
||||
strings.Contains(server, sslUpstreamClientCert)
|
||||
return strings.Contains(server, sslErrorPage)
|
||||
})
|
||||
|
||||
// Send Request without Client Certs
|
||||
|
@ -150,6 +158,51 @@ var _ = framework.DescribeAnnotation("auth-tls-*", func() {
|
|||
Status(http.StatusOK)
|
||||
})
|
||||
|
||||
ginkgo.It("should pass URL-encoded certificate to upstream", func() {
|
||||
host := "authtls.foo.com"
|
||||
nameSpace := f.Namespace
|
||||
|
||||
clientConfig, err := framework.CreateIngressMASecret(
|
||||
f.KubeClientSet,
|
||||
host,
|
||||
host,
|
||||
nameSpace)
|
||||
assert.Nil(ginkgo.GinkgoT(), err)
|
||||
|
||||
annotations := map[string]string{
|
||||
"nginx.ingress.kubernetes.io/auth-tls-secret": nameSpace + "/" + host,
|
||||
"nginx.ingress.kubernetes.io/auth-tls-pass-certificate-to-upstream": "true",
|
||||
}
|
||||
|
||||
f.EnsureIngress(framework.NewSingleIngressWithTLS(host, "/", host, []string{host}, nameSpace, framework.EchoService, 80, annotations))
|
||||
|
||||
assertSslClientCertificateConfig(f, host, "on", "1")
|
||||
|
||||
sslUpstreamClientCert := "proxy_set_header ssl-client-cert $ssl_client_escaped_cert;"
|
||||
|
||||
f.WaitForNginxServer(host,
|
||||
func(server string) bool {
|
||||
return strings.Contains(server, sslUpstreamClientCert)
|
||||
})
|
||||
|
||||
// Send Request without Client Certs
|
||||
f.HTTPTestClient().
|
||||
GET("/").
|
||||
WithURL(f.GetURL(framework.HTTPS)).
|
||||
WithHeader("Host", host).
|
||||
Expect().
|
||||
Status(http.StatusBadRequest)
|
||||
|
||||
// Send Request Passing the Client Certs
|
||||
f.HTTPTestClientWithTLSConfig(clientConfig).
|
||||
GET("/").
|
||||
WithURL(f.GetURL(framework.HTTPS)).
|
||||
WithHeader("Host", host).
|
||||
Expect().
|
||||
Status(http.StatusOK).
|
||||
Body().Contains("ssl-client-cert=-----BEGIN%20CERTIFICATE-----%0A")
|
||||
})
|
||||
|
||||
ginkgo.It("should validate auth-tls-verify-client", func() {
|
||||
host := "authtls.foo.com"
|
||||
nameSpace := f.Namespace
|
||||
|
|
Loading…
Reference in a new issue