Istio Authorization With Client Certificates
Istio has inbuilt capabilities to perform authentication/authorization against external requests using JWT, as explored in a previous post, but not everyone uses JWT. So here’s an idea of performing authentication/authorization for external requests with just the client certificates presented in the mTLS handshake.
tl;dr:
It’s possible to use SPIFFE ID presented in the client certificate, in conjunction with Istio AuthorizationPolicy to control access to applications in the service mesh.
However, it opens up to masquerade attacks if someone is able to gain access to the client key pair. So, additional processes may be required if this method is used, e.g. frequent rotation of SPIFFE ID.
Request Authentication
The following section is heavily borrowed from this article Istio proxy SPIFFE validation for inbound connection, which details generating client certificates with the cluster CA used by Istio.
Every Istio deployment has a cluster Certificate Authority (CA), which is used by istiod
to sign and issue certificates to all istio-proxy
sidecars for pod-to-pod mTLS connections. For an application within an Istio service mesh to receive a request from a client external to the service mesh, the client needs to present a certificate signed by the same cluster CA, and contains a SPIFFE identity within the trust domain.
Before we proceed further, here are a couple of things to note:
- SPIFFE identity within Istio usually has the format
spiffe://<trust-domain>/ns/<namespace>/sa/<service-account>
, and is presented in the subjectAltName (SAN) field of the client certificate:... subjectAltName = URI:spiffe://<trust-domain>/ns/<namespace>/sa/<service-account>
- The trust domain defaults to
cluster.local
on Istio. You can verify this by running$ kubectl -n istio-system get configmap istio -o yaml | grep trustDomain trustDomain: cluster.local
Client Certificate Setup
First, we need the cluster CA key pair, and the root CA certificate if the cluster is using an intermediate CA. These may already exists in the cluster as a Kubernetes Secret cacerts
, appearing as something like ca-cert.pem
, ca-key.pem
and root-cert.pem
in the data
field.
kubectl -n istio-system get secret cacerts -o jsonpath='{.data.ca-cert\.pem}' | base64 -d > ca-cert.pem
kubectl -n istio-system get secret cacerts -o jsonpath='{.data.ca-key\.pem}' | base64 -d > ca-key.pem
kubectl -n istio-system get secret cacerts -o jsonpath='{.data.root-cert\.pem}' | base64 -d > root-cert.pem
Next, we create the client key pair:
# Create client key and CSR
openssl req -new -out curl-spiffe.csr -newkey rsa:2048 -nodes -keyout curl-spiffe.key \
-subj "/CN=curl.example.com/O=test" -addext "subjectAltName = URI:spiffe://cluster.local/external-client"
# Generate and sign client certificate with cluster CA
openssl x509 -req -in curl-spiffe.csr -CA ca-cert.pem -CAkey ca-key.pem -CAcreateserial -out curl-spiffe.crt -days 365 -sha256 \
-extensions v3_ca -extfile <(printf "[ v3_ca ]\nsubjectAltName = URI:spiffe://cluster.local/external-client")
Note the addition of the SAN extension with the SPIFFE ID spiffe://cluster.local/external-client
, indicating the client has a workload ID external-client
and is in the same trust domain cluster.local
as Istio.
$ openssl x509 -text -in curl-spiffe.crt
...
X509v3 extensions:
X509v3 Subject Alternative Name:
URI:spiffe://cluster.local/external-client
...
The final thing we need is to create a certificate chain containing the client certificate, cluster CA certificate and root CA certificate (if present). istio-proxy
or Envoy
requires a full certificate chain to be presented by the client, else the mTLS connection will be rejected.
cp curl-spiffe.crt curl-spiffe-chain.crt
cat ca-cert.pem >>curl-spiffe-chain.crt
cat root-cert.pem >>curl-spiffe-chain.crt
We now have the client key pair curl-spiffe.key
and curl-spiffe-chain.crt
ready to go.
Verifying Client Key Pair
To validate the client key pair, we will deploy an application within the service mesh:
kubectl create namespace foo
kubectl label ns foo istio-injection=enabled
kubectl -n foo create deployment httpbin --image kennethreitz/httpbin
kubectl -n foo apply -f - <<EOF
apiVersion: v1
kind: Service
metadata:
name: httpbin
labels:
app: httpbin
service: httpbin
spec:
ports:
- name: http
port: 8000
targetPort: 80
selector:
app: httpbin
EOF
and a client external to the service mesh, i.e. without a sidecar, using the sidecar.istio.io/inject: "false"
label.
kubectl -n foo apply -f - <<EOF
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: curl
name: curl
spec:
replicas: 1
selector:
matchLabels:
app: curl
template:
metadata:
creationTimestamp: null
labels:
app: curl
sidecar.istio.io/inject: "false"
spec:
containers:
- command:
- tail
args:
- "-f"
- "/dev/null"
image: curlimages/curl
name: curl
EOF
# Copy client key pair and CA cert into the client container
CURL_POD=$(kubectl -n foo get pods --selector app=curl -o jsonpath='{.items[0].metadata.name}')
kubectl -n foo cp curl-spiffe.key $CURL_POD:/tmp/
kubectl -n foo cp curl-spiffe-chain.crt $CURL_POD:/tmp/
kubectl -n foo cp ca-cert.pem $CURL_POD:/tmp/
Attempting to send a request to the httpbin
service without providing the key pair will fail as expected:
$ kubectl -n foo exec $CURL_POD -- curl -sk https://httpbin.foo:8000/get
command terminated with exit code 16
Retrying the request with the certificates and key works:
$ kubectl -n foo exec $CURL_POD -- curl -sk https://httpbin.foo:8000/headers \
--cacert /tmp/ca-cert.pem \
--cert /tmp/curl-spiffe-chain.crt \
--key /tmp/curl-spiffe.key
{
"headers": {
"Accept": "*/*",
"Content-Length": "0",
"Host": "httpbin.foo:8000",
"User-Agent": "curl/7.81.0-DEV",
"X-B3-Sampled": "0",
"X-B3-Spanid": "e54f837029b984fb",
"X-B3-Traceid": "1d46a423e3fd66a6e54f837029b984fb",
"X-Forwarded-Client-Cert": "By=spiffe://cluster.local/ns/foo/sa/default;Hash=8d5816f39f390cb936cabe2151aee79d751830ea07dde2deb2900d8134edf7a7;Subject=\"O=test,CN=curl.example.com\";URI=spiffe://cluster.local/external-client"
}
}
Awesome. With authentication out of the way, let’s move on to authorizing requests from this external client.
Request Authorization
We’ve seen Istio’s AuthorizationPolicy in action using information in JWT, and the good news is we can use it here too! The reason we included the SPIFFE ID in the client certificate is because its value gets extracted and can be used for matching in the source.principals field.
We begin by creating an AuthorizationPolicy that only allows some other workload (workload ID someone-else
) to access our application
kubectl -n foo apply -f - <<EOF
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
name: httpbin
spec:
selector:
matchLabels:
app: httpbin
rules:
- from:
- source:
principals: ["cluster.local/someone-else"]
EOF
Attempting to access the application from the external client will fail:
$ kubectl -n foo exec $CURL_POD -- curl -sk https://httpbin.foo:8000/headers \
--cacert /tmp/ca-cert.pem \
--cert /tmp/curl-spiffe-chain.crt \
--key /tmp/curl-spiffe.key
RBAC: access denied
Now, we update the AuthorizationPolicy to allow our client’s workdload ID external-client
kubectl -n foo apply -f - <<EOF
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
name: httpbin
spec:
selector:
matchLabels:
app: httpbin
rules:
- from:
- source:
principals: ["cluster.local/external-client"]
EOF
And the request succeeds as expected:
$ kubectl -n foo exec $CURL_POD -- curl -sk https://httpbin.foo:8000/headers \
--cacert /tmp/ca-cert.pem \
--cert /tmp/curl-spiffe-chain.crt \
--key /tmp/curl-spiffe.key
{
"headers": {
"Accept": "*/*",
"Content-Length": "0",
"Host": "httpbin.foo:8000",
"User-Agent": "curl/7.81.0-DEV",
"X-B3-Sampled": "0",
"X-B3-Spanid": "5184d2557d92d7c2",
"X-B3-Traceid": "3d54c4b557de8ac05184d2557d92d7c2",
"X-Forwarded-Client-Cert": "By=spiffe://cluster.local/ns/foo/sa/default;Hash=8d5816f39f390cb936cabe2151aee79d751830ea07dde2deb2900d8134edf7a7;Subject=\"O=test,CN=curl.example.com\";URI=spiffe://cluster.local/external-client"
}
}
Point proven! That is, if you expose your application directly out of the cluster…
Now Do It On The Ingress Gateway
Most clusters have their north south traffic through an ingress gateway instead. And applications/clients external to to the cluster will likely have a different CA. Let’s try and replicate the above with the traffic going through the ingress gateway.
Before proceeding, we need to perform some cleanup
kubectl -n foo delete authorizationpolicies.security.istio.io httpbin
Configuring mTLS On Ingress Gateway
We need the ingress gateway to now handle the mTLS connection with the client, which is well documented here. Start by generating the key pairs for the application and client:
# Generate new CA key pair
openssl req -x509 -sha256 -nodes -days 365 -newkey rsa:2048 -subj '/O=example Inc./CN=org' -keyout org.key -out org.crt
# Generate new key pair for server, signed by CA
openssl req -out httpbin.foo.csr -newkey rsa:2048 -nodes -keyout httpbin.foo.key \
-subj "/CN=httpbin.foo/O=httpbin organization"
openssl x509 -req -days 365 -CA org.crt -CAkey org.key -set_serial 0 -in httpbin.foo.csr -out httpbin.foo.crt
# Generate new key pair for client with SPIFFE ID, signed by CA
openssl req -new -out curl-spiffe.csr -newkey rsa:2048 -nodes -keyout curl-spiffe.key \
-subj "/CN=curl.example.com/O=test" -addext "subjectAltName = URI:spiffe://cluster.local/external-client"
openssl x509 -req -in curl-spiffe.csr -CA org.crt -CAkey org.key -CAcreateserial -out curl-spiffe.crt -days 365 -sha256 \
-extensions v3_ca -extfile <(printf "[ v3_ca ]\nsubjectAltName = URI:spiffe://cluster.local/external-client")
# Create full certificate chain containing client and CA certificate
cp curl-spiffe.crt curl-spiffe-chain.crt
cat org.crt >>curl-spiffe-chain.crt
Then, configure the ingress gateway to listen on httpbin.foo.com:443
, with mTLS enabled:
kubectl -n istio-system create secret generic httpbin-foo-credential \
--from-file=tls.key=httpbin.foo.key \
--from-file=tls.crt=httpbin.foo.crt \
--from-file=ca.crt=org.crt
kubectl -n istio-system apply -f - <<EOF
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
name: httpbin-gateway
spec:
selector:
istio: ingressgateway
servers:
- port:
number: 443
name: https
protocol: HTTPS
tls:
mode: MUTUAL
credentialName: httpbin-foo-credential
hosts:
- httpbin.foo.com
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: httpbin
spec:
hosts:
- "httpbin.foo.com"
gateways:
- httpbin-gateway
http:
- route:
- destination:
port:
number: 8000
host: httpbin.foo.svc.cluster.local
EOF
We can verify mTLS configuration with a request using the client key pair:
INGRESS_IP=$(kubectl -n istio-system get svc --selector app=istio-ingressgateway -o jsonpath='{.items[0].status.loadBalancer.ingress[0].ip}')
$ curl --resolve httpbin.foo.com:443:$INGRESS_IP -sk https://httpbin.foo.com/headers \
--cacert org.crt \
--cert curl-spiffe-chain.crt \
--key curl-spiffe.key
{
"headers": {
"Accept": "*/*",
"Content-Length": "0",
"Host": "httpbin.foo.com",
"User-Agent": "curl/7.68.0",
"X-B3-Parentspanid": "ac16bebc94fa3dbc",
"X-B3-Sampled": "0",
"X-B3-Spanid": "47428f1bbb8a30eb",
"X-B3-Traceid": "76b9206d3d55a16bac16bebc94fa3dbc",
"X-Envoy-Attempt-Count": "1",
"X-Envoy-Internal": "true",
"X-Forwarded-Client-Cert": "Hash=e91a9ae1bf68ea3506ee5926a18ce759a0b34a996d5a8fcfe814828ab261e56b;Cert=\"-----BEGIN%20CERTIFICATE-----%0AMIIDEjCCAfqgAwIBAgIUbF88dqNfNl2KJyyd5JuZZaYpL50wDQYJKoZIhvcNAQEL%0ABQAwJTEVMBMGA1UECgwMZXhhbXBsZSBJbmMuMQwwCgYDVQQDDANvcmcwHhcNMjIw%0AMjA5MjMzOTI1WhcNMjMwMjA5MjMzOTI1WjAqMRkwFwYDVQQDDBBjdXJsLmV4YW1w%0AbGUuY29tMQ0wCwYDVQQKDAR0ZXN0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB%0ACgKCAQEA35feAhVqTDnNDRKPfM0a6Vh4Vy79FzPl55m0e1Q%2Fl3xgvkIj%2BAfa788B%0At%2BBpAX4OEFHVYrSGmfjakdJVPP%2BNCHhtcRRl9ndyFLeexN5OxbCOaz8er4FklcJt%0A8VAYt%2BYcb6Yd8sEoYnKPYQSemVH9DQDwlusXA%2Bg61ulF%2BNr0vIq%2Bik26W7ZtaRsH%0A%2BOV6X4QrlBxAps5K8LUxzTz4ROqVjOklHf4AVM2V4ubt7w6qEIBDnZ3VGLUOqT1%2B%0AT59s%2B1U%2BEmI1UTBpDcFlNdWTQ4UXXMVZ5iYyxx317HRt0Boa7LhaP7oC1AUdnhHH%0AqTcB0WMWDF3G0S80nB7M6fK2BKG1PQIDAQABozUwMzAxBgNVHREEKjAohiZzcGlm%0AZmU6Ly9jbHVzdGVyLmxvY2FsL2V4dGVybmFsLWNsaWVudDANBgkqhkiG9w0BAQsF%0AAAOCAQEAXxHhroOtPfaSqu%2ByOTkJg3jWlHQrwYQLOVU7cM1oPFnK%2FXi%2BVgDgqGGe%0ASr80OhHJs%2BkF%2BhSTJOOiUq%2B%2BSUt4ClZo7%2FLPTlK5HvYrVzzgICbzvgdFFEflgd4R%0AincZ2vbODACtcaoXzOkNGzuuScqYm7kFe90iy1XInKw9FhtfE8L7rxMCYzuxKpng%0AgEzFxlt1SWKz%2B%2Fcb%2FfePLiAqqRUpRYLY7SXjlIpgkvW%2F4xQ9nVPtrag%2Fn%2Fl1QMy%2B%0Actq2X46wyuVmN4GY9pLULyjH%2FMhiQDmdJ%2ByU1Sc1%2BXf6QeCpmCb53%2Fi%2BJM1N%2Fkj0%0Azd%2BNeJtM0KIfuR8386r3GDCgXV1wrw%3D%3D%0A-----END%20CERTIFICATE-----%0A\";Subject=\"O=test,CN=curl.example.com\";URI=spiffe://cluster.local/external-client,By=spiffe://cluster.local/ns/foo/sa/default;Hash=3c64fe468fe970fc4c68b4a4dc63a89caf041e2b1db36a27ce4730c7955125a7;Subject=\"\";URI=spiffe://cluster.local/ns/istio-system/sa/istio-ingressgateway-service-account"
}
}
Take note of the
X-Forwarded-Client-Cert
header, as it will be important in the next bit.
Authorization Based On Principal (Spoiler: This Didn’t Work)
If we apply the same AuthorizationPolicy used in the previous section:
kubectl -n foo apply -f - <<EOF
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
name: httpbin
spec:
selector:
matchLabels:
app: httpbin
rules:
- from:
- source:
principals: ["cluster.local/external-client"]
EOF
$ curl --resolve httpbin.foo.com:443:$INGRESS_IP -sk https://httpbin.foo.com/headers \
--cacert org.crt \
--cert curl-spiffe-chain.crt \
--key curl-spiffe.key
RBAC: access denied
We see the request fail even though our client certificate contains the expected SPIFFE ID spiffe://cluster.local/external-client
!
This is because the request that reaches our application does not come directly from the external client, but through the ingress gateway. As a separate mTLS handshake occurs between the ingress gateway and the application, the principal of the request as seen by the istio-sidecar
of our application will actually be the ingress gateway, or spiffe://cluster.local/ns/istio-system/sa/istio-ingressgateway-service-account
. This was seen in the X-Forwarded-Client-Cert
header earlier. What we should do is move the AuthorizationPolicy to the ingress gateway instead:
kubectl -n foo delete authorizationpolicies.security.istio.io httpbin
kubectl -n istio-system apply -f - <<EOF
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
name: httpbin
spec:
selector:
matchLabels:
app: istio-ingressgateway
rules:
- from:
- source:
principals: ["cluster.local/external-client"]
to:
- operation:
hosts: ["httpbin.foo.com"]
EOF
And… that didn’t work:
$ curl --resolve httpbin.foo.com:443:$INGRESS_IP -sk https://httpbin.foo.com/headers \
--cacert org.crt \
--cert curl-spiffe-chain.crt \
--key curl-spiffe.key
RBAC: access denied
A closer look at the ingress gateway logs (debug level) reveals that request principals are not available for matching in AuthorizationPolicy rules. I’m not sure why, please let me know if you do.
Authorization Based On XFCC header
For now, let’s try another way - by checking the value of x-forwarded-client-cert
(XFCC) header ends with the client SPIFFE ID (the sidecar proxy should append the last client ID to the header):
kubectl -n istio-system apply -f - <<EOF
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
name: httpbin
spec:
selector:
matchLabels:
app: istio-ingressgateway
rules:
- to:
- operation:
hosts: ["httpbin.foo.com"]
when:
- key: request.headers[x-forwarded-client-cert]
values: ["*URI=spiffe://cluster.local/external-client"]
EOF
$ curl --resolve httpbin.foo.com:443:$INGRESS_IP -sk https://httpbin.foo.com/headers \
--cacert org.crt \
--cert curl-spiffe-chain.crt \
--key curl-spiffe.key
{
"headers": {
"Accept": "*/*",
"Content-Length": "0",
"Host": "httpbin.foo.com",
"User-Agent": "curl/7.68.0",
"X-B3-Parentspanid": "e70694db7976f375",
"X-B3-Sampled": "0",
"X-B3-Spanid": "cd4549198a32d9bc",
"X-B3-Traceid": "fd48181fb82a2185e70694db7976f375",
"X-Envoy-Attempt-Count": "1",
"X-Envoy-Internal": "true",
"X-Forwarded-Client-Cert": "Hash=e91a9ae1bf68ea3506ee5926a18ce759a0b34a996d5a8fcfe814828ab261e56b;Cert=\"-----BEGIN%20CERTIFICATE-----%0AMIIDEjCCAfqgAwIBAgIUbF88dqNfNl2KJyyd5JuZZaYpL50wDQYJKoZIhvcNAQEL%0ABQAwJTEVMBMGA1UECgwMZXhhbXBsZSBJbmMuMQwwCgYDVQQDDANvcmcwHhcNMjIw%0AMjA5MjMzOTI1WhcNMjMwMjA5MjMzOTI1WjAqMRkwFwYDVQQDDBBjdXJsLmV4YW1w%0AbGUuY29tMQ0wCwYDVQQKDAR0ZXN0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB%0ACgKCAQEA35feAhVqTDnNDRKPfM0a6Vh4Vy79FzPl55m0e1Q%2Fl3xgvkIj%2BAfa788B%0At%2BBpAX4OEFHVYrSGmfjakdJVPP%2BNCHhtcRRl9ndyFLeexN5OxbCOaz8er4FklcJt%0A8VAYt%2BYcb6Yd8sEoYnKPYQSemVH9DQDwlusXA%2Bg61ulF%2BNr0vIq%2Bik26W7ZtaRsH%0A%2BOV6X4QrlBxAps5K8LUxzTz4ROqVjOklHf4AVM2V4ubt7w6qEIBDnZ3VGLUOqT1%2B%0AT59s%2B1U%2BEmI1UTBpDcFlNdWTQ4UXXMVZ5iYyxx317HRt0Boa7LhaP7oC1AUdnhHH%0AqTcB0WMWDF3G0S80nB7M6fK2BKG1PQIDAQABozUwMzAxBgNVHREEKjAohiZzcGlm%0AZmU6Ly9jbHVzdGVyLmxvY2FsL2V4dGVybmFsLWNsaWVudDANBgkqhkiG9w0BAQsF%0AAAOCAQEAXxHhroOtPfaSqu%2ByOTkJg3jWlHQrwYQLOVU7cM1oPFnK%2FXi%2BVgDgqGGe%0ASr80OhHJs%2BkF%2BhSTJOOiUq%2B%2BSUt4ClZo7%2FLPTlK5HvYrVzzgICbzvgdFFEflgd4R%0AincZ2vbODACtcaoXzOkNGzuuScqYm7kFe90iy1XInKw9FhtfE8L7rxMCYzuxKpng%0AgEzFxlt1SWKz%2B%2Fcb%2FfePLiAqqRUpRYLY7SXjlIpgkvW%2F4xQ9nVPtrag%2Fn%2Fl1QMy%2B%0Actq2X46wyuVmN4GY9pLULyjH%2FMhiQDmdJ%2ByU1Sc1%2BXf6QeCpmCb53%2Fi%2BJM1N%2Fkj0%0Azd%2BNeJtM0KIfuR8386r3GDCgXV1wrw%3D%3D%0A-----END%20CERTIFICATE-----%0A\";Subject=\"O=test,CN=curl.example.com\";URI=spiffe://cluster.local/external-client,By=spiffe://cluster.local/ns/foo/sa/default;Hash=3c64fe468fe970fc4c68b4a4dc63a89caf041e2b1db36a27ce4730c7955125a7;Subject=\"\";URI=spiffe://cluster.local/ns/istio-system/sa/istio-ingressgateway-service-account"
}
}
The request got through! But is it safe?!
I updated the AuthorizationPolicy to accept another workload ID someone-else
:
kubectl -n istio-system apply -f - <<EOF
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
name: httpbin
spec:
selector:
matchLabels:
app: istio-ingressgateway
rules:
- to:
- operation:
hosts: ["httpbin.foo.com"]
when:
- key: request.headers[x-forwarded-client-cert]
values: ["*URI=spiffe://cluster.local/someone-else"]
EOF
and attempted to perform a requests from my client with the x-forwarded-client-cert
header set to URI=spiffe://cluster.local/someone-else
$ curl --resolve httpbin.foo.com:443:$INGRESS_IP -sk https://httpbin.foo.com/headers \
-H x-forwarded-client-cert:URI=spiffe://cluster.local/someone-else \
--cacert org.crt \
--cert curl-spiffe-chain.crt \
--key curl-spiffe.key
RBAC: access denied
Good. Looking at the ingress gateway logs, it seems like the ingress gateway sanitizes the x-forwarded-client-cert
header from the client.
2022-02-10T00:59:22.599433Z debug envoy rbac checking request: requestedServerName: httpbin.foo.com, sourceIP: 10.233.90.0:16569, directRemoteIP: 10.233.90.0:16569, remoteIP: 10.233.90.0:16569,localAddress: 10.233.92.41:8443, ssl: uriSanPeerCertificate: spiffe://cluster.local/external-client, dnsSanPeerCertificate: , subjectPeerCertificate: O=test,CN=curl.example.com, headers: ':method', 'GET'
':path', '/headers'
':scheme', 'https'
':authority', 'httpbin.foo.com'
'user-agent', 'curl/7.68.0'
'accept', '*/*'
'x-forwarded-client-cert', 'Hash=e91a9ae1bf68ea3506ee5926a18ce759a0b34a996d5a8fcfe814828ab261e56b;Cert="-----BEGIN%20CERTIFICATE-----%0AMIIDEjCCAfqgAwIBAgIUbF88dqNfNl2KJyyd5JuZZaYpL50wDQYJKoZIhvcNAQEL%0ABQAwJTEVMBMGA1UECgwMZXhhbXBsZSBJbmMuMQwwCgYDVQQDDANvcmcwHhcNMjIw%0AMjA5MjMzOTI1WhcNMjMwMjA5MjMzOTI1WjAqMRkwFwYDVQQDDBBjdXJsLmV4YW1w%0AbGUuY29tMQ0wCwYDVQQKDAR0ZXN0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB%0ACgKCAQEA35feAhVqTDnNDRKPfM0a6Vh4Vy79FzPl55m0e1Q%2Fl3xgvkIj%2BAfa788B%0At%2BBpAX4OEFHVYrSGmfjakdJVPP%2BNCHhtcRRl9ndyFLeexN5OxbCOaz8er4FklcJt%0A8VAYt%2BYcb6Yd8sEoYnKPYQSemVH9DQDwlusXA%2Bg61ulF%2BNr0vIq%2Bik26W7ZtaRsH%0A%2BOV6X4QrlBxAps5K8LUxzTz4ROqVjOklHf4AVM2V4ubt7w6qEIBDnZ3VGLUOqT1%2B%0AT59s%2B1U%2BEmI1UTBpDcFlNdWTQ4UXXMVZ5iYyxx317HRt0Boa7LhaP7oC1AUdnhHH%0AqTcB0WMWDF3G0S80nB7M6fK2BKG1PQIDAQABozUwMzAxBgNVHREEKjAohiZzcGlm%0AZmU6Ly9jbHVzdGVyLmxvY2FsL2V4dGVybmFsLWNsaWVudDANBgkqhkiG9w0BAQsF%0AAAOCAQEAXxHhroOtPfaSqu%2ByOTkJg3jWlHQrwYQLOVU7cM1oPFnK%2FXi%2BVgDgqGGe%0ASr80OhHJs%2BkF%2BhSTJOOiUq%2B%2BSUt4ClZo7%2FLPTlK5HvYrVzzgICbzvgdFFEflgd4R%0AincZ2vbODACtcaoXzOkNGzuuScqYm7kFe90iy1XInKw9FhtfE8L7rxMCYzuxKpng%0AgEzFxlt1SWKz%2B%2Fcb%2FfePLiAqqRUpRYLY7SXjlIpgkvW%2F4xQ9nVPtrag%2Fn%2Fl1QMy%2B%0Actq2X46wyuVmN4GY9pLULyjH%2FMhiQDmdJ%2ByU1Sc1%2BXf6QeCpmCb53%2Fi%2BJM1N%2Fkj0%0Azd%2BNeJtM0KIfuR8386r3GDCgXV1wrw%3D%3D%0A-----END%20CERTIFICATE-----%0A";Subject="O=test,CN=curl.example.com";URI=spiffe://cluster.local/external-client'
'x-forwarded-for', '10.233.90.0'
'x-forwarded-proto', 'https'
'x-envoy-internal', 'true'
'x-request-id', '5cd3255c-618a-4db5-aae6-3ddfb24c9631'
'x-envoy-decorator-operation', 'httpbin.foo.svc.cluster.local:8000/*'
'x-envoy-peer-metadata', 'ChQKDkFQUF9DT05UQUlORVJTEgIaAAoaCgpDTFVTVEVSX0lEEgwaCkt1YmVybmV0ZXMKHAoNSVNUSU9fVkVSU0lPThILGgkxLjkuOC1hbTEKvwMKBkxBQkVMUxK0AyqxAwodCgNhcHASFhoUaXN0aW8taW5ncmVzc2dhdGV3YXkKEwoFY2hhcnQSChoIZ2F0ZXdheXMKFAoIaGVyaXRhZ2USCBoGVGlsbGVyCjYKKWluc3RhbGwub3BlcmF0b3IuaXN0aW8uaW8vb3duaW5nLXJlc291cmNlEgkaB3Vua25vd24KGQoFaXN0aW8SEBoOaW5ncmVzc2dhdGV3YXkKGQoMaXN0aW8uaW8vcmV2EgkaB2RlZmF1bHQKMAobb3BlcmF0b3IuaXN0aW8uaW8vY29tcG9uZW50EhEaD0luZ3Jlc3NHYXRld2F5cwohChFwb2QtdGVtcGxhdGUtaGFzaBIMGgo2OTQ5Y2RkNjVjChIKB3JlbGVhc2USBxoFaXN0aW8KOQofc2VydmljZS5pc3Rpby5pby9jYW5vbmljYWwtbmFtZRIWGhRpc3Rpby1pbmdyZXNzZ2F0ZXdheQovCiNzZXJ2aWNlLmlzdGlvLmlvL2Nhbm9uaWNhbC1yZXZpc2lvbhIIGgZsYXRlc3QKIgoXc2lkZWNhci5pc3Rpby5pby9pbmplY3QSBxoFZmFsc2UKLwoETkFNRRInGiVpc3Rpby1pbmdyZXNzZ2F0ZXdheS02OTQ5Y2RkNjVjLW00amI2ChsKCU5BTUVTUEFDRRIOGgxpc3Rpby1zeXN0ZW0KXQoFT1dORVISVBpSa3ViZXJuZXRlczovL2FwaXMvYXBwcy92MS9uYW1lc3BhY2VzL2lzdGlvLXN5c3RlbS9kZXBsb3ltZW50cy9pc3Rpby1pbmdyZXNzZ2F0ZXdheQoXChFQTEFURk9STV9NRVRBREFUQRICKgAKJwoNV09SS0xPQURfTkFNRRIWGhRpc3Rpby1pbmdyZXNzZ2F0ZXdheQ=='
'x-envoy-peer-metadata-id', 'router~10.233.92.41~istio-ingressgateway-6949cdd65c-m4jb6.istio-system~istio-system.svc.cluster.local'
, dynamicMetadata:
Documentation here further confirms that the default value for forwardClientCertDetails
is set to SANITIZE
on gateways. More information on this behaviour can be found in Envoy documentations (see here and here).
Conclusion
I’ve demonstrated that it is possible to perform some level of authorization based on the SPIFFE ID presented in the client certificate, both at the pod and at the ingress gateway level.
However, it should be used with caution, as someone malicious who has access to the client key pair will be able to pretend to be a legitimate client. There’s no inbuilt certificate rotation here, so treat it like a username/password, and rotate often the SPIFFE ID often!