forked from Deuxfleurs/garage
Compare commits
4 commits
test/disab
...
main
Author | SHA1 | Date | |
---|---|---|---|
8a5bbc3b0b | |||
97f245f218 | |||
8129a98291 | |||
54e02b4c3b |
6 changed files with 131 additions and 36 deletions
86
default.nix
86
default.nix
|
@ -11,14 +11,26 @@ with import ./nix/common.nix;
|
||||||
let
|
let
|
||||||
crossSystem = { config = target; };
|
crossSystem = { config = target; };
|
||||||
in let
|
in let
|
||||||
|
log = v: builtins.trace v v;
|
||||||
|
|
||||||
pkgs = import pkgsSrc {
|
pkgs = import pkgsSrc {
|
||||||
inherit system crossSystem;
|
inherit system crossSystem;
|
||||||
overlays = [ cargo2nixOverlay ];
|
overlays = [ cargo2nixOverlay ];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
Rust and Nix triples are not the same. Cargo2nix has a dedicated library
|
||||||
|
to convert Nix triples to Rust ones. We need this conversion as we want to
|
||||||
|
set later options linked to our (rust) target in a generic way. Not only
|
||||||
|
the triple terminology is different, but also the "roles" are named differently.
|
||||||
|
Nix uses a build/host/target terminology where Nix's "host" maps to Cargo's "target".
|
||||||
|
*/
|
||||||
|
rustTarget = log (pkgs.rustBuilder.rustLib.rustTriple pkgs.stdenv.hostPlatform);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Cargo2nix is built for rustOverlay which installs Rust from Mozilla releases.
|
Cargo2nix is built for rustOverlay which installs Rust from Mozilla releases.
|
||||||
We want our own Rust to avoir incompatibilities, like we had with musl 1.2.0.
|
We want our own Rust to avoid incompatibilities, like we had with musl 1.2.0.
|
||||||
rustc was built with musl < 1.2.0 and nix shipped musl >= 1.2.0 which lead to compilation breakage.
|
rustc was built with musl < 1.2.0 and nix shipped musl >= 1.2.0 which lead to compilation breakage.
|
||||||
So we want a Rust release that is bound to our Nix repository to avoid these problems.
|
So we want a Rust release that is bound to our Nix repository to avoid these problems.
|
||||||
See here for more info: https://musl.libc.org/time64.html
|
See here for more info: https://musl.libc.org/time64.html
|
||||||
|
@ -37,51 +49,69 @@ in let
|
||||||
|
|
||||||
overrides = pkgs.rustBuilder.overrides.all ++ [
|
overrides = pkgs.rustBuilder.overrides.all ++ [
|
||||||
/*
|
/*
|
||||||
We want to inject the git version while keeping the build deterministic.
|
[1] We need to alter Nix hardening to be able to statically compile: PIE,
|
||||||
|
Position Independent Executables seems to be supported only on amd64. Having
|
||||||
|
this flags set either make our executables crash or compile as dynamic on many platforms.
|
||||||
|
In the following section codegenOpts, we reactive it for the supported targets
|
||||||
|
(only amd64 curently) through the `-static-pie` flag. PIE is a feature used
|
||||||
|
by ASLR, which helps mitigate security issues.
|
||||||
|
Learn more about Nix Hardening: https://github.com/NixOS/nixpkgs/blob/master/pkgs/build-support/cc-wrapper/add-hardening.sh
|
||||||
|
|
||||||
|
[2] We want to inject the git version while keeping the build deterministic.
|
||||||
As we do not want to consider the .git folder as part of the input source,
|
As we do not want to consider the .git folder as part of the input source,
|
||||||
we ask the user (the CI often) to pass the value to Nix.
|
we ask the user (the CI often) to pass the value to Nix.
|
||||||
*/
|
*/
|
||||||
(pkgs.rustBuilder.rustLib.makeOverride {
|
(pkgs.rustBuilder.rustLib.makeOverride {
|
||||||
name = "garage";
|
name = "garage";
|
||||||
overrideAttrs = drv: if git_version != null then {
|
overrideAttrs = drv:
|
||||||
preConfigure = ''
|
/* [1] */ { hardeningDisable = [ "pie" ]; }
|
||||||
${drv.preConfigure or ""}
|
//
|
||||||
export GIT_VERSION="${git_version}"
|
/* [2] */ (if git_version != null then {
|
||||||
'';
|
preConfigure = ''
|
||||||
} else {};
|
${drv.preConfigure or ""}
|
||||||
|
export GIT_VERSION="${git_version}"
|
||||||
|
'';
|
||||||
|
} else {});
|
||||||
})
|
})
|
||||||
|
|
||||||
/*
|
|
||||||
On a sandbox pure NixOS environment, /usr/bin/file is not available.
|
|
||||||
This is a known problem: https://github.com/NixOS/nixpkgs/issues/98440
|
|
||||||
We simply patch the file as suggested
|
|
||||||
*/
|
|
||||||
/*(pkgs.rustBuilder.rustLib.makeOverride {
|
|
||||||
name = "libsodium-sys";
|
|
||||||
overrideAttrs = drv: {
|
|
||||||
preConfigure = ''
|
|
||||||
${drv.preConfigure or ""}
|
|
||||||
sed -i 's,/usr/bin/file,${file}/bin/file,g' ./configure
|
|
||||||
'';
|
|
||||||
}
|
|
||||||
})*/
|
|
||||||
];
|
];
|
||||||
|
|
||||||
packageFun = import ./Cargo.nix;
|
packageFun = import ./Cargo.nix;
|
||||||
|
|
||||||
|
/*
|
||||||
|
We compile fully static binaries with musl to simplify deployment on most systems.
|
||||||
|
When possible, we reactivate PIE hardening (see above).
|
||||||
|
|
||||||
|
Also, if you set the RUSTFLAGS environment variable, the following parameters will
|
||||||
|
be ignored.
|
||||||
|
|
||||||
|
For more information on static builds, please refer to Rust's RFC 1721.
|
||||||
|
https://rust-lang.github.io/rfcs/1721-crt-static.html#specifying-dynamicstatic-c-runtime-linkage
|
||||||
|
*/
|
||||||
|
|
||||||
|
codegenOpts = {
|
||||||
|
"armv6l-unknown-linux-musleabihf" = [ "target-feature=+crt-static" "link-arg=-static" ]; /* compile as dynamic with static-pie */
|
||||||
|
"aarch64-unknown-linux-musl" = [ "target-feature=+crt-static" "link-arg=-static" ]; /* segfault with static-pie */
|
||||||
|
"i686-unknown-linux-musl" = [ "target-feature=+crt-static" "link-arg=-static" ]; /* segfault with static-pie */
|
||||||
|
"x86_64-unknown-linux-musl" = [ "target-feature=+crt-static" "link-arg=-static-pie" ];
|
||||||
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
The following definition is not elegant as we use a low level function of Cargo2nix
|
The following definition is not elegant as we use a low level function of Cargo2nix
|
||||||
that enables us to pass our custom rustChannel object
|
that enables us to pass our custom rustChannel object. We need this low level definition
|
||||||
|
to pass Nix's Rust toolchains instead of Mozilla's one.
|
||||||
|
|
||||||
|
target is mandatory but must be kept to null to allow cargo2nix to set it to the appropriate value
|
||||||
|
for each crate.
|
||||||
*/
|
*/
|
||||||
rustPkgs = pkgs.rustBuilder.makePackageSet {
|
rustPkgs = pkgs.rustBuilder.makePackageSet {
|
||||||
inherit packageFun rustChannel release;
|
inherit packageFun rustChannel release codegenOpts;
|
||||||
packageOverrides = overrides;
|
packageOverrides = overrides;
|
||||||
target = null; /* we set target to null because we want that cargo2nix computes it automatically */
|
target = null;
|
||||||
|
|
||||||
buildRustPackages = pkgs.buildPackages.rustBuilder.makePackageSet {
|
buildRustPackages = pkgs.buildPackages.rustBuilder.makePackageSet {
|
||||||
inherit rustChannel packageFun;
|
inherit rustChannel packageFun codegenOpts;
|
||||||
packageOverrides = overrides;
|
packageOverrides = overrides;
|
||||||
target = null; /* we set target to null because we want that cargo2nix computes it automatically */
|
target = null;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -111,9 +111,13 @@ async fn handler_inner(garage: Arc<Garage>, req: Request<Body>) -> Result<Respon
|
||||||
let (endpoint, bucket_name) = Endpoint::from_request(&req, bucket_name.map(ToOwned::to_owned))?;
|
let (endpoint, bucket_name) = Endpoint::from_request(&req, bucket_name.map(ToOwned::to_owned))?;
|
||||||
debug!("Endpoint: {:?}", endpoint);
|
debug!("Endpoint: {:?}", endpoint);
|
||||||
|
|
||||||
if let Endpoint::PostObject {} = endpoint {
|
// Some endpoints are processed early, before we even check for an API key
|
||||||
|
if let Endpoint::PostObject = endpoint {
|
||||||
return handle_post_object(garage, req, bucket_name.unwrap()).await;
|
return handle_post_object(garage, req, bucket_name.unwrap()).await;
|
||||||
}
|
}
|
||||||
|
if let Endpoint::Options = endpoint {
|
||||||
|
return handle_options_s3api(garage, &req, bucket_name).await;
|
||||||
|
}
|
||||||
|
|
||||||
let (api_key, content_sha256) = check_payload_signature(&garage, &req).await?;
|
let (api_key, content_sha256) = check_payload_signature(&garage, &req).await?;
|
||||||
let api_key = api_key.ok_or_else(|| {
|
let api_key = api_key.ok_or_else(|| {
|
||||||
|
@ -161,7 +165,6 @@ async fn handler_inner(garage: Arc<Garage>, req: Request<Body>) -> Result<Respon
|
||||||
};
|
};
|
||||||
|
|
||||||
let resp = match endpoint {
|
let resp = match endpoint {
|
||||||
Endpoint::Options => handle_options(&req, &bucket).await,
|
|
||||||
Endpoint::HeadObject {
|
Endpoint::HeadObject {
|
||||||
key, part_number, ..
|
key, part_number, ..
|
||||||
} => handle_head(garage, &req, bucket_id, &key, part_number).await,
|
} => handle_head(garage, &req, bucket_id, &key, part_number).await,
|
||||||
|
|
|
@ -100,7 +100,63 @@ pub async fn handle_put_cors(
|
||||||
.body(Body::empty())?)
|
.body(Body::empty())?)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn handle_options(req: &Request<Body>, bucket: &Bucket) -> Result<Response<Body>, Error> {
|
pub async fn handle_options_s3api(
|
||||||
|
garage: Arc<Garage>,
|
||||||
|
req: &Request<Body>,
|
||||||
|
bucket_name: Option<String>,
|
||||||
|
) -> Result<Response<Body>, Error> {
|
||||||
|
// FIXME: CORS rules of buckets with local aliases are
|
||||||
|
// not taken into account.
|
||||||
|
|
||||||
|
// If the bucket name is a global bucket name,
|
||||||
|
// we try to apply the CORS rules of that bucket.
|
||||||
|
// If a user has a local bucket name that has
|
||||||
|
// the same name, its CORS rules won't be applied
|
||||||
|
// and will be shadowed by the rules of the globally
|
||||||
|
// existing bucket (but this is inevitable because
|
||||||
|
// OPTIONS calls are not auhtenticated).
|
||||||
|
if let Some(bn) = bucket_name {
|
||||||
|
let helper = garage.bucket_helper();
|
||||||
|
let bucket_id = helper.resolve_global_bucket_name(&bn).await?;
|
||||||
|
if let Some(id) = bucket_id {
|
||||||
|
let bucket = garage
|
||||||
|
.bucket_table
|
||||||
|
.get(&EmptyKey, &id)
|
||||||
|
.await?
|
||||||
|
.filter(|b| !b.state.is_deleted())
|
||||||
|
.ok_or(Error::NoSuchBucket)?;
|
||||||
|
handle_options_for_bucket(req, &bucket)
|
||||||
|
} else {
|
||||||
|
// If there is a bucket name in the request, but that name
|
||||||
|
// does not correspond to a global alias for a bucket,
|
||||||
|
// then it's either a non-existing bucket or a local bucket.
|
||||||
|
// We have no way of knowing, because the request is not
|
||||||
|
// authenticated and thus we can't resolve local aliases.
|
||||||
|
// We take the permissive approach of allowing everything,
|
||||||
|
// because we don't want to prevent web apps that use
|
||||||
|
// local bucket names from making API calls.
|
||||||
|
Ok(Response::builder()
|
||||||
|
.header(ACCESS_CONTROL_ALLOW_ORIGIN, "*")
|
||||||
|
.header(ACCESS_CONTROL_ALLOW_METHODS, "*")
|
||||||
|
.status(StatusCode::OK)
|
||||||
|
.body(Body::empty())?)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// If there is no bucket name in the request,
|
||||||
|
// we are doing a ListBuckets call, which we want to allow
|
||||||
|
// for all origins.
|
||||||
|
Ok(Response::builder()
|
||||||
|
.header(ACCESS_CONTROL_ALLOW_ORIGIN, "*")
|
||||||
|
.header(ACCESS_CONTROL_ALLOW_METHODS, "GET")
|
||||||
|
.status(StatusCode::OK)
|
||||||
|
.body(Body::empty())?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_options_for_bucket(
|
||||||
|
req: &Request<Body>,
|
||||||
|
bucket: &Bucket,
|
||||||
|
) -> Result<Response<Body>, Error> {
|
||||||
let origin = req
|
let origin = req
|
||||||
.headers()
|
.headers()
|
||||||
.get("Origin")
|
.get("Origin")
|
||||||
|
|
|
@ -414,8 +414,7 @@ pub enum Endpoint {
|
||||||
// It's intended to be used with HTML forms, using a multipart/form-data body.
|
// It's intended to be used with HTML forms, using a multipart/form-data body.
|
||||||
// It works a lot like presigned requests, but everything is in the form instead
|
// It works a lot like presigned requests, but everything is in the form instead
|
||||||
// of being query parameters of the URL, so authenticating it is a bit different.
|
// of being query parameters of the URL, so authenticating it is a bit different.
|
||||||
PostObject {
|
PostObject,
|
||||||
},
|
|
||||||
}}
|
}}
|
||||||
|
|
||||||
impl Endpoint {
|
impl Endpoint {
|
||||||
|
@ -430,7 +429,11 @@ impl Endpoint {
|
||||||
let path = uri.path().trim_start_matches('/');
|
let path = uri.path().trim_start_matches('/');
|
||||||
let query = uri.query();
|
let query = uri.query();
|
||||||
if bucket.is_none() && path.is_empty() {
|
if bucket.is_none() && path.is_empty() {
|
||||||
return Ok((Self::ListBuckets, None));
|
if *req.method() == Method::OPTIONS {
|
||||||
|
return Ok((Self::Options, None));
|
||||||
|
} else {
|
||||||
|
return Ok((Self::ListBuckets, None));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let (bucket, key) = if let Some(bucket) = bucket {
|
let (bucket, key) = if let Some(bucket) = bucket {
|
||||||
|
|
|
@ -60,6 +60,9 @@ pub async fn check_payload_signature(
|
||||||
let (_, scope) = parse_credential(&authorization.credential)?;
|
let (_, scope) = parse_credential(&authorization.credential)?;
|
||||||
let string_to_sign = string_to_sign(&authorization.date, &scope, &canonical_request);
|
let string_to_sign = string_to_sign(&authorization.date, &scope, &canonical_request);
|
||||||
|
|
||||||
|
trace!("canonical request:\n{}", canonical_request);
|
||||||
|
trace!("string to sign:\n{}", string_to_sign);
|
||||||
|
|
||||||
let key = verify_v4(
|
let key = verify_v4(
|
||||||
garage,
|
garage,
|
||||||
&authorization.credential,
|
&authorization.credential,
|
||||||
|
|
|
@ -13,7 +13,7 @@ use crate::error::*;
|
||||||
|
|
||||||
use garage_api::error::{Error as ApiError, OkOrBadRequest, OkOrInternalError};
|
use garage_api::error::{Error as ApiError, OkOrBadRequest, OkOrInternalError};
|
||||||
use garage_api::helpers::{authority_to_host, host_to_bucket};
|
use garage_api::helpers::{authority_to_host, host_to_bucket};
|
||||||
use garage_api::s3_cors::{add_cors_headers, find_matching_cors_rule, handle_options};
|
use garage_api::s3_cors::{add_cors_headers, find_matching_cors_rule, handle_options_for_bucket};
|
||||||
use garage_api::s3_get::{handle_get, handle_head};
|
use garage_api::s3_get::{handle_get, handle_head};
|
||||||
|
|
||||||
use garage_model::garage::Garage;
|
use garage_model::garage::Garage;
|
||||||
|
@ -133,7 +133,7 @@ async fn serve_file(garage: Arc<Garage>, req: &Request<Body>) -> Result<Response
|
||||||
);
|
);
|
||||||
|
|
||||||
let ret_doc = match *req.method() {
|
let ret_doc = match *req.method() {
|
||||||
Method::OPTIONS => handle_options(req, &bucket).await,
|
Method::OPTIONS => handle_options_for_bucket(req, &bucket),
|
||||||
Method::HEAD => handle_head(garage.clone(), req, bucket_id, &key, None).await,
|
Method::HEAD => handle_head(garage.clone(), req, bucket_id, &key, None).await,
|
||||||
Method::GET => handle_get(garage.clone(), req, bucket_id, &key, None).await,
|
Method::GET => handle_get(garage.clone(), req, bucket_id, &key, None).await,
|
||||||
_ => Err(ApiError::BadRequest("HTTP method not supported".into())),
|
_ => Err(ApiError::BadRequest("HTTP method not supported".into())),
|
||||||
|
|
Loading…
Reference in a new issue