Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/workflows/pd-store-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,10 @@ jobs:
done
echo "can_run=true" >> "$GITHUB_OUTPUT"

- name: Run PD docker entrypoint secret override tests
run: |
$TRAVIS_DIR/test-pd-docker-entrypoint.sh

- name: Run start-hugegraph-pd.sh foreground mode tests
if: steps.pd-preflight.outputs.can_run == 'true'
run: |
Expand Down
55 changes: 51 additions & 4 deletions docker/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,9 @@ contains a single quote or newline.
echo ".env already exists; edit it instead of overwriting it" >&2
exit 1
}
printf "HUGEGRAPH_ADMIN_PASSWORD='%s'\nHUGEGRAPH_AUTH_TOKEN_SECRET='%s'\n" \
'replace-with-your-password' "${jwt_secret}" > .env
pd_secret="$(openssl rand -hex 24)"
printf "HUGEGRAPH_ADMIN_PASSWORD='%s'\nHUGEGRAPH_AUTH_TOKEN_SECRET='%s'\nHG_PD_AUTH_SECRET_KEY='%s'\n" \
'replace-with-your-password' "${jwt_secret}" "${pd_secret}" > .env
)
```

Expand All @@ -60,12 +61,56 @@ behind an HTTPS reverse proxy and trusted network controls.
first authenticated startup. Changing `.env` does not rotate an existing
administrator password; use the HugeGraph user API for credential changes.

For the verification commands below, set the password in your current shell:
For the verification commands below, load `.env` into your current shell and
set the password:

```bash
set -a; . ./.env; set +a
ADMIN_PASSWORD='the-same-password-used-in-.env'
```

Compose reads `.env` on its own; the line above is so that the `curl` and
`sed` commands on this page can use `${HG_PD_AUTH_SECRET_KEY}` too.

The PD REST API (port 8620, HStore topologies only) has its own credential:
requests other than health probes need HTTP Basic auth with an internal
service name (for example `hg`) and the PD secret as the password. PD ships
no default secret, so `HG_PD_AUTH_SECRET_KEY` is required and the HStore
Compose files refuse to start without it. The `.env` command above generates
one. To list registered stores:

```bash
curl -u "hg:${HG_PD_AUTH_SECRET_KEY}" http://localhost:8620/v1/stores
```

Three consumers read this credential, and all three have to agree or startup
fails:

- PD itself, through `HG_PD_AUTH_SECRET_KEY`.
- The Server, whose `bin/wait-storage.sh` polls `/v1/stores` before the
Server starts. Both Compose files pass `PD_AUTH_PASSWORD` to it from the
same variable, so setting `HG_PD_AUTH_SECRET_KEY` in `.env` covers it. If
the Server sends the wrong secret it retries until
`WAIT_STORAGE_TIMEOUT_S` (300s) expires and the container exits with
`ERROR: Timeout waiting for storage backend`.
- Hubble, through `operations.pd.password` in the file under `conf/hubble/`.
That file is mounted read-only and is not templated, so write the same value
into it by hand. Until you do, Hubble's PD-backed views get 401 from PD;
everything else in Hubble works.

Write it in, after loading `.env` as above. Use `hstore.properties` for the
Minimal HStore topology and `hstore-ha.properties` for HA:

```bash
if [ -n "${HG_PD_AUTH_SECRET_KEY:-}" ]; then
sed -i.bak \
"s#^operations.pd.password=.*#operations.pd.password=${HG_PD_AUTH_SECRET_KEY}#" \
conf/hubble/hstore.properties
else
echo 'HG_PD_AUTH_SECRET_KEY is empty; load .env first' >&2
fi
```

### Standalone

This is the recommended quickstart.
Expand Down Expand Up @@ -312,7 +357,9 @@ docker compose -f docker-compose-hstore.yml up -d --wait
### Hubble configuration

The three small files under `conf/hubble/` contain only topology-specific
discovery settings and container paths:
discovery settings, the PD REST credential (`operations.pd.username` and
`operations.pd.password`, which must match PD's `auth.secret-key`), and
container paths:

- `conf/hubble/standalone.properties` uses direct Server mode.
- `conf/hubble/hstore.properties` uses one PD and one Store REST target.
Expand Down
5 changes: 5 additions & 0 deletions docker/conf/hubble/hstore-ha.properties
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ pd.enabled=true
server.direct_url=http://server0:8080
pd.peers=pd0:8686,pd1:8686,pd2:8686
pd.server=pd0:8620
# PD REST credential. The password must equal PD's auth.secret-key, which has
# no default: set it to the same value as HG_PD_AUTH_SECRET_KEY in .env. While
# it is empty, Hubble's PD-backed views get HTTP 401 from PD.
operations.pd.username=hubble
operations.pd.password=
operations.store.allowed_targets=[http://store0:8520,http://store1:8520,http://store2:8520]
upload_file.location=/hubble/data/upload-files
dashboard.address=
5 changes: 5 additions & 0 deletions docker/conf/hubble/hstore.properties
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ pd.enabled=true
server.direct_url=http://server:8080
pd.peers=pd:8686
pd.server=pd:8620
# PD REST credential. The password must equal PD's auth.secret-key, which has
# no default: set it to the same value as HG_PD_AUTH_SECRET_KEY in .env. While
# it is empty, Hubble's PD-backed views get HTTP 401 from PD.
operations.pd.username=hubble
operations.pd.password=
operations.store.allowed_targets=[http://store:8520]
upload_file.location=/hubble/data/upload-files
dashboard.address=
5 changes: 5 additions & 0 deletions docker/docker-compose-3pd-3store-3server.yml
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ x-server-environment: &server-environment
HG_SERVER_REQUIRE_AUTH_TOKEN_SECRET: "true"
HG_SERVER_AUTH_TOKEN_SECRET: ${HUGEGRAPH_AUTH_TOKEN_SECRET:-}
PASSWORD: ${HUGEGRAPH_ADMIN_PASSWORD:-}
# bin/wait-storage.sh polls the PD REST API, so it needs the same secret
PD_AUTH_PASSWORD: ${HG_PD_AUTH_SECRET_KEY:?set HG_PD_AUTH_SECRET_KEY in .env; see docker/README.md}

x-server-common: &server-common
image: hugegraph/server:${HUGEGRAPH_VERSION:-latest}
Expand Down Expand Up @@ -107,6 +109,7 @@ services:
HG_PD_INITIAL_STORE_LIST: store0:8500,store1:8500,store2:8500
HG_PD_DATA_PATH: /hugegraph-pd/pd_data
HG_PD_INITIAL_STORE_COUNT: 3
HG_PD_AUTH_SECRET_KEY: ${HG_PD_AUTH_SECRET_KEY:?set HG_PD_AUTH_SECRET_KEY in .env; see docker/README.md}
ports: ["8620:8620", "8686:8686"]
volumes:
- hg-pd0-data:/hugegraph-pd/pd_data
Expand All @@ -125,6 +128,7 @@ services:
HG_PD_INITIAL_STORE_LIST: store0:8500,store1:8500,store2:8500
HG_PD_DATA_PATH: /hugegraph-pd/pd_data
HG_PD_INITIAL_STORE_COUNT: 3
HG_PD_AUTH_SECRET_KEY: ${HG_PD_AUTH_SECRET_KEY:?set HG_PD_AUTH_SECRET_KEY in .env; see docker/README.md}
ports: ["8621:8620", "8687:8686"]
volumes:
- hg-pd1-data:/hugegraph-pd/pd_data
Expand All @@ -143,6 +147,7 @@ services:
HG_PD_INITIAL_STORE_LIST: store0:8500,store1:8500,store2:8500
HG_PD_DATA_PATH: /hugegraph-pd/pd_data
HG_PD_INITIAL_STORE_COUNT: 3
HG_PD_AUTH_SECRET_KEY: ${HG_PD_AUTH_SECRET_KEY:?set HG_PD_AUTH_SECRET_KEY in .env; see docker/README.md}
ports: ["8622:8620", "8688:8686"]
volumes:
- hg-pd2-data:/hugegraph-pd/pd_data
Expand Down
3 changes: 3 additions & 0 deletions docker/docker-compose-hstore.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ services:
HG_PD_RAFT_PEERS_LIST: pd:8610
HG_PD_INITIAL_STORE_LIST: store:8500
HG_PD_DATA_PATH: /hugegraph-pd/pd_data
HG_PD_AUTH_SECRET_KEY: ${HG_PD_AUTH_SECRET_KEY:?set HG_PD_AUTH_SECRET_KEY in .env; see docker/README.md}
ports:
- "8620:8620"
volumes:
Expand Down Expand Up @@ -94,6 +95,8 @@ services:
HG_SERVER_INIT_STORE_ENABLED: "false"
HG_SERVER_AUTH_TOKEN_SECRET: ${HUGEGRAPH_AUTH_TOKEN_SECRET:-}
PASSWORD: ${HUGEGRAPH_ADMIN_PASSWORD:-}
# bin/wait-storage.sh polls the PD REST API, so it needs the same secret
PD_AUTH_PASSWORD: ${HG_PD_AUTH_SECRET_KEY:?set HG_PD_AUTH_SECRET_KEY in .env; see docker/README.md}
ports:
- "8080:8080"
healthcheck:
Expand Down
3 changes: 3 additions & 0 deletions docker/test-compose.sh
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ set -Eeuo pipefail
DOCKER_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PASSWORD="ci-compose-password"
SECRET="0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
PD_SECRET="ci-compose-pd-secret"
Comment thread
bitflicker64 marked this conversation as resolved.
VERSION="ci-version"
RENDER_HUBBLE_IMAGE="example.invalid/hugegraph/hubble:ci"
DATASOURCE="jdbc:h2:file:/hubble/data/hubble;DB_CLOSE_ON_EXIT=FALSE"
Expand All @@ -34,6 +35,7 @@ compose_auth() {
HUBBLE_IMAGE="${RENDER_HUBBLE_IMAGE}" \
HUGEGRAPH_ADMIN_PASSWORD="${PASSWORD}" \
HUGEGRAPH_AUTH_TOKEN_SECRET="${SECRET}" \
HG_PD_AUTH_SECRET_KEY="${PD_SECRET}" \
docker compose "$@"
}

Expand Down Expand Up @@ -271,6 +273,7 @@ compose_active() {
HUBBLE_IMAGE="${HUBBLE_IMAGE:-hugegraph/hubble:latest}" \
HUGEGRAPH_ADMIN_PASSWORD="${PASSWORD}" \
HUGEGRAPH_AUTH_TOKEN_SECRET="${SECRET}" \
HG_PD_AUTH_SECRET_KEY="${PD_SECRET}" \
COMPOSE_PROGRESS=plain \
docker compose -p "${ACTIVE_PROJECT}" "${ACTIVE_FILES[@]}" "$@"
}
Expand Down
31 changes: 31 additions & 0 deletions hugegraph-pd/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ Key configuration file: `conf/application.yml`
| `raft.address` | `127.0.0.1:8610` | Raft service address for this PD node |
| `raft.peers-list` | `127.0.0.1:8610` | Comma-separated list of all PD nodes in the Raft cluster |
| `pd.data-path` | `./pd_data` | Directory for storing PD metadata and Raft logs |
| `auth.secret-key` | none (required) | Password required by the REST API with an internal service name (`hg`, `store`, `hubble`, `vermeer`) via HTTP Basic auth. No default is shipped; generate one per deployment and configure every REST client (e.g. Hubble's `operations.pd.password`) with the same value |

#### Single-Node Example

Expand Down Expand Up @@ -236,11 +237,17 @@ Build PD Docker image:
# From project root
docker build -f hugegraph-pd/Dockerfile -t hugegraph/pd:latest .

# Generate the REST secret once and keep it: every PD REST client needs this
# same value, and a new one silently breaks the clients already using the old
# one. Store it somewhere durable rather than only in this shell.
export HG_PD_AUTH_SECRET_KEY="$(openssl rand -hex 24)"

# Run container
docker run -d \
-p 8620:8620 \
-p 8686:8686 \
-p 8610:8610 \
-e HG_PD_AUTH_SECRET_KEY="${HG_PD_AUTH_SECRET_KEY}" \
-e HG_PD_GRPC_HOST=<your-ip> \
-e HG_PD_RAFT_ADDRESS=<your-ip>:8610 \
-e HG_PD_RAFT_PEERS_LIST=<your-ip>:8610 \
Expand Down Expand Up @@ -280,6 +287,30 @@ docker/docker-compose-3pd-3store-3server.yml
- Ensure low latency (<5ms) between PD nodes for Raft consensus
- Open required ports: `8620` (REST), `8686` (gRPC), `8610` (Raft)

### Security

- Keep all three ports on a trusted network. The REST API on `8620` includes
management endpoints that mutate the cluster (peer changes, store removal,
data movement), and the gRPC and Raft ports carry no authentication.
- REST requests need HTTP Basic auth: one of the internal service names
(`hg`, `store`, `hubble`, `vermeer`) with the `auth.secret-key` value as
the password. Health probes (`/v1/health`, `/actuator/*`,
`/v1/prom/targets/*`) stay unauthenticated.
Comment thread
bitflicker64 marked this conversation as resolved.
- `auth.secret-key` has no shipped default, because a secret in the source
tree is published to everyone. Generate one per deployment (`openssl rand
-hex 24`) and set it in the config file, or through
`HG_PD_AUTH_SECRET_KEY`, which the Docker image requires. Give every REST
client the same value: the Server's `bin/wait-storage.sh` reads
`PD_AUTH_PASSWORD` (and `PD_AUTH_USER`, default `store`), and Hubble reads
`operations.pd.password`. A client left on a stale secret gets 401, and for
`wait-storage.sh` that means Server startup aborts after
`WAIT_STORAGE_TIMEOUT_S`.
- An existing `conf/application.yml` carried over from an earlier release has
no `auth` block. PD then starts with an empty secret and refuses every
authenticated REST request, logging an error that names `auth.secret-key`.
Add the key before upgrading. PD refuses to start if the key is set to the
placeholder value that earlier revisions of this repository carried.

### Monitoring

PD exposes metrics via REST API at:
Expand Down
16 changes: 16 additions & 0 deletions hugegraph-pd/docs/api-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -760,6 +760,22 @@ for (Map.Entry<String, byte[]> entry : results.entrySet()) {

PD exposes a REST API for management and monitoring (default port: 8620).

### Authentication

Every endpoint below except the probes needs HTTP Basic auth: one of the
internal service names (`hg`, `store`, `hubble`, `vermeer`) as the user, and
the `auth.secret-key` value from PD's `conf/application.yml` as the password.
A missing or wrong credential gets HTTP 401. The `curl` examples that follow
omit `-u` for readability; add it to every call except `/v1/health`,
`/actuator/*` and `/v1/prom/targets/*`, which stay unauthenticated for probes.

```bash
curl -u hg:<secret> http://localhost:8620/v1/stores
```

Endpoints under `/v1` mutate the cluster (peer list changes, store removal,
partition balancing), so keep port 8620 on a trusted network regardless.

### Health Check

```bash
Expand Down
29 changes: 27 additions & 2 deletions hugegraph-pd/docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,31 @@ server:
- Metrics: `http://<host>:8620/actuator/metrics`
- Prometheus: `http://<host>:8620/actuator/prometheus`

### REST Authentication Settings

Every REST request except the probes below must carry HTTP Basic auth: one of
the internal service names (`hg`, `store`, `hubble`, `vermeer`) as the user,
and the shared secret as the password. A missing or wrong credential gets
HTTP 401. Unauthenticated paths: `/v1/health`, `/actuator/*` and
`/v1/prom/targets/*`.

```yaml
auth:
secret-key: <a value you generate, e.g. `openssl rand -hex 24`>
```

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `auth.secret-key` | String | none (required) | Password checked against the Basic credential. There is no default: a secret shipped in the source tree would be published to everyone. While it is empty PD refuses every authenticated REST request and logs an error naming this parameter, and PD refuses to start at all if it is set to the value that earlier revisions carried as a placeholder. |

Every REST client needs the same value: the Server's `bin/wait-storage.sh`
reads it from `PD_AUTH_PASSWORD`, Hubble from `operations.pd.password`, and
the Docker image takes `HG_PD_AUTH_SECRET_KEY`.

```bash
curl -u hg:<secret> http://<host>:8620/v1/stores
```

### Raft Consensus Settings

Controls Raft consensus for PD cluster coordination.
Expand Down Expand Up @@ -253,13 +278,13 @@ management:
endpoints:
web:
exposure:
include: "*" # Expose all actuator endpoints
include: "health,metrics,prometheus" # Allowlist; see note below
```

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `management.metrics.export.prometheus.enabled` | Boolean | `true` | Enable Prometheus-compatible metrics at `/actuator/prometheus`. |
| `management.endpoints.web.exposure.include` | String | `"*"` | Actuator endpoints to expose. `"*"` = all, or specify comma-separated list (e.g., `"health,metrics"`). |
| `management.endpoints.web.exposure.include` | String | `"health,metrics,prometheus"` | Actuator endpoints to expose. `/actuator/*` is excluded from the REST authentication interceptor, so every endpoint listed here is reachable without a credential on port 8620. Prefer an allowlist over `"*"`. |

## Deployment Scenarios

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@

import org.apache.hugegraph.pd.ConfigService;
import org.apache.hugegraph.pd.IdService;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
Expand All @@ -38,7 +39,15 @@
*/
@Data
@Component
public class PDConfig {
public class PDConfig implements InitializingBean {

/**
* The secret that earlier revisions carried as the placeholder default for
* `auth.secret-key`. It is published in this repository, so a deployment
* still using it authenticates anyone who can read the source. Refuse to
* start rather than let a well-known string look like authentication.
*/
private static final String PUBLISHED_SECRET_KEY = "FXQXbJtbCLxODc6tGci732pkH1cyf8Qg";

// cluster ID
@Value("${pd.cluster_id:1}")
Expand Down Expand Up @@ -69,7 +78,11 @@ public class PDConfig {
@Autowired
private ThreadPoolGrpc threadPoolGrpc;

@Value("${auth.secret-key: 'FXQXbJtbCLxODc6tGci732pkH1cyf8Qg'}")
// No default: Spring takes the text after the first ':' literally, so a
// quoted default would resolve to a value including the quotes and the
// leading space, and no client would ever match it. An absent key must
// yield "" so the REST interceptor can refuse every request and say why.
@Value("${auth.secret-key:}")
@ToString.Exclude
private String secretKey;

Expand All @@ -85,6 +98,17 @@ public class PDConfig {
private ConfigService configService;
private IdService idService;

@Override
public void afterPropertiesSet() {
if (PUBLISHED_SECRET_KEY.equals(this.secretKey)) {
throw new IllegalStateException(
"auth.secret-key is set to the value published in the HugeGraph source " +
"tree, which authenticates anyone who can read it. Set a " +
"deployment-specific secret in conf/application.yml, or through the " +
"HG_PD_AUTH_SECRET_KEY environment variable for the Docker image.");
}
}

public Map<String, String> getInitialStoreMap() {
if (initialStoreMap == null) {
initialStoreMap = new HashMap<>();
Expand Down
Loading
Loading