Skip to content
Merged
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
6 changes: 5 additions & 1 deletion .github/workflows/security-script-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,16 @@ on:
paths:
- rustfs-security-test.sh
- test_rustfs_security.py
- scripts/sts_root_regression.py
- test_sts_root_regression.py
- .github/workflows/security-script-tests.yml
push:
branches: [main]
paths:
- rustfs-security-test.sh
- test_rustfs_security.py
- scripts/sts_root_regression.py
- test_sts_root_regression.py
- .github/workflows/security-script-tests.yml

permissions:
Expand All @@ -27,4 +31,4 @@ jobs:
- name: Validate shell syntax
run: bash -n rustfs-security-test.sh
- name: Test security contracts without a server
run: python3 -m unittest test_rustfs_security.py
run: python3 -B -m unittest test_rustfs_security.py test_sts_root_regression.py
31 changes: 30 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,36 @@ The default log filter is `error,rustfs::admin::auth=debug`; set
commit, while console output includes Bash, curl, and AWS CLI versions.

```bash
python3 -B -m unittest -v test_rustfs_security.py test_sensitive_diagnostics.py
python3 -B -m unittest -v test_rustfs_security.py test_sts_root_regression.py test_sensitive_diagnostics.py
```

### Root AssumeRole regression (#9279)

The security suite runs STS-106 through STS-112 for [rustfs/rustfs#9279](https://github.com/rustfs/rustfs/issues/9279) in every selected topology. STS-106 reproduces the report's form-encoded POST with the `aws:amz:us-east-1:s3` signature scope, requires HTTP 200 and complete, unexpired temporary credentials, then signs ListBuckets with those credentials and the session token.

| Case | Credentials / behavior | Required result |
|---|---|---|
| STS-106 | Configured root, original Query request | HTTP 200, usable STS credentials |
| STS-107 | Root secret: 4 bytes (`root`, as reported) | Nonzero startup exit with a credential-length error |
| STS-108 | Root secret: 7 bytes | Nonzero startup exit with a credential-length error |
| STS-109 | Root access key: 2 bytes | Nonzero startup exit with a credential-length error |
| STS-110 | Root access key: 3 bytes, secret: 8 bytes | Startup succeeds, usable STS credentials |
| STS-111 | Access key `root`, secret: 8 bytes | Startup succeeds, usable STS credentials |
| STS-112 | Root secret: 4 Unicode characters / 8 UTF-8 bytes | Startup succeeds, usable STS credentials |

Startup cases run the installed candidate binary on the topology's first node in separate loopback processes, with fresh temporary volumes and disposable credentials. They leave the topology's system service and credentials unchanged. The helper requires Python 3 and curl with `--aws-sigv4` support on that node. Keep `scripts/sts_root_regression.py` with the suite when copying the runner.

The SSH runner keeps a heartbeat channel open after sending the helper source. Channel EOF or five seconds without a heartbeat requests remote cancellation without depending on a PTY or SIGHUP. Cancellation is checked between bounded requests; teardown completes its wait, forced termination if necessary, and private-directory removal before returning exit status 130. Repeated cancellation cannot interrupt teardown.

The assertions target the intended fix: older versions that accept short root credentials fail these startup cases, even if their STS request returns HTTP 200 or 403. If invalid credentials reach readiness, the report records the STS HTTP status and error code (including the reported `500 InternalError`) before failing. Unrelated startup errors, timeouts, malformed responses, and unusable credentials also fail. Only credential byte lengths and structured outcomes are printed; secret keys, session tokens, and raw server logs are never included.

Run the same matrix against a local candidate binary, or check only the signed Query flow against a running endpoint:

```bash
python3 scripts/sts_root_regression.py --rustfs-bin /absolute/path/to/rustfs
python3 scripts/sts_root_regression.py --rustfs-bin /absolute/path/to/rustfs --case secret-4
# Uses RUSTFS_ACCESS_KEY and RUSTFS_SECRET_KEY from the environment.
python3 scripts/sts_root_regression.py --endpoint http://127.0.0.1:9000
```

## Tier suite artifacts
Expand Down
52 changes: 51 additions & 1 deletion rustfs-security-test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
# RustFS unified IAM / STS / OIDC security test suite (backlog #2024).
#
# Integrates the backlog security case matrix (IAM-102..114, SA-101..104,
# STS-101..105, OIDC-101..108) with the request flows proven by
# STS-101..112, OIDC-101..108) with the request flows proven by
# admin_iam_crud_test / sts_query_compat_test / oidc_keycloak_live.sh.
#
# Every case runs in three topologies, matching the other functional suites:
Expand Down Expand Up @@ -917,6 +917,48 @@ case_sts_105_revoke() {
esac
}

# Issue #9279 uses an S3-scoped SigV4 POST, rather than the AWS CLI's STS
# signer. Also verify every returned field and use the temporary credentials.
case_sts_106_root_query() {
RUSTFS_ACCESS_KEY="${ACCESS_KEY}" RUSTFS_SECRET_KEY="${SECRET_KEY}" \
python3 "${SCRIPT_DIR}/scripts/sts_root_regression.py" --endpoint "${ENDPOINT}"
}

sts_root_startup_case() {
# The installed candidate runs on loopback with private temporary data and
# credentials. The helper always stops its own process; systemd and the
# topology's root credentials/data are never changed for these cases.
local option options=()
for option in "${SSH_OPTS[@]}"; do options+=("--ssh-option=${option}"); done
python3 "${SCRIPT_DIR}/scripts/sts_root_regression.py" \
--ssh-target "${SSH_USER}@${NODES[0]}" "${options[@]}" \
--rustfs-bin /usr/bin/rustfs --case "$1"
}

case_sts_107_short_root_secret() {
sts_root_startup_case secret-4
}

case_sts_108_seven_byte_root_secret() {
sts_root_startup_case secret-7
}

case_sts_109_short_root_access_key() {
sts_root_startup_case access-2
}

case_sts_110_minimum_root_credentials() {
sts_root_startup_case minimum
}

case_sts_111_eight_byte_root_secret() {
sts_root_startup_case secret-8
}

case_sts_112_utf8_root_secret() {
sts_root_startup_case secret-8-utf8
}

# ==================== OIDC / SSO ====================

case_oidc_102_validate_rejects_bad_config() {
Expand Down Expand Up @@ -1093,6 +1135,13 @@ run_security_cases() {
run_test STS-102 "AssumeRole duration clamped to 12h" case_sts_102_duration_clamp
run_test STS-104 "temporary credentials respect identity scope" case_sts_104_temp_boundary
run_test STS-105 "revoke-tokens invalidates temporary credentials" case_sts_105_revoke
run_test STS-106 "root signed Query AssumeRole returns usable credentials (#9279)" case_sts_106_root_query
run_test STS-107 "startup rejects a 4-byte root secret (#9279)" case_sts_107_short_root_secret
run_test STS-108 "startup rejects a 7-byte root secret (#9279)" case_sts_108_seven_byte_root_secret
run_test STS-109 "startup rejects a 2-byte root access key (#9279)" case_sts_109_short_root_access_key
run_test STS-110 "3-byte access key and 8-byte secret support AssumeRole (#9279)" case_sts_110_minimum_root_credentials
run_test STS-111 "root with an 8-byte secret supports AssumeRole (#9279)" case_sts_111_eight_byte_root_secret
run_test STS-112 "root secret minimum counts UTF-8 bytes (#9279)" case_sts_112_utf8_root_secret
run_test OIDC-102 "OIDC validate rejects bad provider config" case_oidc_102_validate_rejects_bad_config
run_test OIDC-108 "STS rejects expired/garbage web identity token" case_oidc_108_reject_garbage_jwt
if [ "${OIDC_LIVE}" -eq 1 ] && [ "${OIDC_RUN}" -eq 0 ]; then
Expand All @@ -1109,6 +1158,7 @@ run_security_cases() {

main() {
parse_args "$@"
[ -f "${SCRIPT_DIR}/scripts/sts_root_regression.py" ] || die "missing scripts/sts_root_regression.py"
need_cmd ssh "openssh client"
need_cmd curl "curl"
need_cmd openssl "openssl"
Expand Down
Loading
Loading