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
5 changes: 5 additions & 0 deletions .github/workflows/e2e-rust-apps-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ env:
RUST_BACKTRACE: 1
CALIMERO_CONTRACTS_VERSION: "latest"
CARGO_TARGET_DIR: ${{ github.workspace }}/target
# Out-of-band secret gating first-root-key bootstrap (fail-closed since the
# TOFU removal). Requires merobox pass-through support (forward into node
# containers + present in the `login` step); inert on older merobox. See
# the matching comment in e2e-rust-apps.yml.
MERO_AUTH_BOOTSTRAP_SECRET: merobox-e2e-bootstrap

jobs:
build-apps:
Expand Down
22 changes: 22 additions & 0 deletions .github/workflows/e2e-rust-apps.yml
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,16 @@ jobs:
uses: ./.github/actions/setup-merobox

- name: Run auth-seam e2e (merobox, embedded auth)
env:
# Out-of-band secret gating first-root-key bootstrap (fail-closed
# since the TOFU removal). Binary-mode merobox spawns merod with
# os.environ inherited, and script steps (target: local) inherit
# it too — so this single export both provisions the node's
# expected secret AND lets scripts/e2e-auth-seam.sh present the
# same value in provider_data.bootstrap_secret on the first
# login. CI-only test value for an ephemeral localhost node; not
# baked into any image or shipped config.
MERO_AUTH_BOOTSTRAP_SECRET: auth-seam-ci-bootstrap
run: |
merobox bootstrap run workflows/auth-seam.yml \
--no-docker --binary-path ./merod --auth-mode embedded
Expand Down Expand Up @@ -463,6 +473,18 @@ jobs:
uses: ./.github/actions/setup-merobox

- name: Run ${{ matrix.workflow }}
env:
# Out-of-band secret gating first-root-key bootstrap (fail-closed
# since the TOFU removal). Requires merobox pass-through support:
# merobox must forward this var into node containers AND present
# it as provider_data.bootstrap_secret in its `login` step
# (tracked cross-repo in calimero-network/merobox). Inert on older
# merobox; once the pass-through ships via the APT repo these
# workflows heal without further changes here. Bump MIN_MEROBOX in
# .github/actions/setup-merobox when that lands. CI-only test
# value for ephemeral containers; not baked into any image or
# shipped config.
MERO_AUTH_BOOTSTRAP_SECRET: merobox-e2e-bootstrap
run: |
# Phase 11.3 (#2237): single attempt, fail loud. The retry loop
# + `merobox nuke --force` between attempts existed to mask
Expand Down
36 changes: 36 additions & 0 deletions .github/workflows/sdk-e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,19 @@ on:
env:
CARGO_TERM_COLOR: always
SERVER_PORT: "2428"
# Out-of-band secret gating first-root-key bootstrap (fail-closed since the
# TOFU removal). The boot step exports it into merod's environment; the
# "Bootstrap root key" step presents the same value to mint the dev/dev root
# key BEFORE the SDK suite runs, so mero-js's plain username/password logins
# authenticate on the existing-user fast path and never need the secret.
# CI-only test value for an ephemeral localhost node.
MERO_AUTH_BOOTSTRAP_SECRET: sdk-e2e-ci-bootstrap
# Root-user credentials, pre-provisioned by the "Bootstrap root key" step
# and consumed by mero-js's e2e harness (tests/e2e/harness.ts resolveCreds()
# reads MERO_E2E_USER / MERO_E2E_PASS, defaulting to dev/dev). Pinning them
# here keeps both sides in lockstep even if the harness defaults change.
MERO_E2E_USER: dev
MERO_E2E_PASS: dev

jobs:
sdk-e2e:
Expand Down Expand Up @@ -99,6 +112,29 @@ jobs:
if [ "$i" = "60" ]; then echo "merod did not become healthy"; exit 1; fi
done

- name: Bootstrap root key (out-of-band secret)
# First-root-key bootstrap requires MERO_AUTH_BOOTSTRAP_SECRET (the
# node inherited it from the boot step's environment). Mint the root
# key for MERO_E2E_USER here so every login the SDK suite performs is
# a plain existing-user authentication — including the negative test
# that asserts wrong/wrong is rejected (a root key exists, so the
# bootstrap path never re-opens).
run: |
BODY=$(jq -n --arg u "$MERO_E2E_USER" --arg p "$MERO_E2E_PASS" \
--arg s "$MERO_AUTH_BOOTSTRAP_SECRET" --argjson ts "$(date +%s)" \
'{auth_method: "user_password", public_key: $u,
client_name: "sdk-e2e-ci", permissions: ["admin"], timestamp: $ts,
provider_data: {username: $u, password: $p, bootstrap_secret: $s}}')
RESP=$(curl -fsS -m 10 -X POST "http://localhost:$SERVER_PORT/auth/token" \
-H 'Content-Type: application/json' -d "$BODY") || {
echo "::error::root-key bootstrap request failed"; exit 1; }
TOKEN=$(echo "$RESP" | jq -r '.data.access_token // empty')
if [ -z "$TOKEN" ]; then
echo "::error::root-key bootstrap did not return a token: $RESP"
exit 1
fi
echo "root key bootstrapped for user $MERO_E2E_USER"

- name: Run mero-js e2e against booted node
working-directory: _mero-js
env:
Expand Down
14 changes: 14 additions & 0 deletions crates/auth/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,13 +91,27 @@ pub struct UserPasswordConfig {
/// Maximum password length
#[serde(default = "default_max_password_length")]
pub max_password_length: usize,

/// Out-of-band secret required to bootstrap the very first root key on a
/// fresh node.
///
/// When this is `None` (and the `MERO_AUTH_BOOTSTRAP_SECRET` environment
/// variable is unset), first-login bootstrap is **disabled** — the node
/// will not mint a root key for an unauthenticated caller, and a root key
/// must be provisioned out of band. When set, the bootstrapping client
/// must present a matching secret before the first root key is created.
/// An empty string is treated the same as `None` (bootstrap disabled), so
/// a blank value from env interpolation can never open the gate.
#[serde(default)]
pub bootstrap_secret: Option<String>,
}

impl Default for UserPasswordConfig {
fn default() -> Self {
Self {
min_password_length: 8,
max_password_length: 128,
bootstrap_secret: None,
}
}
}
Expand Down
Loading
Loading