bitflicker64 commented on code in PR #3189:
URL: https://github.com/apache/hugegraph/pull/3189#discussion_r3923380792
##########
hugegraph-pd/hg-pd-service/src/main/java/org/apache/hugegraph/pd/service/interceptor/Authentication.java:
##########
@@ -77,19 +84,33 @@ protected <T> T authenticate(String authority, String
token, Function<String, T>
}
String name = info.substring(0, delim);
- // TODO: password validation is skipped — only service name is
checked against
- // innerModules. Full credential validation should be added as
part of the auth refactor.
- //String pwd = info.substring(delim + 1);
- if (innerModules.contains(name)) {
- return call.get();
- } else {
+ String pwd = info.substring(delim + 1);
+ if (!innerModules.contains(name)) {
throw new AccessDeniedException("invalid service name");
}
+ if (!verifySecret(pwd)) {
+ throw new BadCredentialsException("invalid credential");
+ }
+ return call.get();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
+ /**
+ * Compare the password of the Basic credential with the shared secret
+ * configured via `auth.secret-key`. A missing or empty secret refuses
every
+ * request instead of falling back to name-only authentication.
Review Comment:
‼️ This contract does not hold. A PD whose config omits `auth.secret-key`
does not refuse every request, it authenticates against a string no client will
send.
`PDConfig.java:72` declares the field as `@Value("${auth.secret-key:
'FXQXbJtbCLxODc6tGci732pkH1cyf8Qg'}")`. Spring's `PropertyPlaceholderHelper`
takes everything after the first `:` as a literal default, with no trimming and
no quote stripping. Against spring-core 5.3.20, the line
`spring-boot-starter-web:2.5.14` resolves:
```
resolved=[ 'FXQXbJtbCLxODc6tGci732pkH1cyf8Qg']
equalsDocumented=false
```
So `getSecretKey()` returns `" 'FXQXbJtbCLxODc6tGci732pkH1cyf8Qg'"`,
`StringUtils.isEmpty` is false, the branch below never runs, and `verifySecret`
rejects the value this PR ships in both `application.yml` files,
`wait-storage.sh` and the Hubble properties.
`start-hugegraph-pd.sh:178` passes
`-Dspring.config.location=${CONF}/application.yml`, which replaces the default
config locations rather than adding to them, so only that one file is read. An
in-place upgrade keeping the operator's existing `conf/application.yml` has no
`auth` block, and the failure is not a warning: every client gets 401,
`wait-storage.sh:118-124` retries until `WAIT_STORAGE_TIMEOUT_S=300` expires,
then exits 1.
Requested change: in `PDConfig.java:72` use `@Value("${auth.secret-key:}")`
so an absent key yields `""` and the fail-closed path this javadoc describes
actually runs, and log an ERROR naming `auth.secret-key` when it is empty. That
makes the failure diagnosable rather than preventing it, so the upgrade still
needs a release note telling operators to add the key to an existing
`conf/application.yml` first. `PDConfig.java:57` has the same quoted-default
shape for `pd.initial-store-list`, worth a separate pass.
##########
hugegraph-pd/README.md:
##########
@@ -280,6 +281,19 @@ 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.
Review Comment:
⚠️ `/actuator/*` covers more than the probes this line describes, on the
port the PR is hardening.
Both shipped configs set `management.endpoints.web.exposure.include: "*"` on
port 8620 (`hg-pd-dist/src/assembly/static/conf/application.yml:30`,
`hg-pd-service/src/main/resources/application.yml:30`), and
`AuthenticationConfigurer.java:35` excludes `/actuator/*` from the interceptor.
Spring Boot 2.5's full actuator set includes endpoints that dump process and
configuration state, and every one of them whose path is a single segment is
anonymous on 8620. The three the docs actually reference are `health`,
`metrics` and `prometheus` (`hugegraph-pd/README.md:198`,
`hugegraph-pd/docs/configuration.md:705,718,731`).
Pre-existing configuration, so not a regression, but this PR is where the
port gets a credential worth protecting, and
`hugegraph-pd/docs/configuration.md:262` already documents the narrower form.
Requested change: allowlist the endpoints in both `application.yml` files
instead of exposing all of them.
management:
endpoints:
web:
exposure:
include: "health,metrics,prometheus"
Then this bullet is accurate as written. A follow-up PR is fine too if you
want to keep this one narrow.
##########
hugegraph-server/hugegraph-dist/src/assembly/static/bin/wait-storage.sh:
##########
@@ -39,7 +39,9 @@ log() {
echo "[wait-storage] $1"
}
-PD_AUTH_ARGS="-u ${PD_AUTH_USER:-store}:${PD_AUTH_PASSWORD:-admin}"
+# PD validates the password against its auth.secret-key; the default below
+# matches PD's shipped default. Override both when the PD secret is changed.
+PD_AUTH_ARGS="-u
${PD_AUTH_USER:-store}:${PD_AUTH_PASSWORD:-FXQXbJtbCLxODc6tGci732pkH1cyf8Qg}"
Review Comment:
🧹 This value ends up as shell source, so a secret containing shell
metacharacters breaks startup or runs.
Line 106 interpolates `${PD_AUTH_ARGS}` unquoted into the `bash -c "..."`
string opened at line 100. Everything else inside that string is escaped
against the outer shell (`\$peer`, `\"$PD_REST_LIST\"`); this one is not, so
the expanded credential becomes part of the inner script's source text. A
secret containing a space splits into two curl arguments, one containing `$` or
a backtick expands inside the inner shell, and `;` or `$(...)` executes. The
failure mode is the 300s timeout and `exit 1` at line 124, with nothing
pointing at the password.
Pre-existing shape, but this PR is what starts telling operators to set an
arbitrary `PD_AUTH_PASSWORD`, so it is worth closing here.
While in this line: `curl -u user:secret` puts the credential in the process
argv, readable by anything that can see `/proc` in the container.
Requested change: keep the credential out of both the inner script text and
argv. Export the two values and let the inner shell read them from the
environment, for example `export PD_AUTH_USER PD_AUTH_PASSWORD` here and `curl
--user "$PD_AUTH_USER:$PD_AUTH_PASSWORD"` at line 106 with the `$` escaped so
the inner shell expands it, or feed a config file with `curl -K -`.
##########
docker/README.md:
##########
@@ -66,6 +66,22 @@ For the verification commands below, set the password in
your current shell:
ADMIN_PASSWORD='the-same-password-used-in-.env'
```
+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
+with a default secret in `conf/application.yml` (`auth.secret-key`), and the
+Hubble files under `conf/hubble/` carry the matching `operations.pd.password`.
+With the shipped default, list registered stores like this:
+
+```bash
+curl -u hg:FXQXbJtbCLxODc6tGci732pkH1cyf8Qg http://localhost:8620/v1/stores
+```
+
+The default secret is public (it is in the source tree), so it only keeps
+casual traffic out. On any shared network, change it: set
+`HG_PD_AUTH_SECRET_KEY` on the PD services and put the same value in the
+Hubble properties files, or do not publish port 8620 at all.
Review Comment:
⚠️ Following this procedure as written stops the Server container coming up.
The Server is a PD REST client too, with its own copy of the default.
`wait-storage.sh:44` sets `PD_AUTH_ARGS` from
`${PD_AUTH_PASSWORD:-FXQXbJtbCLxODc6tGci732pkH1cyf8Qg}`, and the Server
entrypoint runs the script on first boot
(`hugegraph-server/hugegraph-dist/docker/docker-entrypoint.sh:189`). Rotate
`HG_PD_AUTH_SECRET_KEY` on the PD services and update the Hubble files, exactly
as this paragraph says, and the Server still sends the shipped secret: `curl -f
.../v1/stores` gets 401, the `until` loop at `wait-storage.sh:118-121` never
succeeds, `timeout 300s` fires and line 124 exits 1. The only clue is `ERROR:
Timeout waiting for storage backend`.
The plumbing is already there. `env "${WAIT_ENV[@]}" ./bin/wait-storage.sh`
adds to the inherited environment rather than replacing it, so a
container-level `PD_AUTH_PASSWORD` reaches the script. `grep -rn
PD_AUTH_PASSWORD` finds it only in `wait-storage.sh` and its test, so nothing
sets it.
Requested change: name the Server side in this paragraph, and wire it in
both Compose files so one `.env` value drives all three consumers. In
`docker-compose-3pd-3store-3server.yml` that means the `x-pd-common` anchor
(line 34):
HG_PD_AUTH_SECRET_KEY: ${HG_PD_AUTH_SECRET_KEY:-}
and the `x-server-environment` anchor (line 62), plus the `server` service
in `docker-compose-hstore.yml`:
PD_AUTH_PASSWORD:
${HG_PD_AUTH_SECRET_KEY:-FXQXbJtbCLxODc6tGci732pkH1cyf8Qg}
The tarball path needs the same sentence: `hugegraph-pd/README.md:293-295`
should list `PD_AUTH_PASSWORD` for `bin/wait-storage.sh` next to Hubble's
`operations.pd.password`.
##########
hugegraph-pd/hg-pd-service/src/main/java/org/apache/hugegraph/pd/rest/interceptor/RestAuthentication.java:
##########
@@ -59,6 +59,7 @@ public boolean preHandle(HttpServletRequest request,
HttpServletResponse respons
authority = authority.replace("Basic ", "");
return authenticate(authority, token, tokenCall, DEFAULT_HANDLE);
} catch (Exception e) {
+ response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
Review Comment:
🧹 A 401 with no `WWW-Authenticate` is not a challenge, so clients that
authenticate reactively never retry with credentials.
RFC 7235 §3.1 makes the header mandatory on a 401. `curl -u` sends Basic
preemptively, which is why the manual matrix in the PR description and the new
`RestApiTest` cases do not notice, but
`java.net.http.HttpClient.authenticator(...)`, `HttpURLConnection` with a
default `Authenticator`, Apache HttpClient without preemptive auth configured,
and browsers all wait for the challenge and simply fail.
Requested change:
```suggestion
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.setHeader("WWW-Authenticate", "Basic
realm=\"hugegraph-pd\"");
```
##########
hugegraph-pd/hg-pd-service/src/main/java/org/apache/hugegraph/pd/service/interceptor/Authentication.java:
##########
@@ -77,19 +84,33 @@ protected <T> T authenticate(String authority, String
token, Function<String, T>
}
String name = info.substring(0, delim);
- // TODO: password validation is skipped — only service name is
checked against
- // innerModules. Full credential validation should be added as
part of the auth refactor.
- //String pwd = info.substring(delim + 1);
- if (innerModules.contains(name)) {
- return call.get();
- } else {
+ String pwd = info.substring(delim + 1);
+ if (!innerModules.contains(name)) {
throw new AccessDeniedException("invalid service name");
}
+ if (!verifySecret(pwd)) {
+ throw new BadCredentialsException("invalid credential");
+ }
Review Comment:
🧹 The new check lands in the shared base class, which arms a trap on the
gRPC path.
`GrpcAuthentication extends Authentication` and calls this same method
(`GrpcAuthentication.java:56`). It is inert at this head: lognet's
`GRpcServerRunner.java:65-69` (grpc-spring-boot-starter 4.5.5) registers only
`ServerInterceptor` beans annotated `@GRpcGlobalInterceptor`,
`GrpcAuthentication` carries just `@Service`, and `GRpcServerConfig.java:43-44`
has the TODO saying so. This PR does not break the cluster today.
But every in-repo gRPC client sends a password this method now rejects:
`ServiceConstant.AUTHORITY = ""` for Server
(`hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/constant/ServiceConstant.java:28`),
`DefaultPdProvider.authority = "default"` for Store (line 70), `""` for
`hg-pd-cli` (`Command.java:35`). Whoever clears that TODO gets a cluster that
cannot register a store, with no hint that this PR is where the requirement
came from.
Requested change: either move the secret check into the REST subclass so the
base class keeps the name-only behaviour those clients were written against, or
extend the TODO at `GRpcServerConfig.java:43` to record that enabling the
interceptor now also requires giving those three clients the `auth.secret-key`
value.
##########
hugegraph-pd/README.md:
##########
@@ -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` | (public default) | Password required by the REST API
with an internal service name (`hg`, `store`, `hubble`, `vermeer`) via HTTP
Basic auth. Change it in production and configure every REST client (e.g.
Hubble's `operations.pd.password`) with the same value |
Review Comment:
🧹 This row is the only place the credential is documented. The two files an
operator reaches for next do not have it.
`hugegraph-pd/docs/configuration.md` is the PD configuration reference, with
a parameter table per section, and contains zero occurrences of `auth`.
`hugegraph-pd/docs/api-reference.md` is the REST reference and never mentions
authentication either: its REST API section opens at line 759 and the examples
at 804 and 832 (`/v1/partitions?graph_name=...`, `/v1/stores`) carry no
credential.
Requested change: add the `auth.secret-key` row to `configuration.md` and a
two-line authentication note at the top of the REST API section in
`api-reference.md`.
The runbooks under `hugegraph-store/docs/` have the same problem at roughly
twenty more call sites
(`operations-guide.md:92,231,261,280,337,486,496,579,607,613,619`,
`deployment-guide.md:475,589,869,872,875,878`,
`integration-guide.md:709,736,739`, `hugegraph-store/README.md:263`), which is
a reasonable follow-up rather than something to load onto this PR. One is worth
fixing now: `operations-guide.md:613` is `curl -X POST
http://192.168.1.10:8620/v1/balanceLeaders`, a mutating step an operator runs
during an incident and reads as a no-op when it 401s. The `/actuator/*` and
`/v1/health` examples in all of those files stay valid.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]