This is an automated email from the ASF dual-hosted git repository.
imbajin pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/hugegraph.git
The following commit(s) were added to refs/heads/master by this push:
new 4f1a8b341 feat(server): integrate Hubble with PD mode (#3143)
4f1a8b341 is described below
commit 4f1a8b34169e021717906167444c75c4c22d426b
Author: imbajin <[email protected]>
AuthorDate: Fri Aug 7 23:07:38 2026 +0800
feat(server): integrate Hubble with PD mode (#3143)
- Add Hubble to both release and development PD Compose files.
- Configure Hubble to discover the `hg` cluster through PD and reach Server
through the Docker network.
- Let embedded Gremlin authenticate Bearer JWTs while preserving existing
Basic authentication.
- Require a non-default administrator password and validate explicit JWT
secrets at 32 bytes or longer.
- Wait for real Server and Hubble health responses instead of treating an
open port as ready.
---
.dockerignore | 1 +
.github/workflows/server-ci.yml | 59 ++++++
.gitignore | 1 +
docker/README.md | 210 ++++++++++++++++++---
docker/docker-compose.dev.yml | 32 +++-
docker/docker-compose.yml | 34 +++-
.../hugegraph-hubble.properties | 54 ++----
.../hugegraph/auth/WsAndHttpBasicAuthHandler.java | 16 ++
.../docker/docker-entrypoint-test.sh | 205 ++++++++++++++++++++
.../hugegraph-dist/docker/docker-entrypoint.sh | 104 +++++++++-
.../docker/test/test-docker-entrypoint.sh | 4 +-
.../auth/WsAndHttpBasicAuthHandlerTest.java | 34 +++-
12 files changed, 673 insertions(+), 81 deletions(-)
diff --git a/.dockerignore b/.dockerignore
index ee61478a1..28d48eb93 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -46,6 +46,7 @@ apache-hugegraph-*/
# Env files
.env.local
.env.*.local
+/docker/.env
# Git internals
.git
diff --git a/.github/workflows/server-ci.yml b/.github/workflows/server-ci.yml
index 6d5ab2f19..9c4e577d8 100644
--- a/.github/workflows/server-ci.yml
+++ b/.github/workflows/server-ci.yml
@@ -10,10 +10,14 @@ on:
jobs:
wait-storage-shell-test:
+ permissions:
+ contents: read
runs-on: ubuntu-22.04
steps:
- name: Checkout
uses: actions/checkout@v4
+ with:
+ persist-credentials: false
- name: Run wait-storage.sh peer failover tests
run:
hugegraph-server/hugegraph-dist/src/assembly/travis/test-wait-storage.sh
@@ -79,6 +83,61 @@ jobs:
run: |
mvn clean compile -U -Dmaven.javadoc.skip=true -ntp
+ - name: Validate Docker integration
+ if: ${{ env.BACKEND == 'rocksdb' }}
+ run: |
+ bash hugegraph-server/hugegraph-dist/docker/docker-entrypoint-test.sh
+ check_compose() {
+ local file="$1"
+ local server_policy="$2"
+ local hubble_policy="$3"
+ local rendered
+ rendered="$(mktemp)"
+
+ if env -u HUGEGRAPH_ADMIN_PASSWORD \
+ docker compose -f "$file" config -q >/dev/null 2>&1; then
+ echo "$file accepted an unset admin password" >&2
+ return 1
+ fi
+ if HUGEGRAPH_ADMIN_PASSWORD= \
+ docker compose -f "$file" config -q >/dev/null 2>&1; then
+ echo "$file accepted an empty admin password" >&2
+ return 1
+ fi
+
+ HUGEGRAPH_ADMIN_PASSWORD=ci-test-password \
+ docker compose -f "$file" config --format json > "$rendered"
+ jq -e \
+ --arg server_policy "$server_policy" \
+ --arg hubble_policy "$hubble_policy" '
+ .services.server.pull_policy == $server_policy and
+ .services.hubble.pull_policy == $hubble_policy and
+ .services.server.environment.HG_SERVER_USE_PD == "true" and
+ .services.server.environment.HG_SERVER_CLUSTER == "hg" and
+ .services.server.environment.HG_SERVER_REST_URL ==
+ "http://server:8080" and
+ .services.server.environment.HG_SERVER_INIT_STORE_ENABLED ==
+ "false" and
+ .services.server.environment.PASSWORD ==
+ "ci-test-password" and
+ (.services.server.environment.HG_SERVER_AUTH_TOKEN_SECRET !=
+ null) and
+ .services.hubble.depends_on.server.condition ==
+ "service_healthy" and
+ (.services.hubble.healthcheck.test[1] |
+ contains("http://127.0.0.1:8088/about") and
+ contains("\"status\":200") and
+ contains("\"name\":\"hugegraph-hubble\"")) and
+ any(.services.hubble.ports[];
+ .target == 8088 and .published == "8088" and
+ .host_ip == "127.0.0.1")
+ ' "$rendered" >/dev/null
+ rm -f "$rendered"
+ }
+
+ check_compose docker/docker-compose.yml always always
+ check_compose docker/docker-compose.dev.yml build missing
+
- name: Run check_port unit tests
if: ${{ env.BACKEND == 'rocksdb' }}
run: |
diff --git a/.gitignore b/.gitignore
index e27322b73..972d89d35 100644
--- a/.gitignore
+++ b/.gitignore
@@ -45,6 +45,7 @@ build/
.env.development.local
.env.test.local
.env.production.local
+docker/.env
*.orig
*.rej
diff --git a/docker/README.md b/docker/README.md
index e55879874..0ee1f586b 100644
--- a/docker/README.md
+++ b/docker/README.md
@@ -4,14 +4,15 @@ This directory contains Docker Compose files for running
HugeGraph:
| File | Description |
|------|-------------|
-| `docker-compose.yml` | Single-node cluster using pre-built images from
Docker Hub |
-| `docker-compose.dev.yml` | Single-node cluster built from source (for
developers) |
+| `docker-compose.yml` | PD, Store, Server, and Hubble using pre-built images |
+| `docker-compose.dev.yml` | PD, Store, and Server built from source, plus
Hubble |
| `docker-compose-3pd-3store-3server.yml` | 3-node distributed cluster (PD +
Store + Server) |
## Prerequisites
- **Docker Engine** 20.10+ (or Docker Desktop 4.x+)
- **Docker Compose** v2 (included in Docker Desktop)
+- **OpenSSL CLI** (used to generate the initial administrator password)
- **Memory**: Allocate at least **12 GB** to Docker Desktop (Settings →
Resources → Memory). The 3-node cluster runs 9 JVM processes (3 PD + 3 Store +
3 Server) which are memory-intensive. Insufficient memory causes OOM kills that
appear as silent Raft failures.
> [!IMPORTANT]
@@ -20,51 +21,147 @@ This directory contains Docker Compose files for running
HugeGraph:
## Single-Node Setup
-Two compose files are available for running a single-node cluster (1 PD + 1
Store + 1 Server):
+Two compose files run one PD, one Store, one Server, and one Hubble instance:
+
+Create a Compose environment file once so every lifecycle command can resolve
+the required administrator password:
+
+```bash
+(
+ set -eu
+ cd docker
+ if [ -e .env ]; then
+ echo "docker/.env already exists; reusing it"
+ else
+ command -v openssl >/dev/null 2>&1
+ admin_password="$(openssl rand -base64 12)"
+ if [ "${#admin_password}" -ne 16 ]; then
+ echo "Failed to generate a 16-character password" >&2
+ exit 1
+ fi
+ install -m 600 /dev/null .env
+ {
+ printf "HUGEGRAPH_ADMIN_PASSWORD='%s'\n" "${admin_password}"
+ } >> .env
+ unset admin_password
+ fi
+ chmod 600 .env
+ if ! env -u HUGEGRAPH_ADMIN_PASSWORD \
+ docker compose -f docker-compose.yml config --quiet ||
+ ! env -u HUGEGRAPH_ADMIN_PASSWORD \
+ docker compose -f docker-compose.dev.yml config --quiet; then
+ echo "docker/.env is incomplete; repair or move it, then retry" >&2
+ exit 1
+ fi
+)
+```
+
+Compose automatically reads `docker/.env` for `up`, `ps`, `stop`, and `down`.
+The generated password is a 16-character, Compose-safe random value. The file
+is excluded from Git and Docker build contexts; keep its permissions restricted
+and source production credentials from your secret manager instead of
+committing them.
### Option A: Quick Start (pre-built images)
Uses pre-built images from Docker Hub. Best for **end users** who want to run
HugeGraph quickly.
+Set `HUGEGRAPH_VERSION` to the same published release for PD, Store, Server,
+and Hubble. The authenticated PD/Hubble integration is not present in `1.7.x`;
+if no later compatible release is available, use Option B.
```bash
-cd docker
-HUGEGRAPH_VERSION=1.7.0 docker compose up -d
+(
+ cd docker
+ HUGEGRAPH_VERSION='<compatible-release-after-1.7.x>' \
+ docker compose up -d
+)
```
-- Images: `hugegraph/pd:1.7.0`, `hugegraph/store:1.7.0`,
`hugegraph/server:1.7.0`
+- Images: matching `hugegraph/pd`, `hugegraph/store`, `hugegraph/server`, and
+ `hugegraph/hubble` tags from the selected compatible release
- `pull_policy: always` — always pulls the specified image tag
-> **Note**: Use release tags (e.g., `1.7.0`) for stable deployments. The
`latest` tag is intended for testing or development only.
+> **Note**: Do not use `latest` to claim a reproducible deployment. Pin a
+> compatible release tag and keep it unchanged for later lifecycle commands.
- PD healthcheck endpoint: `/v1/health`
-- Single PD, single Store (`HG_PD_INITIAL_STORE_LIST: store:8500`), single
Server
+- Hubble is available at `http://localhost:8088`; sign in as `admin` with the
+ required `HUGEGRAPH_ADMIN_PASSWORD`
+- Hubble binds to host loopback by default. Set `HUBBLE_PUBLISH_HOST`
+ explicitly only behind an HTTPS reverse proxy and trusted network controls.
+- Hubble uses PD discovery and the Docker-network Server address
- Server healthcheck endpoint: `/versions`
### Option B: Development Build (build from source)
Builds images locally from source Dockerfiles. Best for **developers** who
want to test local changes.
+Build the matching `hugegraph-toolchain` Hubble source as
+`local/hugegraph-hubble:dev` before starting this stack.
```bash
-cd docker
-docker compose -f docker-compose.dev.yml up -d
+(
+ cd docker
+ HUBBLE_IMAGE=local/hugegraph-hubble:dev \
+ HUBBLE_PULL_POLICY=never \
+ docker compose -f docker-compose.dev.yml up -d
+)
```
-- Images: built from source via `build: context: ..` with Dockerfiles
-- No `pull_policy` — builds locally, doesn't pull
-- Entrypoint scripts are baked into the built image (no volume mounts)
+- PD, Store, and Server images are built from this repository
+- Hubble uses `HUBBLE_IMAGE` because its source is in `hugegraph-toolchain`
+- Server entrypoint scripts are baked into the built image; Hubble mounts the
+ Docker-local PD configuration
- PD healthcheck endpoint: `/v1/health`
- Otherwise identical env vars and structure to the quickstart file
+Use the same release tag for Option A lifecycle commands:
+
+```bash
+(
+ cd docker
+ export HUGEGRAPH_VERSION='<same-compatible-release>'
+ docker compose ps
+ docker compose stop
+ docker compose down
+)
+```
+
+Use the development Compose file for every Option B lifecycle command:
+
+```bash
+(
+ cd docker
+ docker compose -f docker-compose.dev.yml ps
+ docker compose -f docker-compose.dev.yml stop
+ docker compose -f docker-compose.dev.yml down
+)
+```
+
### Key Differences
| | `docker-compose.yml` (quickstart) | `docker-compose.dev.yml` (dev build) |
|---|---|---|
| **Images** | Pull from Docker Hub | Build from source |
| **Who it's for** | End users | Developers |
-| **pull_policy** | `always` | not set (build) |
+| **Server pull_policy** | `always` | `build` |
+| **Hubble pull_policy** | `always` | `never` in the workflow above (`missing`
in the Compose file by default) |
**Verify** (both options):
```bash
curl http://localhost:8080/versions
+curl -fsS http://localhost:8088/about
+```
+
+To validate local images without Compose replacing them with remote `latest`:
+
+```bash
+(
+ cd docker
+ HUGEGRAPH_SERVER_IMAGE=local/hugegraph-server:test \
+ HUGEGRAPH_SERVER_PULL_POLICY=never \
+ HUBBLE_IMAGE=local/hugegraph-hubble:test \
+ HUBBLE_PULL_POLICY=never \
+ docker compose up -d --wait
+)
```
---
@@ -159,8 +256,13 @@ Configuration is injected via environment variables. The
old `docker/configs/app
|----------|----------|---------|-----------------------------|-------------|
| `HG_SERVER_BACKEND` | Yes | — | `backend` in `hugegraph.properties` |
Storage backend (e.g. `hstore`) |
| `HG_SERVER_PD_PEERS` | Yes | — | `pd.peers` | PD cluster addresses (e.g.
`pd0:8686,pd1:8686,pd2:8686`) |
+| `HG_SERVER_CLUSTER` | No | — | `cluster` in `rest-server.properties` | PD
discovery application name; single-node Compose uses `hg` to match Hubble |
+| `HG_SERVER_USE_PD` | No | — | `usePD` in `rest-server.properties` | Enables
Server PD registration and discovery |
+| `HG_SERVER_REST_URL` | No | — | `restserver.url` | Address registered with
PD and used by clients |
+| `HG_SERVER_MIN_FREE_MEMORY` | No | — | `restserver.min_free_memory` |
Minimum free-memory guard in MB; local Compose uses `0` |
+| `HG_SERVER_AUTH_TOKEN_SECRET` | No | generated in auth mode |
`auth.token_secret` | Shared JWT secret for REST and embedded Gremlin
authentication; explicit values must be at least 32 bytes |
| `STORE_REST` | No | — | Used by `wait-partition.sh` | Store REST endpoint
for partition verification (e.g. `store0:8520`) |
-| `PASSWORD` | No | — | Enables auth mode | Optional authentication password;
ignored when `HG_SERVER_INIT_STORE_ENABLED` is `false` (see below) |
+| `PASSWORD` | No | — | Enables auth and sets `auth.admin_pa` | Initial
administrator password; disabled init-store does not read it from stdin, but
the entrypoint still applies it to the PD bootstrap path |
| `HG_SERVER_INIT_STORE_ENABLED` | No | `true` | `init_store.enabled` in
`rest-server.properties` | Set `false` in PD/HStore deployments so init-store
skips local backend and admin initialization |
> **The built-in authenticator with `HG_SERVER_INIT_STORE_ENABLED=false`
@@ -180,12 +282,74 @@ Configuration is injected via environment variables. The
old `docker/configs/app
> one performs the fail-closed check above first, so a marker left by an
> earlier release or an earlier enabled run cannot bypass it.
>
-> **`PASSWORD` does not reach that path.** init-store reads it from standard
-> input, and a disabled one returns before doing so. The admin is instead
-> created from `auth.admin_pa`, whose `pa` default is public, so init-store
-> refuses to skip unless it is explicitly set to a non-empty value in a mounted
-> `rest-server.properties`. It applies only when the account is first created,
-> so changing it later does not rotate an existing password.
+> The entrypoint maps **`PASSWORD` to `auth.admin_pa`** before init-store runs.
+> A disabled init-store does not read the password from standard input, but the
+> PD startup path uses the explicit `auth.admin_pa` value when it first creates
+> the administrator. Changing it later does not rotate an existing password.
+
+The single-node Compose files also accept these deployment-level overrides:
+
+| Variable | Default | Description |
+|----------|---------|-------------|
+| `HUGEGRAPH_SERVER_IMAGE` | `hugegraph/server:<version>` | Complete Server
image reference |
+| `HUGEGRAPH_SERVER_PULL_POLICY` | `always` (`build` for dev) | Server pull
policy |
+| `HUBBLE_IMAGE` | `hugegraph/hubble:<version>` | Complete Hubble image
reference |
+| `HUBBLE_PULL_POLICY` | `always` (`missing` for dev) | Hubble pull policy |
+| `HUBBLE_PUBLISH_HOST` | `127.0.0.1` | Hubble host bind address; remote
access requires an HTTPS reverse proxy |
+| `HUGEGRAPH_ADMIN_PASSWORD` | required (`docker/.env`) | Initial admin
password; no public default is provided |
+| `HUGEGRAPH_AUTH_TOKEN_SECRET` | generated | JWT signing secret; explicit
values must be at least 32 bytes |
+
+When authentication is enabled and no token secret is supplied, the Server
+entrypoint generates a random secret and writes it to both authentication
+configurations. The value is reused on container restart while the container
+filesystem is preserved. To preserve tokens across container recreation,
+generate a compatible secret once and add it to the mode-600 `docker/.env`:
+
+```bash
+(
+ set -euo pipefail
+ cd docker
+
secret_pattern='^[[:space:]]*(export[[:space:]]+)?HUGEGRAPH_AUTH_TOKEN_SECRET[[:space:]]*='
+ secret_count="$(grep -Ec "${secret_pattern}" .env || true)"
+ case "${secret_count}" in
+ 0)
+ command -v openssl >/dev/null 2>&1
+ token_secret="$(openssl rand -hex 32)"
+ LC_ALL=C
+ if (( ${#token_secret} != 64 )); then
+ echo "Failed to generate a 64-character token secret" >&2
+ exit 1
+ fi
+ printf "HUGEGRAPH_AUTH_TOKEN_SECRET='%s'\n" \
+ "${token_secret}" >> .env
+ unset token_secret
+ echo "Generated HUGEGRAPH_AUTH_TOKEN_SECRET"
+ ;;
+ 1)
+ token_secret="$(
+ sed -nE \
+ "s/${secret_pattern}'([^']*)'[[:space:]]*$/\\2/p" .env
+ )"
+ LC_ALL=C
+ if (( ${#token_secret} < 32 )); then
+ echo "Existing token secret must use the documented single-quoted" \
+ "format and contain at least 32 bytes; .env was not changed" >&2
+ exit 1
+ fi
+ unset token_secret
+ echo "HUGEGRAPH_AUTH_TOKEN_SECRET already exists; reusing it"
+ ;;
+ *)
+ echo "Duplicate HUGEGRAPH_AUTH_TOKEN_SECRET entries; repair .env" >&2
+ exit 1
+ ;;
+ esac
+ chmod 600 .env
+)
+```
+
+The entrypoint rejects shorter explicit values before changing either Server
+configuration file.
**Deprecated aliases** (still work but log a warning):
@@ -199,7 +363,8 @@ Configuration is injected via environment variables. The
old `docker/configs/app
## Port Reference
The table below reflects the published host ports in
`docker-compose-3pd-3store-3server.yml`.
-The single-node compose file (`docker-compose.yml`) only publishes the
REST/API ports (`8620`, `8520`, `8080`) by default.
+The single-node Compose file publishes `8620`, `8520`, `8080`, and Hubble
+`8088`; Hubble defaults to host loopback.
| Service | Container Port | Host Port | Protocol | Purpose |
|---------|---------------|-----------|----------|---------|
@@ -232,6 +397,7 @@ The single-node compose file (`docker-compose.yml`) only
publishes the REST/API
| PD | `GET /v1/health` | `200 OK` |
| Store | `GET /v1/health` | `200 OK` |
| Server | `GET /versions` | `200 OK` with version JSON |
+| Hubble | `GET /about` | `200` JSON with Hubble name and version |
---
diff --git a/docker/docker-compose.dev.yml b/docker/docker-compose.dev.yml
index aa0736a38..70089f488 100644
--- a/docker/docker-compose.dev.yml
+++ b/docker/docker-compose.dev.yml
@@ -83,6 +83,8 @@ services:
start_period: 30s
server:
+ image: ${HUGEGRAPH_SERVER_IMAGE:-hugegraph/server:dev}
+ pull_policy: ${HUGEGRAPH_SERVER_PULL_POLICY:-build}
build:
context: ..
dockerfile: hugegraph-server/Dockerfile-hstore
@@ -96,10 +98,38 @@ services:
environment:
HG_SERVER_BACKEND: hstore
HG_SERVER_PD_PEERS: pd:8686
+ HG_SERVER_CLUSTER: hg
+ HG_SERVER_USE_PD: "true"
+ HG_SERVER_REST_URL: http://server:8080
+ HG_SERVER_MIN_FREE_MEMORY: "0"
+ HG_SERVER_INIT_STORE_ENABLED: "false"
+ HG_SERVER_AUTH_TOKEN_SECRET: ${HUGEGRAPH_AUTH_TOKEN_SECRET:-}
+ PASSWORD: ${HUGEGRAPH_ADMIN_PASSWORD:?Set a non-default admin password}
ports:
- "8080:8080"
healthcheck:
- test: ["CMD-SHELL", "curl -fsS http://localhost:8080/versions >/dev/null
|| exit 1"]
+ test: ["CMD-SHELL", "curl -fsS http://server:8080/versions >/dev/null ||
exit 1"]
+ interval: 10s
+ timeout: 5s
+ retries: 30
+ start_period: 60s
+
+ hubble:
+ image: ${HUBBLE_IMAGE:-hugegraph/hubble:latest}
+ pull_policy: ${HUBBLE_PULL_POLICY:-missing}
+ container_name: hg-hubble
+ hostname: hubble
+ restart: unless-stopped
+ networks: [hg-net]
+ depends_on:
+ server:
+ condition: service_healthy
+ ports:
+ - "${HUBBLE_PUBLISH_HOST:-127.0.0.1}:8088:8088"
+ volumes:
+ -
./hugegraph-hubble.properties:/hubble/conf/hugegraph-hubble.properties:ro
+ healthcheck:
+ test: ["CMD-SHELL", "body=$$(curl -fsS http://127.0.0.1:8088/about) &&
printf '%s' \"$$body\" | grep -q '\"status\":200' && printf '%s' \"$$body\" |
grep -q '\"name\":\"hugegraph-hubble\"'"]
interval: 10s
timeout: 5s
retries: 30
diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml
index 953c438f5..0a7a7ea29 100644
--- a/docker/docker-compose.yml
+++ b/docker/docker-compose.yml
@@ -81,8 +81,8 @@ services:
start_period: 60s
server:
- image: hugegraph/server:${HUGEGRAPH_VERSION:-latest}
- pull_policy: always
+ image:
${HUGEGRAPH_SERVER_IMAGE:-hugegraph/server:${HUGEGRAPH_VERSION:-latest}}
+ pull_policy: ${HUGEGRAPH_SERVER_PULL_POLICY:-always}
container_name: hg-server
hostname: server
restart: unless-stopped
@@ -93,10 +93,38 @@ services:
environment:
HG_SERVER_BACKEND: hstore
HG_SERVER_PD_PEERS: pd:8686
+ HG_SERVER_CLUSTER: hg
+ HG_SERVER_USE_PD: "true"
+ HG_SERVER_REST_URL: http://server:8080
+ HG_SERVER_MIN_FREE_MEMORY: "0"
+ HG_SERVER_INIT_STORE_ENABLED: "false"
+ HG_SERVER_AUTH_TOKEN_SECRET: ${HUGEGRAPH_AUTH_TOKEN_SECRET:-}
+ PASSWORD: ${HUGEGRAPH_ADMIN_PASSWORD:?Set a non-default admin password}
ports:
- "8080:8080"
healthcheck:
- test: ["CMD-SHELL", "curl -fsS http://localhost:8080/versions >/dev/null
|| exit 1"]
+ test: ["CMD-SHELL", "curl -fsS http://server:8080/versions >/dev/null ||
exit 1"]
+ interval: 10s
+ timeout: 5s
+ retries: 30
+ start_period: 60s
+
+ hubble:
+ image: ${HUBBLE_IMAGE:-hugegraph/hubble:${HUGEGRAPH_VERSION:-latest}}
+ pull_policy: ${HUBBLE_PULL_POLICY:-always}
+ container_name: hg-hubble
+ hostname: hubble
+ restart: unless-stopped
+ networks: [hg-net]
+ depends_on:
+ server:
+ condition: service_healthy
+ ports:
+ - "${HUBBLE_PUBLISH_HOST:-127.0.0.1}:8088:8088"
+ volumes:
+ -
./hugegraph-hubble.properties:/hubble/conf/hugegraph-hubble.properties:ro
+ healthcheck:
+ test: ["CMD-SHELL", "body=$$(curl -fsS http://127.0.0.1:8088/about) &&
printf '%s' \"$$body\" | grep -q '\"status\":200' && printf '%s' \"$$body\" |
grep -q '\"name\":\"hugegraph-hubble\"'"]
interval: 10s
timeout: 5s
retries: 30
diff --git a/.dockerignore b/docker/hugegraph-hubble.properties
similarity index 53%
copy from .dockerignore
copy to docker/hugegraph-hubble.properties
index ee61478a1..b44255cfc 100644
--- a/.dockerignore
+++ b/docker/hugegraph-hubble.properties
@@ -1,4 +1,3 @@
-#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
@@ -6,55 +5,26 @@
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
-# http://www.apache.org/licenses/LICENSE-2.0
+# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
-#
-
-# IMPORTANT: .dockerignore does NOT inherit .gitignore — patterns must be
restated.
-
-# Build output
-**/target/
-
-# Pre-extracted release dirs / archives
-apache-hugegraph-*/
-**/*.tar
-**/*.tar.gz
-**/*.zip
-**/*.war
-# IDE / OS
-.idea/
-.vscode/
-**/*.iml
-**/*.iws
-**/.DS_Store
+server.host=0.0.0.0
+server.port=8088
-# Build / runtime artifacts
-**/logs/
-**/*.log
-**/*.class
-**/gen-java/
-**/upload-files/
-**/build/
-**/node_modules/
+cluster=hg
+idc=docker
-# Env files
-.env.local
-.env.*.local
+pd.enabled=true
+server.direct_url=http://server:8080
+pd.peers=pd:8686
+pd.server=pd:8620
-# Git internals
-.git
-.gitignore
-.gitattributes
-.github
+operations.store.allowed_targets=[http://store:8520]
-# Compose / docs not needed in build context
-**/docker-compose*.yml
-**/docker-compose*.yaml
-**/*.md
-docs/
+# Dashboard is not part of this Compose stack.
+dashboard.address=
diff --git
a/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/auth/WsAndHttpBasicAuthHandler.java
b/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/auth/WsAndHttpBasicAuthHandler.java
index 304e712fd..f18171d55 100644
---
a/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/auth/WsAndHttpBasicAuthHandler.java
+++
b/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/auth/WsAndHttpBasicAuthHandler.java
@@ -118,6 +118,13 @@ public class WsAndHttpBasicAuthHandler extends
SaslAuthenticationHandler {
sendError(ctx, msg);
return;
}
+ } else if ("Bearer".equalsIgnoreCase(scheme)) {
+ String token = payload.trim();
+ if (token.isEmpty() || containsWhitespace(token)) {
+ sendError(ctx, msg);
+ return;
+ }
+ credentials.put(HugeAuthenticator.KEY_TOKEN, token);
} else {
sendError(ctx, msg);
return;
@@ -141,6 +148,15 @@ public class WsAndHttpBasicAuthHandler extends
SaslAuthenticationHandler {
}
}
+ private static boolean containsWhitespace(String value) {
+ for (int i = 0; i < value.length(); i++) {
+ if (Character.isWhitespace(value.charAt(i))) {
+ return true;
+ }
+ }
+ return false;
+ }
+
private boolean parseBasicCredentials(String encoded,
Map<String, String> credentials)
{
byte[] userPass;
diff --git a/hugegraph-server/hugegraph-dist/docker/docker-entrypoint-test.sh
b/hugegraph-server/hugegraph-dist/docker/docker-entrypoint-test.sh
new file mode 100755
index 000000000..4b5dcf5ba
--- /dev/null
+++ b/hugegraph-server/hugegraph-dist/docker/docker-entrypoint-test.sh
@@ -0,0 +1,205 @@
+#!/bin/bash
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+set -euo pipefail
+
+SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
+TEST_HOME=$(mktemp -d "${TMPDIR:-/tmp}/hugegraph-entrypoint-test.XXXXXX")
+trap 'rm -rf "${TEST_HOME}"' EXIT
+
+mkdir -p "${TEST_HOME}/bin" "${TEST_HOME}/conf/graphs" "${TEST_HOME}/docker"
+cp "${SCRIPT_DIR}/docker-entrypoint.sh" "${TEST_HOME}/docker-entrypoint.sh"
+touch "${TEST_HOME}/docker/init_complete"
+
+cat > "${TEST_HOME}/conf/rest-server.properties" <<'EOF'
+restserver.url=http://127.0.0.1:8080
+# usePD=true
+EOF
+cat > "${TEST_HOME}/conf/graphs/hugegraph.properties" <<'EOF'
+backend=rocksdb
+#pd.peers=127.0.0.1:8686
+EOF
+cat > "${TEST_HOME}/bin/start-hugegraph.sh" <<'EOF'
+#!/usr/bin/env bash
+exit 0
+EOF
+cat > "${TEST_HOME}/bin/init-store.sh" <<'EOF'
+#!/usr/bin/env bash
+printf 'called\n' >> ./docker/init-store-calls
+if IFS= read -r password; then
+ printf '%s' "${password}" > ./docker/init-store-password
+fi
+EOF
+cat > "${TEST_HOME}/bin/enable-auth.sh" <<'EOF'
+#!/usr/bin/env bash
+printf 'called\n' >> ./docker/enable-auth-calls
+EOF
+cat > "${TEST_HOME}/bin/wait-partition.sh" <<'EOF'
+#!/usr/bin/env bash
+exit 0
+EOF
+cat > "${TEST_HOME}/bin/wait-storage.sh" <<'EOF'
+#!/usr/bin/env bash
+exit 0
+EOF
+chmod +x "${TEST_HOME}/bin/"*.sh
+
+(
+ cd "${TEST_HOME}"
+ HG_SERVER_BACKEND=hstore \
+ HG_SERVER_PD_PEERS=pd:8686 \
+ HG_SERVER_CLUSTER=hg \
+ HG_SERVER_USE_PD=true \
+ HG_SERVER_REST_URL=http://server:8080 \
+ HG_SERVER_MIN_FREE_MEMORY=0 \
+ HG_SERVER_AUTH_TOKEN_SECRET=12345678901234567890123456789012 \
+ bash ./docker-entrypoint.sh
+)
+[[ "$(wc -l < "${TEST_HOME}/docker/init-store-calls")" -eq 1 ]]
+
+grep -qx 'backend=hstore' "${TEST_HOME}/conf/graphs/hugegraph.properties"
+grep -qx 'pd.peers=pd:8686' "${TEST_HOME}/conf/graphs/hugegraph.properties"
+grep -qx 'usePD=true' "${TEST_HOME}/conf/rest-server.properties"
+grep -qx 'pd.peers=pd:8686' "${TEST_HOME}/conf/rest-server.properties"
+grep -qx 'cluster=hg' "${TEST_HOME}/conf/rest-server.properties"
+grep -qx 'restserver.url=http://server:8080' \
+ "${TEST_HOME}/conf/rest-server.properties"
+grep -qx 'restserver.min_free_memory=0' \
+ "${TEST_HOME}/conf/rest-server.properties"
+grep -qx 'auth.token_secret=12345678901234567890123456789012' \
+ "${TEST_HOME}/conf/rest-server.properties"
+grep -qx 'auth.token_secret=12345678901234567890123456789012' \
+ "${TEST_HOME}/conf/graphs/hugegraph.properties"
+
+cp "${TEST_HOME}/conf/rest-server.properties" \
+ "${TEST_HOME}/conf/rest-server.properties.before-short-secret"
+cp "${TEST_HOME}/conf/graphs/hugegraph.properties" \
+ "${TEST_HOME}/conf/graphs/hugegraph.properties.before-short-secret"
+if (
+ cd "${TEST_HOME}"
+ PASSWORD=pa \
+ HG_SERVER_AUTH_TOKEN_SECRET=1234567890123456789012345678901 \
+ bash ./docker-entrypoint.sh
+); then
+ echo "short authentication token secret unexpectedly succeeded" >&2
+ exit 1
+fi
+cmp "${TEST_HOME}/conf/rest-server.properties.before-short-secret" \
+ "${TEST_HOME}/conf/rest-server.properties"
+cmp "${TEST_HOME}/conf/graphs/hugegraph.properties.before-short-secret" \
+ "${TEST_HOME}/conf/graphs/hugegraph.properties"
+[[ ! -e "${TEST_HOME}/docker/enable-auth-calls" ]]
+
+sed -i '/^auth\.token_secret=/d' "${TEST_HOME}/conf/rest-server.properties"
+sed -i '/^auth\.token_secret=/d'
"${TEST_HOME}/conf/graphs/hugegraph.properties"
+(
+ cd "${TEST_HOME}"
+ PASSWORD=pa bash ./docker-entrypoint.sh
+)
+rest_secret=$(sed -n 's/^auth\.token_secret=//p' \
+ "${TEST_HOME}/conf/rest-server.properties")
+graph_secret=$(sed -n 's/^auth\.token_secret=//p' \
+ "${TEST_HOME}/conf/graphs/hugegraph.properties")
+[[ ${#rest_secret} -ge 43 ]]
+[[ "${rest_secret}" == "${graph_secret}" ]]
+grep -qx 'auth.admin_pa=pa' "${TEST_HOME}/conf/rest-server.properties"
+(
+ cd "${TEST_HOME}"
+ PASSWORD=pa bash ./docker-entrypoint.sh
+)
+reused_secret=$(sed -n 's/^auth\.token_secret=//p' \
+ "${TEST_HOME}/conf/rest-server.properties")
+[[ "${reused_secret}" == "${rest_secret}" ]]
+
+sed -i '/^auth\.token_secret=/d' \
+ "${TEST_HOME}/conf/graphs/hugegraph.properties"
+sed -i "s|^auth\\.token_secret=.*|auth.token_secret: ${rest_secret}|" \
+ "${TEST_HOME}/conf/rest-server.properties"
+(
+ cd "${TEST_HOME}"
+ PASSWORD=pa bash ./docker-entrypoint.sh
+)
+grep -qx "auth.token_secret=${rest_secret}" \
+ "${TEST_HOME}/conf/graphs/hugegraph.properties"
+
+sed -i '/^auth\.token_secret=/d' \
+ "${TEST_HOME}/conf/rest-server.properties"
+sed -i "s|^auth\\.token_secret=.*|auth.token_secret ${rest_secret}|" \
+ "${TEST_HOME}/conf/graphs/hugegraph.properties"
+(
+ cd "${TEST_HOME}"
+ PASSWORD=pa bash ./docker-entrypoint.sh
+)
+grep -qx "auth.token_secret=${rest_secret}" \
+ "${TEST_HOME}/conf/rest-server.properties"
+
+[[ "$(wc -l < "${TEST_HOME}/docker/init-store-calls")" -eq 5 ]]
+[[ "$(wc -l < "${TEST_HOME}/docker/enable-auth-calls")" -eq 4 ]]
+
+(
+ cd "${TEST_HOME}"
+ PASSWORD=pa \
+ HG_SERVER_AUTH_TOKEN_SECRET='Strong\Secret 9!0123456789abcdef' \
+ bash ./docker-entrypoint.sh
+)
+complex_secret=$(sed -n 's/^auth\.token_secret=//p' \
+ "${TEST_HOME}/conf/rest-server.properties")
+(
+ cd "${TEST_HOME}"
+ PASSWORD=pa bash ./docker-entrypoint.sh
+)
+reused_complex_secret=$(sed -n 's/^auth\.token_secret=//p' \
+ "${TEST_HOME}/conf/rest-server.properties")
+[[ "${reused_complex_secret}" == "${complex_secret}" ]]
+[[ "${reused_complex_secret}" == \
+ 'Strong\\Secret\ 9!0123456789abcdef' ]]
+
+(
+ cd "${TEST_HOME}"
+ PASSWORD=pa \
+ HG_SERVER_AUTH_TOKEN_SECRET='SecretEnds 0123456789abcdefABCDE ' \
+ bash ./docker-entrypoint.sh
+)
+trailing_space_secret=$(sed -n 's/^auth\.token_secret=//p' \
+ "${TEST_HOME}/conf/rest-server.properties")
+(
+ cd "${TEST_HOME}"
+ PASSWORD=pa bash ./docker-entrypoint.sh
+)
+reused_trailing_space_secret=$(sed -n 's/^auth\.token_secret=//p' \
+ "${TEST_HOME}/conf/rest-server.properties")
+[[ "${trailing_space_secret}" == \
+ 'SecretEnds\ 0123456789abcdefABCDE\ ' ]]
+[[ "${reused_trailing_space_secret}" == "${trailing_space_secret}" ]]
+grep -Fqx 'auth.admin_pa=pa' \
+ "${TEST_HOME}/conf/rest-server.properties"
+
+(
+ cd "${TEST_HOME}"
+ PASSWORD='Strong\Pass 9!' bash ./docker-entrypoint.sh
+)
+grep -Fqx 'auth.admin_pa=Strong\\Pass\ 9!' \
+ "${TEST_HOME}/conf/rest-server.properties"
+
+rm -f "${TEST_HOME}/docker/init_complete"
+(
+ cd "${TEST_HOME}"
+ PASSWORD=-n bash ./docker-entrypoint.sh
+)
+grep -Fqx -- '-n' "${TEST_HOME}/docker/init-store-password"
+
+echo "PASS: Docker entrypoint configures HStore discovery and authentication"
diff --git a/hugegraph-server/hugegraph-dist/docker/docker-entrypoint.sh
b/hugegraph-server/hugegraph-dist/docker/docker-entrypoint.sh
index 11d2c460e..295fe1e2e 100755
--- a/hugegraph-server/hugegraph-dist/docker/docker-entrypoint.sh
+++ b/hugegraph-server/hugegraph-dist/docker/docker-entrypoint.sh
@@ -26,22 +26,58 @@ mkdir -p "${DOCKER_FOLDER}"
log() { echo "[hugegraph-server-entrypoint] $*"; }
-set_prop() {
- local key="$1" val="$2" file="$3"
+encode_prop_value() {
+ local value="$1" encoded="" char
+ local i
+
+ LC_ALL=C
+ for ((i = 0; i < ${#value}; i++)); do
+ char="${value:i:1}"
+ case "${char}" in
+ "\\") encoded+="\\\\" ;;
+ " ") encoded+="\\ " ;;
+ $'\t') encoded+="\\t" ;;
+ $'\n') encoded+="\\n" ;;
+ $'\r') encoded+="\\r" ;;
+ $'\f') encoded+="\\f" ;;
+ *) encoded+="${char}" ;;
+ esac
+ done
+ printf '%s' "${encoded}"
+}
+
+set_prop_encoded() {
+ local key="$1" encoded_val="$2" file="$3"
local esc_key esc_val key_re
esc_key=$(printf '%s' "$key" | sed -e 's/[][(){}.^$*+?|\\/]/\\&/g')
- esc_val=$(printf '%s' "$val" | sed -e 's/[&|\\~]/\\&/g')
+ esc_val=$(printf '%s' "$encoded_val" | sed -e 's/[&|\\~]/\\&/g')
key_re="^[[:space:]]*${esc_key}([[:space:]]*[:=]|[[:space:]]+|[[:space:]]*$)"
if grep -qE "${key_re}" "${file}"; then
sed -ri "0,/${key_re}/!{/${key_re}/d;}" "${file}"
sed -ri "0,/${key_re}/s~${key_re}.*~${key}=${esc_val}~" "${file}"
else
- printf '%s=%s\n' "$key" "$val" >> "${file}"
+ printf '%s=%s\n' "$key" "$encoded_val" >> "${file}"
fi
}
+set_prop() {
+ local key="$1" val="$2" file="$3"
+
+ set_prop_encoded "$key" "$(encode_prop_value "$val")" "$file"
+}
+
+get_prop_encoded() {
+ local key="$1" file="$2"
+ local esc_key
+
+ esc_key=$(printf '%s' "$key" | sed -e 's/[][(){}.^$*+?|\\/]/\\&/g')
+ sed -nE \
+
"s~^[[:space:]]*${esc_key}([[:space:]]*[:=][[:space:]]*|[[:space:]]+)(.*)$~\\2~p"
\
+ "${file}" | head -n 1
+}
+
migrate_env() {
local old_name="$1" new_name="$2"
@@ -54,9 +90,60 @@ migrate_env() {
migrate_env "BACKEND" "HG_SERVER_BACKEND"
migrate_env "PD_PEERS" "HG_SERVER_PD_PEERS"
+if [[ -n "${HG_SERVER_AUTH_TOKEN_SECRET:-}" ]]; then
+ LC_ALL=C
+ if (( ${#HG_SERVER_AUTH_TOKEN_SECRET} < 32 )); then
+ log "ERROR: HG_SERVER_AUTH_TOKEN_SECRET must be at least 32 bytes"
+ exit 1
+ fi
+fi
+
+AUTH_TOKEN_SECRET_ENCODED=""
+if [[ -n "${PASSWORD:-}" && -z "${HG_SERVER_AUTH_TOKEN_SECRET:-}" ]]; then
+ rest_secret=$(get_prop_encoded "auth.token_secret" "${REST_SERVER_CONF}")
+ graph_secret=$(get_prop_encoded "auth.token_secret" "${GRAPH_CONF}")
+ if [[ -n "${rest_secret}" ]]; then
+ AUTH_TOKEN_SECRET_ENCODED="${rest_secret}"
+ if [[ -n "${graph_secret}" && "${graph_secret}" != "${rest_secret}"
]]; then
+ log "WARN: authentication token secrets differ; using REST secret"
+ fi
+ elif [[ -n "${graph_secret}" ]]; then
+ AUTH_TOKEN_SECRET_ENCODED="${graph_secret}"
+ else
+ HG_SERVER_AUTH_TOKEN_SECRET=$(head -c 32 /dev/urandom | base64 | tr -d
'\n')
+ log "generated a shared authentication token secret"
+ fi
+fi
+
# ── Map env → properties file ─────────────────────────────────────────
[[ -n "${HG_SERVER_BACKEND:-}" ]] && set_prop "backend"
"${HG_SERVER_BACKEND}" "${GRAPH_CONF}"
[[ -n "${HG_SERVER_PD_PEERS:-}" ]] && set_prop "pd.peers"
"${HG_SERVER_PD_PEERS}" "${GRAPH_CONF}"
+[[ -n "${HG_SERVER_USE_PD:-}" ]] && \
+ set_prop "usePD" "${HG_SERVER_USE_PD}" "${REST_SERVER_CONF}"
+[[ -n "${HG_SERVER_PD_PEERS:-}" ]] && \
+ set_prop "pd.peers" "${HG_SERVER_PD_PEERS}" "${REST_SERVER_CONF}"
+[[ -n "${HG_SERVER_CLUSTER:-}" ]] && \
+ set_prop "cluster" "${HG_SERVER_CLUSTER}" "${REST_SERVER_CONF}"
+[[ -n "${HG_SERVER_REST_URL:-}" ]] && set_prop "restserver.url" \
+ "${HG_SERVER_REST_URL}" "${REST_SERVER_CONF}"
+[[ -n "${HG_SERVER_MIN_FREE_MEMORY:-}" ]] && set_prop
"restserver.min_free_memory" \
+ "${HG_SERVER_MIN_FREE_MEMORY}" "${REST_SERVER_CONF}"
+if [[ -n "${HG_SERVER_AUTH_TOKEN_SECRET:-}" ]]; then
+ set_prop "auth.token_secret" "${HG_SERVER_AUTH_TOKEN_SECRET}" \
+ "${REST_SERVER_CONF}"
+ set_prop "auth.token_secret" "${HG_SERVER_AUTH_TOKEN_SECRET}"
"${GRAPH_CONF}"
+elif [[ -n "${AUTH_TOKEN_SECRET_ENCODED}" ]]; then
+ set_prop_encoded "auth.token_secret" "${AUTH_TOKEN_SECRET_ENCODED}" \
+ "${REST_SERVER_CONF}"
+ set_prop_encoded "auth.token_secret" "${AUTH_TOKEN_SECRET_ENCODED}" \
+ "${GRAPH_CONF}"
+fi
+if [[ -n "${PASSWORD:-}" ]]; then
+ set_prop "auth.admin_pa" "${PASSWORD}" "${REST_SERVER_CONF}"
+ # This script is idempotent and must run outside the initialization guard:
+ # an upgrade can preserve the marker from an unauthenticated deployment.
+ ./bin/enable-auth.sh
+fi
# Normalized once here and reused by the init-flag guard below. The accepted
# spellings are the ones HugeConfig accepts, case-insensitive: commons-lang 2.x
@@ -102,16 +189,15 @@ if [[ ! -f "${INIT_MARKER_PATH}" ]]; then
./bin/init-store.sh
else
log "init hugegraph with auth mode"
- ./bin/enable-auth.sh
# init-store reads the password from stdin, and a disabled one returns
# before it gets there, so say plainly that PASSWORD is being dropped
case "${INIT_STORE_ENABLED}" in
n | f | no | off | false)
- log "WARN: PASSWORD is ignored while init-store is disabled;" \
- "the admin is created on the PD startup path from" \
- "'auth.admin_pa', which defaults to the public value 'pa'"
;;
+ log "init-store does not read PASSWORD while disabled;" \
+ "the entrypoint applies it through 'auth.admin_pa' for" \
+ "the PD startup path" ;;
esac
- echo "${PASSWORD}" | ./bin/init-store.sh
+ printf '%s\n' "${PASSWORD}" | ./bin/init-store.sh
fi
else
log "HugeGraph initialization already done. Revalidating the config..."
diff --git
a/hugegraph-server/hugegraph-dist/docker/test/test-docker-entrypoint.sh
b/hugegraph-server/hugegraph-dist/docker/test/test-docker-entrypoint.sh
index 1279da555..d5e11c502 100644
--- a/hugegraph-server/hugegraph-dist/docker/test/test-docker-entrypoint.sh
+++ b/hugegraph-server/hugegraph-dist/docker/test/test-docker-entrypoint.sh
@@ -24,9 +24,9 @@ test_dir="$(mktemp -d)"
trap 'rm -rf "${test_dir}"' EXIT
eval "$(awk '
- /^set_prop\(\) \{/ { capture = 1 }
+ /^encode_prop_value\(\) \{/ { capture = 1 }
capture { print }
- capture && /^\}$/ { exit }
+ capture && /^\}$/ && ++function_ends == 3 { exit }
' "${entrypoint}")"
assert_replaced() {
diff --git
a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/auth/WsAndHttpBasicAuthHandlerTest.java
b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/auth/WsAndHttpBasicAuthHandlerTest.java
index bc0c83299..2e486adf5 100644
---
a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/auth/WsAndHttpBasicAuthHandlerTest.java
+++
b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/auth/WsAndHttpBasicAuthHandlerTest.java
@@ -132,9 +132,39 @@ public class WsAndHttpBasicAuthHandlerTest {
channel.finishAndReleaseAll();
}
+ @SuppressWarnings("unchecked")
+ @Test
+ public void testBearerTokenAuthenticatesHttpGremlinRequest()
+ throws Exception {
+ Authenticator authenticator = Mockito.mock(Authenticator.class);
+ AuthenticatedUser user = new AuthenticatedUser("admin");
+ Mockito.when(authenticator.authenticate(Mockito.anyMap()))
+ .thenReturn(user);
+ EmbeddedChannel channel = channel(authenticator);
+
+ channel.writeInbound(request("Bearer server-token"));
+
+ ArgumentCaptor<Map<String, String>> credentials =
+ ArgumentCaptor.forClass(Map.class);
+ Mockito.verify(authenticator, Mockito.times(1))
+ .authenticate(credentials.capture());
+ Assert.assertEquals("server-token", credentials.getValue().get(
+ HugeAuthenticator.KEY_TOKEN));
+ Assert.assertFalse(credentials.getValue().containsKey("username"));
+ Assert.assertFalse(credentials.getValue().containsKey("password"));
+ Assert.assertSame(user,
+ channel.attr(StateKey.AUTHENTICATED_USER).get());
+ channel.finishAndReleaseAll();
+ }
+
+ @Test
+ public void testWhitespaceOnlyBearerTokenIsRejected() throws Exception {
+ assertUnauthorized("Bearer ");
+ }
+
@Test
- public void testBearerTokenIsRejected() throws Exception {
- assertUnauthorized("Bearer server-token");
+ public void testBearerTokenWithWhitespaceIsRejected() throws Exception {
+ assertUnauthorized("Bearer token with-space");
}
@Test