Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Configure mutual TLS (mTLS) for zero-trust service-to-service communication. Use when implementing zero-trust networking, certificate management, or securing internal service communication.
.claude/skills/dicklesworthstone-mtls-configuration/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-04 | ✗→✓ | ▲ Improved | 145% | 0% |
| case-08 | ✗→✓ | ▲ Improved | 72% | 0% |
| case-10 | ✗→✓ | ▲ Improved | 94% | 0% |
| case-03 | ✓→✓ | = Same ✓ | 104% | 0% |
| case-05 | ✓→✓ | = Same ✓ | 195% | 0% |
Comprehensive guide to implementing mutual TLS for zero-trust service mesh communication.
┌─────────┐ ┌─────────┐
│ Service │ │ Service │
│ A │ │ B │
└────┬────┘ └────┬────┘
│ │
┌────┴────┐ TLS Handshake ┌────┴────┐
│ Proxy │◄───────────────────────────►│ Proxy │
│(Sidecar)│ 1. ClientHello │(Sidecar)│
│ │ 2. ServerHello + Cert │ │
│ │ 3. Client Cert │ │
│ │ 4. Verify Both Certs │ │
│ │ 5. Encrypted Channel │ │
└─────────┘ └─────────┘Root CA (Self-signed, long-lived)
│
├── Intermediate CA (Cluster-level)
│ │
│ ├── Workload Cert (Service A)
│ └── Workload Cert (Service B)
│
└── Intermediate CA (Multi-cluster)
│
└── Cross-cluster certsyaml# Enable strict mTLS mesh-wide apiVersion: security.istio.io/v1beta1 kind: PeerAuthentication metadata: name: default namespace: istio-system spec: mtls: mode: STRICT --- # Namespace-level override (permissive for migration) apiVersion: security.istio.io/v1beta1 kind: PeerAuthentication metadata: name: default namespace: legacy-namespace spec: mtls: mode: PERMISSIVE --- # Workload-specific policy apiVersion: security.istio.io/v1beta1 kind: PeerAuthentication metadata: name: payment-service namespace: production spec: selector: matchLabels: app: payment-service mtls: mode: STRICT portLevelMtls: 8080: mode: STRICT 9090: mode: DISABLE # Metrics port, no mTLS
yamlapiVersion: networking.istio.io/v1beta1 kind: DestinationRule metadata: name: default namespace: istio-system spec: host: "*.local" trafficPolicy: tls: mode: ISTIO_MUTUAL --- # TLS to external service apiVersion: networking.istio.io/v1beta1 kind: DestinationRule metadata: name: external-api spec: host: api.external.com trafficPolicy: tls: mode: SIMPLE caCertificates: /etc/certs/external-ca.pem --- # Mutual TLS to external service apiVersion: networking.istio.io/v1beta1 kind: DestinationRule metadata: name: partner-api spec: host: api.partner.com trafficPolicy: tls: mode: MUTUAL clientCertificate: /etc/certs/client.pem privateKey: /etc/certs/client-key.pem caCertificates: /etc/certs/partner-ca.pem
yaml# Install cert-manager issuer for Istio apiVersion: cert-manager.io/v1 kind: ClusterIssuer metadata: name: istio-ca spec: ca: secretName: istio-ca-secret --- # Create Istio CA secret apiVersion: v1 kind: Secret metadata: name: istio-ca-secret namespace: cert-manager type: kubernetes.io/tls data: tls.crt: <base64-encoded-ca-cert> tls.key: <base64-encoded-ca-key> --- # Certificate for workload apiVersion: cert-manager.io/v1 kind: Certificate metadata: name: my-service-cert namespace: my-namespace spec: secretName: my-service-tls duration: 24h renewBefore: 8h issuerRef: name: istio-ca kind: ClusterIssuer commonName: my-service.my-namespace.svc.cluster.local dnsNames: - my-service - my-service.my-namespace - my-service.my-namespace.svc - my-service.my-namespace.svc.cluster.local usages: - server auth - client auth
yaml# SPIRE Server configuration apiVersion: v1 kind: ConfigMap metadata: name: spire-server namespace: spire data: server.conf: | server { bind_address = "0.0.0.0" bind_port = "8081" trust_domain = "example.org" data_dir = "/run/spire/data" log_level = "INFO" ca_ttl = "168h" default_x509_svid_ttl = "1h" } plugins { DataStore "sql" { plugin_data { database_type = "sqlite3" connection_string = "/run/spire/data/datastore.sqlite3" } } NodeAttestor "k8s_psat" { plugin_data { clusters = { "demo-cluster" = { service_account_allow_list = ["spire:spire-agent"] } } } } KeyManager "memory" { plugin_data {} } UpstreamAuthority "disk" { plugin_data { key_file_path = "/run/spire/secrets/bootstrap.key" cert_file_path = "/run/spire/secrets/bootstrap.crt" } } } --- # SPIRE Agent DaemonSet (abbreviated) apiVersion: apps/v1 kind: DaemonSet metadata: name: spire-agent namespace: spire spec: selector: matchLabels: app: spire-agent template: spec: containers: - name: spire-agent image: ghcr.io/spiffe/spire-agent:1.8.0 volumeMounts: - name: spire-agent-socket mountPath: /run/spire/sockets volumes: - name: spire-agent-socket hostPath: path: /run/spire/sockets type: DirectoryOrCreate
yaml# Linkerd enables mTLS automatically # Verify with: # linkerd viz edges deployment -n my-namespace # For external services without mTLS apiVersion: policy.linkerd.io/v1beta1 kind: Server metadata: name: external-api namespace: my-namespace spec: podSelector: matchLabels: app: my-app port: external-api proxyProtocol: HTTP/1 # or TLS for passthrough --- # Skip TLS for specific port apiVersion: v1 kind: Service metadata: name: my-service annotations: config.linkerd.io/skip-outbound-ports: "3306" # MySQL
bash# Istio - Check certificate expiry istioctl proxy-config secret deploy/my-app -o json | \ jq '.dynamicActiveSecrets[0].secret.tlsCertificate.certificateChain.inlineBytes' | \ tr -d '"' | base64 -d | openssl x509 -text -noout # Force certificate rotation kubectl rollout restart deployment/my-app # Check Linkerd identity linkerd identity -n my-namespace
bash# Istio - Check if mTLS is enabled istioctl authn tls-check my-service.my-namespace.svc.cluster.local # Verify peer authentication kubectl get peerauthentication --all-namespaces # Check destination rules kubectl get destinationrule --all-namespaces # Debug TLS handshake istioctl proxy-config log deploy/my-app --level debug kubectl logs deploy/my-app -c istio-proxy | grep -i tls # Linkerd - Check mTLS status linkerd viz edges deployment -n my-namespace linkerd viz tap deploy/my-app --to deploy/my-backend
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→fail | 8,463 | 10,006 | +18% | 1 | 1 | 0% | 1,770 | 3,680 | +108% | 0 | 0 | — |
case-02 | fail→fail | 18,875 | 13,700 | -27% | 1 | 1 | 0% | 3,448 | 5,102 | +48% | 0 | 0 | — |
case-03 | pass→pass | 10,096 | 13,343 | +32% | 1 | 1 | 0% | 1,978 | 4,027 | +104% | 0 | 0 | — |
case-04 | fail→pass | 10,180 | 12,761 | +25% | 1 | 1 | 0% | 1,821 | 4,460 | +145% | 0 | 0 | — |
case-05 | pass→pass | 6,916 | 6,875 | -1% | 1 | 1 | 0% | 1,263 | 3,729 | +195% | 0 | 0 | — |
case-06 | pass→pass | 6,478 | 3,798 | -41% | 1 | 1 | 0% | 1,110 | 3,122 | +181% | 0 | 0 | — |
case-07 | pass→pass | 13,262 | 7,209 | -46% | 1 | 1 | 0% | 2,328 | 3,715 | +60% | 0 | 0 | — |
case-08 | fail→pass | 24,206 | 6,921 | -71% | 1 | 1 | 0% | 2,075 | 3,571 | +72% | 0 | 0 | — |
case-09 | pass→pass | 9,475 | 5,554 | -41% | 1 | 1 | 0% | 1,710 | 3,369 | +97% | 0 | 0 | — |
case-10 | fail→pass | 12,602 | 7,789 | -38% | 1 | 1 | 0% | 1,919 | 3,717 | +94% | 0 | 0 | — |
case-11 | pass→pass | 15,656 | 14,850 | -5% | 1 | 1 | 0% | 2,664 | 5,103 | +92% | 0 | 0 | — |
case-12 | pass→pass | 7,499 | 7,937 | +6% | 1 | 1 | 0% | 1,258 | 3,729 | +196% | 0 | 0 | — |
case-13 | pass→pass | 12,004 | 6,101 | -49% | 1 | 1 | 0% | 2,064 | 3,479 | +69% | 0 | 0 | — |
case-14 | pass→pass | 5,508 | 2,538 | -54% | 1 | 1 | 0% | 953 | 2,776 | +191% | 0 | 0 | — |
case-15 | pass→pass | 9,925 | 4,315 | -57% | 1 | 1 | 0% | 1,663 | 3,094 | +86% | 0 | 0 | — |
case-16 | pass→pass | 15,768 | 8,361 | -47% | 1 | 1 | 0% | 2,904 | 3,984 | +37% | 0 | 0 | — |
case-17 | pass→pass | 6,467 | 5,811 | -10% | 1 | 1 | 0% | 1,060 | 3,323 | +213% | 0 | 0 | — |
case-18 | pass→pass | 13,219 | 8,586 | -35% | 1 | 1 | 0% | 2,096 | 3,765 | +80% | 0 | 0 | — |
case-19 | pass→pass | 6,867 | 3,100 | -55% | 1 | 1 | 0% | 1,023 | 2,897 | +183% | 0 | 0 | — |
case-20 | pass→pass | 4,537 | 2,364 | -48% | 1 | 1 | 0% | 708 | 2,788 | +294% | 0 | 0 | — |
case-21 | pass→pass | 6,259 | 3,258 | -48% | 1 | 1 | 0% | 1,107 | 2,994 | +170% | 0 | 0 | — |
case-22 | pass→pass | 7,096 | 5,427 | -24% | 1 | 1 | 0% | 1,270 | 3,294 | +159% | 0 | 0 | — |
case-23 | pass→pass | 5,446 | 4,467 | -18% | 1 | 1 | 0% | 745 | 3,179 | +327% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 23 cases were attempted. The headline lift of +13 percentage points is the difference between those two pass rates over the 23 comparable cases. 1 case got worse with the skill loaded, and it is included in that figure.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.