design/tls-backend-verification.md
Status: Approved
Contour 0.11 added the ability to communicate via TLS between Envoy and pods in the backend service. This proposal describes the facility for Envoy to verify the backend service's certificate.
Contour 0.11 added the contour.heptio.com/upstream-protocol.tls Service annotation which told Contour to use TLS when communicating with the members of the backend cluster, the Service's pods.
However, this connection is not validated, Envoy will accept any certificate the client presents.
This proposal seeks to improve the validation of TLS connections .
At a high level I propose the following:
The CA to be used to validate the certificate presented by the backend should be packaged in a Secret. The secret will be placed in the namespace of the IngressRoute.
IngressRoute's spec.routes.services.service entry will grow a new subkey, validation.
The validation key will have a required caSecret key which is the name of the secret where the ca to be validated is stored.
Certificate Delegation is not in scope.
The validation key will have a required subjectname key which is expected to be present in the subjectAltName of the presented certificate.
If subjectname is not present, any certificate with a valid chain to the supplied CA is considered valid.
If spec.routes.services[].validation is present, spec.routes.services[].{name,port} must point to a service with a matching contour.heptio.com/upstream-protocol.tls Service annotation.
If the Service annotation is not present or incorrect, the route is rejected with an appropriate status message.
apiVersion: contour.heptio.com/v1beta1
kind: IngressRoute
metadata:
name: secure-backend
spec:
virtualhost:
fqdn: www.example.com
routes:
- match: /
services:
- name: service
port: 8443
validation:
caSecret: my-certificate-authority
subjectName: backend.example.com
Implementation will consist of these steps:
The store of CA information is an opaque kubernetes secret. The secret will be stored in the same namespace as the corresponding IngressRoute. TLS certificate delegation is not in scope for this proposal.
The secret object should contain one entry named ca.key, the contents will be the CA public key material.
Example:
$ kubectl create secret generic my-certificate-authority --from-file=./ca.key
Contour already subscribes to Secrets in all namespaces so Secrets will be piped through to the dag.KubernetsCache automatically.
A new typed will be added to the dag package, UpstreamValidation to capture the validation parameters.
The name upstream matches Envoy's nomenclature; Cluster members are upstream of Envoy.
package dag
type UpstreamValidation struct {
// Certificate holds a reference to the Secret containing the CA to be used to
// verify the upstream connection.
Certificate *Secret
// SubjectName holds the subject name which Envoy will check against the
// certificate presented by the upstream.
SubjectName string
}
UpstreamValidation will be stored on the dag.Cluster object and populated from the YAML described in the previous section.
envoy.Cluster already accepts a dag.Cluster which gives us a path to pass the UpstreamValidation parameters.
envoy.Clustername will have to change to reflect UpstreamValidation parameters, if present, in the cluster's hash name.
Test cases will need to be updated.
envoy.UpstreamTLSContext will have to be refactored to take the UpstreamValidation parameters if provided.
Test cases will need to be updated.
No changes will be required to the code in cluster.go.
Test cases will need to be updated.
Test cases will need to be updated.
An alternative to storing CA information in Secrets is to store it in ConfigMaps. This was rejected for two reasons
This proposal assumes that the API server is secure. If secret or CA data stored in the API server is modified, verification will be ineffective.
This proposal also assumes that RBAC is in place and only the owners of the Service, Secret, IngressRoute documents in a namespace can modify them.