This document describes manual test procedure for projectcontour/contour#2910
Generate the certificates for the test case by using https://github.com/tsaarni/certyaml
$ cat >certs.yaml <<EOF
subject: cn=internal-root-ca
---
subject: cn=httpbin
issuer: cn=internal-root-ca
sans:
- DNS:httpbin
---
subject: cn=envoy
issuer: cn=internal-root-ca
---
subject: cn=untrusted-client
ca: false
key_usage:
- KeyEncipherment
- DigitalSignature
EOF
$ mkdir certs
$ go get github.com/tsaarni/certyaml
$ go run github.com/tsaarni/certyaml --destination certs
Loading manifest file: certs.yaml
Reading certificate state file: certs/certs.state
Writing: certs/internal-root-ca.pem certs/internal-root-ca-key.pem
Writing: certs/httpbin.pem certs/httpbin-key.pem
Writing: certs/envoy.pem certs/envoy-key.pem
Writing: certs/untrusted-client.pem certs/untrusted-client-key.pem
Writing state: certs/certs.state
Following certificates were created:
internal-root-ca
: CA that issues certificates that are considered valid between services running within the Kubernetes cluster.httpbin
: Server certificate for the backend service. The certificate is issued byinternal-root-ca
.envoy
: Client certificate for Envoy. The certificate is issued byinternal-root-ca
.untrusted-client
: Client certificate for Envoy. The certificate is self-signed and NOT issued byinternal-root-ca
, therefore backend will reject connections with this certificate.
Store the certificates and keys as secrets
$ kubectl create secret generic httpbin --dry-run=client -o yaml --from-file=certs/httpbin.pem --from-file=certs/httpbin-key.pem | kubectl apply -f -
$ kubectl create secret generic internal-root-ca --from-file=ca.crt=certs/internal-root-ca.pem --dry-run=client -o yaml | kubectl apply -f -
$ kubectl -n projectcontour create secret tls client --cert=certs/envoy.pem --key=certs/envoy-key.pem --dry-run=client -o yaml | kubectl apply -f -
$ kubectl -n projectcontour create secret tls untrusted-client --cert=certs/untrusted-client.pem --key=certs/untrusted-client-key.pem --dry-run=client -o yaml | kubectl apply -f -
Deploy backend service (see httpbin.yaml
in this gist)
$ kubectl apply -f https://gist.githubusercontent.com/tsaarni/245584e8b4e40b9aa5a9924c7d13eadf/raw//httpbin.yaml
Note: The HTTPProxy in the manifest has been set up with FQDN assuming Envoy is listening at host1.127-0-0-101.nip.io
. As well as curl
requests in the following test cases are written to use host1.127-0-0-101.nip.io
as the host name. Change these to fit your test environment!
Run Contour with configuration file including envoy-client-certificate
parameter:
$ cat >contour-config.yaml <<EOF
tls:
envoy-client-certificate:
name: client
namespace: projectcontour
EOF
Wait for configuration to be propagated to Envoy.
Check that you get successful response from the backend service
$ curl http://host1.127-0-0-101.nip.io/headers
{
"headers": {
"Accept": "*/*",
"Host": "host1.127-0-0-101.nip.io",
"User-Agent": "curl/7.68.0",
"X-Envoy-Expected-Rq-Timeout-Ms": "15000",
"X-Envoy-Internal": "true"
}
}
Remove envoy certificate and private key and regenerate them:
$ rm certs/envoy*
$ go run github.com/tsaarni/certyaml --destination certs
Loading manifest file: certs.yaml
Reading certificate state file: certs/certs.state
No changes: skipping internal-root-ca
No changes: skipping httpbin
Writing: certs/envoy.pem certs/envoy-key.pem
No changes: skipping untrusted-client
Writing state: certs/certs.state
Update the secret with the newly generated certificate and private key:
$ kubectl -n projectcontour create secret tls client --cert=certs/envoy.pem --key=certs/envoy-key.pem --dry-run=client -o yaml | kubectl apply -f -
Observe from Contour logs that the secret update was picked up.
Note: Following part of the procedure works only on Linux + kind. I don't know if it is possible to do this on macOS as well?
To prove that the new certificate was taken into use by Envoy, the network traffic needs to be captured between Envoy and httpbin to check that the client certificate sent by Envoy has really changed. One way to do this is by checking that the TLS handshake has the correct client certificate with the updated Not Before / Not After dates.
First check the new validity dates from the new certificate:
$ openssl x509 -in certs/envoy.pem -text | grep -A2 Validity
Validity
Not Before: Sep 16 05:38:44 2020 GMT
Not After : Sep 16 05:38:44 2021 GMT
Run Wireshak in the network namespace of httpbin container, which in this case runs on hypercorn web server (similar to unicorn but http2 capable):
$ sudo nsenter --target $(pgrep hypercorn) --net wireshark -f "port 443" -k
# make a new request which will be captured by wireshark
$ curl http://host1.127-0-0-101.nip.io/headers
{
"headers": {
"Accept": "*/*",
"Content-Length": "0",
"Host": "host1.127-0-0-101.nip.io",
"User-Agent": "curl/7.68.0",
"X-Envoy-Expected-Rq-Timeout-Ms": "15000",
"X-Envoy-Internal": "true"
}
}
In wireshark, look for TLS 1.2 protocol message Certificate
(the packet originating from Envoy, which is after Server Hello
) and unfold the TLS message until you see the certificate validity times.
Check that they match with the expected dates printed by openssl.
Note: if you do not see Certificate
message, it is because authentication was already executed during previous request and TLS session resumption was used during this TLS handshake. This triggers shorter TLS handshake that does not include the certificates.
You can rotate Envoy certificate again to trigger reset for the TLS state or alternatively restart httpbin pod (which then requires restarting wireshark as well).
Run Contour with configuration file including envoy-client-certificate
parameter:
$ cat >contour-config.yaml <<EOF
tls:
envoy-client-certificate:
name: untrusted-client
namespace: projectcontour
EOF
Wait for configuration to be propagated to Envoy.
Check that request with invalid certificate is rejected by the backend service
$ curl http://host1.127-0-0-101.nip.io/headers
upstream connect error or disconnect/reset before headers. reset reason: connection failure
You can additionally run wireshark to observe that the Certificate
message has a certificate with subject CN=untrusted-client
.
Run Contour without envoy-client-certificate
parameter.
Wait for configuration to be propagated to Envoy.
Check that request without certificate is rejected by the backend service
$ curl http://host1.127-0-0-101.nip.io/headers
upstream connect error or disconnect/reset before headers. reset reason: connection failure
You can additionally run wireshark to observe that the Certificate
message has field Certificates Length
with value 0.
The Certificate
message is still sent as a response to the backend server Certificate Request
, but the lenght is zero, because Envoy was not configured with a client certificate.