gateway icon indicating copy to clipboard operation
gateway copied to clipboard

Mutual TLS - Skip client certificate validation

Open haritzsaiz opened this issue 1 year ago • 16 comments

Description:

Is it possible to request a client certificate during the (mutual) TLS handshake that:

  • If no certificate is presented, the requests is forwarded to the upstream services
  • If given by the client, the certificate is not validated at all by the Gateway. Simply the certificate information is forwarded to the upstream services using XFCC headers.
apiVersion: gateway.envoyproxy.io/v1alpha1
kind: ClientTrafficPolicy
metadata:
  name: enable-mtls
spec:
  targetRefs:
  - group: gateway.networking.k8s.io
    kind: Gateway
    name: eg
  tls:
    maxVersion: "1.2"
    clientValidation:
      optional: true
      caCertificateRefs:
      - kind: "Secret"
        group: ""
        name: "my-ca-secret"
  headers:
    xForwardedClientCert:
      mode: AppendForward
      certDetailsToAdd: 
      - Chain

If I've understood the documentation correctly, it's only possible configuring optional mutual TLS, that is:

  • If no certificate is presented, the request is forwarded to the upstream services
  • If given by the client, the certificate is ALWAYS validated at all by the Gateway using the given caCertificateRefs .

In fact, I've seen that if caCertificateRefs is not correcly specified, theCertificate Request is not present in the TLS handshake. Would it be possible perphaps if caCertificateRefs is null / not set, that simply no client certificate validation is performed?

haritzsaiz avatar Sep 19 '24 06:09 haritzsaiz

caCertificateRefs not being set can be perceived as a misconfiguration, and not intent, in the future we plan on return 500 for these cases https://github.com/envoyproxy/gateway/issues/4153

The optional setting should help you support both cases

arkodg avatar Sep 19 '24 21:09 arkodg

For sure I need to set optional to true, but I also need to define caCertificateRefs with a valid secret so that the server will send the client certificate request in the handshake, but it will then add the Distinguished Name of the CA certificate in the certificate request.

The TLS specification states the following (https://datatracker.ietf.org/doc/html/rfc5246#section-7.4.4):

certificate_authorities

    A list of the distinguished names [[X501](https://datatracker.ietf.org/doc/html/rfc5246#ref-X501)] of acceptable
    certificate_authorities, represented in DER-encoded format.  These
    distinguished names may specify a desired distinguished name for a
    root CA or for a subordinate CA; thus, this message can be used to
    describe known roots as well as a desired authorization space.  If
    the certificate_authorities list is empty, then the client MAY
    send any certificate of the appropriate ClientCertificateType,
    unless there is some external arrangement to the contrary.

I'm interested in this part of the specification because some HTTP clients (or other protocols that support TLS) refuse sending a client certificate because they check against the Distinguished Names provided by the handshake.

If the certificate_authorities list is empty, then the client MAYsend any certificate of the appropriate ClientCertificateType, unless there is some external arrangement to the contrary.

How can achieve the server sending the Certificate Request in the TLS handsahak with an empty list of Distinguished Names?

I was able to do this with Envoy right away, so I think it should be possible with Envoy Gateway.

haritzsaiz avatar Sep 20 '24 08:09 haritzsaiz

@haritzsaiz which field in envoy are you referring to ?

arkodg avatar Sep 20 '24 17:09 arkodg

@arkodg Hi, forgot to reply. With this configuration, you achieve that (not defining the trusted_ca param):

transport_socket:
  name: envoy.transport_sockets.tls
  typed_config:
    "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext
    common_tls_context:
      tls_certificates:
        - certificate_chain: { filename: "/certs/bundle.crt" }
          private_key: { filename: "/certs/downstream.key" }
      validation_context:
        trust_chain_verification: ACCEPT_UNTRUSTED
          # trusted_ca: /certs/validation-ca.crt

haritzsaiz avatar Oct 21 '24 18:10 haritzsaiz

Hi @arkodg could you take a look at this commit? I've tried to implement what's missing, but I'm not sure it's implemented correctly. Could you have a look at it? (Don't want to open a PR if you dont agree with the implementation) https://github.com/envoyproxy/gateway/commit/a8ba0974a81b0064c3adc91cf171ab22748a7fc1

haritzsaiz avatar Oct 28 '24 11:10 haritzsaiz

reopening this issue, to make a decision on the definition of optional, and should it also accommodate untrusted certs / certs thats fail validation ptal @envoyproxy/gateway-maintainers

arkodg avatar Oct 28 '24 16:10 arkodg

reopening this issue, to make a decision on the definition of optional, and should it also accommodate untrusted certs / certs thats fail validation

Some users may want to have the following setup: Verify a cert if presented and require a different auth option otherwise. The other auth method may be implemented by the backend server, an ext-auth server, patching EG config to apply validation matching to certain paths or a lua script inspecting the validation status etc.

I think (not sure) that envoy doesn't distinguish between successful validation and acceptance of an untrusted cert:
https://github.com/envoyproxy/envoy/blob/1b1bb4716ced9bd9dc84f87ea46bc5cb9c469da9/source/common/tls/cert_validator/default_validator.cc#L237

So, if optional would now also mean that non-verified and verified certificates are treated the same, the above setup would not be possible.

I'm +1 for this feature in general, to allow users to onboard MTLS gradually, but maybe it requires a dedicated knob.

guydc avatar Nov 21 '24 19:11 guydc

+1 on this - I think it's reasonable to support this as Envoy already supports it, and EG should distinguish between these two options because they have different semantics:

  • optional: EG allows requests without client cert, or requests with a client cert and it should be validated by EG
  • client-cert-passthrough: client certs are required, and they'll not be validated by EG

zhaohuabing avatar Nov 25 '24 03:11 zhaohuabing

prefer waiting a little longer to hear more similar use cases for this feature from other users

arkodg avatar Nov 25 '24 17:11 arkodg

This issue has been automatically marked as stale because it has not had activity in the last 30 days.

github-actions[bot] avatar Dec 25 '24 20:12 github-actions[bot]

We have a similar use case, where we need to forward the client certificate to the upstream service without verifying it at the Gateway level. I think this option should be available as it also exists in envoy as already mentioned.

It should be an "opt in" boolean to prevent configuring an "insecure/at-your-own-risk" Gateway.

mikelamutxastegi avatar Jan 16 '25 09:01 mikelamutxastegi

If you are not in favour of implementing this feature, do you have any suggestions on how to achieve it? Perhaps using other proxies in fronnt of EG? I am not a big fan of having more proxies than are absolutely necessary. I would like to see EG support this, but if not, I would need another approach.

haritzsaiz avatar Jan 29 '25 09:01 haritzsaiz

Sounds like @guydc and @zhaohuabing are in favor of adding this

ingress-nginx supports this today https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/annotations/#client-certificate-authentication

and calls it

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.

EG could support this with a acceptUntrusted like knob

arkodg avatar Jan 30 '25 02:01 arkodg

another option here is to deprecate the existing optional field and define a mode field with 4 options

	// +kubebuilder:default=RequireAndVerify
	Mode *ClientValidationModeType `json:"mode,omitempty"`
}

// ClientValidationModeType type defines how a Gateway or Listener validates client certificates.
//
// +kubebuilder:validation:Enum=Request;RequireAny;VerifyIfGiven;RequireAndVerify
type ClientValidationModeType string

const (
	// Request indicates that a client certificate is requested
	// during the TLS handshake but does not require one.
	Request ClientValidationModeType = "Request"

	// RequireAny indicates that a client certificate is required during
	// the handshake, but the connection is permitted even when the
	// client certificate verification fails.
	RequireAny ClientValidationModeType = "RequireAny"

	// VerifyIfGiven indicates that a client certificate is requested
	// but not required. If presented, the certificate must be valid.
	VerifyIfGiven ClientValidationModeType = "VerifyIfGiven"

	// RequireAndVerify indicates that a valid client certificate must be
	// presented during the handshake and validated
	// using CA certificates defined in CACertificateRefs.
	RequireAndVerify ClientValidationModeType = "RequireAndVerify"
)

arkodg avatar Aug 14 '25 17:08 arkodg

We would also like to have this feature implemented as we are migrating away from ingress-nginx but rely on it's optional_no_ca mTLS mode to pass the client certficitate upstream without needing to configure the CA at the ingress level.

gaborbodis avatar Dec 08 '25 12:12 gaborbodis

adding this to the v1.7 milestone, hoping someone can pick this one up and add the the mode support that handles all the 4 cases

arkodg avatar Dec 10 '25 15:12 arkodg