Merge branch 'main' of github.com:rustfs/s3-rustfs into feature/logger

# Conflicts:
#	Cargo.toml
This commit is contained in:
houseme
2025-02-27 19:03:32 +08:00
36 changed files with 629 additions and 416 deletions
+5
View File
@@ -9,6 +9,9 @@ inputs:
cache-shared-key:
required: true
default: ""
cache-save-if:
required: true
default: true
runs:
using: "composite"
@@ -39,7 +42,9 @@ runs:
- uses: Swatinem/rust-cache@v2
with:
cache-on-failure: true
cache-all-crates: true
shared-key: ${{ inputs.cache-shared-key }}
save-if: ${{ inputs.cache-save-if }}
- uses: mlugg/setup-zig@v1
- uses: taiki-e/install-action@cargo-zigbuild
+130 -7
View File
@@ -33,20 +33,143 @@ jobs:
develop:
needs: skip-check
if: needs.skip-check.outputs.should_skip != 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/setup
with:
cache-shared-key: "develop"
- name: Install s3s-e2e
run: |
cargo install s3s-e2e --git https://github.com/Nugine/s3s.git
s3s-e2e --version
- name: Format
run: cargo fmt --all --check
- name: Lint
run: cargo check
# run: cargo clippy
# run: cargo clippy -- -D warnings
run: cargo check --all-targets
# TODO: cargo clippy
- name: Test
run: cargo test --all --exclude e2e_test
- name: Build debug
run: |
touch rustfs/build.rs
cargo build -p rustfs --bins
# - name: Build release
# run: |
# touch rustfs/build.rs
# cargo build -p rustfs --bins --release
- run: |
mkdir -p ./target/artifacts
cp target/debug/rustfs ./target/artifacts/rustfs-debug
# cp target/release/rustfs ./target/artifacts/rustfs-release
- uses: actions/upload-artifact@v4
with:
name: rustfs
path: ./target/artifacts/*
test:
name: Test
needs:
- skip-check
- develop
if: needs.skip-check.outputs.should_skip != 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/setup
with:
cache-shared-key: "develop"
cache-save-if: "false"
- run: cargo test --all --exclude e2e_test
s3s-e2e:
name: E2E (s3s-e2e)
needs:
- skip-check
- develop
if: needs.skip-check.outputs.should_skip != 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/setup
with:
cache-shared-key: "develop"
cache-save-if: false
- name: Install s3s-e2e
run: |
cargo install s3s-e2e --git https://github.com/Nugine/s3s.git
s3s-e2e --version
- uses: actions/download-artifact@v4
with:
name: rustfs
path: ./target/artifacts
- name: Run rustfs
run: |
./scripts/e2e-run.sh ./target/artifacts/rustfs-debug /data/rustfs
sleep 10
- name: Run s3s-e2e
timeout-minutes: 10
run: |
export AWS_ACCESS_KEY_ID=rustfsadmin
export AWS_SECRET_ACCESS_KEY=rustfsadmin
export AWS_REGION=us-east-1
export AWS_ENDPOINT_URL=http://localhost:9000
export RUST_LOG="s3s_e2e=debug,s3s_test=info,s3s=debug"
export RUST_BACKTRACE=full
s3s-e2e
sudo killall rustfs-debug
- uses: actions/upload-artifact@v4
with:
name: s3s-e2e.logs
path: /tmp/rustfs.log
# mint:
# name: E2E (mint)
# needs:
# - skip-check
# - develop
# if: needs.skip-check.outputs.should_skip != 'true'
# runs-on: ubuntu-latest
# steps:
# - uses: actions/checkout@v4
# - uses: ./.github/actions/setup
# with:
# cache-shared-key: "develop"
# cache-save-if: false
# - uses: actions/download-artifact@v4
# with:
# name: rustfs
# path: ./target/artifacts
# - name: Run rustfs
# run: |
# ./scripts/e2e-run.sh ./target/artifacts/rustfs-release /data/rustfs
# sleep 10
# - name: Run mint
# timeout-minutes: 10
# run: |
# docker run \
# -e "SERVER_ENDPOINT=localhost:9000" \
# -e "ACCESS_KEY=rustfsadmin" \
# -e "SECRET_KEY=rustfsadmin" \
# -e "ENABLE_HTTPS=0" \
# --network host \
# minio/mint:edge
# sudo killall rustfs-release
# - uses: actions/upload-artifact@v4
# with:
# name: mint.logs
# path: /tmp/rustfs.log
-146
View File
@@ -1,146 +0,0 @@
name: E2E
on:
workflow_dispatch:
schedule:
- cron: '0 0 * * 0' # at midnight of each sunday
push:
branches:
- main
pull_request:
branches:
- main
env:
CARGO_TERM_COLOR: always
jobs:
skip-check:
permissions:
actions: write
contents: read
runs-on: ubuntu-latest
outputs:
should_skip: ${{ steps.skip_check.outputs.should_skip }}
steps:
- id: skip_check
uses: fkirc/skip-duplicate-actions@v5
with:
concurrent_skipping: 'same_content_newer'
cancel_others: true
paths_ignore: '["*.md"]'
s3s-e2e:
needs: skip-check
if: needs.skip-check.outputs.should_skip != 'true'
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/setup
- name: Build binaries
run: |
touch rustfs/build.rs
cargo build -p rustfs --bins
- name: Install s3s-e2e
run: |
mkdir -p target/s3s-e2e
cargo install s3s-e2e \
--git https://github.com/Nugine/s3s.git \
--target-dir target/s3s-e2e \
--force
s3s-e2e --version
- name: Run tests
run: |
export SKIP_BUILD=1
nohup ./scripts/run.sh &
sleep 3
export AWS_ACCESS_KEY_ID=rustfsadmin
export AWS_SECRET_ACCESS_KEY=rustfsadmin
export AWS_REGION=us-east-1
export AWS_ENDPOINT_URL=http://localhost:9000
export RUST_LOG="s3s_e2e=debug,s3s_test=info,s3s=debug"
export RUST_BACKTRACE=full
s3s-e2e
killall rustfs
mint:
needs: skip-check
if: needs.skip-check.outputs.should_skip != 'true'
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/setup
- name: Build binaries
run: |
touch rustfs/build.rs
cargo build -p rustfs --bins --release
- name: run this rustfs server
run: |
sudo mkdir -p /data/rustfs
sudo nohup ./target/release/rustfs /data/rustfs &
- name: run mint test task
run: |
ps aux | grep rustfs
SERVER_ADDRESS=$(ifconfig eth0 | awk 'NR==2 { print $2 }')
docker run -e SERVER_ENDPOINT=${SERVER_ADDRESS}:9000 \
-e ACCESS_KEY=rustfsadmin \
-e SECRET_KEY=rustfsadmin \
-e ENABLE_HTTPS=0 \
minio/mint
### Temporarily disabled because it is not working
# e2e:
# # See this url if required matrix jobs are hanging
# # https://github.com/fkirc/skip-duplicate-actions#how-to-use-skip-check-with-required-matrix-jobs
# needs: skip-check
# if: needs.skip-check.outputs.should_skip != 'true'
# timeout-minutes: 30
# runs-on: ubuntu-latest
# strategy:
# matrix:
# rust:
# - stable
# # - nightly
# steps:
# - uses: arduino/setup-protoc@v3
# with:
# version: "27.0"
# - uses: Nugine/setup-flatc@v1
# with:
# version: "24.3.25"
# - uses: actions/checkout@v4
# - name: toolchain
# uses: dtolnay/rust-toolchain@master
# with:
# toolchain: ${{ matrix.rust }}
# # components: rustfmt, clippy
# - uses: Swatinem/rust-cache@v2
# with:
# cache-on-failure: true
# - name: run fs start
# working-directory: .
# run: |
# nohup make e2e-server &
# - name: run fs test
# working-directory: .
# run: |
# make probe-e2e
# - name: e2e test
# run: cargo test -p e2e_test --lib
+1
View File
@@ -5,3 +5,4 @@
/test
/logs
.devcontainer
rustfs/static/*
Generated
+86 -28
View File
@@ -1829,13 +1829,34 @@ dependencies = [
"syn 2.0.98",
]
[[package]]
name = "dirs"
version = "5.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225"
dependencies = [
"dirs-sys 0.4.1",
]
[[package]]
name = "dirs"
version = "6.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e"
dependencies = [
"dirs-sys",
"dirs-sys 0.5.0",
]
[[package]]
name = "dirs-sys"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c"
dependencies = [
"libc",
"option-ext",
"redox_users 0.4.6",
"windows-sys 0.48.0",
]
[[package]]
@@ -1846,7 +1867,7 @@ checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab"
dependencies = [
"libc",
"option-ext",
"redox_users",
"redox_users 0.5.0",
"windows-sys 0.59.0",
]
@@ -3185,25 +3206,6 @@ dependencies = [
"icu_properties",
]
[[package]]
name = "include_dir"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "923d117408f1e49d914f1a379a309cffe4f18c05cf4e3d12e613a15fc81bd0dd"
dependencies = [
"include_dir_macros",
]
[[package]]
name = "include_dir_macros"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7cab85a7ed0bd5f0e76d93846e0147172bed2e2d3f859bcc33a8d9699cad1a75"
dependencies = [
"proc-macro2",
"quote",
]
[[package]]
name = "indexmap"
version = "1.9.3"
@@ -4047,9 +4049,9 @@ dependencies = [
[[package]]
name = "numeric_cast"
version = "0.2.2"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6699ec124a5322dd30184abe24b2b9a92237ed0fe8e038f1f410bf7cccfe8b33"
checksum = "a3c00a0c9600379bd32f8972de90676a7672cba3bf4886986bc05902afc1e093"
[[package]]
name = "objc"
@@ -5244,6 +5246,17 @@ dependencies = [
"bitflags 2.8.0",
]
[[package]]
name = "redox_users"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43"
dependencies = [
"getrandom 0.2.15",
"libredox",
"thiserror 1.0.69",
]
[[package]]
name = "redox_users"
version = "0.5.0"
@@ -5447,6 +5460,41 @@ dependencies = [
"serde",
]
[[package]]
name = "rust-embed"
version = "8.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa66af4a4fdd5e7ebc276f115e895611a34739a9c1c01028383d612d550953c0"
dependencies = [
"rust-embed-impl",
"rust-embed-utils",
"walkdir",
]
[[package]]
name = "rust-embed-impl"
version = "8.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6125dbc8867951125eec87294137f4e9c2c96566e61bf72c45095a7c77761478"
dependencies = [
"proc-macro2",
"quote",
"rust-embed-utils",
"shellexpand",
"syn 2.0.98",
"walkdir",
]
[[package]]
name = "rust-embed-utils"
version = "8.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e5347777e9aacb56039b0e1f28785929a8a3b709e87482e7442c72e7c12529d"
dependencies = [
"sha2 0.10.8",
"walkdir",
]
[[package]]
name = "rustc-demangle"
version = "0.1.24"
@@ -5497,7 +5545,6 @@ dependencies = [
"hyper",
"hyper-util",
"iam",
"include_dir",
"jsonwebtoken",
"lazy_static",
"lock",
@@ -5505,6 +5552,7 @@ dependencies = [
"madmin",
"matchit 0.8.6",
"mime",
"mime_guess",
"netif",
"pin-project-lite",
"prost",
@@ -5513,6 +5561,7 @@ dependencies = [
"protobuf",
"protos",
"rmp-serde",
"rust-embed",
"s3s",
"serde",
"serde_json",
@@ -5541,7 +5590,7 @@ version = "0.0.1"
dependencies = [
"chrono",
"dioxus",
"dirs",
"dirs 6.0.0",
"futures-util",
"keyring",
"reqwest",
@@ -5647,7 +5696,7 @@ checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd"
[[package]]
name = "s3s"
version = "0.11.0-dev"
source = "git+https://github.com/Nugine/s3s.git?rev=529c8933a11528c506d5fbf7c4c2ab155db37dfe#529c8933a11528c506d5fbf7c4c2ab155db37dfe"
source = "git+https://github.com/Nugine/s3s.git?rev=ab139f72fe768fb9d8cecfe36269451da1ca9779#ab139f72fe768fb9d8cecfe36269451da1ca9779"
dependencies = [
"arrayvec",
"async-trait",
@@ -5694,7 +5743,7 @@ dependencies = [
[[package]]
name = "s3s-policy"
version = "0.11.0-dev"
source = "git+https://github.com/Nugine/s3s.git?rev=529c8933a11528c506d5fbf7c4c2ab155db37dfe#529c8933a11528c506d5fbf7c4c2ab155db37dfe"
source = "git+https://github.com/Nugine/s3s.git?rev=ab139f72fe768fb9d8cecfe36269451da1ca9779#ab139f72fe768fb9d8cecfe36269451da1ca9779"
dependencies = [
"indexmap 2.7.1",
"serde",
@@ -6017,6 +6066,15 @@ dependencies = [
"lazy_static",
]
[[package]]
name = "shellexpand"
version = "3.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da03fa3b94cc19e3ebfc88c4229c49d8f08cdbd1228870a45f0ffdf84988e14b"
dependencies = [
"dirs 5.0.1",
]
[[package]]
name = "shlex"
version = "1.3.0"
@@ -6946,7 +7004,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eadd75f5002e2513eaa19b2365f533090cc3e93abd38788452d9ea85cff7b48a"
dependencies = [
"crossbeam-channel",
"dirs",
"dirs 6.0.0",
"libappindicator",
"muda 0.15.3",
"objc2 0.6.0",
+2 -2
View File
@@ -80,10 +80,10 @@ rfd = { version = "0.15.2", default-features = false, features = ["xdg-portal",
rmp = "0.8.14"
rmp-serde = "1.3.0"
rustfs-logging = { path = "packages/logging", version = "0.0.1" }
s3s = { git = "https://github.com/Nugine/s3s.git", rev = "529c8933a11528c506d5fbf7c4c2ab155db37dfe", default-features = true, features = [
s3s = { git = "https://github.com/Nugine/s3s.git", rev = "ab139f72fe768fb9d8cecfe36269451da1ca9779", default-features = true, features = [
"tower",
] }
s3s-policy = { git = "https://github.com/Nugine/s3s.git", rev = "529c8933a11528c506d5fbf7c4c2ab155db37dfe" }
s3s-policy = { git = "https://github.com/Nugine/s3s.git", rev = "ab139f72fe768fb9d8cecfe36269451da1ca9779" }
shadow-rs = { version = "0.38.0", default-features = false }
serde = { version = "1.0.217", features = ["derive"] }
serde_json = "1.0.138"
+2 -4
View File
@@ -19,7 +19,7 @@ use crate::disk::error::{
use crate::disk::os::{check_path_length, is_empty_dir};
use crate::disk::{LocalFileReader, LocalFileWriter, STORAGE_FORMAT_FILE};
use crate::error::{Error, Result};
use crate::file_meta::read_xl_meta_no_data;
use crate::file_meta::{get_file_info, read_xl_meta_no_data, FileInfoOpts};
use crate::global::{GLOBAL_IsErasureSD, GLOBAL_RootDiskThreshold};
use crate::heal::data_scanner::{has_active_rules, scan_data_folder, ScannerItem, ShouldSleepFn, SizeSummary};
use crate::heal::data_scanner_metric::{ScannerMetric, ScannerMetrics};
@@ -1995,10 +1995,8 @@ impl DiskAPI for LocalDisk {
let (data, _) = self.read_raw(volume, file_dir, file_path, read_data).await?;
let mut meta = FileMeta::default();
meta.unmarshal_msg(&data)?;
let fi = get_file_info(&data, volume, path, version_id, FileInfoOpts { data: read_data }).await?;
let fi = meta.into_fileinfo(volume, path, version_id, read_data, true)?;
Ok(fi)
}
async fn read_xl(&self, volume: &str, path: &str, read_data: bool) -> Result<RawFileInfo> {
+2 -1
View File
@@ -1208,8 +1208,9 @@ pub struct VolumeInfo {
pub created: Option<OffsetDateTime>,
}
#[derive(Deserialize, Serialize, Debug)]
#[derive(Deserialize, Serialize, Debug, Default)]
pub struct ReadOptions {
pub incl_free_versions: bool,
pub read_data: bool,
pub healing: bool,
}
+1 -1
View File
@@ -504,7 +504,7 @@ impl EndpointServerPools {
self.0
.first()
.and_then(|v| v.endpoints.as_ref().first())
.map_or(false, |v| v.is_local)
.is_some_and(|v| v.is_local)
}
/// returns a sorted list of nodes in this cluster
+2 -3
View File
@@ -554,7 +554,7 @@ impl FileMeta {
let header = &ver.header;
if let Some(vid) = has_vid {
if header.version_id == Some(vid) {
if header.version_id != Some(vid) {
is_latest = false;
succ_mod_time = header.mod_time;
continue;
@@ -2045,7 +2045,7 @@ pub struct FileInfoOpts {
pub data: bool,
}
async fn get_file_info(buf: &[u8], volume: &str, path: &str, version_id: &str, opts: FileInfoOpts) -> Result<FileInfo> {
pub async fn get_file_info(buf: &[u8], volume: &str, path: &str, version_id: &str, opts: FileInfoOpts) -> Result<FileInfo> {
let vid = {
if version_id.is_empty() {
None
@@ -2068,7 +2068,6 @@ async fn get_file_info(buf: &[u8], volume: &str, path: &str, version_id: &str, o
}
let fi = meta.into_fileinfo(volume, path, version_id, opts.data, true)?;
Ok(fi)
}
+11 -3
View File
@@ -1088,7 +1088,11 @@ impl SetDisks {
let mut errors = Vec::with_capacity(disks.len());
for disk in disks.iter() {
let opts = ReadOptions { read_data, healing };
let opts = ReadOptions {
read_data,
healing,
..Default::default()
};
futures.push(async move {
if let Some(disk) = disk {
if version_id.is_empty() {
@@ -3634,7 +3638,7 @@ impl ObjectIO for SetDisks {
Ok(reader)
}
#[tracing::instrument(level = "debug", skip(self, data))]
#[tracing::instrument(level = "debug", skip(self, data,))]
async fn put_object(&self, bucket: &str, object: &str, data: &mut PutObjReader, opts: &ObjectOptions) -> Result<ObjectInfo> {
let disks = self.disks.read().await;
@@ -5463,7 +5467,11 @@ fn get_complete_multipart_md5(parts: &[CompletePart]) -> String {
for part in parts.iter() {
if let Some(etag) = &part.e_tag {
buf.extend(etag.bytes());
if let Ok(etag_bytes) = hex_simd::decode_to_vec(etag.as_bytes()) {
buf.extend(etag_bytes);
} else {
buf.extend(etag.bytes());
}
}
}
+1 -3
View File
@@ -58,7 +58,7 @@ use tokio::select;
use tokio::sync::mpsc::Sender;
use tokio::sync::{broadcast, mpsc, RwLock};
use tokio::time::{interval, sleep};
use tracing::{debug, info, warn};
use tracing::{debug, info};
use uuid::Uuid;
const MAX_UPLOADS_LIST: usize = 10000;
@@ -1235,10 +1235,8 @@ impl StorageAPI for ECStore {
let mut buckets = self.peer_sys.list_bucket(opts).await?;
if !opts.no_metadata {
warn!("list_bucket meta");
for bucket in buckets.iter_mut() {
if let Ok(created) = metadata_sys::created_at(&bucket.name).await {
warn!("list_bucket created {:?}", &created);
bucket.created = Some(created);
}
}
+1 -1
View File
@@ -140,7 +140,7 @@ pub fn get_format_erasure_in_quorum(formats: &[Option<FormatV3>]) -> Result<Form
let format = formats
.iter()
.find(|f| f.as_ref().map_or(false, |v| v.drives().eq(max_drives)))
.find(|f| f.as_ref().is_some_and(|v| v.drives().eq(max_drives)))
.ok_or(Error::new(ErasureError::ErasureReadQuorum))?;
let mut format = format.as_ref().unwrap().clone();
+12 -2
View File
@@ -8,6 +8,7 @@ use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize};
use serde_json::{json, Value};
use std::collections::HashMap;
use time::macros::offset;
use time::OffsetDateTime;
const ACCESS_KEY_MIN_LEN: usize = 3;
@@ -212,10 +213,19 @@ pub fn create_new_credentials_with_metadata(
return Err(Error::new(IamError::InvalidAccessKeyLength));
}
if token_secret.is_empty() {
return Ok(Credentials {
access_key: ak.to_owned(),
secret_key: sk.to_owned(),
status: ACCOUNT_OFF.to_owned(),
..Default::default()
});
}
let expiration = {
if let Some(v) = claims.get("exp") {
if let Some(expiry) = v.as_i64() {
Some(OffsetDateTime::from_unix_timestamp(expiry)?.to_offset(OffsetDateTime::now_utc().offset()))
Some(OffsetDateTime::from_unix_timestamp(expiry)?.to_offset(offset!(+8)))
} else {
None
}
@@ -224,7 +234,7 @@ pub fn create_new_credentials_with_metadata(
}
};
let token = utils::generate_jwt(claims, token_secret)?;
let token = utils::generate_jwt(&claims, token_secret)?;
Ok(Credentials {
access_key: ak.to_owned(),
+21 -1
View File
@@ -4,6 +4,7 @@ use crate::{
cache::{Cache, CacheEntity},
error::{is_err_no_such_group, is_err_no_such_policy, is_err_no_such_user, Error as IamError},
format::Format,
get_global_action_cred,
policy::{Policy, PolicyDoc, DEFAULT_POLICIES},
store::{object::IAM_CONFIG_PREFIX, GroupInfo, MappedPolicy, Store, UserType},
sys::{
@@ -1577,8 +1578,27 @@ fn set_default_canned_policies(policies: &mut HashMap<String, PolicyDoc>) {
}
}
pub fn get_token_signing_key() -> Option<String> {
if let Some(s) = get_global_action_cred() {
Some(s.secret_key.clone())
} else {
None
}
}
pub fn extract_jwt_claims(u: &UserIdentity) -> Result<HashMap<String, Value>> {
get_claims_from_token_with_secret(&u.credentials.session_token, &u.credentials.secret_key)
let Some(sys_key) = get_token_signing_key() else {
return Err(Error::msg("global active sk not init"));
};
let keys = vec![&sys_key, &u.credentials.secret_key];
for key in keys {
if let Ok(claims) = get_claims_from_token_with_secret(&u.credentials.session_token, key) {
return Ok(claims);
}
}
Err(Error::msg("unable to extract claims"))
}
fn filter_policies(cache: &Cache, policy_name: &str, bucket_name: &str) -> (String, Policy) {
+3 -3
View File
@@ -131,9 +131,9 @@ impl<'de> Deserialize<'de> for Functions {
}
}
// if inner_data.is_empty() {
// return Err(Error::custom("has no condition element"));
// }
if inner_data.is_empty() {
return Err(Error::custom("has no condition element"));
}
Ok(inner_data)
}
+20 -6
View File
@@ -4,7 +4,7 @@ use crate::{
cache::{Cache, CacheEntity},
error::{is_err_no_such_policy, is_err_no_such_user},
get_global_action_cred,
manager::get_default_policyes,
manager::{extract_jwt_claims, get_default_policyes},
policy::PolicyDoc,
};
use ecstore::{
@@ -420,8 +420,20 @@ impl Store for ObjectStore {
u.credentials.access_key = name.to_owned();
}
if u.credentials.session_token.is_empty() {
// TODO: 解析sts
if !u.credentials.session_token.is_empty() {
match extract_jwt_claims(&u) {
Ok(claims) => {
u.credentials.claims = Some(claims);
}
Err(err) => {
if u.credentials.is_temp() {
let _ = self.delete_iam_config(get_user_identity_path(name, user_type)).await;
let _ = self.delete_iam_config(get_mapped_policy_path(name, user_type, false)).await;
}
warn!("extract_jwt_claims failed: {}", err);
return Err(Error::new(crate::error::Error::NoSuchUser(name.to_owned())));
}
}
}
Ok(u)
@@ -807,7 +819,7 @@ impl Store for ObjectStore {
}
// group policy
if let Some(item_name_list) = listed_config_items.get(SVC_ACC_LIST_KEY) {
if let Some(item_name_list) = listed_config_items.get(POLICY_DB_GROUPS_LIST_KEY) {
let mut items_cache = CacheEntity::default();
for item in item_name_list.iter() {
@@ -815,7 +827,9 @@ impl Store for ObjectStore {
info!("load group policy: {}", name);
if let Err(err) = self.load_mapped_policy(name, UserType::Reg, true, &mut items_cache).await {
return Err(Error::msg(std::format!("load group failed: {}", err)));
if !is_err_no_such_policy(&err) {
return Err(Error::msg(std::format!("load group policy failed: {}", err)));
}
};
}
@@ -825,7 +839,7 @@ impl Store for ObjectStore {
let mut sts_policies_cache = CacheEntity::default();
// svc users
if let Some(item_name_list) = listed_config_items.get(POLICY_DB_GROUPS_LIST_KEY) {
if let Some(item_name_list) = listed_config_items.get(SVC_ACC_LIST_KEY) {
let mut items_cache = HashMap::default();
for item in item_name_list.iter() {
+5
View File
@@ -229,6 +229,11 @@ impl<T: Store> IamSys<T> {
}
}
m.insert(
"exp".to_string(),
serde_json::Value::Number(serde_json::Number::from(opts.expiration.map_or(0, |t| t.unix_timestamp()))),
);
let (access_key, secret_key) = if !opts.access_key.is_empty() || !opts.secret_key.is_empty() {
(opts.access_key, opts.secret_key)
} else {
+17 -17
View File
@@ -71,29 +71,29 @@ impl ServiceTraceOpts {
})
.collect();
self.s3 = query_pairs.get("s3").map_or(false, |v| v == "true");
self.os = query_pairs.get("os").map_or(false, |v| v == "true");
self.scanner = query_pairs.get("scanner").map_or(false, |v| v == "true");
self.decommission = query_pairs.get("decommission").map_or(false, |v| v == "true");
self.healing = query_pairs.get("healing").map_or(false, |v| v == "true");
self.batch_replication = query_pairs.get("batch-replication").map_or(false, |v| v == "true");
self.batch_key_rotation = query_pairs.get("batch-keyrotation").map_or(false, |v| v == "true");
self.batch_expire = query_pairs.get("batch-expire").map_or(false, |v| v == "true");
if query_pairs.get("all").map_or(false, |v| v == "true") {
self.s3 = query_pairs.get("s3").is_some_and(|v| v == "true");
self.os = query_pairs.get("os").is_some_and(|v| v == "true");
self.scanner = query_pairs.get("scanner").is_some_and(|v| v == "true");
self.decommission = query_pairs.get("decommission").is_some_and(|v| v == "true");
self.healing = query_pairs.get("healing").is_some_and(|v| v == "true");
self.batch_replication = query_pairs.get("batch-replication").is_some_and(|v| v == "true");
self.batch_key_rotation = query_pairs.get("batch-keyrotation").is_some_and(|v| v == "true");
self.batch_expire = query_pairs.get("batch-expire").is_some_and(|v| v == "true");
if query_pairs.get("all").is_some_and(|v| v == "true") {
self.s3 = true;
self.internal = true;
self.storage = true;
self.os = true;
}
self.rebalance = query_pairs.get("rebalance").map_or(false, |v| v == "true");
self.storage = query_pairs.get("storage").map_or(false, |v| v == "true");
self.internal = query_pairs.get("internal").map_or(false, |v| v == "true");
self.only_errors = query_pairs.get("err").map_or(false, |v| v == "true");
self.replication_resync = query_pairs.get("replication-resync").map_or(false, |v| v == "true");
self.bootstrap = query_pairs.get("bootstrap").map_or(false, |v| v == "true");
self.ftp = query_pairs.get("ftp").map_or(false, |v| v == "true");
self.ilm = query_pairs.get("ilm").map_or(false, |v| v == "true");
self.rebalance = query_pairs.get("rebalance").is_some_and(|v| v == "true");
self.storage = query_pairs.get("storage").is_some_and(|v| v == "true");
self.internal = query_pairs.get("internal").is_some_and(|v| v == "true");
self.only_errors = query_pairs.get("err").is_some_and(|v| v == "true");
self.replication_resync = query_pairs.get("replication-resync").is_some_and(|v| v == "true");
self.bootstrap = query_pairs.get("bootstrap").is_some_and(|v| v == "true");
self.ftp = query_pairs.get("ftp").is_some_and(|v| v == "true");
self.ilm = query_pairs.get("ilm").is_some_and(|v| v == "true");
if let Some(threshold) = query_pairs.get("threshold") {
let duration = parse_duration(threshold)?;
+2 -2
View File
@@ -104,7 +104,7 @@ pub struct ServiceAccountInfo {
#[serde(rename = "description", skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(rename = "expiration", skip_serializing_if = "Option::is_none")]
#[serde(rename = "expiration", with = "time::serde::rfc3339::option")]
pub expiration: Option<OffsetDateTime>,
}
@@ -134,7 +134,7 @@ pub struct AddServiceAccountReq {
#[serde(rename = "description", skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(rename = "expiration", skip_serializing_if = "Option::is_none")]
#[serde(rename = "expiration", with = "time::serde::rfc3339::option")]
pub expiration: Option<OffsetDateTime>,
}
+5 -1
View File
@@ -7,6 +7,9 @@ repository.workspace = true
rust-version.workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[[bin]]
name = "rustfs"
path = "src/main.rs"
[lints]
workspace = true
@@ -69,7 +72,8 @@ crypto = { path = "../crypto" }
iam = { path = "../iam" }
jsonwebtoken = "9.3.0"
tower-http = { version = "0.6.2", features = ["cors"] }
include_dir = "0.7.4"
mime_guess = "2.0.5"
rust-embed = { version = "8.5.0", features = ["interpolate-folder-path"] }
[build-dependencies]
prost-build.workspace = true
+3 -138
View File
@@ -18,12 +18,11 @@ use ecstore::store::is_valid_object_prefix;
use ecstore::store_api::StorageAPI;
use ecstore::utils::crypto::base64_encode;
use ecstore::utils::path::path_join;
use ecstore::utils::xml;
use ecstore::GLOBAL_Endpoints;
use futures::{Stream, StreamExt};
use http::{HeaderMap, Uri};
use hyper::StatusCode;
use iam::auth::{get_claims_from_token_with_secret, get_new_credentials_with_metadata};
use iam::auth::get_claims_from_token_with_secret;
use iam::error::Error as IamError;
use iam::policy::Policy;
use iam::sys::SESSION_POLICY_NAME;
@@ -33,10 +32,7 @@ use madmin::utils::parse_duration;
use matchit::Params;
use s3s::header::CONTENT_TYPE;
use s3s::stream::{ByteStream, DynByteStream};
use s3s::{
dto::{AssumeRoleOutput, Credentials, Timestamp},
s3_error, Body, S3Error, S3Request, S3Response, S3Result,
};
use s3s::{s3_error, Body, S3Error, S3Request, S3Response, S3Result};
use s3s::{S3ErrorCode, StdError};
use serde::{Deserialize, Serialize};
use serde_json::Value;
@@ -47,7 +43,6 @@ use std::pin::Pin;
use std::sync::Arc;
use std::task::{Context, Poll};
use std::time::Duration as std_Duration;
use time::{Duration, OffsetDateTime};
use tokio::sync::mpsc::{self};
use tokio::time::interval;
use tokio::{select, spawn};
@@ -57,32 +52,10 @@ use tracing::{error, info, warn};
pub mod group;
pub mod policy;
pub mod service_account;
pub mod sts;
pub mod trace;
pub mod user;
const ASSUME_ROLE_ACTION: &str = "AssumeRole";
const ASSUME_ROLE_VERSION: &str = "2011-06-15";
#[derive(Deserialize, Debug, Default)]
#[serde(rename_all = "PascalCase", default)]
pub struct AssumeRoleRequest {
pub action: String,
pub duration_seconds: usize,
pub version: String,
pub role_arn: String,
pub role_session_name: String,
pub policy: String,
pub external_id: String,
}
fn get_token_signing_key() -> Option<String> {
if let Some(s) = get_global_action_cred() {
Some(s.secret_key.clone())
} else {
None
}
}
// check_key_valid get auth.cred
pub async fn check_key_valid(security_token: Option<String>, ak: &str) -> S3Result<(auth::Credentials, bool)> {
let Some(mut cred) = get_global_action_cred() else {
@@ -216,114 +189,6 @@ pub fn populate_session_policy(claims: &mut HashMap<String, Value>, policy: &str
Ok(())
}
pub struct AssumeRoleHandle {}
#[async_trait::async_trait]
impl Operation for AssumeRoleHandle {
async fn call(&self, req: S3Request<Body>, _params: Params<'_, '_>) -> S3Result<S3Response<(StatusCode, Body)>> {
warn!("handle AssumeRoleHandle");
let Some(user) = req.credentials else { return Err(s3_error!(InvalidRequest, "get cred failed")) };
let session_token = get_session_token(&req.headers);
if session_token.is_some() {
return Err(s3_error!(InvalidRequest, "AccessDenied1"));
}
let (cred, _owner) = check_key_valid(session_token, &user.access_key).await?;
// // TODO: 判断权限, 不允许sts访问
if cred.is_temp() || cred.is_service_account() {
return Err(s3_error!(InvalidRequest, "AccessDenied"));
}
let mut input = req.input;
let bytes = match input.store_all_unlimited().await {
Ok(b) => b,
Err(e) => {
warn!("get body failed, e: {:?}", e);
return Err(s3_error!(InvalidRequest, "get body failed"));
}
};
let body: AssumeRoleRequest = from_bytes(&bytes).map_err(|_e| s3_error!(InvalidRequest, "get body failed"))?;
if body.action.as_str() != ASSUME_ROLE_ACTION {
return Err(s3_error!(InvalidArgument, "not suport action"));
}
if body.version.as_str() != ASSUME_ROLE_VERSION {
return Err(s3_error!(InvalidArgument, "not suport version"));
}
let mut claims = cred.claims.unwrap_or_default();
populate_session_policy(&mut claims, &body.policy)?;
let exp = {
if body.duration_seconds > 0 {
body.duration_seconds
} else {
3600
}
};
claims.insert(
"exp".to_string(),
serde_json::Value::Number(serde_json::Number::from(OffsetDateTime::now_utc().unix_timestamp() + exp as i64)),
);
claims.insert("parent".to_string(), serde_json::Value::String(cred.access_key.clone()));
// warn!("AssumeRole get cred {:?}", &user);
// warn!("AssumeRole get body {:?}", &body);
let Ok(iam_store) = iam::get() else { return Err(s3_error!(InvalidRequest, "iam not init")) };
if let Err(_err) = iam_store.policy_db_get(&cred.access_key, &cred.groups).await {
return Err(s3_error!(InvalidArgument, "invalid policy arg"));
}
let Some(secret) = get_token_signing_key() else {
return Err(s3_error!(InvalidArgument, "global active sk not init"));
};
info!("AssumeRole get claims {:?}", &claims);
let mut new_cred = get_new_credentials_with_metadata(&claims, &secret)
.map_err(|e| S3Error::with_message(S3ErrorCode::InternalError, format!("get new cred failed {}", e)))?;
new_cred.parent_user = cred.access_key.clone();
info!("AssumeRole get new_cred {:?}", &new_cred);
if let Err(_err) = iam_store.set_temp_user(&new_cred.access_key, &new_cred, None).await {
return Err(s3_error!(InternalError, "set_temp_user failed"));
}
// TODO: globalSiteReplicationSys
let resp = AssumeRoleOutput {
credentials: Some(Credentials {
access_key_id: new_cred.access_key,
expiration: Timestamp::from(
new_cred
.expiration
.unwrap_or(OffsetDateTime::now_utc().saturating_add(Duration::seconds(3600))),
),
secret_access_key: new_cred.secret_key,
session_token: new_cred.session_token,
}),
..Default::default()
};
// getAssumeRoleCredentials
let output = xml::serialize::<AssumeRoleOutput>(&resp).unwrap();
Ok(S3Response::new((StatusCode::OK, Body::from(output))))
}
}
#[derive(Debug, Serialize, Default)]
#[serde(rename_all = "PascalCase", default)]
pub struct AccountInfo {
+5 -4
View File
@@ -40,9 +40,10 @@ impl Operation for AddServiceAccount {
}
};
let mut create_req: AddServiceAccountReq =
let create_req: AddServiceAccountReq =
serde_json::from_slice(&body[..]).map_err(|e| s3_error!(InvalidRequest, "unmarshal body failed, e: {:?}", e))?;
create_req.expiration = create_req.expiration.and_then(|expire| expire.replace_millisecond(0).ok());
// create_req.expiration = create_req.expiration.and_then(|expire| expire.replace_millisecond(0).ok());
if has_space_be(&create_req.access_key) {
return Err(s3_error!(InvalidRequest, "access key has spaces"));
@@ -71,7 +72,7 @@ impl Operation for AddServiceAccount {
let req_groups = cred.groups.clone();
let mut req_is_derived_cred = false;
if cred.is_owner() || cred.is_service_account() {
if cred.is_service_account() || cred.is_temp() {
req_parent_user = cred.parent_user.clone();
req_is_derived_cred = true;
}
@@ -128,7 +129,7 @@ impl Operation for AddServiceAccount {
.await
.map_err(|e| {
debug!("create service account failed, e: {:?}", e);
s3_error!(InternalError, "create service account failed")
s3_error!(InternalError, "create service account failed, e: {:?}", e)
})?;
let resp = AddServiceAccountResp {
+139
View File
@@ -0,0 +1,139 @@
use crate::admin::{
handlers::{check_key_valid, get_session_token, populate_session_policy},
router::Operation,
};
use ecstore::utils::xml;
use http::StatusCode;
use iam::{auth::get_new_credentials_with_metadata, manager::get_token_signing_key};
use matchit::Params;
use s3s::{
dto::{AssumeRoleOutput, Credentials, Timestamp},
s3_error, Body, S3Error, S3ErrorCode, S3Request, S3Response, S3Result,
};
use serde::Deserialize;
use serde_urlencoded::from_bytes;
use time::{Duration, OffsetDateTime};
use tracing::{info, warn};
const ASSUME_ROLE_ACTION: &str = "AssumeRole";
const ASSUME_ROLE_VERSION: &str = "2011-06-15";
#[derive(Deserialize, Debug, Default)]
#[serde(rename_all = "PascalCase", default)]
pub struct AssumeRoleRequest {
pub action: String,
pub duration_seconds: usize,
pub version: String,
pub role_arn: String,
pub role_session_name: String,
pub policy: String,
pub external_id: String,
}
pub struct AssumeRoleHandle {}
#[async_trait::async_trait]
impl Operation for AssumeRoleHandle {
async fn call(&self, req: S3Request<Body>, _params: Params<'_, '_>) -> S3Result<S3Response<(StatusCode, Body)>> {
warn!("handle AssumeRoleHandle");
let Some(user) = req.credentials else { return Err(s3_error!(InvalidRequest, "get cred failed")) };
let session_token = get_session_token(&req.headers);
if session_token.is_some() {
return Err(s3_error!(InvalidRequest, "AccessDenied1"));
}
let (cred, _owner) = check_key_valid(session_token, &user.access_key).await?;
// // TODO: 判断权限, 不允许sts访问
if cred.is_temp() || cred.is_service_account() {
return Err(s3_error!(InvalidRequest, "AccessDenied"));
}
let mut input = req.input;
let bytes = match input.store_all_unlimited().await {
Ok(b) => b,
Err(e) => {
warn!("get body failed, e: {:?}", e);
return Err(s3_error!(InvalidRequest, "get body failed"));
}
};
let body: AssumeRoleRequest = from_bytes(&bytes).map_err(|_e| s3_error!(InvalidRequest, "get body failed"))?;
if body.action.as_str() != ASSUME_ROLE_ACTION {
return Err(s3_error!(InvalidArgument, "not suport action"));
}
if body.version.as_str() != ASSUME_ROLE_VERSION {
return Err(s3_error!(InvalidArgument, "not suport version"));
}
let mut claims = cred.claims.unwrap_or_default();
populate_session_policy(&mut claims, &body.policy)?;
let exp = {
if body.duration_seconds > 0 {
body.duration_seconds
} else {
3600
}
};
claims.insert(
"exp".to_string(),
serde_json::Value::Number(serde_json::Number::from(OffsetDateTime::now_utc().unix_timestamp() + exp as i64)),
);
claims.insert("parent".to_string(), serde_json::Value::String(cred.access_key.clone()));
// warn!("AssumeRole get cred {:?}", &user);
// warn!("AssumeRole get body {:?}", &body);
let Ok(iam_store) = iam::get() else { return Err(s3_error!(InvalidRequest, "iam not init")) };
if let Err(_err) = iam_store.policy_db_get(&cred.access_key, &cred.groups).await {
return Err(s3_error!(InvalidArgument, "invalid policy arg"));
}
let Some(secret) = get_token_signing_key() else {
return Err(s3_error!(InvalidArgument, "global active sk not init"));
};
info!("AssumeRole get claims {:?}", &claims);
let mut new_cred = get_new_credentials_with_metadata(&claims, &secret)
.map_err(|e| S3Error::with_message(S3ErrorCode::InternalError, format!("get new cred failed {}", e)))?;
new_cred.parent_user = cred.access_key.clone();
info!("AssumeRole get new_cred {:?}", &new_cred);
if let Err(_err) = iam_store.set_temp_user(&new_cred.access_key, &new_cred, None).await {
return Err(s3_error!(InternalError, "set_temp_user failed"));
}
// TODO: globalSiteReplicationSys
let resp = AssumeRoleOutput {
credentials: Some(Credentials {
access_key_id: new_cred.access_key,
expiration: Timestamp::from(
new_cred
.expiration
.unwrap_or(OffsetDateTime::now_utc().saturating_add(Duration::seconds(3600))),
),
secret_access_key: new_cred.secret_key,
session_token: new_cred.session_token,
}),
..Default::default()
};
// getAssumeRoleCredentials
let output = xml::serialize::<AssumeRoleOutput>(&resp).unwrap();
Ok(S3Response::new((StatusCode::OK, Body::from(output))))
}
}
+2 -2
View File
@@ -7,7 +7,7 @@ use common::error::Result;
use handlers::{
group, policy,
service_account::{AddServiceAccount, DeleteServiceAccount, InfoServiceAccount, ListServiceAccount, UpdateServiceAccount},
user,
sts, user,
};
use hyper::Method;
use router::{AdminOperation, S3Router};
@@ -19,7 +19,7 @@ pub fn make_admin_route() -> Result<impl S3Route> {
let mut r: S3Router<AdminOperation> = S3Router::new();
// 1
r.insert(Method::POST, "/", AdminOperation(&handlers::AssumeRoleHandle {}))?;
r.insert(Method::POST, "/", AdminOperation(&sts::AssumeRoleHandle {}))?;
regist_user_route(&mut r)?;
+2 -6
View File
@@ -1,4 +1,3 @@
use log::warn;
use s3s::auth::S3Auth;
use s3s::auth::SecretKey;
use s3s::auth::SimpleAuth;
@@ -20,22 +19,19 @@ impl IAMAuth {
impl S3Auth for IAMAuth {
async fn get_secret_key(&self, access_key: &str) -> S3Result<SecretKey> {
if access_key.is_empty() {
return Err(s3_error!(NotSignedUp, "Your account is not signed up"));
return Err(s3_error!(UnauthorizedAccess, "Your account is not signed up"));
}
if let Ok(key) = self.simple_auth.get_secret_key(access_key).await {
return Ok(key);
}
warn!("Failed to get secret key from simple auth");
if let Ok(iam_store) = iam::get() {
if let Some(id) = iam_store.get_user(access_key).await {
warn!("get cred {:?}", id.credentials);
return Ok(SecretKey::from(id.credentials.secret_key.clone()));
}
}
Err(s3_error!(NotSignedUp, "Your account is not signed up2"))
Err(s3_error!(UnauthorizedAccess, "Your account is not signed up2"))
}
}
+4 -1
View File
@@ -35,13 +35,16 @@ const LONG_VERSION: &str = concat!(
#[command(version = SHORT_VERSION, long_version = LONG_VERSION)]
pub struct Opt {
/// DIR points to a directory on a filesystem.
#[arg(required = true)]
#[arg(required = true, env = "RUSTFS_VOLUMES")]
pub volumes: Vec<String>,
/// bind to a specific ADDRESS:PORT, ADDRESS can be an IP or hostname
#[arg(long, default_value_t = format!("0.0.0.0:{}", DEFAULT_PORT), env = "RUSTFS_ADDRESS")]
pub address: String,
#[arg(long, env = "RUSTFS_SERVER_DOMAINS")]
pub server_domains: Vec<String>,
/// Access key used for authentication.
#[arg(long, default_value_t = DEFAULT_ACCESS_KEY.to_string(), env = "RUSTFS_ACCESS_KEY")]
pub access_key: String,
+94 -8
View File
@@ -6,16 +6,32 @@ use axum::{
Router,
};
use include_dir::{include_dir, Dir};
use mime_guess::from_path;
use rust_embed::RustEmbed;
use serde::Serialize;
static STATIC_DIR: Dir = include_dir!("$CARGO_MANIFEST_DIR/static");
#[derive(RustEmbed)]
#[folder = "$CARGO_MANIFEST_DIR/static"]
struct StaticFiles;
async fn static_handler(uri: axum::http::Uri) -> impl IntoResponse {
let path = uri.path().trim_start_matches('/');
if let Some(file) = STATIC_DIR.get_file(path) {
let mut path = uri.path().trim_start_matches('/');
if path.is_empty() {
path = "index.html"
}
if let Some(file) = StaticFiles::get(path) {
let mime_type = from_path(path).first_or_octet_stream();
Response::builder()
.status(StatusCode::OK)
.body(Body::from(file.contents()))
.header("Content-Type", mime_type.to_string())
.body(Body::from(file.data))
.unwrap()
} else if let Some(file) = StaticFiles::get("index.html") {
let mime_type = from_path("index.html").first_or_octet_stream();
Response::builder()
.status(StatusCode::OK)
.header("Content-Type", mime_type.to_string())
.body(Body::from(file.data))
.unwrap()
} else {
Response::builder()
@@ -25,13 +41,83 @@ async fn static_handler(uri: axum::http::Uri) -> impl IntoResponse {
}
}
pub async fn start_static_file_server(addrs: &str) {
#[derive(Debug, Serialize)]
struct Config {
api: Api,
s3: S3,
release: Release,
license: License,
}
impl Config {
fn new(url: &str, version: &str, date: &str) -> Self {
Config {
api: Api {
base_url: format!("{}/rustfs/admin/v3", url),
},
s3: S3 {
endpoint: url.to_owned(),
region: "cn-east-1".to_owned(),
},
release: Release {
version: version.to_string(),
date: date.to_string(),
},
license: License {
name: "Apache-2.0".to_string(),
url: "https://www.apache.org/licenses/LICENSE-2.0".to_string(),
},
}
}
fn to_json(&self) -> String {
serde_json::to_string(self).unwrap_or_default()
}
}
#[derive(Debug, Serialize)]
struct Api {
#[serde(rename = "baseURL")]
base_url: String,
}
#[derive(Debug, Serialize)]
struct S3 {
endpoint: String,
region: String,
}
#[derive(Debug, Serialize)]
struct Release {
version: String,
date: String,
}
#[derive(Debug, Serialize)]
struct License {
name: String,
url: String,
}
async fn config_handler(axum::extract::Extension(fs_addr): axum::extract::Extension<String>) -> impl IntoResponse {
let cfg = Config::new(&fs_addr, "v0.0.1", "2025-01-01").to_json();
Response::builder()
.header("content-type", "application/json")
.status(StatusCode::OK)
.body(Body::from(cfg))
.unwrap()
}
pub async fn start_static_file_server(addrs: &str, fs_addr: &str) {
// 创建路由
let app = Router::new().route("/*file", get(static_handler));
let app = Router::new()
.route("/config.json", get(config_handler).layer(axum::extract::Extension(fs_addr.to_owned())))
.nest_service("/", get(static_handler));
let listener = tokio::net::TcpListener::bind(addrs).await.unwrap();
println!("console listening on: {}", listener.local_addr().unwrap());
println!("console running on: http://{} with s3 api {}", listener.local_addr().unwrap(), fs_addr);
axum::serve(listener, app).await.unwrap();
}
+1 -4
View File
@@ -67,10 +67,7 @@ fn match_for_io_error(err_status: &Status) -> Option<&std::io::Error> {
}
}
err = match err.source() {
Some(err) => err,
None => return None,
};
err = err.source()?;
}
}
+13 -2
View File
@@ -31,7 +31,7 @@ use hyper_util::{
};
use iam::init_iam_sys;
use protos::proto_gen::node_service::node_service_server::NodeServiceServer;
use s3s::service::S3ServiceBuilder;
use s3s::{host::MultiDomain, service::S3ServiceBuilder};
use service::hybrid;
use std::{io::IsTerminal, net::SocketAddr};
use tokio::net::TcpListener;
@@ -139,6 +139,11 @@ async fn run(opt: config::Opt) -> Result<()> {
b.set_route(admin::make_admin_route()?);
if !opt.server_domains.is_empty() {
info!("virtual-hosted-style requests are enabled use domain_name {:?}", &opt.server_domains);
b.set_host(MultiDomain::new(&opt.server_domains)?);
}
// // Enable parsing virtual-hosted-style requests
// if let Some(dm) = opt.domain_name {
// info!("virtual-hosted-style requests are enabled use domain_name {}", &dm);
@@ -236,7 +241,13 @@ async fn run(opt: config::Opt) -> Result<()> {
if opt.console_enable {
info!("console is enabled");
tokio::spawn(async move {
console::start_static_file_server(&opt.console_address).await;
let ep = if !opt.server_domains.is_empty() {
format!("http://{}", opt.server_domains[0].clone())
} else {
format!("http://127.0.0.1:{}", server_port)
};
console::start_static_file_server(&opt.console_address, &ep).await;
});
}
+1 -1
View File
@@ -52,7 +52,7 @@ impl S3Access for FS {
);
if cx.credentials().is_none() {
return Err(s3_error!(AccessDenied, "Signature is required"));
return Err(s3_error!(UnauthorizedAccess, "Signature is required"));
};
// TODO: FIXME: check auth
+14 -4
View File
@@ -47,6 +47,7 @@ use s3s::S3;
use s3s::{S3Request, S3Response};
use std::fmt::Debug;
use std::str::FromStr;
use tracing::debug;
use tracing::error;
use tracing::info;
use transform_stream::AsyncTryStream;
@@ -549,6 +550,7 @@ impl S3 for FS {
.map(|v| Bucket {
creation_date: v.created.map(Timestamp::from),
name: Some(v.name.clone()),
..Default::default()
})
.collect();
@@ -751,6 +753,7 @@ impl S3 for FS {
content_length,
tagging,
metadata,
version_id,
..
} = input;
@@ -784,10 +787,12 @@ impl S3 for FS {
metadata.insert(xhttp::AMZ_OBJECT_TAGGING.to_owned(), tags);
}
let opts: ObjectOptions = put_opts(&bucket, &key, None, &req.headers, Some(metadata))
let opts: ObjectOptions = put_opts(&bucket, &key, version_id, &req.headers, Some(metadata))
.await
.map_err(to_s3_error)?;
debug!("put_object opts {:?}", &opts);
let obj_info = store
.put_object(&bucket, &key, &mut reader, &opts)
.await
@@ -810,7 +815,11 @@ impl S3 for FS {
req: S3Request<CreateMultipartUploadInput>,
) -> S3Result<S3Response<CreateMultipartUploadOutput>> {
let CreateMultipartUploadInput {
bucket, key, tagging, ..
bucket,
key,
tagging,
version_id,
..
} = req.input;
// mc cp step 3
@@ -827,7 +836,7 @@ impl S3 for FS {
metadata.insert(xhttp::AMZ_OBJECT_TAGGING.to_owned(), tags);
}
let opts: ObjectOptions = put_opts(&bucket, &key, None, &req.headers, Some(metadata))
let opts: ObjectOptions = put_opts(&bucket, &key, version_id, &req.headers, Some(metadata))
.await
.map_err(to_s3_error)?;
@@ -952,7 +961,7 @@ impl S3 for FS {
return Err(S3Error::with_message(S3ErrorCode::InternalError, "Not init".to_string()));
};
store
let oi = store
.complete_multipart_upload(&bucket, &key, &upload_id, uploaded_parts, opts)
.await
.map_err(to_s3_error)?;
@@ -960,6 +969,7 @@ impl S3 for FS {
let output = CompleteMultipartUploadOutput {
bucket: Some(bucket),
key: Some(key),
e_tag: oi.etag,
..Default::default()
};
Ok(S3Response::new(output))
-1
View File
@@ -1 +0,0 @@
static index
+1
View File
@@ -0,0 +1 @@
console static path, do not delete
+11
View File
@@ -0,0 +1,11 @@
#!/bin/bash -ex
BIN=$1
VOLUME=$2
chmod +x $BIN
sudo mkdir -p $VOLUME
export RUST_LOG="rustfs=debug,ecstore=debug,s3s=debug,iam=debug"
export RUST_BACKTRACE=full
sudo nohup $BIN $VOLUME > /tmp/rustfs.log 2>&1 &
+10 -14
View File
@@ -7,10 +7,11 @@ fi
current_dir=$(pwd)
mkdir -p ./target/volume/test
mkdir -p ./target/volume/test{0..4}
# mkdir -p ./target/volume/test{0..4}
if [ -z "$RUST_LOG" ]; then
export RUST_BACKTRACE=1
export RUST_LOG="rustfs=debug,ecstore=debug,s3s=debug,iam=debug"
fi
@@ -18,21 +19,16 @@ fi
# export RUSTFS_STORAGE_CLASS_INLINE_BLOCK="512 KB"
# DATA_DIR_ARG="./target/volume/test{0...4}"
DATA_DIR_ARG="./target/volume/test"
# RUSTFS_VOLUMES="./target/volume/test{0...4}"
export RUSTFS_VOLUMES="./target/volume/test"
export RUSTFS_ADDRESS="0.0.0.0:9000"
export RUSTFS_CONSOLE_ENABLE=true
export RUSTFS_CONSOLE_ADDRESS="0.0.0.0:9002"
# export RUSTFS_SERVER_DOMAINS="localhost:9000"
if [ -n "$1" ]; then
DATA_DIR_ARG="$1"
export RUSTFS_VOLUMES="$1"
fi
# cargo run "$DATA_DIR_ARG"
# -- --access-key AKEXAMPLERUSTFS \
# --secret-key SKEXAMPLERUSTFS \
# --address 0.0.0.0:9010 \
# --domain-name 127.0.0.1:9010 \
# "$DATA_DIR_ARG"
./target/debug/rustfs "$DATA_DIR_ARG"
# cargo run ./target/volume/test
cargo run --bin rustfs