Skip to content

Commit c891335

Browse files
committed
feat: add S3 authentication and fix Docker multi-segment routes
S3 Storage: - Implement AWS Signature v4 for S3-compatible storage (MinIO, AWS) - Add s3_access_key, s3_secret_key, s3_region config options - Support both authenticated and anonymous S3 access - Add proper URI encoding for S3 canonical requests Docker Registry: - Fix routing for multi-segment image names (e.g., library/alpine) - Add namespace routes for two-segment paths (/v2/{ns}/{name}/...) - Add debug tracing for upstream proxy operations Config: - Add NORA_STORAGE_S3_ACCESS_KEY env var - Add NORA_STORAGE_S3_SECRET_KEY env var - Add NORA_STORAGE_S3_REGION env var (default: us-east-1)
1 parent 7c26048 commit c891335

File tree

9 files changed

+489
-225
lines changed

9 files changed

+489
-225
lines changed

Cargo.lock

Lines changed: 38 additions & 20 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,5 @@ tracing-subscriber = { version = "0.3", features = ["env-filter", "json"] }
2424
reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls"] }
2525
sha2 = "0.10"
2626
async-trait = "0.1"
27+
hmac = "0.12"
28+
hex = "0.4"

nora-registry/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ tracing-subscriber.workspace = true
2424
reqwest.workspace = true
2525
sha2.workspace = true
2626
async-trait.workspace = true
27+
hmac.workspace = true
28+
hex.workspace = true
2729
toml = "0.8"
2830
uuid = { version = "1", features = ["v4"] }
2931
bcrypt = "0.17"

nora-registry/src/config.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,19 @@ pub struct StorageConfig {
5353
pub s3_url: String,
5454
#[serde(default = "default_bucket")]
5555
pub bucket: String,
56+
/// S3 access key (optional, uses anonymous access if not set)
57+
#[serde(default)]
58+
pub s3_access_key: Option<String>,
59+
/// S3 secret key (optional, uses anonymous access if not set)
60+
#[serde(default)]
61+
pub s3_secret_key: Option<String>,
62+
/// S3 region (default: us-east-1)
63+
#[serde(default = "default_s3_region")]
64+
pub s3_region: String,
65+
}
66+
67+
fn default_s3_region() -> String {
68+
"us-east-1".to_string()
5669
}
5770

5871
fn default_storage_path() -> String {
@@ -325,6 +338,15 @@ impl Config {
325338
if let Ok(val) = env::var("NORA_STORAGE_BUCKET") {
326339
self.storage.bucket = val;
327340
}
341+
if let Ok(val) = env::var("NORA_STORAGE_S3_ACCESS_KEY") {
342+
self.storage.s3_access_key = if val.is_empty() { None } else { Some(val) };
343+
}
344+
if let Ok(val) = env::var("NORA_STORAGE_S3_SECRET_KEY") {
345+
self.storage.s3_secret_key = if val.is_empty() { None } else { Some(val) };
346+
}
347+
if let Ok(val) = env::var("NORA_STORAGE_S3_REGION") {
348+
self.storage.s3_region = val;
349+
}
328350

329351
// Auth config
330352
if let Ok(val) = env::var("NORA_AUTH_ENABLED") {
@@ -455,6 +477,9 @@ impl Default for Config {
455477
path: String::from("data/storage"),
456478
s3_url: String::from("http://127.0.0.1:3000"),
457479
bucket: String::from("registry"),
480+
s3_access_key: None,
481+
s3_secret_key: None,
482+
s3_region: String::from("us-east-1"),
458483
},
459484
maven: MavenConfig::default(),
460485
npm: NpmConfig::default(),

nora-registry/src/main.rs

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -104,10 +104,18 @@ async fn main() {
104104
info!(
105105
s3_url = %config.storage.s3_url,
106106
bucket = %config.storage.bucket,
107+
region = %config.storage.s3_region,
108+
has_credentials = config.storage.s3_access_key.is_some(),
107109
"Using S3 storage"
108110
);
109111
}
110-
Storage::new_s3(&config.storage.s3_url, &config.storage.bucket)
112+
Storage::new_s3(
113+
&config.storage.s3_url,
114+
&config.storage.bucket,
115+
&config.storage.s3_region,
116+
config.storage.s3_access_key.as_deref(),
117+
config.storage.s3_secret_key.as_deref(),
118+
)
111119
}
112120
};
113121

@@ -131,7 +139,13 @@ async fn main() {
131139
Some(Commands::Migrate { from, to, dry_run }) => {
132140
let source = match from.as_str() {
133141
"local" => Storage::new_local(&config.storage.path),
134-
"s3" => Storage::new_s3(&config.storage.s3_url, &config.storage.bucket),
142+
"s3" => Storage::new_s3(
143+
&config.storage.s3_url,
144+
&config.storage.bucket,
145+
&config.storage.s3_region,
146+
config.storage.s3_access_key.as_deref(),
147+
config.storage.s3_secret_key.as_deref(),
148+
),
135149
_ => {
136150
error!("Invalid source: '{}'. Use 'local' or 's3'", from);
137151
std::process::exit(1);
@@ -140,7 +154,13 @@ async fn main() {
140154

141155
let dest = match to.as_str() {
142156
"local" => Storage::new_local(&config.storage.path),
143-
"s3" => Storage::new_s3(&config.storage.s3_url, &config.storage.bucket),
157+
"s3" => Storage::new_s3(
158+
&config.storage.s3_url,
159+
&config.storage.bucket,
160+
&config.storage.s3_region,
161+
config.storage.s3_access_key.as_deref(),
162+
config.storage.s3_secret_key.as_deref(),
163+
),
144164
_ => {
145165
error!("Invalid destination: '{}'. Use 'local' or 's3'", to);
146166
std::process::exit(1);

0 commit comments

Comments
 (0)