From dfce8c17c3e3cc031d95639d06acf79f81076a15 Mon Sep 17 00:00:00 2001
From: Gautier Delorme
Date: Fri, 14 Oct 2022 16:50:15 +0200
Subject: [PATCH 1/3] Add support for optional certificate validation
Signed-off-by: Gautier Delorme
---
apis/projectcontour/v1/httpproxy.go | 8 +
.../unreleased/4796-gautierdelorme-minor.md | 5 +
examples/contour/01-crds.yaml | 8 +
examples/render/contour-deployment.yaml | 8 +
.../render/contour-gateway-provisioner.yaml | 8 +
examples/render/contour-gateway.yaml | 8 +
examples/render/contour.yaml | 8 +
internal/dag/builder_test.go | 61 ++++++++
internal/dag/dag.go | 3 +
internal/dag/httpproxy_processor.go | 1 +
internal/envoy/v3/auth.go | 2 +-
internal/envoy/v3/listener_test.go | 26 ++++
.../v3/downstreamvalidation_test.go | 49 +++++++
.../docs/main/config/api-reference.html | 17 +++
.../docs/main/config/tls-termination.md | 21 +++
test/e2e/httpproxy/client_cert_auth_test.go | 137 ++++++++++++++++++
16 files changed, 369 insertions(+), 1 deletion(-)
create mode 100644 changelogs/unreleased/4796-gautierdelorme-minor.md
diff --git a/apis/projectcontour/v1/httpproxy.go b/apis/projectcontour/v1/httpproxy.go
index 2304d27cda8..8871e5c97f6 100644
--- a/apis/projectcontour/v1/httpproxy.go
+++ b/apis/projectcontour/v1/httpproxy.go
@@ -1131,6 +1131,14 @@ type DownstreamValidation struct {
// certificate chain will be subject to validation by CRL.
// +optional
OnlyVerifyLeafCertCrl bool `json:"crlOnlyVerifyLeafCert"`
+
+ // OnlyRequestClientCert when set to true will request a client certificate
+ // but allow the connection to continue if the client does not provide one.
+ // If a client certificate is sent, it will be verified according to the
+ // other properties, which includes disabling validation if
+ // SkipClientCertValidation is set. Defaults to false.
+ // +optional
+ OnlyRequestClientCert bool `json:"onlyRequestClientCert"`
}
// HTTPProxyStatus reports the current state of the HTTPProxy.
diff --git a/changelogs/unreleased/4796-gautierdelorme-minor.md b/changelogs/unreleased/4796-gautierdelorme-minor.md
new file mode 100644
index 00000000000..06c536c5eb4
--- /dev/null
+++ b/changelogs/unreleased/4796-gautierdelorme-minor.md
@@ -0,0 +1,5 @@
+## Optional Client Certificate Validation
+
+By default, client certificates are required but some applications might support different authentication schemes.
+You can now set the `httpproxy.spec.virtualhost.tls.clientValidation.onlyRequestClientCert` field to `true`. A client certificate will be requested, but the connection is allowed to continue if the client does not provide one.
+If a client certificate is sent, it will be verified according to the other properties, which includes disabling validations if `httpproxy.spec.virtualhost.tls.clientValidation.skipClientCertValidation` is set.
diff --git a/examples/contour/01-crds.yaml b/examples/contour/01-crds.yaml
index f23726a0930..39cb833fbaa 100644
--- a/examples/contour/01-crds.yaml
+++ b/examples/contour/01-crds.yaml
@@ -5766,6 +5766,14 @@ spec:
secrets are limited to 1MiB in size.
minLength: 1
type: string
+ onlyRequestClientCert:
+ description: OnlyRequestClientCert when set to true will
+ request a client certificate but allow the connection
+ to continue if the client does not provide one. If a
+ client certificate is sent, it will be verified according
+ to the other properties, which includes disabling validation
+ if SkipClientCertValidation is set. Defaults to false.
+ type: boolean
skipClientCertValidation:
description: SkipClientCertValidation disables downstream
client certificate validation. Defaults to false. This
diff --git a/examples/render/contour-deployment.yaml b/examples/render/contour-deployment.yaml
index b61b359c7e6..bd4e0b133b8 100644
--- a/examples/render/contour-deployment.yaml
+++ b/examples/render/contour-deployment.yaml
@@ -5975,6 +5975,14 @@ spec:
secrets are limited to 1MiB in size.
minLength: 1
type: string
+ onlyRequestClientCert:
+ description: OnlyRequestClientCert when set to true will
+ request a client certificate but allow the connection
+ to continue if the client does not provide one. If a
+ client certificate is sent, it will be verified according
+ to the other properties, which includes disabling validation
+ if SkipClientCertValidation is set. Defaults to false.
+ type: boolean
skipClientCertValidation:
description: SkipClientCertValidation disables downstream
client certificate validation. Defaults to false. This
diff --git a/examples/render/contour-gateway-provisioner.yaml b/examples/render/contour-gateway-provisioner.yaml
index 13c563a9ab3..b07e69997e9 100644
--- a/examples/render/contour-gateway-provisioner.yaml
+++ b/examples/render/contour-gateway-provisioner.yaml
@@ -5780,6 +5780,14 @@ spec:
secrets are limited to 1MiB in size.
minLength: 1
type: string
+ onlyRequestClientCert:
+ description: OnlyRequestClientCert when set to true will
+ request a client certificate but allow the connection
+ to continue if the client does not provide one. If a
+ client certificate is sent, it will be verified according
+ to the other properties, which includes disabling validation
+ if SkipClientCertValidation is set. Defaults to false.
+ type: boolean
skipClientCertValidation:
description: SkipClientCertValidation disables downstream
client certificate validation. Defaults to false. This
diff --git a/examples/render/contour-gateway.yaml b/examples/render/contour-gateway.yaml
index 7851d6a1557..991f97db64b 100644
--- a/examples/render/contour-gateway.yaml
+++ b/examples/render/contour-gateway.yaml
@@ -5981,6 +5981,14 @@ spec:
secrets are limited to 1MiB in size.
minLength: 1
type: string
+ onlyRequestClientCert:
+ description: OnlyRequestClientCert when set to true will
+ request a client certificate but allow the connection
+ to continue if the client does not provide one. If a
+ client certificate is sent, it will be verified according
+ to the other properties, which includes disabling validation
+ if SkipClientCertValidation is set. Defaults to false.
+ type: boolean
skipClientCertValidation:
description: SkipClientCertValidation disables downstream
client certificate validation. Defaults to false. This
diff --git a/examples/render/contour.yaml b/examples/render/contour.yaml
index 634873c3c11..47a62cd8d35 100644
--- a/examples/render/contour.yaml
+++ b/examples/render/contour.yaml
@@ -5975,6 +5975,14 @@ spec:
secrets are limited to 1MiB in size.
minLength: 1
type: string
+ onlyRequestClientCert:
+ description: OnlyRequestClientCert when set to true will
+ request a client certificate but allow the connection
+ to continue if the client does not provide one. If a
+ client certificate is sent, it will be verified according
+ to the other properties, which includes disabling validation
+ if SkipClientCertValidation is set. Defaults to false.
+ type: boolean
skipClientCertValidation:
description: SkipClientCertValidation disables downstream
client certificate validation. Defaults to false. This
diff --git a/internal/dag/builder_test.go b/internal/dag/builder_test.go
index b5b26f06214..ddd12379df3 100644
--- a/internal/dag/builder_test.go
+++ b/internal/dag/builder_test.go
@@ -6521,6 +6521,35 @@ func TestDAGInsert(t *testing.T) {
},
}
+ // proxy24 is downstream validation, optional cert validation
+ proxy24 := &contour_api_v1.HTTPProxy{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "example-com",
+ Namespace: "default",
+ },
+ Spec: contour_api_v1.HTTPProxySpec{
+ VirtualHost: &contour_api_v1.VirtualHost{
+ Fqdn: "example.com",
+ TLS: &contour_api_v1.TLS{
+ SecretName: sec1.Name,
+ ClientValidation: &contour_api_v1.DownstreamValidation{
+ CACertificate: cert1.Name,
+ OnlyRequestClientCert: true,
+ },
+ },
+ },
+ Routes: []contour_api_v1.Route{{
+ Conditions: []contour_api_v1.MatchCondition{{
+ Prefix: "/",
+ }},
+ Services: []contour_api_v1.Service{{
+ Name: s1.Name,
+ Port: 8080,
+ }},
+ }},
+ },
+ }
+
// invalid because tcpproxy both includes another and
// has a list of services.
proxy37 := &contour_api_v1.HTTPProxy{
@@ -9774,6 +9803,38 @@ func TestDAGInsert(t *testing.T) {
},
),
},
+ "insert httpproxy w/ tls termination with optional client validation": {
+ objs: []interface{}{
+ proxy24, s1, sec1, cert1, crl,
+ },
+ want: listeners(
+ &Listener{
+ Name: HTTP_LISTENER_NAME,
+ Port: 80,
+ VirtualHosts: virtualhosts(
+ virtualhost("example.com", routeUpgrade("/", service(s1))),
+ ),
+ }, &Listener{
+ Name: HTTPS_LISTENER_NAME,
+ Port: 443,
+ SecureVirtualHosts: securevirtualhosts(
+ &SecureVirtualHost{
+ VirtualHost: VirtualHost{
+ Name: "example.com",
+ Routes: routes(
+ routeUpgrade("/", service(s1))),
+ },
+ MinTLSVersion: "1.2",
+ Secret: secret(sec1),
+ DownstreamValidation: &PeerValidationContext{
+ CACertificate: &Secret{Object: cert1},
+ OnlyRequestClientCert: true,
+ },
+ },
+ ),
+ },
+ ),
+ },
"insert httpproxy with downstream verification, missing ca certificate": {
objs: []interface{}{
proxy18, s1, sec1,
diff --git a/internal/dag/dag.go b/internal/dag/dag.go
index 53e60635a49..401881e34f7 100644
--- a/internal/dag/dag.go
+++ b/internal/dag/dag.go
@@ -540,6 +540,9 @@ type PeerValidationContext struct {
// OnlyVerifyLeafCertCrl when set to true, only the certificate at the end of the
// certificate chain will be subject to validation by CRL.
OnlyVerifyLeafCertCrl bool
+ // OnlyRequestClientCert when set to true will ensure Envoy does not require
+ // that the client sends a certificate but if one is sent it will process it.
+ OnlyRequestClientCert bool
}
// GetCACertificate returns the CA certificate from PeerValidationContext.
diff --git a/internal/dag/httpproxy_processor.go b/internal/dag/httpproxy_processor.go
index 92b1ca73bd3..2555e3cf14b 100644
--- a/internal/dag/httpproxy_processor.go
+++ b/internal/dag/httpproxy_processor.go
@@ -263,6 +263,7 @@ func (p *HTTPProxyProcessor) computeHTTPProxy(proxy *contour_api_v1.HTTPProxy) {
if tls.ClientValidation != nil {
dv := &PeerValidationContext{
SkipClientCertValidation: tls.ClientValidation.SkipClientCertValidation,
+ OnlyRequestClientCert: tls.ClientValidation.OnlyRequestClientCert,
}
if tls.ClientValidation.CACertificate != "" {
secretName := k8s.NamespacedNameFrom(tls.ClientValidation.CACertificate, k8s.DefaultNamespace(proxy.Namespace))
diff --git a/internal/envoy/v3/auth.go b/internal/envoy/v3/auth.go
index 5ba085320b1..bb6774e24d6 100644
--- a/internal/envoy/v3/auth.go
+++ b/internal/envoy/v3/auth.go
@@ -125,7 +125,7 @@ func DownstreamTLSContext(serverSecret *dag.Secret, tlsMinProtoVersion envoy_v3_
peerValidationContext.GetCRL(), peerValidationContext.OnlyVerifyLeafCertCrl)
if vc != nil {
context.CommonTlsContext.ValidationContextType = vc
- context.RequireClientCertificate = protobuf.Bool(true)
+ context.RequireClientCertificate = protobuf.Bool(!peerValidationContext.OnlyRequestClientCert)
}
}
diff --git a/internal/envoy/v3/listener_test.go b/internal/envoy/v3/listener_test.go
index d9aef3ce289..2776a91497e 100644
--- a/internal/envoy/v3/listener_test.go
+++ b/internal/envoy/v3/listener_test.go
@@ -323,6 +323,20 @@ func TestDownstreamTLSContext(t *testing.T) {
},
},
}
+ peerValidationContextOptionalClientCertValidationWithCA := &dag.PeerValidationContext{
+ CACertificate: &dag.Secret{
+ Object: &v1.Secret{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "secret",
+ Namespace: "default",
+ },
+ Data: map[string][]byte{
+ dag.CACertificateKey: ca,
+ },
+ },
+ },
+ OnlyRequestClientCert: true,
+ }
peerValidationContextWithCRLCheck := &dag.PeerValidationContext{
CACertificate: &dag.Secret{
Object: &v1.Secret{
@@ -465,6 +479,18 @@ func TestDownstreamTLSContext(t *testing.T) {
RequireClientCertificate: protobuf.Bool(true),
},
},
+ "optional client cert validation with ca": {
+ DownstreamTLSContext(serverSecret, envoy_tls_v3.TlsParameters_TLSv1_2, cipherSuites, peerValidationContextOptionalClientCertValidationWithCA, "h2", "http/1.1"),
+ &envoy_tls_v3.DownstreamTlsContext{
+ CommonTlsContext: &envoy_tls_v3.CommonTlsContext{
+ TlsParams: tlsParams,
+ TlsCertificateSdsSecretConfigs: tlsCertificateSdsSecretConfigs,
+ AlpnProtocols: alpnProtocols,
+ ValidationContextType: validationContext,
+ },
+ RequireClientCertificate: protobuf.Bool(false),
+ },
+ },
"Downstream validation with CRL check": {
DownstreamTLSContext(serverSecret, envoy_tls_v3.TlsParameters_TLSv1_2, cipherSuites, peerValidationContextWithCRLCheck, "h2", "http/1.1"),
&envoy_tls_v3.DownstreamTlsContext{
diff --git a/internal/featuretests/v3/downstreamvalidation_test.go b/internal/featuretests/v3/downstreamvalidation_test.go
index 73f8f2b4a7f..bdc3bf80a1b 100644
--- a/internal/featuretests/v3/downstreamvalidation_test.go
+++ b/internal/featuretests/v3/downstreamvalidation_test.go
@@ -320,4 +320,53 @@ func TestDownstreamTLSCertificateValidation(t *testing.T) {
statsListener(),
),
}).Status(proxy5).IsValid()
+
+ proxy6 := fixture.NewProxy("example.com").
+ WithSpec(contour_api_v1.HTTPProxySpec{
+ VirtualHost: &contour_api_v1.VirtualHost{
+ Fqdn: "example.com",
+ TLS: &contour_api_v1.TLS{
+ SecretName: serverTLSSecret.Name,
+ ClientValidation: &contour_api_v1.DownstreamValidation{
+ CACertificate: clientCASecret.Name,
+ OnlyRequestClientCert: true,
+ },
+ },
+ },
+ Routes: []contour_api_v1.Route{{
+ Services: []contour_api_v1.Service{{
+ Name: "kuard",
+ Port: 8080,
+ }},
+ }},
+ })
+ rh.OnUpdate(proxy5, proxy6)
+
+ ingressHTTPSOptionalVerify := &envoy_listener_v3.Listener{
+ Name: "ingress_https",
+ Address: envoy_v3.SocketAddress("0.0.0.0", 8443),
+ ListenerFilters: envoy_v3.ListenerFilters(
+ envoy_v3.TLSInspector(),
+ ),
+ FilterChains: appendFilterChains(
+ filterchaintls("example.com", serverTLSSecret,
+ httpsFilterFor("example.com"),
+ &dag.PeerValidationContext{
+ CACertificate: &dag.Secret{
+ Object: clientCASecret,
+ },
+ OnlyRequestClientCert: true,
+ },
+ "h2", "http/1.1",
+ ),
+ ),
+ SocketOptions: envoy_v3.TCPKeepaliveSocketOptions(),
+ }
+ c.Request(listenerType).Equals(&envoy_discovery_v3.DiscoveryResponse{
+ Resources: resources(t,
+ defaultHTTPListener(),
+ ingressHTTPSOptionalVerify,
+ statsListener(),
+ ),
+ }).Status(proxy6).IsValid()
}
diff --git a/site/content/docs/main/config/api-reference.html b/site/content/docs/main/config/api-reference.html
index 9be7712ace6..cf25af05359 100644
--- a/site/content/docs/main/config/api-reference.html
+++ b/site/content/docs/main/config/api-reference.html
@@ -977,6 +977,23 @@ DownstreamValidation
certificate chain will be subject to validation by CRL.
+
+
+onlyRequestClientCert
+
+
+bool
+
+ |
+
+(Optional)
+ OnlyRequestClientCert when set to true will request a client certificate
+but allow the connection to continue if the client does not provide one.
+If a client certificate is sent, it will be verified according to the
+other properties, which includes disabling validation if
+SkipClientCertValidation is set. Defaults to false.
+ |
+
ExtensionServiceReference
diff --git a/site/content/docs/main/config/tls-termination.md b/site/content/docs/main/config/tls-termination.md
index da3d4cb3808..04ef560e487 100644
--- a/site/content/docs/main/config/tls-termination.md
+++ b/site/content/docs/main/config/tls-termination.md
@@ -166,6 +166,27 @@ Its mandatory attribute `caSecret` contains a name of an existing Kubernetes Sec
The data value of the key `ca.crt` must be a PEM-encoded certificate bundle and it must contain all the trusted CA certificates that are to be used for validating the client certificate.
If the Opaque Secret also contains one of either `tls.crt` or `tls.key` keys, it will be ignored.
+By default, client certificates are required but some applications might support different authentication schemes. In that case you can set the `onlyRequestClientCert` field to `true`. A client certificate will be requested, but the connection is allowed to continue if the client does not provide one. If a client certificate is sent, it will be verified according to the other properties, which includes disabling validations if `skipClientCertValidation` is set.
+
+```yaml
+apiVersion: projectcontour.io/v1
+kind: HTTPProxy
+metadata:
+ name: with-optional-client-auth
+spec:
+ virtualhost:
+ fqdn: www.example.com
+ tls:
+ secretName: secret
+ clientValidation:
+ caSecret: client-root-ca
+ onlyRequestClientCert: true
+ routes:
+ - services:
+ - name: s1
+ port: 80
+```
+
When using external authorization, it may be desirable to use an external authorization server to validate client certificates on requests, rather than the Envoy proxy.
```yaml
diff --git a/test/e2e/httpproxy/client_cert_auth_test.go b/test/e2e/httpproxy/client_cert_auth_test.go
index 17ee2f8318e..240d0da3080 100644
--- a/test/e2e/httpproxy/client_cert_auth_test.go
+++ b/test/e2e/httpproxy/client_cert_auth_test.go
@@ -228,6 +228,50 @@ func testClientCertAuth(namespace string) {
}
require.NoError(t, f.Client.Create(context.TODO(), echoWithAuthSkipVerifyWithCACert))
+ f.Fixtures.Echo.Deploy(namespace, "echo-with-optional-auth")
+
+ // Get a server certificate for echo-with-optional-auth.
+ echoWithOptionalAuth := &certmanagerv1.Certificate{
+ ObjectMeta: metav1.ObjectMeta{
+ Namespace: namespace,
+ Name: "echo-with-optional-auth-cert",
+ },
+ Spec: certmanagerv1.CertificateSpec{
+
+ Usages: []certmanagerv1.KeyUsage{
+ certmanagerv1.UsageServerAuth,
+ },
+ DNSNames: []string{"echo-with-optional-auth.projectcontour.io"},
+ SecretName: "echo-with-optional-auth",
+ IssuerRef: certmanagermetav1.ObjectReference{
+ Name: "ca-projectcontour-io",
+ },
+ },
+ }
+ require.NoError(t, f.Client.Create(context.TODO(), echoWithOptionalAuth))
+
+ f.Fixtures.Echo.Deploy(namespace, "echo-with-optional-auth-no-ca")
+
+ // Get a server certificate for echo-with-optional-auth-no-ca.
+ echoWithOptionalAuthNoCA := &certmanagerv1.Certificate{
+ ObjectMeta: metav1.ObjectMeta{
+ Namespace: namespace,
+ Name: "echo-with-optional-auth-no-ca-cert",
+ },
+ Spec: certmanagerv1.CertificateSpec{
+
+ Usages: []certmanagerv1.KeyUsage{
+ certmanagerv1.UsageServerAuth,
+ },
+ DNSNames: []string{"echo-with-optional-auth-no-ca.projectcontour.io"},
+ SecretName: "echo-with-optional-auth-no-ca",
+ IssuerRef: certmanagermetav1.ObjectReference{
+ Name: "ca-projectcontour-io",
+ },
+ },
+ }
+ require.NoError(t, f.Client.Create(context.TODO(), echoWithOptionalAuthNoCA))
+
// Get a client certificate.
clientCert := &certmanagerv1.Certificate{
ObjectMeta: metav1.ObjectMeta{
@@ -394,6 +438,68 @@ func testClientCertAuth(namespace string) {
}
f.CreateHTTPProxyAndWaitFor(authSkipVerifyWithCAProxy, e2e.HTTPProxyValid)
+ // This proxy requests a client certificate but only verifies it if sent.
+ optionalAuthProxy := &contourv1.HTTPProxy{
+ ObjectMeta: metav1.ObjectMeta{
+ Namespace: namespace,
+ Name: "echo-with-optional-auth",
+ },
+ Spec: contourv1.HTTPProxySpec{
+ VirtualHost: &contourv1.VirtualHost{
+ Fqdn: "echo-with-optional-auth.projectcontour.io",
+ TLS: &contourv1.TLS{
+ SecretName: "echo-with-optional-auth",
+ ClientValidation: &contourv1.DownstreamValidation{
+ OnlyRequestClientCert: true,
+ CACertificate: "echo-with-auth",
+ },
+ },
+ },
+ Routes: []contourv1.Route{
+ {
+ Services: []contourv1.Service{
+ {
+ Name: "echo-with-optional-auth",
+ Port: 80,
+ },
+ },
+ },
+ },
+ },
+ }
+ f.CreateHTTPProxyAndWaitFor(optionalAuthProxy, e2e.HTTPProxyValid)
+
+ // This proxy requests a client certificate but doesn't verify it if sent.
+ optionalAuthNoCAProxy := &contourv1.HTTPProxy{
+ ObjectMeta: metav1.ObjectMeta{
+ Namespace: namespace,
+ Name: "echo-with-optional-auth-no-ca",
+ },
+ Spec: contourv1.HTTPProxySpec{
+ VirtualHost: &contourv1.VirtualHost{
+ Fqdn: "echo-with-optional-auth-no-ca.projectcontour.io",
+ TLS: &contourv1.TLS{
+ SecretName: "echo-with-optional-auth-no-ca",
+ ClientValidation: &contourv1.DownstreamValidation{
+ OnlyRequestClientCert: true,
+ SkipClientCertValidation: true,
+ },
+ },
+ },
+ Routes: []contourv1.Route{
+ {
+ Services: []contourv1.Service{
+ {
+ Name: "echo-with-optional-auth-no-ca",
+ Port: 80,
+ },
+ },
+ },
+ },
+ },
+ }
+ f.CreateHTTPProxyAndWaitFor(optionalAuthNoCAProxy, e2e.HTTPProxyValid)
+
// get the valid & invalid client certs
validClientCert, _ := f.Certs.GetTLSCertificate(namespace, clientCert.Spec.SecretName)
invalidClientCert, _ := f.Certs.GetTLSCertificate(namespace, clientCertInvalid.Spec.SecretName)
@@ -466,6 +572,37 @@ func testClientCertAuth(namespace string) {
clientCert: &invalidClientCert,
wantErr: "",
},
+
+ "echo-with-optional-auth without a client cert should succeed": {
+ host: optionalAuthProxy.Spec.VirtualHost.Fqdn,
+ clientCert: nil,
+ wantErr: "",
+ },
+ "echo-with-optional-auth with echo-client-cert should succeed": {
+ host: optionalAuthProxy.Spec.VirtualHost.Fqdn,
+ clientCert: &validClientCert,
+ wantErr: "",
+ },
+ "echo-with-optional-auth with echo-client-cert-invalid should error": {
+ host: optionalAuthProxy.Spec.VirtualHost.Fqdn,
+ clientCert: &invalidClientCert,
+ wantErr: "tls: unknown certificate authority",
+ },
+ "echo-with-optional-auth-no-ca without a client cert should succeed": {
+ host: optionalAuthNoCAProxy.Spec.VirtualHost.Fqdn,
+ clientCert: nil,
+ wantErr: "",
+ },
+ "echo-with-optional-auth-no-ca with echo-client-cert should succeed": {
+ host: optionalAuthNoCAProxy.Spec.VirtualHost.Fqdn,
+ clientCert: &validClientCert,
+ wantErr: "",
+ },
+ "echo-with-optional-auth-no-ca with echo-client-cert-invalid should succeed": {
+ host: optionalAuthNoCAProxy.Spec.VirtualHost.Fqdn,
+ clientCert: &invalidClientCert,
+ wantErr: "",
+ },
}
for name, tc := range cases {
From 0f03cb1b58645144b6fe63b8e3ba149de9eeac32 Mon Sep 17 00:00:00 2001
From: Gautier Delorme
Date: Thu, 20 Oct 2022 19:13:02 +0200
Subject: [PATCH 2/3] OnlyRequestClientCert => OptionalClientCertificate
Signed-off-by: Gautier Delorme
---
apis/projectcontour/v1/httpproxy.go | 4 ++--
changelogs/unreleased/4796-gautierdelorme-minor.md | 2 +-
examples/contour/01-crds.yaml | 6 +++---
examples/render/contour-deployment.yaml | 6 +++---
examples/render/contour-gateway-provisioner.yaml | 6 +++---
examples/render/contour-gateway.yaml | 6 +++---
examples/render/contour.yaml | 6 +++---
internal/dag/builder_test.go | 8 ++++----
internal/dag/dag.go | 4 ++--
internal/dag/httpproxy_processor.go | 4 ++--
internal/envoy/v3/auth.go | 2 +-
internal/envoy/v3/listener_test.go | 2 +-
internal/featuretests/v3/downstreamvalidation_test.go | 6 +++---
site/content/docs/main/config/api-reference.html | 4 ++--
site/content/docs/main/config/tls-termination.md | 4 ++--
test/e2e/httpproxy/client_cert_auth_test.go | 8 ++++----
16 files changed, 39 insertions(+), 39 deletions(-)
diff --git a/apis/projectcontour/v1/httpproxy.go b/apis/projectcontour/v1/httpproxy.go
index 8871e5c97f6..eb53a01566d 100644
--- a/apis/projectcontour/v1/httpproxy.go
+++ b/apis/projectcontour/v1/httpproxy.go
@@ -1132,13 +1132,13 @@ type DownstreamValidation struct {
// +optional
OnlyVerifyLeafCertCrl bool `json:"crlOnlyVerifyLeafCert"`
- // OnlyRequestClientCert when set to true will request a client certificate
+ // OptionalClientCertificate when set to true will request a client certificate
// but allow the connection to continue if the client does not provide one.
// If a client certificate is sent, it will be verified according to the
// other properties, which includes disabling validation if
// SkipClientCertValidation is set. Defaults to false.
// +optional
- OnlyRequestClientCert bool `json:"onlyRequestClientCert"`
+ OptionalClientCertificate bool `json:"optionalClientCertificate"`
}
// HTTPProxyStatus reports the current state of the HTTPProxy.
diff --git a/changelogs/unreleased/4796-gautierdelorme-minor.md b/changelogs/unreleased/4796-gautierdelorme-minor.md
index 06c536c5eb4..04ff04f61f9 100644
--- a/changelogs/unreleased/4796-gautierdelorme-minor.md
+++ b/changelogs/unreleased/4796-gautierdelorme-minor.md
@@ -1,5 +1,5 @@
## Optional Client Certificate Validation
By default, client certificates are required but some applications might support different authentication schemes.
-You can now set the `httpproxy.spec.virtualhost.tls.clientValidation.onlyRequestClientCert` field to `true`. A client certificate will be requested, but the connection is allowed to continue if the client does not provide one.
+You can now set the `httpproxy.spec.virtualhost.tls.clientValidation.optionalClientCertificate` field to `true`. A client certificate will be requested, but the connection is allowed to continue if the client does not provide one.
If a client certificate is sent, it will be verified according to the other properties, which includes disabling validations if `httpproxy.spec.virtualhost.tls.clientValidation.skipClientCertValidation` is set.
diff --git a/examples/contour/01-crds.yaml b/examples/contour/01-crds.yaml
index 39cb833fbaa..138c81a936c 100644
--- a/examples/contour/01-crds.yaml
+++ b/examples/contour/01-crds.yaml
@@ -5766,9 +5766,9 @@ spec:
secrets are limited to 1MiB in size.
minLength: 1
type: string
- onlyRequestClientCert:
- description: OnlyRequestClientCert when set to true will
- request a client certificate but allow the connection
+ optionalClientCertificate:
+ description: OptionalClientCertificate when set to true
+ will request a client certificate but allow the connection
to continue if the client does not provide one. If a
client certificate is sent, it will be verified according
to the other properties, which includes disabling validation
diff --git a/examples/render/contour-deployment.yaml b/examples/render/contour-deployment.yaml
index bd4e0b133b8..78be7e3221d 100644
--- a/examples/render/contour-deployment.yaml
+++ b/examples/render/contour-deployment.yaml
@@ -5975,9 +5975,9 @@ spec:
secrets are limited to 1MiB in size.
minLength: 1
type: string
- onlyRequestClientCert:
- description: OnlyRequestClientCert when set to true will
- request a client certificate but allow the connection
+ optionalClientCertificate:
+ description: OptionalClientCertificate when set to true
+ will request a client certificate but allow the connection
to continue if the client does not provide one. If a
client certificate is sent, it will be verified according
to the other properties, which includes disabling validation
diff --git a/examples/render/contour-gateway-provisioner.yaml b/examples/render/contour-gateway-provisioner.yaml
index b07e69997e9..bd6b7f9b928 100644
--- a/examples/render/contour-gateway-provisioner.yaml
+++ b/examples/render/contour-gateway-provisioner.yaml
@@ -5780,9 +5780,9 @@ spec:
secrets are limited to 1MiB in size.
minLength: 1
type: string
- onlyRequestClientCert:
- description: OnlyRequestClientCert when set to true will
- request a client certificate but allow the connection
+ optionalClientCertificate:
+ description: OptionalClientCertificate when set to true
+ will request a client certificate but allow the connection
to continue if the client does not provide one. If a
client certificate is sent, it will be verified according
to the other properties, which includes disabling validation
diff --git a/examples/render/contour-gateway.yaml b/examples/render/contour-gateway.yaml
index 991f97db64b..631c3c34be6 100644
--- a/examples/render/contour-gateway.yaml
+++ b/examples/render/contour-gateway.yaml
@@ -5981,9 +5981,9 @@ spec:
secrets are limited to 1MiB in size.
minLength: 1
type: string
- onlyRequestClientCert:
- description: OnlyRequestClientCert when set to true will
- request a client certificate but allow the connection
+ optionalClientCertificate:
+ description: OptionalClientCertificate when set to true
+ will request a client certificate but allow the connection
to continue if the client does not provide one. If a
client certificate is sent, it will be verified according
to the other properties, which includes disabling validation
diff --git a/examples/render/contour.yaml b/examples/render/contour.yaml
index 47a62cd8d35..5658dcc515b 100644
--- a/examples/render/contour.yaml
+++ b/examples/render/contour.yaml
@@ -5975,9 +5975,9 @@ spec:
secrets are limited to 1MiB in size.
minLength: 1
type: string
- onlyRequestClientCert:
- description: OnlyRequestClientCert when set to true will
- request a client certificate but allow the connection
+ optionalClientCertificate:
+ description: OptionalClientCertificate when set to true
+ will request a client certificate but allow the connection
to continue if the client does not provide one. If a
client certificate is sent, it will be verified according
to the other properties, which includes disabling validation
diff --git a/internal/dag/builder_test.go b/internal/dag/builder_test.go
index ddd12379df3..b128e16c604 100644
--- a/internal/dag/builder_test.go
+++ b/internal/dag/builder_test.go
@@ -6533,8 +6533,8 @@ func TestDAGInsert(t *testing.T) {
TLS: &contour_api_v1.TLS{
SecretName: sec1.Name,
ClientValidation: &contour_api_v1.DownstreamValidation{
- CACertificate: cert1.Name,
- OnlyRequestClientCert: true,
+ CACertificate: cert1.Name,
+ OptionalClientCertificate: true,
},
},
},
@@ -9827,8 +9827,8 @@ func TestDAGInsert(t *testing.T) {
MinTLSVersion: "1.2",
Secret: secret(sec1),
DownstreamValidation: &PeerValidationContext{
- CACertificate: &Secret{Object: cert1},
- OnlyRequestClientCert: true,
+ CACertificate: &Secret{Object: cert1},
+ OptionalClientCertificate: true,
},
},
),
diff --git a/internal/dag/dag.go b/internal/dag/dag.go
index 401881e34f7..32395c8d38d 100644
--- a/internal/dag/dag.go
+++ b/internal/dag/dag.go
@@ -540,9 +540,9 @@ type PeerValidationContext struct {
// OnlyVerifyLeafCertCrl when set to true, only the certificate at the end of the
// certificate chain will be subject to validation by CRL.
OnlyVerifyLeafCertCrl bool
- // OnlyRequestClientCert when set to true will ensure Envoy does not require
+ // OptionalClientCertificate when set to true will ensure Envoy does not require
// that the client sends a certificate but if one is sent it will process it.
- OnlyRequestClientCert bool
+ OptionalClientCertificate bool
}
// GetCACertificate returns the CA certificate from PeerValidationContext.
diff --git a/internal/dag/httpproxy_processor.go b/internal/dag/httpproxy_processor.go
index 2555e3cf14b..c5ae593fa53 100644
--- a/internal/dag/httpproxy_processor.go
+++ b/internal/dag/httpproxy_processor.go
@@ -262,8 +262,8 @@ func (p *HTTPProxyProcessor) computeHTTPProxy(proxy *contour_api_v1.HTTPProxy) {
// Fill in DownstreamValidation when external client validation is enabled.
if tls.ClientValidation != nil {
dv := &PeerValidationContext{
- SkipClientCertValidation: tls.ClientValidation.SkipClientCertValidation,
- OnlyRequestClientCert: tls.ClientValidation.OnlyRequestClientCert,
+ SkipClientCertValidation: tls.ClientValidation.SkipClientCertValidation,
+ OptionalClientCertificate: tls.ClientValidation.OptionalClientCertificate,
}
if tls.ClientValidation.CACertificate != "" {
secretName := k8s.NamespacedNameFrom(tls.ClientValidation.CACertificate, k8s.DefaultNamespace(proxy.Namespace))
diff --git a/internal/envoy/v3/auth.go b/internal/envoy/v3/auth.go
index bb6774e24d6..eab2022a374 100644
--- a/internal/envoy/v3/auth.go
+++ b/internal/envoy/v3/auth.go
@@ -125,7 +125,7 @@ func DownstreamTLSContext(serverSecret *dag.Secret, tlsMinProtoVersion envoy_v3_
peerValidationContext.GetCRL(), peerValidationContext.OnlyVerifyLeafCertCrl)
if vc != nil {
context.CommonTlsContext.ValidationContextType = vc
- context.RequireClientCertificate = protobuf.Bool(!peerValidationContext.OnlyRequestClientCert)
+ context.RequireClientCertificate = protobuf.Bool(!peerValidationContext.OptionalClientCertificate)
}
}
diff --git a/internal/envoy/v3/listener_test.go b/internal/envoy/v3/listener_test.go
index 2776a91497e..64ae3a883c8 100644
--- a/internal/envoy/v3/listener_test.go
+++ b/internal/envoy/v3/listener_test.go
@@ -335,7 +335,7 @@ func TestDownstreamTLSContext(t *testing.T) {
},
},
},
- OnlyRequestClientCert: true,
+ OptionalClientCertificate: true,
}
peerValidationContextWithCRLCheck := &dag.PeerValidationContext{
CACertificate: &dag.Secret{
diff --git a/internal/featuretests/v3/downstreamvalidation_test.go b/internal/featuretests/v3/downstreamvalidation_test.go
index bdc3bf80a1b..9b022a6cdba 100644
--- a/internal/featuretests/v3/downstreamvalidation_test.go
+++ b/internal/featuretests/v3/downstreamvalidation_test.go
@@ -328,8 +328,8 @@ func TestDownstreamTLSCertificateValidation(t *testing.T) {
TLS: &contour_api_v1.TLS{
SecretName: serverTLSSecret.Name,
ClientValidation: &contour_api_v1.DownstreamValidation{
- CACertificate: clientCASecret.Name,
- OnlyRequestClientCert: true,
+ CACertificate: clientCASecret.Name,
+ OptionalClientCertificate: true,
},
},
},
@@ -355,7 +355,7 @@ func TestDownstreamTLSCertificateValidation(t *testing.T) {
CACertificate: &dag.Secret{
Object: clientCASecret,
},
- OnlyRequestClientCert: true,
+ OptionalClientCertificate: true,
},
"h2", "http/1.1",
),
diff --git a/site/content/docs/main/config/api-reference.html b/site/content/docs/main/config/api-reference.html
index cf25af05359..71f05232a0b 100644
--- a/site/content/docs/main/config/api-reference.html
+++ b/site/content/docs/main/config/api-reference.html
@@ -979,7 +979,7 @@ DownstreamValidation
-onlyRequestClientCert
+optionalClientCertificate
bool
@@ -987,7 +987,7 @@ DownstreamValidation
|
(Optional)
- OnlyRequestClientCert when set to true will request a client certificate
+ OptionalClientCertificate when set to true will request a client certificate
but allow the connection to continue if the client does not provide one.
If a client certificate is sent, it will be verified according to the
other properties, which includes disabling validation if
diff --git a/site/content/docs/main/config/tls-termination.md b/site/content/docs/main/config/tls-termination.md
index 04ef560e487..b552481a2ba 100644
--- a/site/content/docs/main/config/tls-termination.md
+++ b/site/content/docs/main/config/tls-termination.md
@@ -166,7 +166,7 @@ Its mandatory attribute `caSecret` contains a name of an existing Kubernetes Sec
The data value of the key `ca.crt` must be a PEM-encoded certificate bundle and it must contain all the trusted CA certificates that are to be used for validating the client certificate.
If the Opaque Secret also contains one of either `tls.crt` or `tls.key` keys, it will be ignored.
-By default, client certificates are required but some applications might support different authentication schemes. In that case you can set the `onlyRequestClientCert` field to `true`. A client certificate will be requested, but the connection is allowed to continue if the client does not provide one. If a client certificate is sent, it will be verified according to the other properties, which includes disabling validations if `skipClientCertValidation` is set.
+By default, client certificates are required but some applications might support different authentication schemes. In that case you can set the `optionalClientCertificate` field to `true`. A client certificate will be requested, but the connection is allowed to continue if the client does not provide one. If a client certificate is sent, it will be verified according to the other properties, which includes disabling validations if `skipClientCertValidation` is set.
```yaml
apiVersion: projectcontour.io/v1
@@ -180,7 +180,7 @@ spec:
secretName: secret
clientValidation:
caSecret: client-root-ca
- onlyRequestClientCert: true
+ optionalClientCertificate: true
routes:
- services:
- name: s1
diff --git a/test/e2e/httpproxy/client_cert_auth_test.go b/test/e2e/httpproxy/client_cert_auth_test.go
index 240d0da3080..11d1457685b 100644
--- a/test/e2e/httpproxy/client_cert_auth_test.go
+++ b/test/e2e/httpproxy/client_cert_auth_test.go
@@ -450,8 +450,8 @@ func testClientCertAuth(namespace string) {
TLS: &contourv1.TLS{
SecretName: "echo-with-optional-auth",
ClientValidation: &contourv1.DownstreamValidation{
- OnlyRequestClientCert: true,
- CACertificate: "echo-with-auth",
+ OptionalClientCertificate: true,
+ CACertificate: "echo-with-auth",
},
},
},
@@ -481,8 +481,8 @@ func testClientCertAuth(namespace string) {
TLS: &contourv1.TLS{
SecretName: "echo-with-optional-auth-no-ca",
ClientValidation: &contourv1.DownstreamValidation{
- OnlyRequestClientCert: true,
- SkipClientCertValidation: true,
+ OptionalClientCertificate: true,
+ SkipClientCertValidation: true,
},
},
},
From 45a2250fc53d087bb4b053479492e88df0fa9a6c Mon Sep 17 00:00:00 2001
From: Gautier Delorme
Date: Thu, 20 Oct 2022 23:09:59 +0200
Subject: [PATCH 3/3] update changelog
Signed-off-by: Gautier Delorme
---
changelogs/unreleased/4796-gautierdelorme-minor.md | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/changelogs/unreleased/4796-gautierdelorme-minor.md b/changelogs/unreleased/4796-gautierdelorme-minor.md
index 04ff04f61f9..0ea08b5a282 100644
--- a/changelogs/unreleased/4796-gautierdelorme-minor.md
+++ b/changelogs/unreleased/4796-gautierdelorme-minor.md
@@ -1,5 +1,6 @@
## Optional Client Certificate Validation
-By default, client certificates are required but some applications might support different authentication schemes.
+By default, when client certificate validation is configured, client certificates are required.
+However, some applications might support different authentication schemes.
You can now set the `httpproxy.spec.virtualhost.tls.clientValidation.optionalClientCertificate` field to `true`. A client certificate will be requested, but the connection is allowed to continue if the client does not provide one.
If a client certificate is sent, it will be verified according to the other properties, which includes disabling validations if `httpproxy.spec.virtualhost.tls.clientValidation.skipClientCertValidation` is set.
|