mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-30 16:59:52 +00:00
fix(security): harden proxy auth and default credentials (#2981)
* fix(security): harden proxy auth and default credentials * fix(security): address proxy and credential feedback
This commit is contained in:
@@ -23,8 +23,8 @@ services:
|
|||||||
- RUSTFS_VOLUMES=http://node{1...4}:9000/data/rustfs{0...3}
|
- RUSTFS_VOLUMES=http://node{1...4}:9000/data/rustfs{0...3}
|
||||||
- RUSTFS_ADDRESS=:9000
|
- RUSTFS_ADDRESS=:9000
|
||||||
- RUSTFS_CONSOLE_ENABLE=true
|
- RUSTFS_CONSOLE_ENABLE=true
|
||||||
- RUSTFS_ACCESS_KEY=${RUSTFS_ACCESS_KEY:-rustfsadmin}
|
- RUSTFS_ACCESS_KEY=${RUSTFS_ACCESS_KEY:-rustfs-cluster-admin}
|
||||||
- RUSTFS_SECRET_KEY=${RUSTFS_SECRET_KEY:-rustfsadmin}
|
- RUSTFS_SECRET_KEY=${RUSTFS_SECRET_KEY:-rustfs-cluster-secret}
|
||||||
- RUSTFS_OBS_ENDPOINT=${RUSTFS_OBS_ENDPOINT:-http://host.docker.internal:4318}
|
- RUSTFS_OBS_ENDPOINT=${RUSTFS_OBS_ENDPOINT:-http://host.docker.internal:4318}
|
||||||
- RUSTFS_OBS_LOGGER_LEVEL=${RUSTFS_OBS_LOGGER_LEVEL:-info}
|
- RUSTFS_OBS_LOGGER_LEVEL=${RUSTFS_OBS_LOGGER_LEVEL:-info}
|
||||||
- RUSTFS_UNSAFE_BYPASS_DISK_CHECK=${RUSTFS_UNSAFE_BYPASS_DISK_CHECK:-true}
|
- RUSTFS_UNSAFE_BYPASS_DISK_CHECK=${RUSTFS_UNSAFE_BYPASS_DISK_CHECK:-true}
|
||||||
@@ -50,8 +50,8 @@ services:
|
|||||||
- RUSTFS_VOLUMES=http://node{1...4}:9000/data/rustfs{0...3}
|
- RUSTFS_VOLUMES=http://node{1...4}:9000/data/rustfs{0...3}
|
||||||
- RUSTFS_ADDRESS=:9000
|
- RUSTFS_ADDRESS=:9000
|
||||||
- RUSTFS_CONSOLE_ENABLE=true
|
- RUSTFS_CONSOLE_ENABLE=true
|
||||||
- RUSTFS_ACCESS_KEY=${RUSTFS_ACCESS_KEY:-rustfsadmin}
|
- RUSTFS_ACCESS_KEY=${RUSTFS_ACCESS_KEY:-rustfs-cluster-admin}
|
||||||
- RUSTFS_SECRET_KEY=${RUSTFS_SECRET_KEY:-rustfsadmin}
|
- RUSTFS_SECRET_KEY=${RUSTFS_SECRET_KEY:-rustfs-cluster-secret}
|
||||||
- RUSTFS_OBS_ENDPOINT=${RUSTFS_OBS_ENDPOINT:-http://host.docker.internal:4318}
|
- RUSTFS_OBS_ENDPOINT=${RUSTFS_OBS_ENDPOINT:-http://host.docker.internal:4318}
|
||||||
- RUSTFS_OBS_LOGGER_LEVEL=${RUSTFS_OBS_LOGGER_LEVEL:-info}
|
- RUSTFS_OBS_LOGGER_LEVEL=${RUSTFS_OBS_LOGGER_LEVEL:-info}
|
||||||
- RUSTFS_UNSAFE_BYPASS_DISK_CHECK=${RUSTFS_UNSAFE_BYPASS_DISK_CHECK:-true}
|
- RUSTFS_UNSAFE_BYPASS_DISK_CHECK=${RUSTFS_UNSAFE_BYPASS_DISK_CHECK:-true}
|
||||||
@@ -77,8 +77,8 @@ services:
|
|||||||
- RUSTFS_VOLUMES=http://node{1...4}:9000/data/rustfs{0...3}
|
- RUSTFS_VOLUMES=http://node{1...4}:9000/data/rustfs{0...3}
|
||||||
- RUSTFS_ADDRESS=:9000
|
- RUSTFS_ADDRESS=:9000
|
||||||
- RUSTFS_CONSOLE_ENABLE=true
|
- RUSTFS_CONSOLE_ENABLE=true
|
||||||
- RUSTFS_ACCESS_KEY=${RUSTFS_ACCESS_KEY:-rustfsadmin}
|
- RUSTFS_ACCESS_KEY=${RUSTFS_ACCESS_KEY:-rustfs-cluster-admin}
|
||||||
- RUSTFS_SECRET_KEY=${RUSTFS_SECRET_KEY:-rustfsadmin}
|
- RUSTFS_SECRET_KEY=${RUSTFS_SECRET_KEY:-rustfs-cluster-secret}
|
||||||
- RUSTFS_OBS_ENDPOINT=${RUSTFS_OBS_ENDPOINT:-http://host.docker.internal:4318}
|
- RUSTFS_OBS_ENDPOINT=${RUSTFS_OBS_ENDPOINT:-http://host.docker.internal:4318}
|
||||||
- RUSTFS_OBS_LOGGER_LEVEL=${RUSTFS_OBS_LOGGER_LEVEL:-info}
|
- RUSTFS_OBS_LOGGER_LEVEL=${RUSTFS_OBS_LOGGER_LEVEL:-info}
|
||||||
- RUSTFS_UNSAFE_BYPASS_DISK_CHECK=${RUSTFS_UNSAFE_BYPASS_DISK_CHECK:-true}
|
- RUSTFS_UNSAFE_BYPASS_DISK_CHECK=${RUSTFS_UNSAFE_BYPASS_DISK_CHECK:-true}
|
||||||
@@ -104,8 +104,8 @@ services:
|
|||||||
- RUSTFS_VOLUMES=http://node{1...4}:9000/data/rustfs{0...3}
|
- RUSTFS_VOLUMES=http://node{1...4}:9000/data/rustfs{0...3}
|
||||||
- RUSTFS_ADDRESS=:9000
|
- RUSTFS_ADDRESS=:9000
|
||||||
- RUSTFS_CONSOLE_ENABLE=true
|
- RUSTFS_CONSOLE_ENABLE=true
|
||||||
- RUSTFS_ACCESS_KEY=${RUSTFS_ACCESS_KEY:-rustfsadmin}
|
- RUSTFS_ACCESS_KEY=${RUSTFS_ACCESS_KEY:-rustfs-cluster-admin}
|
||||||
- RUSTFS_SECRET_KEY=${RUSTFS_SECRET_KEY:-rustfsadmin}
|
- RUSTFS_SECRET_KEY=${RUSTFS_SECRET_KEY:-rustfs-cluster-secret}
|
||||||
- RUSTFS_OBS_ENDPOINT=${RUSTFS_OBS_ENDPOINT:-http://host.docker.internal:4318}
|
- RUSTFS_OBS_ENDPOINT=${RUSTFS_OBS_ENDPOINT:-http://host.docker.internal:4318}
|
||||||
- RUSTFS_OBS_LOGGER_LEVEL=${RUSTFS_OBS_LOGGER_LEVEL:-info}
|
- RUSTFS_OBS_LOGGER_LEVEL=${RUSTFS_OBS_LOGGER_LEVEL:-info}
|
||||||
- RUSTFS_UNSAFE_BYPASS_DISK_CHECK=${RUSTFS_UNSAFE_BYPASS_DISK_CHECK:-true}
|
- RUSTFS_UNSAFE_BYPASS_DISK_CHECK=${RUSTFS_UNSAFE_BYPASS_DISK_CHECK:-true}
|
||||||
|
|||||||
@@ -21,8 +21,8 @@ services:
|
|||||||
- RUSTFS_VOLUMES=http://node{0...3}:9000/data/rustfs{0...3}
|
- RUSTFS_VOLUMES=http://node{0...3}:9000/data/rustfs{0...3}
|
||||||
- RUSTFS_ADDRESS=0.0.0.0:9000
|
- RUSTFS_ADDRESS=0.0.0.0:9000
|
||||||
- RUSTFS_CONSOLE_ENABLE=true
|
- RUSTFS_CONSOLE_ENABLE=true
|
||||||
- RUSTFS_ACCESS_KEY=rustfsadmin
|
- RUSTFS_ACCESS_KEY=rustfs-cluster-admin
|
||||||
- RUSTFS_SECRET_KEY=rustfsadmin
|
- RUSTFS_SECRET_KEY=rustfs-cluster-secret
|
||||||
platform: linux/amd64
|
platform: linux/amd64
|
||||||
ports:
|
ports:
|
||||||
- "9000:9000" # Map port 9001 of the host to port 9000 of the container
|
- "9000:9000" # Map port 9001 of the host to port 9000 of the container
|
||||||
@@ -38,8 +38,8 @@ services:
|
|||||||
- RUSTFS_VOLUMES=http://node{0...3}:9000/data/rustfs{0...3}
|
- RUSTFS_VOLUMES=http://node{0...3}:9000/data/rustfs{0...3}
|
||||||
- RUSTFS_ADDRESS=0.0.0.0:9000
|
- RUSTFS_ADDRESS=0.0.0.0:9000
|
||||||
- RUSTFS_CONSOLE_ENABLE=true
|
- RUSTFS_CONSOLE_ENABLE=true
|
||||||
- RUSTFS_ACCESS_KEY=rustfsadmin
|
- RUSTFS_ACCESS_KEY=rustfs-cluster-admin
|
||||||
- RUSTFS_SECRET_KEY=rustfsadmin
|
- RUSTFS_SECRET_KEY=rustfs-cluster-secret
|
||||||
platform: linux/amd64
|
platform: linux/amd64
|
||||||
ports:
|
ports:
|
||||||
- "9001:9000" # Map port 9002 of the host to port 9000 of the container
|
- "9001:9000" # Map port 9002 of the host to port 9000 of the container
|
||||||
@@ -55,8 +55,8 @@ services:
|
|||||||
- RUSTFS_VOLUMES=http://node{0...3}:9000/data/rustfs{0...3}
|
- RUSTFS_VOLUMES=http://node{0...3}:9000/data/rustfs{0...3}
|
||||||
- RUSTFS_ADDRESS=0.0.0.0:9000
|
- RUSTFS_ADDRESS=0.0.0.0:9000
|
||||||
- RUSTFS_CONSOLE_ENABLE=true
|
- RUSTFS_CONSOLE_ENABLE=true
|
||||||
- RUSTFS_ACCESS_KEY=rustfsadmin
|
- RUSTFS_ACCESS_KEY=rustfs-cluster-admin
|
||||||
- RUSTFS_SECRET_KEY=rustfsadmin
|
- RUSTFS_SECRET_KEY=rustfs-cluster-secret
|
||||||
platform: linux/amd64
|
platform: linux/amd64
|
||||||
ports:
|
ports:
|
||||||
- "9002:9000" # Map port 9003 of the host to port 9000 of the container
|
- "9002:9000" # Map port 9003 of the host to port 9000 of the container
|
||||||
@@ -72,8 +72,8 @@ services:
|
|||||||
- RUSTFS_VOLUMES=http://node{0...3}:9000/data/rustfs{0...3}
|
- RUSTFS_VOLUMES=http://node{0...3}:9000/data/rustfs{0...3}
|
||||||
- RUSTFS_ADDRESS=0.0.0.0:9000
|
- RUSTFS_ADDRESS=0.0.0.0:9000
|
||||||
- RUSTFS_CONSOLE_ENABLE=true
|
- RUSTFS_CONSOLE_ENABLE=true
|
||||||
- RUSTFS_ACCESS_KEY=rustfsadmin
|
- RUSTFS_ACCESS_KEY=rustfs-cluster-admin
|
||||||
- RUSTFS_SECRET_KEY=rustfsadmin
|
- RUSTFS_SECRET_KEY=rustfs-cluster-secret
|
||||||
platform: linux/amd64
|
platform: linux/amd64
|
||||||
ports:
|
ports:
|
||||||
- "9003:9000" # Map port 9004 of the host to port 9000 of the container
|
- "9003:9000" # Map port 9004 of the host to port 9000 of the container
|
||||||
|
|||||||
@@ -40,8 +40,8 @@ on:
|
|||||||
|
|
||||||
env:
|
env:
|
||||||
# main user
|
# main user
|
||||||
S3_ACCESS_KEY: rustfsadmin
|
S3_ACCESS_KEY: rustfs-ci-admin
|
||||||
S3_SECRET_KEY: rustfsadmin
|
S3_SECRET_KEY: rustfs-ci-secret
|
||||||
# alt user (must be different from main for many s3-tests)
|
# alt user (must be different from main for many s3-tests)
|
||||||
S3_ALT_ACCESS_KEY: rustfsalt
|
S3_ALT_ACCESS_KEY: rustfsalt
|
||||||
S3_ALT_SECRET_KEY: rustfsalt
|
S3_ALT_SECRET_KEY: rustfsalt
|
||||||
|
|||||||
@@ -105,8 +105,6 @@ RUN groupadd -g 10001 rustfs && \
|
|||||||
|
|
||||||
ENV RUSTFS_ADDRESS=":9000" \
|
ENV RUSTFS_ADDRESS=":9000" \
|
||||||
RUSTFS_CONSOLE_ADDRESS=":9001" \
|
RUSTFS_CONSOLE_ADDRESS=":9001" \
|
||||||
RUSTFS_ACCESS_KEY="rustfsadmin" \
|
|
||||||
RUSTFS_SECRET_KEY="rustfsadmin" \
|
|
||||||
RUSTFS_CONSOLE_ENABLE="true" \
|
RUSTFS_CONSOLE_ENABLE="true" \
|
||||||
RUSTFS_CONSOLE_CORS_ALLOWED_ORIGINS="*" \
|
RUSTFS_CONSOLE_CORS_ALLOWED_ORIGINS="*" \
|
||||||
RUSTFS_VOLUMES="/data" \
|
RUSTFS_VOLUMES="/data" \
|
||||||
|
|||||||
@@ -155,6 +155,12 @@ pub const ENV_RUSTFS_SECRET_KEY: &str = "RUSTFS_SECRET_KEY";
|
|||||||
/// Environment variable for server secret key file.
|
/// Environment variable for server secret key file.
|
||||||
pub const ENV_RUSTFS_SECRET_KEY_FILE: &str = "RUSTFS_SECRET_KEY_FILE";
|
pub const ENV_RUSTFS_SECRET_KEY_FILE: &str = "RUSTFS_SECRET_KEY_FILE";
|
||||||
|
|
||||||
|
/// Environment variable to explicitly allow public default root credentials.
|
||||||
|
///
|
||||||
|
/// This is intended for local development only. Production startup paths should
|
||||||
|
/// provide non-default `RUSTFS_ACCESS_KEY` and `RUSTFS_SECRET_KEY` values.
|
||||||
|
pub const ENV_RUSTFS_ALLOW_INSECURE_DEFAULT_CREDENTIALS: &str = "RUSTFS_ALLOW_INSECURE_DEFAULT_CREDENTIALS";
|
||||||
|
|
||||||
/// Environment variable for server OBS endpoint.
|
/// Environment variable for server OBS endpoint.
|
||||||
pub const ENV_RUSTFS_OBS_ENDPOINT: &str = "RUSTFS_OBS_ENDPOINT";
|
pub const ENV_RUSTFS_OBS_ENDPOINT: &str = "RUSTFS_OBS_ENDPOINT";
|
||||||
|
|
||||||
|
|||||||
@@ -37,6 +37,9 @@ pub const SIMPLE_INTERNAL_ONLY_DEFAULT: bool = true;
|
|||||||
|
|
||||||
const HEADER_FORWARDED: &str = "forwarded";
|
const HEADER_FORWARDED: &str = "forwarded";
|
||||||
const HEADER_X_FORWARDED_FOR: &str = "x-forwarded-for";
|
const HEADER_X_FORWARDED_FOR: &str = "x-forwarded-for";
|
||||||
|
const HEADER_X_FORWARDED_HOST: &str = "x-forwarded-host";
|
||||||
|
const HEADER_X_FORWARDED_PROTO: &str = "x-forwarded-proto";
|
||||||
|
const HEADER_X_FORWARDED_SCHEME: &str = "x-forwarded-scheme";
|
||||||
const HEADER_X_REAL_IP: &str = "x-real-ip";
|
const HEADER_X_REAL_IP: &str = "x-real-ip";
|
||||||
|
|
||||||
static ENABLED: OnceLock<bool> = OnceLock::new();
|
static ENABLED: OnceLock<bool> = OnceLock::new();
|
||||||
@@ -257,9 +260,15 @@ fn resolve_client_info(peer_addr: Option<SocketAddr>, headers: &HeaderMap) -> Cl
|
|||||||
}
|
}
|
||||||
|
|
||||||
match forwarded_client_ip(headers) {
|
match forwarded_client_ip(headers) {
|
||||||
Some(real_ip) if is_usable_ip(real_ip) && real_ip != peer_addr.ip() => {
|
Some(real_ip) if is_usable_ip(real_ip) && real_ip != peer_addr.ip() => ClientInfo::from_trusted_proxy(
|
||||||
ClientInfo::from_trusted_proxy(real_ip, None, None, peer_addr.ip(), 1, ValidationMode::Lenient, Vec::new())
|
real_ip,
|
||||||
}
|
forwarded_host(headers),
|
||||||
|
forwarded_proto(headers),
|
||||||
|
peer_addr.ip(),
|
||||||
|
1,
|
||||||
|
ValidationMode::Lenient,
|
||||||
|
Vec::new(),
|
||||||
|
),
|
||||||
_ => ClientInfo::direct(peer_addr),
|
_ => ClientInfo::direct(peer_addr),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -270,6 +279,72 @@ fn forwarded_client_ip(headers: &HeaderMap) -> Option<IpAddr> {
|
|||||||
.or_else(|| parse_forwarded_header(headers))
|
.or_else(|| parse_forwarded_header(headers))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn forwarded_host(headers: &HeaderMap) -> Option<String> {
|
||||||
|
parse_single_value_header(headers, HEADER_X_FORWARDED_HOST)
|
||||||
|
.and_then(sanitize_forwarded_host)
|
||||||
|
.or_else(|| parse_forwarded_header_value(headers, "host").and_then(sanitize_forwarded_host))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn forwarded_proto(headers: &HeaderMap) -> Option<String> {
|
||||||
|
parse_single_value_header(headers, HEADER_X_FORWARDED_PROTO)
|
||||||
|
.and_then(sanitize_forwarded_proto)
|
||||||
|
.or_else(|| parse_single_value_header(headers, HEADER_X_FORWARDED_SCHEME).and_then(sanitize_forwarded_proto))
|
||||||
|
.or_else(|| parse_forwarded_header_value(headers, "proto").and_then(sanitize_forwarded_proto))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_single_value_header(headers: &HeaderMap, name: &str) -> Option<String> {
|
||||||
|
headers
|
||||||
|
.get(name)?
|
||||||
|
.to_str()
|
||||||
|
.ok()?
|
||||||
|
.split(',')
|
||||||
|
.next()
|
||||||
|
.map(str::trim)
|
||||||
|
.filter(|value| !value.is_empty())
|
||||||
|
.map(ToOwned::to_owned)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_forwarded_header_value(headers: &HeaderMap, name: &str) -> Option<String> {
|
||||||
|
let value = headers.get(HEADER_FORWARDED)?.to_str().ok()?;
|
||||||
|
let first = value.split(',').next()?.trim();
|
||||||
|
|
||||||
|
for part in first.split(';') {
|
||||||
|
let Some((key, value)) = part.trim().split_once('=') else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
if key.trim().eq_ignore_ascii_case(name) {
|
||||||
|
let value = value.trim().trim_matches('"');
|
||||||
|
if !value.is_empty() {
|
||||||
|
return Some(value.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sanitize_forwarded_host(value: String) -> Option<String> {
|
||||||
|
sanitize_forwarded_value(&value).filter(|value| !value.chars().any(char::is_whitespace))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sanitize_forwarded_proto(value: String) -> Option<String> {
|
||||||
|
let value = sanitize_forwarded_value(&value)?;
|
||||||
|
if value.eq_ignore_ascii_case("http") || value.eq_ignore_ascii_case("https") {
|
||||||
|
return Some(value.to_ascii_lowercase());
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sanitize_forwarded_value(value: &str) -> Option<String> {
|
||||||
|
let value = value.trim().trim_matches('"');
|
||||||
|
if value.is_empty() || value.len() > 256 || value.chars().any(char::is_control) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(value.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
fn parse_x_forwarded_for(headers: &HeaderMap) -> Option<IpAddr> {
|
fn parse_x_forwarded_for(headers: &HeaderMap) -> Option<IpAddr> {
|
||||||
let value = headers.get(HEADER_X_FORWARDED_FOR)?.to_str().ok()?;
|
let value = headers.get(HEADER_X_FORWARDED_FOR)?.to_str().ok()?;
|
||||||
let first = value.split(',').next()?.trim();
|
let first = value.split(',').next()?.trim();
|
||||||
@@ -345,8 +420,10 @@ fn parse_implementation(value: Option<&str>) -> TrustedProxyImplementation {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::{
|
use super::{
|
||||||
ENV_TRUSTED_PROXY_IMPLEMENTATION, HEADER_FORWARDED, HEADER_X_FORWARDED_FOR, HEADER_X_REAL_IP, TrustedProxyImplementation,
|
ENV_TRUSTED_PROXY_IMPLEMENTATION, HEADER_FORWARDED, HEADER_X_FORWARDED_FOR, HEADER_X_FORWARDED_HOST,
|
||||||
TrustedProxyLayer, forwarded_client_ip, is_internal_ip, parse_implementation, parse_ip_token, resolve_client_info,
|
HEADER_X_FORWARDED_PROTO, HEADER_X_FORWARDED_SCHEME, HEADER_X_REAL_IP, TrustedProxyImplementation, TrustedProxyLayer,
|
||||||
|
forwarded_client_ip, forwarded_host, forwarded_proto, is_internal_ip, parse_implementation, parse_ip_token,
|
||||||
|
resolve_client_info,
|
||||||
};
|
};
|
||||||
use crate::ClientInfo;
|
use crate::ClientInfo;
|
||||||
use axum::http::{HeaderMap, HeaderValue};
|
use axum::http::{HeaderMap, HeaderValue};
|
||||||
@@ -403,6 +480,55 @@ mod tests {
|
|||||||
assert_eq!(client_info.proxy_ip, Some(IpAddr::from([10, 0, 0, 5])));
|
assert_eq!(client_info.proxy_ip, Some(IpAddr::from([10, 0, 0, 5])));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_internal_peer_preserves_forwarded_proto_and_host() {
|
||||||
|
let mut headers = HeaderMap::new();
|
||||||
|
headers.insert(HEADER_X_FORWARDED_FOR, HeaderValue::from_static("203.0.113.10"));
|
||||||
|
headers.insert(HEADER_X_FORWARDED_HOST, HeaderValue::from_static("s3.example.test"));
|
||||||
|
headers.insert(HEADER_X_FORWARDED_PROTO, HeaderValue::from_static("https"));
|
||||||
|
|
||||||
|
let client_info = resolve_client_info(Some(SocketAddr::from(([10, 0, 0, 5], 9000))), &headers);
|
||||||
|
|
||||||
|
assert_eq!(client_info.forwarded_host.as_deref(), Some("s3.example.test"));
|
||||||
|
assert_eq!(client_info.forwarded_proto.as_deref(), Some("https"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_internal_peer_preserves_forwarded_scheme_fallback() {
|
||||||
|
let mut headers = HeaderMap::new();
|
||||||
|
headers.insert(HEADER_X_FORWARDED_FOR, HeaderValue::from_static("203.0.113.10"));
|
||||||
|
headers.insert(HEADER_X_FORWARDED_SCHEME, HeaderValue::from_static("https"));
|
||||||
|
|
||||||
|
let client_info = resolve_client_info(Some(SocketAddr::from(([10, 0, 0, 5], 9000))), &headers);
|
||||||
|
|
||||||
|
assert_eq!(client_info.forwarded_proto.as_deref(), Some("https"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_forwarded_header_preserves_proto_and_host() {
|
||||||
|
let mut headers = HeaderMap::new();
|
||||||
|
headers.insert(
|
||||||
|
HEADER_FORWARDED,
|
||||||
|
HeaderValue::from_static("for=203.0.113.10;proto=https;host=s3.example.test"),
|
||||||
|
);
|
||||||
|
|
||||||
|
let client_info = resolve_client_info(Some(SocketAddr::from(([10, 0, 0, 5], 9000))), &headers);
|
||||||
|
|
||||||
|
assert_eq!(client_info.real_ip, IpAddr::from([203, 0, 113, 10]));
|
||||||
|
assert_eq!(client_info.forwarded_host.as_deref(), Some("s3.example.test"));
|
||||||
|
assert_eq!(client_info.forwarded_proto.as_deref(), Some("https"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_invalid_forwarded_host_and_proto_are_ignored() {
|
||||||
|
let mut headers = HeaderMap::new();
|
||||||
|
headers.insert(HEADER_X_FORWARDED_HOST, HeaderValue::from_static("bad host"));
|
||||||
|
headers.insert(HEADER_X_FORWARDED_PROTO, HeaderValue::from_static("ftp"));
|
||||||
|
|
||||||
|
assert_eq!(forwarded_host(&headers), None);
|
||||||
|
assert_eq!(forwarded_proto(&headers), None);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_public_peer_keeps_direct_ip() {
|
fn test_public_peer_keeps_direct_ip() {
|
||||||
let mut headers = HeaderMap::new();
|
let mut headers = HeaderMap::new();
|
||||||
|
|||||||
+2
-4
@@ -33,10 +33,8 @@ services:
|
|||||||
- RUSTFS_CONSOLE_ADDRESS=0.0.0.0:9001
|
- RUSTFS_CONSOLE_ADDRESS=0.0.0.0:9001
|
||||||
- RUSTFS_CONSOLE_ENABLE=true
|
- RUSTFS_CONSOLE_ENABLE=true
|
||||||
- RUSTFS_CONSOLE_CORS_ALLOWED_ORIGINS=*
|
- RUSTFS_CONSOLE_CORS_ALLOWED_ORIGINS=*
|
||||||
# SECURITY: these defaults are public and well-known. Override before
|
- RUSTFS_ACCESS_KEY=${RUSTFS_ACCESS_KEY:?Set RUSTFS_ACCESS_KEY to a non-default value}
|
||||||
# exposing the listener beyond localhost.
|
- RUSTFS_SECRET_KEY=${RUSTFS_SECRET_KEY:?Set RUSTFS_SECRET_KEY to a non-default value}
|
||||||
- RUSTFS_ACCESS_KEY=rustfsadmin
|
|
||||||
- RUSTFS_SECRET_KEY=rustfsadmin
|
|
||||||
- RUSTFS_OBS_LOGGER_LEVEL=info
|
- RUSTFS_OBS_LOGGER_LEVEL=info
|
||||||
- RUSTFS_TLS_PATH=/opt/tls
|
- RUSTFS_TLS_PATH=/opt/tls
|
||||||
- RUSTFS_OBS_ENDPOINT=http://otel-collector:4318
|
- RUSTFS_OBS_ENDPOINT=http://otel-collector:4318
|
||||||
|
|||||||
+2
-2
@@ -113,8 +113,8 @@ process_data_volumes
|
|||||||
process_log_directory
|
process_log_directory
|
||||||
|
|
||||||
# 4) Default credentials warning
|
# 4) Default credentials warning
|
||||||
if [ "${RUSTFS_ACCESS_KEY}" = "rustfsadmin" ] || [ "${RUSTFS_SECRET_KEY}" = "rustfsadmin" ]; then
|
if [ "${RUSTFS_ACCESS_KEY:-}" = "rustfsadmin" ] || [ "${RUSTFS_SECRET_KEY:-}" = "rustfsadmin" ]; then
|
||||||
echo "!!!WARNING: Using default RUSTFS_ACCESS_KEY or RUSTFS_SECRET_KEY. Override them in production!"
|
echo "!!!WARNING: Default credentials are only allowed on loopback or with explicit insecure local-dev opt-in."
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# 5) Append DATA_VOLUMES only if no data paths in arguments
|
# 5) Append DATA_VOLUMES only if no data paths in arguments
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ use crate::admin::handlers::site_replication::{
|
|||||||
site_replication_bucket_meta_hook, site_replication_delete_bucket_hook, site_replication_make_bucket_hook,
|
site_replication_bucket_meta_hook, site_replication_delete_bucket_hook, site_replication_make_bucket_hook,
|
||||||
};
|
};
|
||||||
use crate::app::context::{AppContext, default_notify_interface, get_global_app_context};
|
use crate::app::context::{AppContext, default_notify_interface, get_global_app_context};
|
||||||
use crate::auth::get_condition_values;
|
use crate::auth::get_condition_values_with_client_info;
|
||||||
use crate::error::ApiError;
|
use crate::error::ApiError;
|
||||||
use crate::server::RemoteAddr;
|
use crate::server::RemoteAddr;
|
||||||
use crate::storage::access::{ReqInfo, authorize_request, req_info_ref};
|
use crate::storage::access::{ReqInfo, authorize_request, req_info_ref};
|
||||||
@@ -69,6 +69,7 @@ use rustfs_targets::{
|
|||||||
EventName,
|
EventName,
|
||||||
arn::{ARN, TargetIDError},
|
arn::{ARN, TargetIDError},
|
||||||
};
|
};
|
||||||
|
use rustfs_trusted_proxies::ClientInfo;
|
||||||
use rustfs_utils::http::{SUFFIX_FORCE_DELETE, get_header};
|
use rustfs_utils::http::{SUFFIX_FORCE_DELETE, get_header};
|
||||||
use rustfs_utils::obj::extract_user_defined_metadata;
|
use rustfs_utils::obj::extract_user_defined_metadata;
|
||||||
use rustfs_utils::string::parse_bool;
|
use rustfs_utils::string::parse_bool;
|
||||||
@@ -1308,7 +1309,15 @@ impl DefaultBucketUsecase {
|
|||||||
.map_err(ApiError::from)?;
|
.map_err(ApiError::from)?;
|
||||||
|
|
||||||
let remote_addr = req.extensions.get::<Option<RemoteAddr>>().and_then(|opt| opt.map(|a| a.0));
|
let remote_addr = req.extensions.get::<Option<RemoteAddr>>().and_then(|opt| opt.map(|a| a.0));
|
||||||
let conditions = get_condition_values(&req.headers, &rustfs_credentials::Credentials::default(), None, None, remote_addr);
|
let client_info = req.extensions.get::<ClientInfo>();
|
||||||
|
let conditions = get_condition_values_with_client_info(
|
||||||
|
&req.headers,
|
||||||
|
&rustfs_credentials::Credentials::default(),
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
remote_addr,
|
||||||
|
client_info,
|
||||||
|
);
|
||||||
|
|
||||||
let read_allowed = PolicySys::is_allowed(&BucketPolicyArgs {
|
let read_allowed = PolicySys::is_allowed(&BucketPolicyArgs {
|
||||||
bucket: &bucket,
|
bucket: &bucket,
|
||||||
|
|||||||
+95
-32
@@ -20,9 +20,8 @@ use rustfs_iam::sys::{
|
|||||||
SESSION_POLICY_NAME, get_claims_from_token_with_secret, get_claims_from_token_with_secret_allow_missing_exp,
|
SESSION_POLICY_NAME, get_claims_from_token_with_secret, get_claims_from_token_with_secret_allow_missing_exp,
|
||||||
};
|
};
|
||||||
use rustfs_policy::policy::{ClaimLookup, get_claim_case_insensitive};
|
use rustfs_policy::policy::{ClaimLookup, get_claim_case_insensitive};
|
||||||
use rustfs_utils::http::{
|
use rustfs_trusted_proxies::ClientInfo;
|
||||||
AMZ_OBJECT_LOCK_LEGAL_HOLD_LOWER, AMZ_OBJECT_LOCK_MODE_LOWER, AMZ_OBJECT_LOCK_RETAIN_UNTIL_DATE_LOWER, ip::get_source_ip_raw,
|
use rustfs_utils::http::{AMZ_OBJECT_LOCK_LEGAL_HOLD_LOWER, AMZ_OBJECT_LOCK_MODE_LOWER, AMZ_OBJECT_LOCK_RETAIN_UNTIL_DATE_LOWER};
|
||||||
};
|
|
||||||
use s3s::S3Error;
|
use s3s::S3Error;
|
||||||
use s3s::S3ErrorCode;
|
use s3s::S3ErrorCode;
|
||||||
use s3s::S3Result;
|
use s3s::S3Result;
|
||||||
@@ -32,6 +31,7 @@ use s3s::auth::SimpleAuth;
|
|||||||
use s3s::s3_error;
|
use s3s::s3_error;
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
use std::net::SocketAddr;
|
||||||
use subtle::ConstantTimeEq;
|
use subtle::ConstantTimeEq;
|
||||||
use time::OffsetDateTime;
|
use time::OffsetDateTime;
|
||||||
use time::format_description::well_known::Rfc3339;
|
use time::format_description::well_known::Rfc3339;
|
||||||
@@ -436,6 +436,20 @@ pub(crate) fn extract_string_list_claim(claims: &HashMap<String, Value>, claim_n
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn policy_source_ip(remote_addr: Option<SocketAddr>, client_info: Option<&ClientInfo>) -> String {
|
||||||
|
client_info
|
||||||
|
.map(|info| info.real_ip.to_string())
|
||||||
|
.or_else(|| remote_addr.map(|addr| addr.ip().to_string()))
|
||||||
|
.unwrap_or_default()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn policy_secure_transport(client_info: Option<&ClientInfo>) -> bool {
|
||||||
|
client_info
|
||||||
|
.and_then(|info| info.forwarded_proto.as_deref())
|
||||||
|
.map(|proto| proto.eq_ignore_ascii_case("https"))
|
||||||
|
.unwrap_or(false)
|
||||||
|
}
|
||||||
|
|
||||||
/// Get condition values for policy evaluation
|
/// Get condition values for policy evaluation
|
||||||
///
|
///
|
||||||
/// # Arguments
|
/// # Arguments
|
||||||
@@ -453,9 +467,21 @@ pub fn get_condition_values(
|
|||||||
cred: &Credentials,
|
cred: &Credentials,
|
||||||
version_id: Option<&str>,
|
version_id: Option<&str>,
|
||||||
region: Option<s3s::region::Region>,
|
region: Option<s3s::region::Region>,
|
||||||
remote_addr: Option<std::net::SocketAddr>,
|
remote_addr: Option<SocketAddr>,
|
||||||
) -> HashMap<String, Vec<String>> {
|
) -> HashMap<String, Vec<String>> {
|
||||||
get_condition_values_with_query(header, cred, version_id, region, remote_addr, None)
|
get_condition_values_with_client_info(header, cred, version_id, region, remote_addr, None)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get condition values for policy evaluation with verified client information.
|
||||||
|
pub fn get_condition_values_with_client_info(
|
||||||
|
header: &HeaderMap,
|
||||||
|
cred: &Credentials,
|
||||||
|
version_id: Option<&str>,
|
||||||
|
region: Option<s3s::region::Region>,
|
||||||
|
remote_addr: Option<SocketAddr>,
|
||||||
|
client_info: Option<&ClientInfo>,
|
||||||
|
) -> HashMap<String, Vec<String>> {
|
||||||
|
get_condition_values_with_query_and_client_info(header, cred, version_id, region, remote_addr, None, client_info)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get condition values for policy evaluation with optional query-string values.
|
/// Get condition values for policy evaluation with optional query-string values.
|
||||||
@@ -475,8 +501,22 @@ pub fn get_condition_values_with_query(
|
|||||||
cred: &Credentials,
|
cred: &Credentials,
|
||||||
version_id: Option<&str>,
|
version_id: Option<&str>,
|
||||||
region: Option<s3s::region::Region>,
|
region: Option<s3s::region::Region>,
|
||||||
remote_addr: Option<std::net::SocketAddr>,
|
remote_addr: Option<SocketAddr>,
|
||||||
query: Option<&str>,
|
query: Option<&str>,
|
||||||
|
) -> HashMap<String, Vec<String>> {
|
||||||
|
get_condition_values_with_query_and_client_info(header, cred, version_id, region, remote_addr, query, None)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get condition values for policy evaluation with optional query-string values
|
||||||
|
/// and verified client information from trusted proxy middleware.
|
||||||
|
pub fn get_condition_values_with_query_and_client_info(
|
||||||
|
header: &HeaderMap,
|
||||||
|
cred: &Credentials,
|
||||||
|
version_id: Option<&str>,
|
||||||
|
region: Option<s3s::region::Region>,
|
||||||
|
remote_addr: Option<SocketAddr>,
|
||||||
|
query: Option<&str>,
|
||||||
|
client_info: Option<&ClientInfo>,
|
||||||
) -> HashMap<String, Vec<String>> {
|
) -> HashMap<String, Vec<String>> {
|
||||||
let username = if cred.is_temp() || cred.is_service_account() {
|
let username = if cred.is_temp() || cred.is_service_account() {
|
||||||
cred.parent_user.clone()
|
cred.parent_user.clone()
|
||||||
@@ -510,21 +550,8 @@ pub fn get_condition_values_with_query(
|
|||||||
// Determine auth type and signature version from headers and query
|
// Determine auth type and signature version from headers and query
|
||||||
let (auth_type, signature_version) = determine_auth_type_and_version_with_query(header, query);
|
let (auth_type, signature_version) = determine_auth_type_and_version_with_query(header, query);
|
||||||
|
|
||||||
// Get TLS status from header
|
let is_tls = policy_secure_transport(client_info);
|
||||||
let is_tls = header
|
let source_ip = policy_source_ip(remote_addr, client_info);
|
||||||
.get("x-forwarded-proto")
|
|
||||||
.and_then(|v| v.to_str().ok())
|
|
||||||
.map(|s| s == "https")
|
|
||||||
.or_else(|| {
|
|
||||||
header
|
|
||||||
.get("x-forwarded-scheme")
|
|
||||||
.and_then(|v| v.to_str().ok())
|
|
||||||
.map(|s| s == "https")
|
|
||||||
})
|
|
||||||
.unwrap_or(false);
|
|
||||||
|
|
||||||
// Get remote address from header or use default
|
|
||||||
let remote_addr_s = remote_addr.map(|a| a.ip().to_string()).unwrap_or_default();
|
|
||||||
|
|
||||||
let mut args = HashMap::new();
|
let mut args = HashMap::new();
|
||||||
|
|
||||||
@@ -532,7 +559,7 @@ pub fn get_condition_values_with_query(
|
|||||||
args.insert("CurrentTime".to_owned(), vec![curr_time.format(&Rfc3339).unwrap_or_default()]);
|
args.insert("CurrentTime".to_owned(), vec![curr_time.format(&Rfc3339).unwrap_or_default()]);
|
||||||
args.insert("EpochTime".to_owned(), vec![epoch_time.to_string()]);
|
args.insert("EpochTime".to_owned(), vec![epoch_time.to_string()]);
|
||||||
args.insert("SecureTransport".to_owned(), vec![is_tls.to_string()]);
|
args.insert("SecureTransport".to_owned(), vec![is_tls.to_string()]);
|
||||||
args.insert("SourceIp".to_owned(), vec![get_source_ip_raw(header, &remote_addr_s)]);
|
args.insert("SourceIp".to_owned(), vec![source_ip]);
|
||||||
|
|
||||||
// Add user agent and referer
|
// Add user agent and referer
|
||||||
if let Some(user_agent) = header.get("user-agent") {
|
if let Some(user_agent) = header.get("user-agent") {
|
||||||
@@ -888,6 +915,7 @@ mod tests {
|
|||||||
use super::*;
|
use super::*;
|
||||||
use http::{HeaderMap, HeaderValue, Uri};
|
use http::{HeaderMap, HeaderValue, Uri};
|
||||||
use rustfs_credentials::Credentials;
|
use rustfs_credentials::Credentials;
|
||||||
|
use rustfs_trusted_proxies::ValidationMode;
|
||||||
use s3s::auth::SecretKey;
|
use s3s::auth::SecretKey;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
@@ -1600,32 +1628,32 @@ mod tests {
|
|||||||
let conditions = get_condition_values(&headers, &cred, None, None, Some(remote_addr));
|
let conditions = get_condition_values(&headers, &cred, None, None, Some(remote_addr));
|
||||||
assert_eq!(conditions.get("SourceIp").unwrap()[0], "192.168.0.10");
|
assert_eq!(conditions.get("SourceIp").unwrap()[0], "192.168.0.10");
|
||||||
|
|
||||||
// Case 3: X-Forwarded-For present -> XFF (takes precedence over remote_addr)
|
// Case 3: X-Forwarded-For is ignored without verified proxy context
|
||||||
headers.insert("x-forwarded-for", HeaderValue::from_static("10.0.0.1"));
|
headers.insert("x-forwarded-for", HeaderValue::from_static("10.0.0.1"));
|
||||||
let conditions = get_condition_values(&headers, &cred, None, None, Some(remote_addr));
|
let conditions = get_condition_values(&headers, &cred, None, None, Some(remote_addr));
|
||||||
assert_eq!(conditions.get("SourceIp").unwrap()[0], "10.0.0.1");
|
assert_eq!(conditions.get("SourceIp").unwrap()[0], "192.168.0.10");
|
||||||
|
|
||||||
// Case 4: X-Forwarded-For with multiple IPs -> First IP
|
// Case 4: X-Forwarded-For with multiple IPs is ignored without verified proxy context
|
||||||
headers.insert("x-forwarded-for", HeaderValue::from_static("10.0.0.3, 10.0.0.4"));
|
headers.insert("x-forwarded-for", HeaderValue::from_static("10.0.0.3, 10.0.0.4"));
|
||||||
let conditions = get_condition_values(&headers, &cred, None, None, Some(remote_addr));
|
let conditions = get_condition_values(&headers, &cred, None, None, Some(remote_addr));
|
||||||
assert_eq!(conditions.get("SourceIp").unwrap()[0], "10.0.0.3");
|
assert_eq!(conditions.get("SourceIp").unwrap()[0], "192.168.0.10");
|
||||||
|
|
||||||
// Case 5: X-Real-IP present (XFF removed) -> X-Real-IP
|
// Case 5: X-Real-IP is ignored without verified proxy context
|
||||||
headers.remove("x-forwarded-for");
|
headers.remove("x-forwarded-for");
|
||||||
headers.insert("x-real-ip", HeaderValue::from_static("10.0.0.2"));
|
headers.insert("x-real-ip", HeaderValue::from_static("10.0.0.2"));
|
||||||
let conditions = get_condition_values(&headers, &cred, None, None, Some(remote_addr));
|
let conditions = get_condition_values(&headers, &cred, None, None, Some(remote_addr));
|
||||||
assert_eq!(conditions.get("SourceIp").unwrap()[0], "10.0.0.2");
|
assert_eq!(conditions.get("SourceIp").unwrap()[0], "192.168.0.10");
|
||||||
|
|
||||||
// Case 6: Forwarded header present (X-Real-IP removed) -> Forwarded
|
// Case 6: Forwarded is ignored without verified proxy context
|
||||||
headers.remove("x-real-ip");
|
headers.remove("x-real-ip");
|
||||||
headers.insert("forwarded", HeaderValue::from_static("for=10.0.0.5;proto=http"));
|
headers.insert("forwarded", HeaderValue::from_static("for=10.0.0.5;proto=http"));
|
||||||
let conditions = get_condition_values(&headers, &cred, None, None, Some(remote_addr));
|
let conditions = get_condition_values(&headers, &cred, None, None, Some(remote_addr));
|
||||||
assert_eq!(conditions.get("SourceIp").unwrap()[0], "10.0.0.5");
|
assert_eq!(conditions.get("SourceIp").unwrap()[0], "192.168.0.10");
|
||||||
|
|
||||||
// Case 7: Forwarded header with quotes and multiple values
|
// Case 7: Forwarded with quotes and multiple values is ignored without verified proxy context
|
||||||
headers.insert("forwarded", HeaderValue::from_static("for=\"10.0.0.6\", for=10.0.0.7"));
|
headers.insert("forwarded", HeaderValue::from_static("for=\"10.0.0.6\", for=10.0.0.7"));
|
||||||
let conditions = get_condition_values(&headers, &cred, None, None, Some(remote_addr));
|
let conditions = get_condition_values(&headers, &cred, None, None, Some(remote_addr));
|
||||||
assert_eq!(conditions.get("SourceIp").unwrap()[0], "10.0.0.6");
|
assert_eq!(conditions.get("SourceIp").unwrap()[0], "192.168.0.10");
|
||||||
|
|
||||||
// Case 8: IPv6 Remote Addr
|
// Case 8: IPv6 Remote Addr
|
||||||
let remote_addr_v6: std::net::SocketAddr = "[2001:db8::1]:8080".parse().unwrap();
|
let remote_addr_v6: std::net::SocketAddr = "[2001:db8::1]:8080".parse().unwrap();
|
||||||
@@ -1634,6 +1662,41 @@ mod tests {
|
|||||||
assert_eq!(conditions.get("SourceIp").unwrap()[0], "2001:db8::1");
|
assert_eq!(conditions.get("SourceIp").unwrap()[0], "2001:db8::1");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_get_condition_values_uses_verified_client_info() {
|
||||||
|
let mut headers = HeaderMap::new();
|
||||||
|
headers.insert("x-forwarded-for", HeaderValue::from_static("10.0.0.1"));
|
||||||
|
headers.insert("x-forwarded-proto", HeaderValue::from_static("https"));
|
||||||
|
let cred = Credentials::default();
|
||||||
|
let remote_addr: std::net::SocketAddr = "192.168.0.10:12345".parse().unwrap();
|
||||||
|
let client_info = ClientInfo::from_trusted_proxy(
|
||||||
|
"10.0.0.1".parse().unwrap(),
|
||||||
|
None,
|
||||||
|
Some("https".to_string()),
|
||||||
|
"192.168.0.10".parse().unwrap(),
|
||||||
|
1,
|
||||||
|
ValidationMode::Lenient,
|
||||||
|
Vec::new(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let conditions =
|
||||||
|
get_condition_values_with_client_info(&headers, &cred, None, None, Some(remote_addr), Some(&client_info));
|
||||||
|
|
||||||
|
assert_eq!(conditions.get("SourceIp").unwrap()[0], "10.0.0.1");
|
||||||
|
assert_eq!(conditions.get("SecureTransport").unwrap()[0], "true");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_get_condition_values_ignores_unverified_secure_transport_header() {
|
||||||
|
let mut headers = HeaderMap::new();
|
||||||
|
headers.insert("x-forwarded-proto", HeaderValue::from_static("https"));
|
||||||
|
let cred = Credentials::default();
|
||||||
|
|
||||||
|
let conditions = get_condition_values(&headers, &cred, None, None, None);
|
||||||
|
|
||||||
|
assert_eq!(conditions.get("SecureTransport").unwrap()[0], "false");
|
||||||
|
}
|
||||||
|
|
||||||
// ========== KEYSTONE AUTHENTICATION TESTS ==========
|
// ========== KEYSTONE AUTHENTICATION TESTS ==========
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ use rustfs_config::{
|
|||||||
};
|
};
|
||||||
use rustfs_credentials::{DEFAULT_ACCESS_KEY, DEFAULT_SECRET_KEY, Masked};
|
use rustfs_credentials::{DEFAULT_ACCESS_KEY, DEFAULT_SECRET_KEY, Masked};
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
use std::net::SocketAddr;
|
||||||
use std::sync::{Mutex, OnceLock};
|
use std::sync::{Mutex, OnceLock};
|
||||||
|
|
||||||
pub(crate) const LEGACY_ENV_RUSTFS_ROOT_USER: &str = "RUSTFS_ROOT_USER";
|
pub(crate) const LEGACY_ENV_RUSTFS_ROOT_USER: &str = "RUSTFS_ROOT_USER";
|
||||||
@@ -174,6 +175,14 @@ impl Config {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_using_default_credentials(&self) -> bool {
|
||||||
|
DEFAULT_ACCESS_KEY.eq(&self.access_key) && DEFAULT_SECRET_KEY.eq(&self.secret_key)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn default_credentials_allowed_for_addr(&self, server_addr: SocketAddr, allow_insecure_defaults: bool) -> bool {
|
||||||
|
!self.is_using_default_credentials() || server_addr.ip().is_loopback() || allow_insecure_defaults
|
||||||
|
}
|
||||||
|
|
||||||
/// Create Config from Opt
|
/// Create Config from Opt
|
||||||
pub(super) fn from_opt(opt: Opt) -> std::io::Result<Self> {
|
pub(super) fn from_opt(opt: Opt) -> std::io::Result<Self> {
|
||||||
let Opt {
|
let Opt {
|
||||||
|
|||||||
@@ -113,6 +113,24 @@ mod tests {
|
|||||||
assert_eq!(config.buffer_profile, "GeneralPurpose");
|
assert_eq!(config.buffer_profile, "GeneralPurpose");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn default_credentials_allowed_only_for_loopback_or_explicit_opt_in() {
|
||||||
|
let config = Config::new("0.0.0.0:9000", vec!["/tmp/rustfs-vol1".to_string()]);
|
||||||
|
|
||||||
|
assert!(!config.default_credentials_allowed_for_addr("0.0.0.0:9000".parse().unwrap(), false));
|
||||||
|
assert!(config.default_credentials_allowed_for_addr("127.0.0.1:9000".parse().unwrap(), false));
|
||||||
|
assert!(config.default_credentials_allowed_for_addr("0.0.0.0:9000".parse().unwrap(), true));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn custom_credentials_allowed_on_non_loopback() {
|
||||||
|
let mut config = Config::new("0.0.0.0:9000", vec!["/tmp/rustfs-vol1".to_string()]);
|
||||||
|
config.access_key = "custom-access-key".to_string();
|
||||||
|
config.secret_key = "custom-secret-key".to_string();
|
||||||
|
|
||||||
|
assert!(config.default_credentials_allowed_for_addr("0.0.0.0:9000".parse().unwrap(), false));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[serial]
|
#[serial]
|
||||||
fn test_custom_console_configuration() {
|
fn test_custom_console_configuration() {
|
||||||
|
|||||||
+23
-14
@@ -72,7 +72,7 @@ use rustfs_ecstore::{
|
|||||||
};
|
};
|
||||||
use rustfs_iam::init_iam_sys;
|
use rustfs_iam::init_iam_sys;
|
||||||
use rustfs_obs::{init_obs, set_global_guard};
|
use rustfs_obs::{init_obs, set_global_guard};
|
||||||
use rustfs_utils::net::parse_and_resolve_address;
|
use rustfs_utils::{get_env_bool, net::parse_and_resolve_address};
|
||||||
use rustls::crypto::aws_lc_rs::default_provider;
|
use rustls::crypto::aws_lc_rs::default_provider;
|
||||||
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
|
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
@@ -303,19 +303,8 @@ impl RustFSServerBuilder {
|
|||||||
// Trusted proxies.
|
// Trusted proxies.
|
||||||
rustfs_trusted_proxies::init();
|
rustfs_trusted_proxies::init();
|
||||||
|
|
||||||
// Credentials.
|
// Resolve listen address before credential initialization so unsafe
|
||||||
init_global_action_credentials(Some(config.access_key.clone()), Some(config.secret_key.clone()))
|
// default credentials can fail before the server binds a listener.
|
||||||
.map_err(|e| ServerError::Init(format!("credentials: {e:?}")))?;
|
|
||||||
|
|
||||||
// Region.
|
|
||||||
if let Some(region_str) = &config.region {
|
|
||||||
let region = region_str
|
|
||||||
.parse()
|
|
||||||
.map_err(|e| ServerError::Init(format!("invalid region '{region_str}': {e}")))?;
|
|
||||||
rustfs_ecstore::global::set_global_region(region);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Resolve listen address.
|
|
||||||
let server_addr =
|
let server_addr =
|
||||||
parse_and_resolve_address(config.address.as_str()).map_err(|e| ServerError::Init(format!("address: {e}")))?;
|
parse_and_resolve_address(config.address.as_str()).map_err(|e| ServerError::Init(format!("address: {e}")))?;
|
||||||
|
|
||||||
@@ -328,6 +317,26 @@ impl RustFSServerBuilder {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let allow_insecure_defaults = get_env_bool(rustfs_config::ENV_RUSTFS_ALLOW_INSECURE_DEFAULT_CREDENTIALS, false);
|
||||||
|
if !config.default_credentials_allowed_for_addr(server_addr, allow_insecure_defaults) {
|
||||||
|
return Err(ServerError::Init(
|
||||||
|
"default root credentials are not allowed on non-loopback listeners; set access_key and secret_key to non-default values, bind to loopback, or set RUSTFS_ALLOW_INSECURE_DEFAULT_CREDENTIALS=true for local development only"
|
||||||
|
.to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Credentials.
|
||||||
|
init_global_action_credentials(Some(config.access_key.clone()), Some(config.secret_key.clone()))
|
||||||
|
.map_err(|e| ServerError::Init(format!("credentials: {e:?}")))?;
|
||||||
|
|
||||||
|
// Region.
|
||||||
|
if let Some(region_str) = &config.region {
|
||||||
|
let region = region_str
|
||||||
|
.parse()
|
||||||
|
.map_err(|e| ServerError::Init(format!("invalid region '{region_str}': {e}")))?;
|
||||||
|
rustfs_ecstore::global::set_global_region(region);
|
||||||
|
}
|
||||||
|
|
||||||
let server_port = server_addr.port();
|
let server_port = server_addr.port();
|
||||||
|
|
||||||
set_global_rustfs_port(server_port);
|
set_global_rustfs_port(server_port);
|
||||||
|
|||||||
+25
-15
@@ -59,7 +59,7 @@ use rustfs_iam::{init_iam_sys, init_oidc_sys};
|
|||||||
use rustfs_obs::{init_metrics_runtime, init_obs, set_global_guard};
|
use rustfs_obs::{init_metrics_runtime, init_obs, set_global_guard};
|
||||||
use rustfs_scanner::init_data_scanner;
|
use rustfs_scanner::init_data_scanner;
|
||||||
use rustfs_utils::{
|
use rustfs_utils::{
|
||||||
ExternalEnvCompatReport, apply_external_env_compat, get_env_bool_with_aliases, net::parse_and_resolve_address,
|
ExternalEnvCompatReport, apply_external_env_compat, get_env_bool, get_env_bool_with_aliases, net::parse_and_resolve_address,
|
||||||
};
|
};
|
||||||
use rustls::crypto::aws_lc_rs::default_provider;
|
use rustls::crypto::aws_lc_rs::default_provider;
|
||||||
use std::io::{Error, Result};
|
use std::io::{Error, Result};
|
||||||
@@ -138,11 +138,15 @@ fn format_external_prefix_mappings(report: &ExternalEnvCompatReport) -> String {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn is_using_default_credentials(config: &rustfs::config::Config) -> bool {
|
fn is_using_default_credentials(config: &rustfs::config::Config) -> bool {
|
||||||
rustfs_credentials::DEFAULT_ACCESS_KEY.eq(&config.access_key) && rustfs_credentials::DEFAULT_SECRET_KEY.eq(&config.secret_key)
|
config.is_using_default_credentials()
|
||||||
}
|
}
|
||||||
|
|
||||||
const DEFAULT_CREDENTIALS_WARNING_MESSAGE: &str =
|
const DEFAULT_CREDENTIALS_WARNING_MESSAGE: &str = "Detected default root credentials; set RUSTFS_ACCESS_KEY and RUSTFS_SECRET_KEY to non-default values, or use RUSTFS_ALLOW_INSECURE_DEFAULT_CREDENTIALS=true only for local development";
|
||||||
"Detected default root credentials; change them with the RUSTFS_ACCESS_KEY and RUSTFS_SECRET_KEY environment variables";
|
const DEFAULT_CREDENTIALS_ERROR_MESSAGE: &str = "Default root credentials are not allowed on non-loopback listeners; set RUSTFS_ACCESS_KEY and RUSTFS_SECRET_KEY to non-default values, bind to loopback, or set RUSTFS_ALLOW_INSECURE_DEFAULT_CREDENTIALS=true for local development only";
|
||||||
|
|
||||||
|
fn allow_insecure_default_credentials() -> bool {
|
||||||
|
get_env_bool(rustfs_config::ENV_RUSTFS_ALLOW_INSECURE_DEFAULT_CREDENTIALS, false)
|
||||||
|
}
|
||||||
|
|
||||||
async fn async_main() -> Result<()> {
|
async fn async_main() -> Result<()> {
|
||||||
// Parse command line arguments
|
// Parse command line arguments
|
||||||
@@ -265,6 +269,15 @@ async fn run(config: rustfs::config::Config) -> Result<()> {
|
|||||||
let server_port = server_addr.port();
|
let server_port = server_addr.port();
|
||||||
let server_address = server_addr.to_string();
|
let server_address = server_addr.to_string();
|
||||||
|
|
||||||
|
if !config.default_credentials_allowed_for_addr(server_addr, allow_insecure_default_credentials()) {
|
||||||
|
error!("{DEFAULT_CREDENTIALS_ERROR_MESSAGE}");
|
||||||
|
return Err(Error::other(DEFAULT_CREDENTIALS_ERROR_MESSAGE));
|
||||||
|
}
|
||||||
|
|
||||||
|
if is_using_default_credentials(&config) {
|
||||||
|
warn!("{}", DEFAULT_CREDENTIALS_WARNING_MESSAGE);
|
||||||
|
}
|
||||||
|
|
||||||
info!(
|
info!(
|
||||||
target: "rustfs::main::run",
|
target: "rustfs::main::run",
|
||||||
server_address = %server_address,
|
server_address = %server_address,
|
||||||
@@ -360,10 +373,6 @@ async fn run(config: rustfs::config::Config) -> Result<()> {
|
|||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
if is_using_default_credentials(&config) {
|
|
||||||
warn!("{}", DEFAULT_CREDENTIALS_WARNING_MESSAGE);
|
|
||||||
}
|
|
||||||
|
|
||||||
let ctx = CancellationToken::new();
|
let ctx = CancellationToken::new();
|
||||||
|
|
||||||
// init store
|
// init store
|
||||||
@@ -832,12 +841,13 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn default_credentials_warning_message_does_not_expose_values() {
|
fn default_credentials_messages_are_actionable_without_exposing_values() {
|
||||||
let message = DEFAULT_CREDENTIALS_WARNING_MESSAGE;
|
for message in [DEFAULT_CREDENTIALS_WARNING_MESSAGE, DEFAULT_CREDENTIALS_ERROR_MESSAGE] {
|
||||||
|
assert!(message.contains(rustfs_config::ENV_RUSTFS_ACCESS_KEY));
|
||||||
assert!(message.contains(rustfs_config::ENV_RUSTFS_ACCESS_KEY));
|
assert!(message.contains(rustfs_config::ENV_RUSTFS_SECRET_KEY));
|
||||||
assert!(message.contains(rustfs_config::ENV_RUSTFS_SECRET_KEY));
|
assert!(message.contains(rustfs_config::ENV_RUSTFS_ALLOW_INSECURE_DEFAULT_CREDENTIALS));
|
||||||
assert!(!message.contains(rustfs_credentials::DEFAULT_ACCESS_KEY));
|
assert!(!message.contains(rustfs_credentials::DEFAULT_ACCESS_KEY));
|
||||||
assert!(!message.contains(rustfs_credentials::DEFAULT_SECRET_KEY));
|
assert!(!message.contains(rustfs_credentials::DEFAULT_SECRET_KEY));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
use super::ecfs::FS;
|
use super::ecfs::FS;
|
||||||
use crate::auth::{check_key_valid, get_condition_values_with_query, get_session_token};
|
use crate::auth::{check_key_valid, get_condition_values_with_query_and_client_info, get_session_token};
|
||||||
use crate::error::ApiError;
|
use crate::error::ApiError;
|
||||||
use crate::license::license_check;
|
use crate::license::license_check;
|
||||||
use crate::server::RemoteAddr;
|
use crate::server::RemoteAddr;
|
||||||
@@ -30,6 +30,7 @@ use rustfs_policy::policy::{
|
|||||||
Args, BucketPolicy, BucketPolicyArgs, bucket_policy_needs_existing_object_tag_for_args,
|
Args, BucketPolicy, BucketPolicyArgs, bucket_policy_needs_existing_object_tag_for_args,
|
||||||
bucket_policy_uses_existing_object_tag_conditions,
|
bucket_policy_uses_existing_object_tag_conditions,
|
||||||
};
|
};
|
||||||
|
use rustfs_trusted_proxies::ClientInfo;
|
||||||
use rustfs_utils::http::AMZ_OBJECT_LOCK_BYPASS_GOVERNANCE;
|
use rustfs_utils::http::AMZ_OBJECT_LOCK_BYPASS_GOVERNANCE;
|
||||||
use s3s::access::{S3Access, S3AccessContext};
|
use s3s::access::{S3Access, S3AccessContext};
|
||||||
use s3s::{S3Error, S3ErrorCode, S3Request, S3Result, dto::*, s3_error};
|
use s3s::{S3Error, S3ErrorCode, S3Request, S3Result, dto::*, s3_error};
|
||||||
@@ -334,8 +335,16 @@ pub async fn authorize_request<T>(req: &mut S3Request<T>, action: Action) -> S3R
|
|||||||
|
|
||||||
let default_claims = HashMap::new();
|
let default_claims = HashMap::new();
|
||||||
let claims = cred.claims.as_ref().unwrap_or(&default_claims);
|
let claims = cred.claims.as_ref().unwrap_or(&default_claims);
|
||||||
let mut conditions =
|
let client_info = req.extensions.get::<ClientInfo>();
|
||||||
get_condition_values_with_query(&req.headers, cred, version_id.as_deref(), None, remote_addr, req.uri.query());
|
let mut conditions = get_condition_values_with_query_and_client_info(
|
||||||
|
&req.headers,
|
||||||
|
cred,
|
||||||
|
version_id.as_deref(),
|
||||||
|
None,
|
||||||
|
remote_addr,
|
||||||
|
req.uri.query(),
|
||||||
|
client_info,
|
||||||
|
);
|
||||||
merge_list_bucket_query_conditions(action, req.uri.query(), &mut conditions);
|
merge_list_bucket_query_conditions(action, req.uri.query(), &mut conditions);
|
||||||
|
|
||||||
let action_args = Args {
|
let action_args = Args {
|
||||||
@@ -543,13 +552,15 @@ pub async fn authorize_request<T>(req: &mut S3Request<T>, action: Action) -> S3R
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let default_cred = rustfs_credentials::Credentials::default();
|
let default_cred = rustfs_credentials::Credentials::default();
|
||||||
let mut conditions = get_condition_values_with_query(
|
let client_info = req.extensions.get::<ClientInfo>();
|
||||||
|
let mut conditions = get_condition_values_with_query_and_client_info(
|
||||||
&req.headers,
|
&req.headers,
|
||||||
&default_cred,
|
&default_cred,
|
||||||
version_id.as_deref(),
|
version_id.as_deref(),
|
||||||
req.region.clone(),
|
req.region.clone(),
|
||||||
remote_addr,
|
remote_addr,
|
||||||
req.uri.query(),
|
req.uri.query(),
|
||||||
|
client_info,
|
||||||
);
|
);
|
||||||
merge_list_bucket_query_conditions(action, req.uri.query(), &mut conditions);
|
merge_list_bucket_query_conditions(action, req.uri.query(), &mut conditions);
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
RUSTFS_ACCESS_KEY=rustfsadmin
|
RUSTFS_ACCESS_KEY=rustfs-dev-admin
|
||||||
RUSTFS_SECRET_KEY=rustfsadmin
|
RUSTFS_SECRET_KEY=rustfs-dev-secret
|
||||||
|
|
||||||
RUSTFS_VOLUMES="http://node{1...4}:7000/data/rustfs{0...3} http://node{5...8}:7000/data/rustfs{0...3}"
|
RUSTFS_VOLUMES="http://node{1...4}:7000/data/rustfs{0...3} http://node{5...8}:7000/data/rustfs{0...3}"
|
||||||
RUSTFS_ADDRESS=":7000"
|
RUSTFS_ADDRESS=":7000"
|
||||||
|
|||||||
+4
-2
@@ -22,12 +22,14 @@ mkdir -p $VOLUME
|
|||||||
|
|
||||||
export RUST_LOG="rustfs=debug,ecstore=debug,s3s=debug,iam=debug"
|
export RUST_LOG="rustfs=debug,ecstore=debug,s3s=debug,iam=debug"
|
||||||
export RUST_BACKTRACE=full
|
export RUST_BACKTRACE=full
|
||||||
|
export RUSTFS_ACCESS_KEY=rustfs-e2e-admin
|
||||||
|
export RUSTFS_SECRET_KEY=rustfs-e2e-secret
|
||||||
$BIN $VOLUME > /tmp/rustfs.log 2>&1 &
|
$BIN $VOLUME > /tmp/rustfs.log 2>&1 &
|
||||||
|
|
||||||
sleep 10
|
sleep 10
|
||||||
|
|
||||||
export AWS_ACCESS_KEY_ID=rustfsadmin
|
export AWS_ACCESS_KEY_ID=$RUSTFS_ACCESS_KEY
|
||||||
export AWS_SECRET_ACCESS_KEY=rustfsadmin
|
export AWS_SECRET_ACCESS_KEY=$RUSTFS_SECRET_KEY
|
||||||
export AWS_REGION=us-east-1
|
export AWS_REGION=us-east-1
|
||||||
export AWS_ENDPOINT_URL=http://localhost:9000
|
export AWS_ENDPOINT_URL=http://localhost:9000
|
||||||
export RUST_LOG="s3s_e2e=debug,s3s_test=info,s3s=debug"
|
export RUST_LOG="s3s_e2e=debug,s3s_test=info,s3s=debug"
|
||||||
|
|||||||
@@ -134,7 +134,7 @@ start_rustfs() {
|
|||||||
|
|
||||||
# Start RustFS in background with environment variables
|
# Start RustFS in background with environment variables
|
||||||
cd "$TARGET_DIR"
|
cd "$TARGET_DIR"
|
||||||
RUSTFS_ACCESS_KEY=rustfsadmin RUSTFS_SECRET_KEY=rustfsadmin \
|
RUSTFS_ACCESS_KEY=rustfs-e2e-admin RUSTFS_SECRET_KEY=rustfs-e2e-secret \
|
||||||
RUSTFS_OBS_LOG_DIRECTORY="$TARGET_DIR/logs" \
|
RUSTFS_OBS_LOG_DIRECTORY="$TARGET_DIR/logs" \
|
||||||
./rustfs --address :9000 "$DATA_DIR" > rustfs.log 2>&1 &
|
./rustfs --address :9000 "$DATA_DIR" > rustfs.log 2>&1 &
|
||||||
RUSTFS_PID=$!
|
RUSTFS_PID=$!
|
||||||
@@ -317,4 +317,4 @@ main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# Run main function
|
# Run main function
|
||||||
main "$@"
|
main "$@"
|
||||||
|
|||||||
@@ -14,8 +14,8 @@ KEEP_UP="${KEEP_UP:-false}"
|
|||||||
PRECHECK_AUTO_CLEANUP="${PRECHECK_AUTO_CLEANUP:-true}"
|
PRECHECK_AUTO_CLEANUP="${PRECHECK_AUTO_CLEANUP:-true}"
|
||||||
WAIT_PROBE_MODE="${WAIT_PROBE_MODE:-service}"
|
WAIT_PROBE_MODE="${WAIT_PROBE_MODE:-service}"
|
||||||
|
|
||||||
RUSTFS_ACCESS_KEY="${RUSTFS_ACCESS_KEY:-rustfsadmin}"
|
RUSTFS_ACCESS_KEY="${RUSTFS_ACCESS_KEY:-rustfs-cluster-admin}"
|
||||||
RUSTFS_SECRET_KEY="${RUSTFS_SECRET_KEY:-rustfsadmin}"
|
RUSTFS_SECRET_KEY="${RUSTFS_SECRET_KEY:-rustfs-cluster-secret}"
|
||||||
RUSTFS_OBS_ENDPOINT="${RUSTFS_OBS_ENDPOINT:-}"
|
RUSTFS_OBS_ENDPOINT="${RUSTFS_OBS_ENDPOINT:-}"
|
||||||
RUSTFS_UNSAFE_BYPASS_DISK_CHECK="${RUSTFS_UNSAFE_BYPASS_DISK_CHECK:-true}"
|
RUSTFS_UNSAFE_BYPASS_DISK_CHECK="${RUSTFS_UNSAFE_BYPASS_DISK_CHECK:-true}"
|
||||||
|
|
||||||
|
|||||||
@@ -130,8 +130,8 @@ DEPLOY_MODE=existing S3_HOST=192.168.1.100 S3_PORT=9000 ./scripts/s3-tests/run.s
|
|||||||
|
|
||||||
### Service Configuration
|
### Service Configuration
|
||||||
|
|
||||||
- `S3_ACCESS_KEY`: Main user access key (default: `rustfsadmin`)
|
- `S3_ACCESS_KEY`: Main user access key (default: `rustfs-ci-admin`)
|
||||||
- `S3_SECRET_KEY`: Main user secret key (default: `rustfsadmin`)
|
- `S3_SECRET_KEY`: Main user secret key (default: `rustfs-ci-secret`)
|
||||||
- `S3_ALT_ACCESS_KEY`: Alt user access key (default: `rustfsalt`)
|
- `S3_ALT_ACCESS_KEY`: Alt user access key (default: `rustfsalt`)
|
||||||
- `S3_ALT_SECRET_KEY`: Alt user secret key (default: `rustfsalt`)
|
- `S3_ALT_SECRET_KEY`: Alt user secret key (default: `rustfsalt`)
|
||||||
- `S3_REGION`: S3 region (default: `us-east-1`)
|
- `S3_REGION`: S3 region (default: `us-east-1`)
|
||||||
@@ -420,8 +420,8 @@ curl http://192.168.1.100:9000/health
|
|||||||
|
|
||||||
# Verify S3 API is responding
|
# Verify S3 API is responding
|
||||||
awscurl --service s3 --region us-east-1 \
|
awscurl --service s3 --region us-east-1 \
|
||||||
--access_key rustfsadmin \
|
--access_key rustfs-ci-admin \
|
||||||
--secret_key rustfsadmin \
|
--secret_key rustfs-ci-secret \
|
||||||
-X GET "http://192.168.1.100:9000/"
|
-X GET "http://192.168.1.100:9000/"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -25,8 +25,8 @@ PYTHON_VERSION=$(python3 -c "import sys; print(f'{sys.version_info.major}.{sys.v
|
|||||||
export PATH="$HOME/Library/Python/${PYTHON_VERSION}/bin:$HOME/.local/bin:$PATH"
|
export PATH="$HOME/Library/Python/${PYTHON_VERSION}/bin:$HOME/.local/bin:$PATH"
|
||||||
|
|
||||||
# Configuration
|
# Configuration
|
||||||
S3_ACCESS_KEY="${S3_ACCESS_KEY:-rustfsadmin}"
|
S3_ACCESS_KEY="${S3_ACCESS_KEY:-rustfs-ci-admin}"
|
||||||
S3_SECRET_KEY="${S3_SECRET_KEY:-rustfsadmin}"
|
S3_SECRET_KEY="${S3_SECRET_KEY:-rustfs-ci-secret}"
|
||||||
S3_ALT_ACCESS_KEY="${S3_ALT_ACCESS_KEY:-rustfsalt}"
|
S3_ALT_ACCESS_KEY="${S3_ALT_ACCESS_KEY:-rustfsalt}"
|
||||||
S3_ALT_SECRET_KEY="${S3_ALT_SECRET_KEY:-rustfsalt}"
|
S3_ALT_SECRET_KEY="${S3_ALT_SECRET_KEY:-rustfsalt}"
|
||||||
S3_REGION="${S3_REGION:-us-east-1}"
|
S3_REGION="${S3_REGION:-us-east-1}"
|
||||||
@@ -218,8 +218,8 @@ Environment Variables:
|
|||||||
RUSTFS_BINARY - Path to RustFS binary (for binary mode, default: ./target/release/rustfs)
|
RUSTFS_BINARY - Path to RustFS binary (for binary mode, default: ./target/release/rustfs)
|
||||||
S3_HOST - S3 service host (default: 127.0.0.1)
|
S3_HOST - S3 service host (default: 127.0.0.1)
|
||||||
S3_PORT - S3 service port (default: 9000)
|
S3_PORT - S3 service port (default: 9000)
|
||||||
S3_ACCESS_KEY - Main user access key (default: rustfsadmin)
|
S3_ACCESS_KEY - Main user access key (default: rustfs-ci-admin)
|
||||||
S3_SECRET_KEY - Main user secret key (default: rustfsadmin)
|
S3_SECRET_KEY - Main user secret key (default: rustfs-ci-secret)
|
||||||
S3_ALT_ACCESS_KEY - Alt user access key (default: rustfsalt)
|
S3_ALT_ACCESS_KEY - Alt user access key (default: rustfsalt)
|
||||||
S3_ALT_SECRET_KEY - Alt user secret key (default: rustfsalt)
|
S3_ALT_SECRET_KEY - Alt user secret key (default: rustfsalt)
|
||||||
MAXFAIL - Stop after N failures (default: 1)
|
MAXFAIL - Stop after N failures (default: 1)
|
||||||
@@ -690,7 +690,7 @@ envsubst < "${TEMPLATE_PATH}" > "${CONF_OUTPUT_PATH}" || {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# Step 7: Provision s3-tests alt user
|
# Step 7: Provision s3-tests alt user
|
||||||
# Note: Main user (rustfsadmin) is a system user and doesn't need to be created via API
|
# Note: The configured main user is a system user and doesn't need to be created via API
|
||||||
log_info "Provisioning s3-tests alt user..."
|
log_info "Provisioning s3-tests alt user..."
|
||||||
|
|
||||||
# Helper function to install Python packages with fallback for externally-managed environments
|
# Helper function to install Python packages with fallback for externally-managed environments
|
||||||
|
|||||||
Reference in New Issue
Block a user