Skip to main content

Encrypt Traffic Https Load Balancers With Tls Certificates

More Info:

Encrypt traffic to HTTPS load balancers using TLS certificates.

Risk Level

Low

Address

Security

Compliance Standards

  • APRA CPS 234 (Australia)
  • BSI C5 (Germany)
  • Brazil LGPD
  • CCPA / CPRA (California)
  • CIS Critical Security Controls v8
  • CIS OKE
  • CMMC 2.0
  • CSA Cloud Controls Matrix v4
  • DPDPA
  • Digital Operational Resilience Act (EU)
  • ISO/IEC 27017
  • ISO/IEC 27018
  • ISO/IEC 27701
  • KSA PDPL
  • MAS Technology Risk Management (Singapore)
  • MITRE ATT&CK (Cloud)
  • NIST SP 800-171
  • NYDFS 23 NYCRR 500
  • SWIFT Customer Security Controls Framework
  • Sarbanes-Oxley IT General Controls
  • Securities and Exchange Board of India (SEBI) - Cloud Security Adoption Framework
  • UK NCSC Cyber Assessment Framework

Triage and Remediation

Remediation

Manual Steps
  1. Inventory all external load balancers terminating client traffic

    • Using your cloud CLI, list all load balancers in front of Kubernetes/API workloads.
    • AWS example (Application/Network Load Balancers):
      aws elbv2 describe-load-balancers --query 'LoadBalancers[].{Name:LoadBalancerName,DNS:DNSName,Scheme:Scheme,Type:Type}' --output table
    • GCP example (HTTPS load balancers):
      gcloud compute forwarding-rules list --global --filter='target~https'
    • Azure example (public load balancers + app gateways):
      az network application-gateway list --query '[].{name:name, frontendPublicIp:frontendIpConfigurations[0].publicIpAddress.id}' -o table
      az network lb list --query '[].{name:name, sku:sku.name, public:frontendIPConfigurations[0].publicIpAddress!=null}' -o table
  2. Verify listeners use HTTPS/TLS, not plain HTTP

    • For each identified load balancer, list listeners and confirm protocol is HTTPS/TLS.
    • AWS:
      aws elbv2 describe-listeners --load-balancer-arn <LOAD_BALANCER_ARN> \
      --query 'Listeners[].{Port:Port,Protocol:Protocol,SSLPolicy:SslPolicy,Certs:Certificates}' --output table
    • GCP:
      gcloud compute target-https-proxies list
    • Azure App Gateway:
      az network application-gateway http-listener list -g <RESOURCE_GROUP> --gateway-name <APP_GW_NAME> \
      --query '[].{name:name, protocol:protocol, hostName:hostName, sslCert:sslCertificate.id}' -o table
    • If any external listener to your workloads is HTTP, plan to migrate it to HTTPS.
  3. Confirm TLS certificates are configured and valid on HTTPS listeners

    • AWS (check certificate ARNs and SSL policy):
      aws elbv2 describe-listeners --load-balancer-arn <LOAD_BALANCER_ARN> \
      --query 'Listeners[].{Port:Port,Protocol:Protocol,SSLPolicy:SslPolicy,Certs:Certificates}' --output json
      Then inspect each Certificates[].CertificateArn in ACM:
      aws acm describe-certificate --certificate-arn <CERT_ARN> \
      --query 'Certificate.{DomainName:DomainName, NotAfter:NotAfter, InUseBy:InUseBy}' --output table
    • GCP (check SSL certificates on HTTPS proxies):
      gcloud compute target-https-proxies describe <PROXY_NAME> \
      --format='value(sslCertificates)'
      gcloud compute ssl-certificates describe <CERT_NAME> \
      --format='table(name,managed.status,managed.domainStatus,expireTime)'
    • Azure App Gateway:
      az network application-gateway ssl-cert list -g <RESOURCE_GROUP> --gateway-name <APP_GW_NAME> \
      --query '[].{name:name, expiry:dataNotAfter}' -o table
    • Ensure certificates are not expired, match the DNS names used by clients, and are correctly attached to all HTTPS listeners.
  4. Enforce HTTPS-only access and redirect HTTP to HTTPS where needed

    • In the console or IaC, for each internet-facing load balancer:
      • Ensure there is an HTTPS listener (e.g., port 443) with a valid TLS certificate.
      • Either remove any HTTP (port 80) listener or configure it to perform an HTTP → HTTPS redirect rather than forwarding plain HTTP to backends.
    • Validate externally (from any machine):
      curl -I http://<LB_DNS_NAME>
      curl -I https://<LB_DNS_NAME>
    • Confirm HTTP either refuses or returns a redirect (3xx) to HTTPS, and HTTPS connects successfully with a valid certificate.
  5. Align with vendor best practices for TLS configuration

    • For each load balancer type, review and adjust TLS settings per vendor docs (cipher suites, minimum TLS version, security policies). Examples:
      • AWS: set SslPolicy to a current recommended policy (e.g., ELBSecurityPolicy-TLS13-1-2-Ext1-2021-06).
      • GCP: configure SSL policies and set minimum TLS version.
      • Azure: choose a strong SSL policy (e.g., App Gateway predefined “2019-12” or later).
    • Use the console or IaC (CloudFormation/Terraform/ARM/Bicep) to update these and re-apply.
  6. Re-verify end-to-end encryption and document the result

    • From a client machine, inspect the certificate actually served:
      echo | openssl s_client -connect <LB_DNS_NAME>:443 -servername <LB_DNS_NAME> 2>/dev/null | openssl x509 -noout -subject -issuer -dates
    • Confirm:
      • A certificate is presented, not a bare TCP connection.
      • notBefore/notAfter are valid; subject/issuer match expectations.
    • Record which load balancers now use HTTPS with TLS, which HTTP endpoints (if any) remain, and the rationale or remediation plan for any exceptions.
Using kubectl

kubectl cannot configure TLS on HTTPS load balancers; this must be done in your cloud provider or external load balancer configuration (console/CLI/IaC) where certificates and listeners are managed. Refer to the Manual Steps section for reviewing and updating the load balancer’s TLS settings.

Automation
#!/usr/bin/env bash
#
# Report load balancer services that are likely NOT using HTTPS with TLS.
# Run on: any machine with kubectl access and correct kubeconfig.
#
# Requirements: kubectl, jq

set -euo pipefail

echo "Collecting Services of type LoadBalancer across all namespaces..."
echo

# Header
printf "%-30s %-20s %-15s %-8s %-10s %-40s\n" \
"NAMESPACE/NAME" "EXTERNAL-IP" "PORT" "PROTO" "TLS?" "INFERRED_ISSUE"
printf '%*s\n' 130 '' | tr ' ' '-'

kubectl get svc --all-namespaces -o json | jq -r '
.items[]
| select(.spec.type=="LoadBalancer")
| {
ns: .metadata.namespace,
name: .metadata.name,
externalIP: (
( .status.loadBalancer.ingress[0].ip // .status.loadBalancer.ingress[0].hostname )
// "PENDING"
),
ports: .spec.ports,
annotations: .metadata.annotations
}
| .ports[]
| {
ns,
name,
externalIP,
port: .port,
targetPort: (.targetPort // ""),
protocol: .protocol,
# Heuristics for TLS:
# 1) Port 443 or named "https"
# 2) Ingress-style SSL annotations (common cloud providers)
tls_annotations: (
(annotations["service.beta.kubernetes.io/aws-load-balancer-ssl-cert"] // "") +
(annotations["service.beta.kubernetes.io/aws-load-balancer-backend-protocol"] // "") +
(annotations["service.beta.kubernetes.io/aws-load-balancer-ssl-ports"] // "") +
(annotations["service.beta.kubernetes.io/azure-load-balancer-tls-cert"] // "") +
(annotations["cloud.google.com/neg"] // "") +
(annotations["cloud.google.com/backend-config"] // "") +
(annotations["service.beta.kubernetes.io/do-loadbalancer-certificate-id"] // "") +
(annotations["service.beta.kubernetes.io/do-loadbalancer-protocol"] // "") +
(annotations["service.beta.kubernetes.io/huaweicloud-load-balancer-cert-id"] // "")
),
portName: (.name // "")
}
| @tsv
' | while IFS=$'\t' read -r ns name externalIP port targetPort protocol tls_annotations portName; do

svc_id="${ns}/${name}"
tls_flag="unknown"
issue=""

# Heuristic: ports expected to be HTTPS
is_https_like="false"
if [[ "$port" -eq 443 ]] || [[ "$portName" =~ [Hh][Tt][Tt][Pp][Ss] ]]; then
is_https_like="true"
fi

# If explicit TLS-related annotations exist, mark as likely TLS-enabled
if [[ -n "$tls_annotations" ]]; then
tls_flag="likely"
fi

# If port is HTTPS-like but we see no TLS hints, flag it
if [[ "$is_https_like" == "true" && "$tls_flag" != "likely" ]]; then
tls_flag="unknown"
issue="HTTPS-like port with no provider-specific TLS annotations detected"
fi

# If port is 80 or named http, flag as potential plain HTTP
if [[ "$port" -eq 80 ]] || [[ "$portName" =~ ^[Hh][Tt][Tt][Pp]$ ]]; then
if [[ -z "$issue" ]]; then
issue="Port 80/http exposed via LoadBalancer (likely unencrypted HTTP)"
else
issue="${issue}; also exposes port 80/http"
fi
fi

# Default issue for services with no obvious TLS configuration but with external IP
if [[ -z "$issue" && "$externalIP" != "PENDING" && "$protocol" == "TCP" && "$port" -ne 443 ]]; then
issue="TCP LoadBalancer with no clear HTTPS/TLS indication (manual review required)"
fi

printf "%-30s %-20s %-15s %-8s %-10s %-40s\n" \
"$svc_id" "$externalIP" "$port" "$protocol" "$tls_flag" "${issue:-OK}"
done

echo
echo "INTERPRETATION:"
echo " - Rows where INFERRED_ISSUE is 'OK' are not clearly problematic, but still require manual review."
echo " - Rows mentioning 'Port 80/http exposed' indicate likely unencrypted HTTP to the load balancer."
echo " - Rows with 'HTTPS-like port with no provider-specific TLS annotations' may be using TLS terminated"
echo " on the backend or misconfigured TLS at the load balancer; validate with the cloud console/CLI."
echo " - Rows with 'TCP LoadBalancer with no clear HTTPS/TLS indication' need provider-side review to confirm"
echo " whether TLS is configured at the load balancer or on the backend pods."
echo
echo "NOTE: This script CANNOT prove TLS is configured at the cloud load balancer. Use it to prioritize"
echo "manual verification in your cloud provider console/CLI or IaC for each external LoadBalancer."

Additional Reading: