← Back to Topics
defensive

Hiding Services from Enumeration

Preventing internal service discovery by disabling automatic injection of service environment variables

By default, Kubernetes injects environment variables into every pod for each service in the same namespace. This behavior was originally designed to provide backward compatibility with Docker links, a legacy feature from early container orchestration. While convenient for simple deployments, this automatic injection creates a security concern since it makes service discovery trivial for anyone with access to a pod.

When a pod starts, Kubernetes automatically creates environment variables following the pattern {SERVICENAME}_SERVICE_HOST and {SERVICENAME}_SERVICE_PORT for every service in the namespace:

env | grep _SERVICE_
REDIS_SERVICE_HOST=10.109.0.45
REDIS_SERVICE_PORT=6379
MYSQL_SERVICE_HOST=10.109.0.82
MYSQL_SERVICE_PORT=3306
BACKEND_API_SERVICE_HOST=10.109.0.120
BACKEND_API_SERVICE_PORT=8080

This passive reconnaissance technique requires no network scanning and cannot be detected by network monitoring tools. An attacker immediately knows what services exist and how to reach them.

Set enableServiceLinks: false in your pod specification to prevent Kubernetes from injecting service environment variables:

apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  enableServiceLinks: false
  containers:
    - name: nginx
      image: nginx

After applying the configuration, verify that service environment variables are no longer injected:

kubectl exec -it nginx -- env | grep _SERVICE_
KUBERNETES_SERVICE_HOST=10.96.0.1
KUBERNETES_SERVICE_PORT=443
KUBERNETES_SERVICE_PORT_HTTPS=443

Only the Kubernetes API service variables remain, as these are always injected regardless of the enableServiceLinks setting.

With enableServiceLinks: false, applications should use DNS-based service discovery instead of environment variables. Kubernetes provides built-in DNS resolution for services:

# Instead of using $REDIS_SERVICE_HOST
# Use the DNS name directly
redis.default.svc.cluster.local

# Short form works within the same namespace
redis

The DNS naming convention follows this pattern:

FormatExampleScope
<service>redisSame namespace
<service>.<namespace>redis.productionCross-namespace
<service>.<namespace>.svc.cluster.localredis.production.svc.cluster.localFully qualified

DNS-based discovery is the recommended approach for several reasons. Service names are predictable and well-documented, there’s no environment variable clutter, it works across namespaces when network policies allow.

When to Disable

Consider disabling service links when:

  • Applications use DNS for service discovery, making environment variables redundant
  • Namespaces contain many services, which increases pod startup time due to environment variable injection
  • Following the principle of least privilege by exposing only necessary information to pods

Limitations

Keep in mind that disabling enableServiceLinks is one layer of defense, not a complete solution. An attacker can still:

  • DNS resolution remains available for service discovery since CoreDNS resolves <service>.<namespace>.svc.cluster.local for any valid service name
  • Network connectivity to other pods is still allowed unless restricted by NetworkPolicy resources
  • Kubernetes API access can list services via kubectl get services if the service account has the list verb on services resources

Impact

Reduces the attack surface by hiding internal service endpoints from any pods

Mitigation

  • Use DNS-based service discovery instead of environment variables

MITRE ATT&CK Techniques