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:
Ray 2021-09-08 01:35:16 +08:00 committed by GitHub
parent e7791941ba
commit cf9ae96d72
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 143 additions and 62 deletions

View file

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

View file

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

View file

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