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
Remediation
Manual Steps
-
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 tableaz network lb list --query '[].{name:name, sku:sku.name, public:frontendIPConfigurations[0].publicIpAddress!=null}' -o table
-
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.
-
Confirm TLS certificates are configured and valid on HTTPS listeners
- AWS (check certificate ARNs and SSL policy):
Then inspect eachaws elbv2 describe-listeners --load-balancer-arn <LOAD_BALANCER_ARN> \--query 'Listeners[].{Port:Port,Protocol:Protocol,SSLPolicy:SslPolicy,Certs:Certificates}' --output json
Certificates[].CertificateArnin 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.
- AWS (check certificate ARNs and SSL policy):
-
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.
- In the console or IaC, for each internet-facing load balancer:
-
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
SslPolicyto 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).
- AWS: set
- Use the console or IaC (CloudFormation/Terraform/ARM/Bicep) to update these and re-apply.
- For each load balancer type, review and adjust TLS settings per vendor docs (cipher suites, minimum TLS version, security policies). Examples:
-
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/notAfterare 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.
- From a client machine, inspect the certificate actually served:
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."