johanneskueber.com

Envoy Gateway Configuration to Reach SSL Labs A+

2026-05-14

This article documents the Envoy Gateway configuration required to achieve an A+ rating on Qualys SSL Labs, with the rationale for each setting.


1. TLS security background

Before any application data flows over https://, the TLS session completes two steps:

  1. Authentication. The server presents an X.509 certificate signed by a trusted Certificate Authority.
  2. Key agreement and encryption. Client and server negotiate a session key via a cipher suite (key exchange, signature, bulk cipher, MAC) and encrypt the channel.

Weaknesses in this chain enable specific attack classes:

  • Deprecated protocol versions (SSL 3, TLS 1.0, TLS 1.1) are vulnerable to POODLE, BEAST, and downgrade attacks, and rely on broken primitives such as RC4 and MD5-HMAC.
  • Weak cipher suites (RC4, 3DES, CBC modes with HMAC-SHA1, anything without forward secrecy) leak data or enable padding-oracle attacks.
  • Missing forward secrecy allows a single compromised private key to decrypt all previously recorded traffic.
  • Missing HSTS allows a network attacker to strip the https:// redirect on first visit and serve plain HTTP.
  • Certificate hygiene issues (weak keys, SHA-1 signatures, missing intermediates) break the trust chain.

An A+ configuration eliminates these weaknesses while preserving interoperability with modern clients.


2. The SSL Labs grading methodology

Qualys SSL Labs is a remote scanner that probes a public HTTPS endpoint and grades it from F to A+. The grade is derived from four scored categories combined with overriding hard rules.

CategoryWeightMeasures
Certificate30%Trust chain, key size, signature algorithm, expiry, hostname match
Protocol Support30%Enabled and disabled TLS versions
Key Exchange30%Strength of DH/ECDH parameters and forward secrecy
Cipher Strength10%The weakest cipher suite the server will accept

Overriding hard rules:

  • TLS 1.0 or 1.1 enabled -> grade capped at B.
  • TLS 1.3 not supported -> grade capped at A- (TLS 1.3 is graded as 100% protocol strength).
  • HSTS absent or invalid -> grade reduced from A to A-.
  • No forward secrecy -> capped at B.
  • No AEAD ciphers -> capped at B.

An A+ grade requires an A-grade configuration with no warnings and a valid Strict-Transport-Security header with max-age of at least six months (15 768 000 seconds).


3. Configuration requirements

Each item below maps directly to a YAML field in the following sections.

  1. TLS 1.2 and 1.3 only. All earlier versions disabled. TLS 1.3 must be enabled.
  2. AEAD cipher suites with forward secrecy only: ECDHE key exchange with AES-GCM or ChaCha20-Poly1305. No CBC, RC4, or 3DES.
  3. Strong certificate: 2048-bit RSA minimum or 256/384-bit ECDSA, SHA-256 signature, full chain including intermediates.
  4. HSTS on every HTTPS response with max-age >= 15 768 000, preferably with includeSubDomains. Inject via ClientTrafficPolicy.headers.lateResponseHeaders so the header is present on backend responses and on Envoy-generated error responses.
  5. HTTP-to-HTTPS redirect on port 80.
  6. CAA DNS record restricting which CAs may issue certificates for the domain. Not directly scored, but a recommended practice.

4. The Envoy Gateway resources

The complete A+ configuration consists of four resources:

  1. A Secret containing the full chain and key – produced by cert-manager (see section 5).
  2. A Gateway with HTTP (port 80, redirect) and HTTPS (port 443) listeners, annotated to drive cert-manager.
  3. A ClientTrafficPolicy scoped to the HTTPS listener that pins TLS 1.2/1.3 with AEAD-only forward-secret ciphers, modern curves, ALPN, and safe signature algorithms, and injects Strict-Transport-Security and related security headers gateway-wide via headers.lateResponseHeaders.
  4. Two HTTPRoute resources – one to 301-redirect HTTP to HTTPS, one to forward traffic to the backend.

Combined with cert-manager using DNS-01 issuance, the full set of hardening annotations, and a CAA record, this configuration satisfies SSL Labs A+ and the transport-layer requirements of PCI-DSS 4.0, ISO 27001:2022, and comparable frameworks.

The diagram below shows how the resources interact, including cert-manager populating the Secret out of band:

flowchart LR
    Client((Client))

    subgraph Gateway["Gateway: eg"]
        HTTP["listener http :80"]
        HTTPS["listener https :443<br/>TLS Terminate"]
    end

    Redirect["HTTPRoute<br/>http -> https (301)"]
    App["HTTPRoute<br/>app"]
    CTP["ClientTrafficPolicy<br/>=============<br/>TLS 1.2/1.3 + ciphers<br/>Late HSTS header"]
    Secret[("Secret<br/>johanneskueber-com-tls")]
    Backend[("Backend Service")]

    Client -->|":80"| HTTP
    Client -->|":443"| HTTPS
    HTTP --> Redirect
    Redirect -.->|"301 Location"| Client
    HTTPS --> App --> Backend

    CTP -.->|"sectionName: https"| HTTPS
    Secret -.->|"certificateRefs"| HTTPS

    CM["cert-manager<br/>gateway-shim"] -.->|"creates / renews"| Secret
    CI["ClusterIssuer<br/>Let's Encrypt DNS-01"] --> CM
    Gateway -.->|"cluster-issuer annotation"| CM

The examples assume Envoy Gateway v1.5+ and the Gateway API standard channel (gateway.networking.k8s.io/v1).


4.1 The TLS certificate Secret

1
2
3
4
5
6
7
8
9
apiVersion: v1
kind: Secret
metadata:
  name: johanneskueber-com-tls
  namespace: envoy-gateway-system
type: kubernetes.io/tls
data:
  tls.crt: <base64 fullchain.pem>
  tls.key: <base64 privkey.pem>

Field reference:

  • type: kubernetes.io/tls – the standard Kubernetes TLS secret type. Envoy Gateway requires this exact type; an Opaque secret with the same keys is rejected by the Gateway API.
  • tls.crt – must contain the full chain: leaf certificate followed by every intermediate up to but not including the root, concatenated in PEM. Serving only the leaf produces an “incomplete chain” warning that caps the grade below A.
  • tls.key – the matching private key. Recommended: ECDSA P-256 or P-384, or 2048-bit RSA at minimum. Anything below 2048-bit RSA fails the certificate category outright.

Envoy Gateway supports OCSP stapling via an additional tls.ocsp-staple key in the Secret, but it is intentionally omitted here. Let’s Encrypt removed OCSP URLs from issued certificates in May 2025 and shut down its OCSP responders in August 2025; revocation for Let’s Encrypt-issued certificates is now distributed exclusively via CRLs. Stapling applies only when using a CA that still publishes OCSP responses (DigiCert, Sectigo, private PKI). cert-manager does not populate the staple field, so a separate controller (e.g. ocsp-manager) is required if stapling is needed.

In production the Secret is generated by cert-manager rather than authored manually – see section 5.


4.2 The Gateway: two listeners

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: eg
  namespace: envoy-gateway-system
spec:
  gatewayClassName: eg
  listeners:
    - name: http
      protocol: HTTP
      port: 80
      allowedRoutes:
        namespaces:
          from: All
    - name: https
      protocol: HTTPS
      port: 443
      hostname: "www.johanneskueber.com"
      tls:
        mode: Terminate
        certificateRefs:
          - kind: Secret
            group: ""
            name: johanneskueber-com-tls
      allowedRoutes:
        namespaces:
          from: All

The port-80 listener exists solely to host the HTTP-to-HTTPS redirect. SSL Labs and other scanners require the redirect to grade the deployment correctly; without the listener, port 80 returns connection-refused and the redirect is flagged as missing.

Field reference:

  • gatewayClassName: eg – references the GatewayClass installed by the Envoy Gateway Helm chart, identifying the controller responsible for this Gateway.
  • listeners[].name – stable identifier referenced from ClientTrafficPolicy via sectionName, allowing distinct policies per listener.
  • protocol: HTTPS – terminates TLS at the listener and parses HTTP inside the encrypted channel. The alternative protocol: TLS performs SNI-based TCP passthrough, which prevents HTTP-level routing and HSTS injection.
  • port: 443 – required by SSL Labs, which does not test arbitrary ports by default.
  • hostname: "www.johanneskueber.com" – restricts the SNI values this listener accepts and allows multiple sites to share a Gateway. Wildcards such as "*.johanneskueber.com" are supported.
  • tls.mode: Terminate – Envoy decrypts at the listener and forwards plaintext (or re-encrypted via a BackendTLSPolicy) to the backend.
  • tls.certificateRefs – one or more Secret references. Multiple Secrets enable dual RSA + ECDSA serving. Cross-namespace references require a ReferenceGrant.
  • allowedRoutes.namespaces.from: All – allows HTTPRoute resources in any namespace to attach. Tighten to Same or Selector for stricter multi-tenancy.

This Gateway does not yet enforce TLS 1.2+, restrict cipher suites, or send HSTS. Those are configured next.


4.3 The ClientTrafficPolicy: TLS hardening and gateway-wide security headers

This resource is the core of the A+ rating. ClientTrafficPolicy controls how Envoy behaves toward downstream clients, including TLS parameters and globally injected response headers.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
apiVersion: gateway.envoyproxy.io/v1alpha1
kind: ClientTrafficPolicy
metadata:
  name: tls-hardening
  namespace: envoy-gateway-system
spec:
  targetRefs:
    - group: gateway.networking.k8s.io
      kind: Gateway
      name: eg
      sectionName: https
  tls:
    minVersion: "1.2"
    maxVersion: "1.3"
    ciphers:
      - ECDHE-ECDSA-AES256-GCM-SHA384
      - ECDHE-RSA-AES256-GCM-SHA384
      - ECDHE-ECDSA-CHACHA20-POLY1305
      - ECDHE-RSA-CHACHA20-POLY1305
      - ECDHE-ECDSA-AES128-GCM-SHA256
      - ECDHE-RSA-AES128-GCM-SHA256
    ecdhCurves:
      - X25519
      - P-256
    alpnProtocols:
      - h2
      - http/1.1
    signatureAlgorithms:
      - ecdsa_secp256r1_sha256
      - rsa_pss_rsae_sha256
      - rsa_pkcs1_sha256
      - ecdsa_secp384r1_sha384
      - rsa_pss_rsae_sha384
      - rsa_pkcs1_sha384
  headers:
    lateResponseHeaders:
      set:
        - name: Strict-Transport-Security
          value: "max-age=63072000; includeSubDomains; preload"
        - name: X-Content-Type-Options
          value: "nosniff"
        - name: Referrer-Policy
          value: "strict-origin-when-cross-origin"
      addIfAbsent:
        - name: Content-Security-Policy
          value: "default-src 'self'"
        - name: X-Frame-Options
          value: "DENY"

TLS parameters

  • targetRefs[].sectionName: https – binds the policy to the https listener only. Without sectionName the policy attaches to every listener on the Gateway.
  • tls.minVersion: "1.2" – refuses handshakes using TLS 1.0 or 1.1, moving the protocol-support score from “capped at B” into A range. Valid values: "1.0", "1.1", "1.2", "1.3". Versions below 1.2 are deprecated by RFC 8996 (2021). The value must be quoted; an unquoted 1.3 is parsed as a float and rejected.
  • tls.maxVersion: "1.3" – permits TLS 1.3 in negotiation. SSL Labs grades TLS 1.3 at 100% protocol strength and caps the grade at A- if absent. Under TLS 1.3 the ciphers list is ignored; TLS 1.3 has a fixed AEAD-only cipher set (TLS_AES_128_GCM_SHA256, TLS_AES_256_GCM_SHA384, TLS_CHACHA20_POLY1305_SHA256) that cannot be subsetted.
  • tls.ciphers – applies only to TLS 1.2. The list is in server preference order; the first cipher the client also supports wins. Selection rationale:
    • ECDHE-ECDSA-AES256-GCM-SHA384 / ...-RSA-... – strongest AEAD with ECDHE for forward secrecy. ECDSA variants are listed first because they are faster when the server presents an ECDSA certificate.
    • ECDHE-...-CHACHA20-POLY1305 – preferred on mobile clients lacking AES-NI hardware acceleration.
    • ...-AES128-GCM-SHA256 – for compatibility with older clients. AES-128 in GCM mode remains fully secure.
    • All CBC suites, non-ephemeral RSA key exchange, SHA-1, 3DES, and RC4 are excluded. CBC suites are vulnerable to Lucky13-style timing attacks; non-ephemeral RSA lacks forward secrecy and caps the grade at B.
  • tls.ecdhCurves – the elliptic curves offered for ECDHE. X25519 is the modern default; P-256 is included for legacy and FIPS compatibility. P-384 and P-521 are slower and rarely required.
  • tls.alpnProtocols – Application-Layer Protocol Negotiation. h2 advertises HTTP/2; http/1.1 is the fallback. Enables HTTP/2 without an additional round-trip.
  • tls.signatureAlgorithms – restricts the signature schemes the server advertises. SHA-1 schemes (rsa_pkcs1_sha1 etc.) are excluded; their presence triggers a T (trust) penalty on SSL Labs. Modern RSA-PSS (rsa_pss_rsae_*) and PKCS#1 (rsa_pkcs1_*, for TLS 1.2 clients) are included.

With no minVersion or maxVersion specified, Envoy Gateway defaults to TLS 1.2 minimum and TLS 1.3 maximum. The explicit block above documents the contract and adds cipher hardening.

Gateway-wide security headers

headers.lateResponseHeaders is the recommended location for HSTS and other security headers. It runs after all per-route filters and after the backend response, immediately before bytes leave Envoy. Two consequences follow:

  1. Headers apply to Envoy-generated responses – 503s when the backend is unreachable, 504s on timeout, 400s on malformed requests. Per-route ResponseHeaderModifier filters do not run on these responses.
  2. Single source of truth – no need to replicate HSTS on every HTTPRoute.

Verb selection:

  • set – overwrites any value sent by the backend. Used for Strict-Transport-Security, X-Content-Type-Options, and Referrer-Policy, where there is one correct value at the gateway edge.
  • addIfAbsent – applied only if the backend did not set the header. Used for Content-Security-Policy and X-Frame-Options, where individual applications may require a stricter or more permissive policy.
  • add – appends to existing values. Avoided for security headers, where duplicate values produce undefined browser behaviour. Reserved for telemetry and tracing headers.

Strict-Transport-Security directive breakdown:

  • max-age=63072000 – two years. SSL Labs requires at least 15768000 (6 months) for A+; two years is required for hstspreload.org submission.
  • includeSubDomains – extends the policy to every subdomain. Enable only when every subdomain has a valid certificate and serves HTTPS; otherwise the policy locks legitimate subdomains out for max-age seconds.
  • preload – signals intent for inclusion in the HSTS preload list. Browsers ship the list pre-installed, so even first-time visitors refuse plain HTTP. Effectively irreversible – submit at hstspreload.org only after running with max-age=63072000; includeSubDomains for several weeks without issue.

4.4 HTTP -> HTTPS redirect

Without this, clients reaching port 80 receive a connection refusal or 404 and never reach the HTTPS site.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: http-to-https-redirect
  namespace: envoy-gateway-system
spec:
  parentRefs:
    - name: eg
      sectionName: http
  hostnames:
    - "www.johanneskueber.com"
  rules:
    - filters:
        - type: RequestRedirect
          requestRedirect:
            scheme: https
            statusCode: 301
            port: 443

Field reference:

  • parentRefs[].sectionName: http – attaches the route to the HTTP listener only. Applying it to HTTPS would cause HTTPS requests to redirect to themselves.
  • hostnames – restrict to owned domains to prevent open-redirect abuse.
  • filters[].type: RequestRedirect – built-in Gateway API filter; no custom resource required.
  • requestRedirect.scheme: https – destination scheme.
  • requestRedirect.statusCode: 301 – permanent redirect, cached by browsers. HSTS preload checkers require a 301.
  • requestRedirect.port: 443 – explicit destination port. Envoy can infer it from the scheme, but explicit values avoid ambiguity behind non-standard load balancers.

4.5 The application HTTPRoute

With security headers managed by ClientTrafficPolicy, the application route only needs to forward traffic. Per-route exceptions (such as a stricter CSP for a specific path) can be added via a ResponseHeaderModifier filter; the late-headers policy applies afterwards.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: app
  namespace: default
spec:
  parentRefs:
    - name: eg
      namespace: envoy-gateway-system
      sectionName: https
  hostnames:
    - "www.johanneskueber.com"
  rules:
    - matches:
        - path:
            type: PathPrefix
            value: /
      backendRefs:
        - name: my-app
          port: 8080
  • parentRefs[].sectionName: https – attaches the route to the HTTPS listener only.
  • matches[].path.type: PathPrefix, value: / – catch-all match. Replace with more specific matches for finer routing.

5. cert-manager with maximum-security annotations

Two manifests are required: a ClusterIssuer and an annotated Gateway. The cert-manager gateway-shim controller watches Gateways for the trigger annotation and creates and renews Certificate resources automatically; no Certificate resource is authored by the user.

5.1 Precondition

The cert-manager controller must be started with Gateway API support enabled, and the Gateway API CRDs must have been present at startup. In Helm:

1
2
3
4
5
6
# cert-manager Helm values
config:
  apiVersion: controller.config.cert-manager.io/v1alpha1
  kind: ControllerConfiguration
  featureGates:
    ExperimentalGatewayAPISupport: true

This is enabled by default in cert-manager 1.15+. If the Gateway API CRDs were installed after cert-manager, restart its Deployment (kubectl rollout restart deployment cert-manager -n cert-manager); the shim checks for the CRDs only at startup and silently does nothing if they are missing.

5.2 The ClusterIssuer (Let’s Encrypt with DNS-01)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-prod-dns
spec:
  acme:
    server: https://acme-v02.api.letsencrypt.org/directory
    email: security@johanneskueber.com
    privateKeySecretRef:
      name: letsencrypt-prod-account-key
    privateKey:
      algorithm: ECDSA
      size: 256
    solvers:
      - dns01:
          cloudflare:
            apiTokenSecretRef:
              name: cloudflare-api-token
              key: api-token
        selector:
          dnsZones:
            - "johanneskueber.com"

DNS-01 is preferred over HTTP-01 for several reasons:

  • The HTTP-01 challenge endpoint on port 80 is not required, removing an attack surface.
  • Wildcard certificates (*.johanneskueber.com) are supported; HTTP-01 cannot issue them.
  • The challenge transits between cert-manager and the DNS provider API; an attacker controlling the HTTP path cannot answer it.
  • The CA validates via public DNS, which is harder to MITM than HTTP from arbitrary validator nodes.

The apiTokenSecretRef must reference a token scoped to DNS edit on the relevant zone only, not a global account token.

The privateKey block configures the ACME account key – separate from the certificate keypair – used to authenticate cert-manager to Let’s Encrypt.

5.3 The annotated Gateway

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: eg
  namespace: envoy-gateway-system
  annotations:
    # --- The trigger ---
    cert-manager.io/cluster-issuer: letsencrypt-prod-dns

    # --- Private-key hardening ---
    cert-manager.io/private-key-algorithm: ECDSA
    cert-manager.io/private-key-size: "384"
    cert-manager.io/private-key-encoding: PKCS8
    cert-manager.io/private-key-rotation-policy: Always

    # --- Lifecycle ---
    cert-manager.io/duration: "2160h"        # 90 days (Let's Encrypt max)
    cert-manager.io/renew-before: "720h"     # renew 30 days before expiry
    cert-manager.io/revision-history-limit: "3"

    # --- Usage restriction (defence in depth) ---
    cert-manager.io/usages: "digital signature,key encipherment,server auth"

    # --- Subject metadata ---
    cert-manager.io/common-name: "www.johanneskueber.com"
    cert-manager.io/subject-organizations: "Johnny Inc."
spec:
  gatewayClassName: eg
  listeners:
    - name: http
      protocol: HTTP
      port: 80
    - name: https
      protocol: HTTPS
      port: 443
      hostname: "www.johanneskueber.com"
      tls:
        mode: Terminate
        certificateRefs:
          - kind: Secret
            name: johanneskueber-com-tls
      allowedRoutes:
        namespaces:
          from: All

Trigger annotations

Exactly one of the two annotations below causes cert-manager to watch the Gateway and create a Certificate for each TLS listener’s Secret. No other annotation triggers cert-manager; the remainder are optional tuning applied to the generated Certificate.

  • cert-manager.io/cluster-issuer: <name> – references a cluster-scoped ClusterIssuer. Works from any namespace.
  • cert-manager.io/issuer: <name> – references a namespaced Issuer. Must be in the Gateway’s namespace.

Two additional annotations are required only for out-of-tree issuers (Venafi, AWS PCA, HashiCorp Vault, etc.):

  • cert-manager.io/issuer-kind: <Kind>
  • cert-manager.io/issuer-group: <api-group>

Hardening annotations

  • cert-manager.io/private-key-algorithm: ECDSA – ECDSA is preferred over RSA for smaller handshakes and faster signing. 256-bit ECDSA provides cryptographic strength equivalent to ~3072-bit RSA. RSA remains relevant only for compatibility with very old clients (pre-Android 4.0, pre-IE 11, pre-Java 7).
  • cert-manager.io/private-key-size: "384" – P-384 over P-256. P-256 is sufficient for A+; P-384 provides ~192-bit security and aligns with CNSA “Top Secret”. The cost is approximately 2x CPU on the signing operation, negligible at any realistic QPS. The value must be quoted. Use "256" for minimum handshake latency.
  • cert-manager.io/private-key-encoding: PKCS8 – modern key encoding. PKCS#1 (cert-manager’s legacy default) is RSA-only; PKCS#8 supports any algorithm.
  • cert-manager.io/private-key-rotation-policy: Always – generates a new keypair at every reissue. With Never (the legacy default before cert-manager 1.18), the same key persists for the Secret’s lifetime, meaning a leaked key remains valid across renewals. The default flipped to Always in 1.18; pinning it explicitly prevents config drift.
  • cert-manager.io/duration: "2160h" – 90 days, the Let’s Encrypt maximum. Shorter durations limit blast radius on compromise. Private CAs typically support shorter durations (24h-7d is common for service-to-service mTLS).
  • cert-manager.io/renew-before: "720h" – renew 30 days before expiry, approximately one-third of the total lifetime. This buffer absorbs ACME rate-limit hits and DNS propagation delays.
  • cert-manager.io/revision-history-limit: "3" – retain three CertificateRequest resources. The default in cert-manager 1.18+ is 1; 3 provides a small audit trail without unbounded growth.
  • cert-manager.io/usages: "digital signature,key encipherment,server auth" – restricts the X.509 Extended Key Usage and Key Usage extensions to TLS server use only. client auth and code signing are excluded, so a leaked private key cannot sign client certificates or binaries. Public CAs always set server auth; this annotation primarily affects private CAs that honour requested usages.
  • cert-manager.io/common-name / cert-manager.io/subject-organizations – the CN is deprecated by RFC in favour of SANs but remains visible in openssl x509 -text output and audit reports.

The dnsNames field on the generated Certificate is taken automatically from the hostname field of each TLS listener. For multiple hostnames, add more listeners or use a wildcard hostname.

5.4 Verifying issuance

1
kubectl -n envoy-gateway-system get certificate,certificaterequest,order,challenge

Within a few minutes the Certificate should reach Ready: True and the Secret referenced by certificateRefs will be populated. Envoy Gateway reloads listeners with no downtime when the Secret is updated.


6. CAA DNS record

johanneskueber.com.    IN  CAA  0 issue     "letsencrypt.org"
johanneskueber.com.    IN  CAA  0 issuewild "letsencrypt.org"
johanneskueber.com.    IN  CAA  0 iodef     "mailto:security@johanneskueber.com"

CAA declares to every public CA which authorities are authorised to issue certificates for the domain; others are required to refuse. SSL Labs reports CAA presence as a positive signal, and the record materially reduces the risk of CA-level mis-issuance. The iodef line specifies a contact for CAs that observe violations.


7. Verifying the result

Three layers of verification, in order of increasing thoroughness.

Handshake check:

1
2
openssl s_client -connect www.johanneskueber.com:443 -servername www.johanneskueber.com -tls1_3 < /dev/null
openssl s_client -connect www.johanneskueber.com:443 -servername www.johanneskueber.com -tls1   < /dev/null  # should fail

Cipher enumeration:

1
nmap --script ssl-enum-ciphers -p 443 www.johanneskueber.com

Only TLSv1.2 and TLSv1.3 sections should appear, every cipher graded A, with no warnings about CBC or weak DH.

HSTS on every response, including errors:

1
2
curl -sI https://www.johanneskueber.com/  | grep -i strict-transport
curl -sI https://www.johanneskueber.com/this-path-does-not-exist | grep -i strict-transport

Both must return the Strict-Transport-Security header. If only the first does, HSTS is being injected per-route rather than late; switch to ClientTrafficPolicy.headers.lateResponseHeaders.

SSL Labs:

https://www.ssllabs.com/ssltest/analyze.html?d=www.johanneskueber.com

For production servers, select “Do not show the results on the boards”. The full scan takes 60-90 seconds.


8. Outlook

Topics worth looking into next:

  • HTTP/3 (QUIC) via ClientTrafficPolicy.spec.http3 – performance gain on lossy networks; not graded by SSL Labs.
  • Dual RSA + ECDSA certificates – list two Secrets in certificateRefs when clients without ECDSA support must be served.
  • Production-grade Content Security Policy – replace the placeholder default-src 'self' with explicit script, style, image, and connect sources, ideally with nonces or hashes.
  • DNSSEC – closes a DNS-level downgrade vector. Not graded by SSL Labs.
  • CT log monitoring (crt.sh, Cert Spotter, Censys) – alerts on unexpected certificate issuance for the domain.
  • Session ticket policy – Envoy’s defaults are safe; disable entirely if the threat model warrants it.
  • Backend TLS via BackendTLSPolicy – encrypts the Envoy-to-backend leg. Pair with an EnvoyProxy resource for backend mTLS.
  • ListenerSet (GEP-1713) – self-service TLS for application teams on a shared Gateway. cert-manager 1.20+ supports it with the same annotations as Gateway.