Compare commits

...

27 Commits

Author SHA1 Message Date
Alex 083e982f5f Merge pull request 'Garage v0.8.5' (#687) from rel-0.8.5 into main-0.8.x
Reviewed-on: https://git.deuxfleurs.fr/Deuxfleurs/garage/pulls/687
2024-01-16 10:30:54 +00:00
Alex Auvolat 50643e61bf Bump version to 0.8.5 2024-01-16 10:47:33 +01:00
Alex a6421ee5a5 Merge pull request 'monitoring: finer histogram boundaries in prometheus metrics (fix #531)' (#686) from fix-531 into main-0.8.x
Reviewed-on: https://git.deuxfleurs.fr/Deuxfleurs/garage/pulls/686
2024-01-15 16:44:58 +00:00
Alex 993ce74976 Merge pull request '0.8.x: config: refactor secret sourcing' (#685) from secret-sourcing into main-0.8.x
Reviewed-on: https://git.deuxfleurs.fr/Deuxfleurs/garage/pulls/685
2024-01-15 16:41:50 +00:00
Alex Auvolat f512609123 monitoring: finer histogram boundaries in prometheus metrics (fix #531) 2024-01-15 17:33:35 +01:00
Alex Auvolat 97bae7213a config: additional tests for secret sourcing 2024-01-15 17:30:30 +01:00
Alex Auvolat 7228695ee2 config: refactor secret sourcing 2024-01-15 17:18:46 +01:00
Alex ee7fe27d3d Merge pull request 'Add allow_world_readable_secrets option to config file' (#663) from PicNoir/garage:nin/world-readable-conf-file into main-0.8.x
Reviewed-on: https://git.deuxfleurs.fr/Deuxfleurs/garage/pulls/663
2024-01-15 15:20:16 +00:00
Félix Baylac Jacqué f83fa02193 Add allow_world_readable_secrets option to config file
Sometimes, the secret files permissions checks gets in the way. It's
by no mean complete, it doesn't take the Posix ACLs into account among
other things. Correctly checking the ACLs would be too involving (see
https://git.deuxfleurs.fr/Deuxfleurs/garage/issues/658#issuecomment-7102)
and would likely still fail in some weird chmod settings.

We're adding a new configuration file key allowing the user to disable
this permission check altogether.

The (already existing) env variable counterpart always take precedence
to this config file option. That's useful in cases where the
configuration file is static and cannot be easily altered.

Fixes https://git.deuxfleurs.fr/Deuxfleurs/garage/issues/658

Co-authored-by: Florian Klink <flokli@flokli.de>
2023-10-26 18:25:13 +02:00
Alex 4b3dee2ca3 Merge pull request 's3 api: add missing CORS headers to PostObject responses (fix #609)' (#656) from fix-cors-post-object into main-0.8.x
Reviewed-on: https://git.deuxfleurs.fr/Deuxfleurs/garage/pulls/656
2023-10-26 09:17:14 +00:00
Alex Auvolat ac04934dae s3 api: add missing CORS headers to PostObject responses (fix #609) 2023-10-20 10:37:48 +02:00
Alex 0215b11402 Merge pull request 'Add support for specifying rpc_secret_file, metrics_token_file and admin_token_file using environment variables' (#643) from networkException/garage:token-file-env into main-0.8.x
Reviewed-on: https://git.deuxfleurs.fr/Deuxfleurs/garage/pulls/643
2023-10-19 09:33:12 +00:00
networkException 8599051c49 garage: support specifying token / secret as environment variables
this patch adds support for specifying the `rpc_secret_file`,
`metrics_token_file` and `admin_token_file` as environment variables.
2023-10-19 03:39:02 +02:00
networkException 4a19ee94bb garage: fix admin-token description 2023-10-19 03:31:50 +02:00
networkException c99cb58d71 util: move reading secret file into seperate helper
this patch moves the logic to read a secret file (and check for correct
permissions) from `secret_from_file` into a new `read_secret_file`
helper.
2023-10-19 03:29:48 +02:00
networkException 5feb6a1f64 docs: add documentation for specifying token / secret file as environment variables 2023-10-19 03:28:44 +02:00
Alex e75fe2157d Merge pull request 'Move convert_db command into main garage binary' (#645) from convert-db-main-binary into main
Reviewed-on: https://git.deuxfleurs.fr/Deuxfleurs/garage/pulls/645
2023-10-10 11:42:14 +00:00
Alex Auvolat 2d5d7a7031 Move convert_db command into main garage binary 2023-10-10 12:13:15 +02:00
Alex 1243db87f2 Merge pull request 'Add support for binding to unix domain sockets' (#640) from networkException/garage:unix-sockets into main
Reviewed-on: https://git.deuxfleurs.fr/Deuxfleurs/garage/pulls/640
2023-10-03 16:23:02 +00:00
networkException 6f8a87814b doc: add documentation for specifying unix socket paths 2023-10-03 17:56:34 +02:00
networkException 7907a09acc api: allow custom unix bind mode and use 0o220 for admin server 2023-10-03 17:31:40 +02:00
Alex 16aa418e47 Merge pull request 'doc: update endpoint_url documentation' (#641) from flokli/garage:aws-endpoint-url into main
Reviewed-on: https://git.deuxfleurs.fr/Deuxfleurs/garage/pulls/641
2023-10-02 14:30:53 +00:00
Florian Klink cb359b4434 doc: update endpoint_url documentation
Since `awscli` `>=1.29.0` or `>=2.13.0` it is now possible to use the
`AWS_ENDPOINT_URL` environment variable, or the `endpoint_url` config
key to override the endpoint URL. This means, the aws bash function to
wrap with --endpoint-url is not necessary anymore. Update invocations to
reflect that.

https://docs.aws.amazon.com/sdkref/latest/guide/feature-ss-endpoints.html
https://github.com/aws/aws-cli/issues/4454#issuecomment-1626116607
2023-10-02 17:16:11 +03:00
networkException 8ec6a53b35 everywhere: support unix sockets when binding in various places
this patch implements binding to paths as a unix socket for generic
server and web server.
2023-09-29 18:57:44 +02:00
networkException 7353038a64 config: allow using paths for unix domain sockets in various places
this patch updates the config format to also allow paths in bind
addresses for unix domain sockets.

this has been added to all apis except rpc.
2023-09-29 18:38:30 +02:00
networkException 10195f1567 util: add helper sum type for unix and tcp socket addresses
this patch introduces a new sum type that can represent either a
tcp socket address or a unix domain socket path.
2023-09-29 18:37:36 +02:00
networkException 6086a3fa07 cargo: add hyperlocal as a dependency 2023-09-29 18:37:12 +02:00
34 changed files with 718 additions and 346 deletions
Generated
+26 -9
View File
@@ -1198,7 +1198,7 @@ dependencies = [
[[package]]
name = "garage"
version = "0.8.4"
version = "0.8.5"
dependencies = [
"assert-json-diff",
"async-trait",
@@ -1227,6 +1227,7 @@ dependencies = [
"hyper",
"k2v-client",
"kuska-sodiumoxide",
"mktemp",
"netapp",
"opentelemetry",
"opentelemetry-otlp",
@@ -1249,7 +1250,7 @@ dependencies = [
[[package]]
name = "garage_api"
version = "0.8.4"
version = "0.8.5"
dependencies = [
"async-trait",
"base64 0.21.3",
@@ -1271,6 +1272,7 @@ dependencies = [
"http-range",
"httpdate",
"hyper",
"hyperlocal",
"idna",
"md-5",
"multer",
@@ -1294,7 +1296,7 @@ dependencies = [
[[package]]
name = "garage_block"
version = "0.8.4"
version = "0.8.5"
dependencies = [
"arc-swap",
"async-compression",
@@ -1319,7 +1321,7 @@ dependencies = [
[[package]]
name = "garage_db"
version = "0.8.4"
version = "0.8.5"
dependencies = [
"clap 4.4.0",
"err-derive",
@@ -1334,7 +1336,7 @@ dependencies = [
[[package]]
name = "garage_model"
version = "0.8.4"
version = "0.8.5"
dependencies = [
"arc-swap",
"async-trait",
@@ -1361,7 +1363,7 @@ dependencies = [
[[package]]
name = "garage_rpc"
version = "0.8.4"
version = "0.8.5"
dependencies = [
"arc-swap",
"async-trait",
@@ -1393,7 +1395,7 @@ dependencies = [
[[package]]
name = "garage_table"
version = "0.8.4"
version = "0.8.5"
dependencies = [
"arc-swap",
"async-trait",
@@ -1415,7 +1417,7 @@ dependencies = [
[[package]]
name = "garage_util"
version = "0.8.4"
version = "0.8.5"
dependencies = [
"arc-swap",
"async-trait",
@@ -1449,7 +1451,7 @@ dependencies = [
[[package]]
name = "garage_web"
version = "0.8.4"
version = "0.8.5"
dependencies = [
"err-derive",
"futures",
@@ -1459,8 +1461,10 @@ dependencies = [
"garage_util",
"http",
"hyper",
"hyperlocal",
"opentelemetry",
"percent-encoding",
"tokio",
"tracing",
]
@@ -1770,6 +1774,19 @@ dependencies = [
"tokio-io-timeout",
]
[[package]]
name = "hyperlocal"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fafdf7b2b2de7c9784f76e02c0935e65a8117ec3b768644379983ab333ac98c"
dependencies = [
"futures-util",
"hex",
"hyper",
"pin-project",
"tokio",
]
[[package]]
name = "iana-time-zone"
version = "0.1.57"
+81 -60
View File
@@ -33,7 +33,7 @@ args@{
ignoreLockHash,
}:
let
nixifiedLockHash = "ab8dba64b487c83fa25ebafbad99538d2f38afa5b53f83e4e031b7909665166b";
nixifiedLockHash = "79d4a9e02f28a0c0f31576781738e132ac96ff4944b3bc2ca7375c6dd15b83ce";
workspaceSrc = if args.workspaceSrc == null then ./. else args.workspaceSrc;
currentLockHash = builtins.hashFile "sha256" (workspaceSrc + /Cargo.lock);
lockHashIgnored = if ignoreLockHash
@@ -57,15 +57,15 @@ in
{
cargo2nixVersion = "0.11.0";
workspace = {
garage_db = rustPackages.unknown.garage_db."0.8.4";
garage_util = rustPackages.unknown.garage_util."0.8.4";
garage_rpc = rustPackages.unknown.garage_rpc."0.8.4";
garage_table = rustPackages.unknown.garage_table."0.8.4";
garage_block = rustPackages.unknown.garage_block."0.8.4";
garage_model = rustPackages.unknown.garage_model."0.8.4";
garage_api = rustPackages.unknown.garage_api."0.8.4";
garage_web = rustPackages.unknown.garage_web."0.8.4";
garage = rustPackages.unknown.garage."0.8.4";
garage_db = rustPackages.unknown.garage_db."0.8.5";
garage_util = rustPackages.unknown.garage_util."0.8.5";
garage_rpc = rustPackages.unknown.garage_rpc."0.8.5";
garage_table = rustPackages.unknown.garage_table."0.8.5";
garage_block = rustPackages.unknown.garage_block."0.8.5";
garage_model = rustPackages.unknown.garage_model."0.8.5";
garage_api = rustPackages.unknown.garage_api."0.8.5";
garage_web = rustPackages.unknown.garage_web."0.8.5";
garage = rustPackages.unknown.garage."0.8.5";
format_table = rustPackages.unknown.format_table."0.1.1";
k2v-client = rustPackages.unknown.k2v-client."0.0.4";
};
@@ -1705,9 +1705,9 @@ in
};
});
"unknown".garage."0.8.4" = overridableMkRustCrate (profileName: rec {
"unknown".garage."0.8.5" = overridableMkRustCrate (profileName: rec {
name = "garage";
version = "0.8.4";
version = "0.8.5";
registry = "unknown";
src = fetchCrateLocal (workspaceSrc + "/src/garage");
features = builtins.concatLists [
@@ -1734,14 +1734,14 @@ in
format_table = (rustPackages."unknown".format_table."0.1.1" { inherit profileName; }).out;
futures = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".futures."0.3.28" { inherit profileName; }).out;
futures_util = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".futures-util."0.3.28" { inherit profileName; }).out;
garage_api = (rustPackages."unknown".garage_api."0.8.4" { inherit profileName; }).out;
garage_block = (rustPackages."unknown".garage_block."0.8.4" { inherit profileName; }).out;
garage_db = (rustPackages."unknown".garage_db."0.8.4" { inherit profileName; }).out;
garage_model = (rustPackages."unknown".garage_model."0.8.4" { inherit profileName; }).out;
garage_rpc = (rustPackages."unknown".garage_rpc."0.8.4" { inherit profileName; }).out;
garage_table = (rustPackages."unknown".garage_table."0.8.4" { inherit profileName; }).out;
garage_util = (rustPackages."unknown".garage_util."0.8.4" { inherit profileName; }).out;
garage_web = (rustPackages."unknown".garage_web."0.8.4" { inherit profileName; }).out;
garage_api = (rustPackages."unknown".garage_api."0.8.5" { inherit profileName; }).out;
garage_block = (rustPackages."unknown".garage_block."0.8.5" { inherit profileName; }).out;
garage_db = (rustPackages."unknown".garage_db."0.8.5" { inherit profileName; }).out;
garage_model = (rustPackages."unknown".garage_model."0.8.5" { inherit profileName; }).out;
garage_rpc = (rustPackages."unknown".garage_rpc."0.8.5" { inherit profileName; }).out;
garage_table = (rustPackages."unknown".garage_table."0.8.5" { inherit profileName; }).out;
garage_util = (rustPackages."unknown".garage_util."0.8.5" { inherit profileName; }).out;
garage_web = (rustPackages."unknown".garage_web."0.8.5" { inherit profileName; }).out;
git_version = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".git-version."0.3.5" { inherit profileName; }).out;
hex = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".hex."0.4.3" { inherit profileName; }).out;
sodiumoxide = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".kuska-sodiumoxide."0.2.5-0" { inherit profileName; }).out;
@@ -1771,15 +1771,16 @@ in
http = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".http."0.2.9" { inherit profileName; }).out;
hyper = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".hyper."0.14.27" { inherit profileName; }).out;
k2v_client = (rustPackages."unknown".k2v-client."0.0.4" { inherit profileName; }).out;
mktemp = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".mktemp."0.5.0" { inherit profileName; }).out;
serde_json = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".serde_json."1.0.105" { inherit profileName; }).out;
sha2 = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".sha2."0.10.7" { inherit profileName; }).out;
static_init = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".static_init."1.0.3" { inherit profileName; }).out;
};
});
"unknown".garage_api."0.8.4" = overridableMkRustCrate (profileName: rec {
"unknown".garage_api."0.8.5" = overridableMkRustCrate (profileName: rec {
name = "garage_api";
version = "0.8.4";
version = "0.8.5";
registry = "unknown";
src = fetchCrateLocal (workspaceSrc + "/src/api");
features = builtins.concatLists [
@@ -1798,17 +1799,18 @@ in
form_urlencoded = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".form_urlencoded."1.2.0" { inherit profileName; }).out;
futures = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".futures."0.3.28" { inherit profileName; }).out;
futures_util = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".futures-util."0.3.28" { inherit profileName; }).out;
garage_block = (rustPackages."unknown".garage_block."0.8.4" { inherit profileName; }).out;
garage_model = (rustPackages."unknown".garage_model."0.8.4" { inherit profileName; }).out;
garage_rpc = (rustPackages."unknown".garage_rpc."0.8.4" { inherit profileName; }).out;
garage_table = (rustPackages."unknown".garage_table."0.8.4" { inherit profileName; }).out;
garage_util = (rustPackages."unknown".garage_util."0.8.4" { inherit profileName; }).out;
garage_block = (rustPackages."unknown".garage_block."0.8.5" { inherit profileName; }).out;
garage_model = (rustPackages."unknown".garage_model."0.8.5" { inherit profileName; }).out;
garage_rpc = (rustPackages."unknown".garage_rpc."0.8.5" { inherit profileName; }).out;
garage_table = (rustPackages."unknown".garage_table."0.8.5" { inherit profileName; }).out;
garage_util = (rustPackages."unknown".garage_util."0.8.5" { inherit profileName; }).out;
hex = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".hex."0.4.3" { inherit profileName; }).out;
hmac = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".hmac."0.12.1" { inherit profileName; }).out;
http = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".http."0.2.9" { inherit profileName; }).out;
http_range = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".http-range."0.1.5" { inherit profileName; }).out;
httpdate = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".httpdate."1.0.3" { inherit profileName; }).out;
hyper = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".hyper."0.14.27" { inherit profileName; }).out;
hyperlocal = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".hyperlocal."0.8.0" { inherit profileName; }).out;
idna = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".idna."0.4.0" { inherit profileName; }).out;
md5 = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".md-5."0.10.5" { inherit profileName; }).out;
multer = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".multer."2.1.0" { inherit profileName; }).out;
@@ -1831,9 +1833,9 @@ in
};
});
"unknown".garage_block."0.8.4" = overridableMkRustCrate (profileName: rec {
"unknown".garage_block."0.8.5" = overridableMkRustCrate (profileName: rec {
name = "garage_block";
version = "0.8.4";
version = "0.8.5";
registry = "unknown";
src = fetchCrateLocal (workspaceSrc + "/src/block");
features = builtins.concatLists [
@@ -1846,10 +1848,10 @@ in
bytes = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".bytes."1.4.0" { inherit profileName; }).out;
futures = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".futures."0.3.28" { inherit profileName; }).out;
futures_util = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".futures-util."0.3.28" { inherit profileName; }).out;
garage_db = (rustPackages."unknown".garage_db."0.8.4" { inherit profileName; }).out;
garage_rpc = (rustPackages."unknown".garage_rpc."0.8.4" { inherit profileName; }).out;
garage_table = (rustPackages."unknown".garage_table."0.8.4" { inherit profileName; }).out;
garage_util = (rustPackages."unknown".garage_util."0.8.4" { inherit profileName; }).out;
garage_db = (rustPackages."unknown".garage_db."0.8.5" { inherit profileName; }).out;
garage_rpc = (rustPackages."unknown".garage_rpc."0.8.5" { inherit profileName; }).out;
garage_table = (rustPackages."unknown".garage_table."0.8.5" { inherit profileName; }).out;
garage_util = (rustPackages."unknown".garage_util."0.8.5" { inherit profileName; }).out;
hex = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".hex."0.4.3" { inherit profileName; }).out;
opentelemetry = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".opentelemetry."0.17.0" { inherit profileName; }).out;
rand = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".rand."0.8.5" { inherit profileName; }).out;
@@ -1862,9 +1864,9 @@ in
};
});
"unknown".garage_db."0.8.4" = overridableMkRustCrate (profileName: rec {
"unknown".garage_db."0.8.5" = overridableMkRustCrate (profileName: rec {
name = "garage_db";
version = "0.8.4";
version = "0.8.5";
registry = "unknown";
src = fetchCrateLocal (workspaceSrc + "/src/db");
features = builtins.concatLists [
@@ -1894,9 +1896,9 @@ in
};
});
"unknown".garage_model."0.8.4" = overridableMkRustCrate (profileName: rec {
"unknown".garage_model."0.8.5" = overridableMkRustCrate (profileName: rec {
name = "garage_model";
version = "0.8.4";
version = "0.8.5";
registry = "unknown";
src = fetchCrateLocal (workspaceSrc + "/src/model");
features = builtins.concatLists [
@@ -1914,11 +1916,11 @@ in
err_derive = (buildRustPackages."registry+https://github.com/rust-lang/crates.io-index".err-derive."0.3.1" { profileName = "__noProfile"; }).out;
futures = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".futures."0.3.28" { inherit profileName; }).out;
futures_util = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".futures-util."0.3.28" { inherit profileName; }).out;
garage_block = (rustPackages."unknown".garage_block."0.8.4" { inherit profileName; }).out;
garage_db = (rustPackages."unknown".garage_db."0.8.4" { inherit profileName; }).out;
garage_rpc = (rustPackages."unknown".garage_rpc."0.8.4" { inherit profileName; }).out;
garage_table = (rustPackages."unknown".garage_table."0.8.4" { inherit profileName; }).out;
garage_util = (rustPackages."unknown".garage_util."0.8.4" { inherit profileName; }).out;
garage_block = (rustPackages."unknown".garage_block."0.8.5" { inherit profileName; }).out;
garage_db = (rustPackages."unknown".garage_db."0.8.5" { inherit profileName; }).out;
garage_rpc = (rustPackages."unknown".garage_rpc."0.8.5" { inherit profileName; }).out;
garage_table = (rustPackages."unknown".garage_table."0.8.5" { inherit profileName; }).out;
garage_util = (rustPackages."unknown".garage_util."0.8.5" { inherit profileName; }).out;
hex = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".hex."0.4.3" { inherit profileName; }).out;
netapp = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".netapp."0.5.2" { inherit profileName; }).out;
opentelemetry = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".opentelemetry."0.17.0" { inherit profileName; }).out;
@@ -1931,9 +1933,9 @@ in
};
});
"unknown".garage_rpc."0.8.4" = overridableMkRustCrate (profileName: rec {
"unknown".garage_rpc."0.8.5" = overridableMkRustCrate (profileName: rec {
name = "garage_rpc";
version = "0.8.4";
version = "0.8.5";
registry = "unknown";
src = fetchCrateLocal (workspaceSrc + "/src/rpc");
features = builtins.concatLists [
@@ -1953,8 +1955,8 @@ in
${ if rootFeatures' ? "garage/consul-discovery" || rootFeatures' ? "garage_rpc/consul-discovery" || rootFeatures' ? "garage_rpc/err-derive" then "err_derive" else null } = (buildRustPackages."registry+https://github.com/rust-lang/crates.io-index".err-derive."0.3.1" { profileName = "__noProfile"; }).out;
futures = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".futures."0.3.28" { inherit profileName; }).out;
futures_util = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".futures-util."0.3.28" { inherit profileName; }).out;
garage_db = (rustPackages."unknown".garage_db."0.8.4" { inherit profileName; }).out;
garage_util = (rustPackages."unknown".garage_util."0.8.4" { inherit profileName; }).out;
garage_db = (rustPackages."unknown".garage_db."0.8.5" { inherit profileName; }).out;
garage_util = (rustPackages."unknown".garage_util."0.8.5" { inherit profileName; }).out;
gethostname = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".gethostname."0.4.3" { inherit profileName; }).out;
hex = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".hex."0.4.3" { inherit profileName; }).out;
${ if rootFeatures' ? "garage/kubernetes-discovery" || rootFeatures' ? "garage_rpc/k8s-openapi" || rootFeatures' ? "garage_rpc/kubernetes-discovery" then "k8s_openapi" else null } = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".k8s-openapi."0.16.0" { inherit profileName; }).out;
@@ -1976,9 +1978,9 @@ in
};
});
"unknown".garage_table."0.8.4" = overridableMkRustCrate (profileName: rec {
"unknown".garage_table."0.8.5" = overridableMkRustCrate (profileName: rec {
name = "garage_table";
version = "0.8.4";
version = "0.8.5";
registry = "unknown";
src = fetchCrateLocal (workspaceSrc + "/src/table");
dependencies = {
@@ -1987,9 +1989,9 @@ in
bytes = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".bytes."1.4.0" { inherit profileName; }).out;
futures = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".futures."0.3.28" { inherit profileName; }).out;
futures_util = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".futures-util."0.3.28" { inherit profileName; }).out;
garage_db = (rustPackages."unknown".garage_db."0.8.4" { inherit profileName; }).out;
garage_rpc = (rustPackages."unknown".garage_rpc."0.8.4" { inherit profileName; }).out;
garage_util = (rustPackages."unknown".garage_util."0.8.4" { inherit profileName; }).out;
garage_db = (rustPackages."unknown".garage_db."0.8.5" { inherit profileName; }).out;
garage_rpc = (rustPackages."unknown".garage_rpc."0.8.5" { inherit profileName; }).out;
garage_util = (rustPackages."unknown".garage_util."0.8.5" { inherit profileName; }).out;
hex = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".hex."0.4.3" { inherit profileName; }).out;
hexdump = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".hexdump."0.1.1" { inherit profileName; }).out;
opentelemetry = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".opentelemetry."0.17.0" { inherit profileName; }).out;
@@ -2001,9 +2003,9 @@ in
};
});
"unknown".garage_util."0.8.4" = overridableMkRustCrate (profileName: rec {
"unknown".garage_util."0.8.5" = overridableMkRustCrate (profileName: rec {
name = "garage_util";
version = "0.8.4";
version = "0.8.5";
registry = "unknown";
src = fetchCrateLocal (workspaceSrc + "/src/util");
features = builtins.concatLists [
@@ -2019,7 +2021,7 @@ in
digest = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".digest."0.10.7" { inherit profileName; }).out;
err_derive = (buildRustPackages."registry+https://github.com/rust-lang/crates.io-index".err-derive."0.3.1" { profileName = "__noProfile"; }).out;
futures = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".futures."0.3.28" { inherit profileName; }).out;
garage_db = (rustPackages."unknown".garage_db."0.8.4" { inherit profileName; }).out;
garage_db = (rustPackages."unknown".garage_db."0.8.5" { inherit profileName; }).out;
hex = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".hex."0.4.3" { inherit profileName; }).out;
hexdump = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".hexdump."0.1.1" { inherit profileName; }).out;
http = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".http."0.2.9" { inherit profileName; }).out;
@@ -2045,22 +2047,24 @@ in
};
});
"unknown".garage_web."0.8.4" = overridableMkRustCrate (profileName: rec {
"unknown".garage_web."0.8.5" = overridableMkRustCrate (profileName: rec {
name = "garage_web";
version = "0.8.4";
version = "0.8.5";
registry = "unknown";
src = fetchCrateLocal (workspaceSrc + "/src/web");
dependencies = {
err_derive = (buildRustPackages."registry+https://github.com/rust-lang/crates.io-index".err-derive."0.3.1" { profileName = "__noProfile"; }).out;
futures = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".futures."0.3.28" { inherit profileName; }).out;
garage_api = (rustPackages."unknown".garage_api."0.8.4" { inherit profileName; }).out;
garage_model = (rustPackages."unknown".garage_model."0.8.4" { inherit profileName; }).out;
garage_table = (rustPackages."unknown".garage_table."0.8.4" { inherit profileName; }).out;
garage_util = (rustPackages."unknown".garage_util."0.8.4" { inherit profileName; }).out;
garage_api = (rustPackages."unknown".garage_api."0.8.5" { inherit profileName; }).out;
garage_model = (rustPackages."unknown".garage_model."0.8.5" { inherit profileName; }).out;
garage_table = (rustPackages."unknown".garage_table."0.8.5" { inherit profileName; }).out;
garage_util = (rustPackages."unknown".garage_util."0.8.5" { inherit profileName; }).out;
http = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".http."0.2.9" { inherit profileName; }).out;
hyper = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".hyper."0.14.27" { inherit profileName; }).out;
hyperlocal = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".hyperlocal."0.8.0" { inherit profileName; }).out;
opentelemetry = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".opentelemetry."0.17.0" { inherit profileName; }).out;
percent_encoding = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".percent-encoding."2.3.0" { inherit profileName; }).out;
tokio = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".tokio."1.32.0" { inherit profileName; }).out;
tracing = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".tracing."0.1.37" { inherit profileName; }).out;
};
});
@@ -2486,6 +2490,23 @@ in
};
});
"registry+https://github.com/rust-lang/crates.io-index".hyperlocal."0.8.0" = overridableMkRustCrate (profileName: rec {
name = "hyperlocal";
version = "0.8.0";
registry = "registry+https://github.com/rust-lang/crates.io-index";
src = fetchCratesIo { inherit name version; sha256 = "0fafdf7b2b2de7c9784f76e02c0935e65a8117ec3b768644379983ab333ac98c"; };
features = builtins.concatLists [
[ "server" ]
];
dependencies = {
futures_util = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".futures-util."0.3.28" { inherit profileName; }).out;
hex = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".hex."0.4.3" { inherit profileName; }).out;
hyper = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".hyper."0.14.27" { inherit profileName; }).out;
pin_project = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".pin-project."1.1.3" { inherit profileName; }).out;
tokio = (rustPackages."registry+https://github.com/rust-lang/crates.io-index".tokio."1.32.0" { inherit profileName; }).out;
};
});
"registry+https://github.com/rust-lang/crates.io-index".iana-time-zone."0.1.57" = overridableMkRustCrate (profileName: rec {
name = "iana-time-zone";
version = "0.1.57";
+8 -8
View File
@@ -18,14 +18,14 @@ default-members = ["src/garage"]
[workspace.dependencies]
format_table = { version = "0.1.1", path = "src/format-table" }
garage_api = { version = "0.8.4", path = "src/api" }
garage_block = { version = "0.8.4", path = "src/block" }
garage_db = { version = "0.8.4", path = "src/db", default-features = false }
garage_model = { version = "0.8.4", path = "src/model", default-features = false }
garage_rpc = { version = "0.8.4", path = "src/rpc" }
garage_table = { version = "0.8.4", path = "src/table" }
garage_util = { version = "0.8.4", path = "src/util" }
garage_web = { version = "0.8.4", path = "src/web" }
garage_api = { version = "0.8.5", path = "src/api" }
garage_block = { version = "0.8.5", path = "src/block" }
garage_db = { version = "0.8.5", path = "src/db", default-features = false }
garage_model = { version = "0.8.5", path = "src/model", default-features = false }
garage_rpc = { version = "0.8.5", path = "src/rpc" }
garage_table = { version = "0.8.5", path = "src/table" }
garage_util = { version = "0.8.5", path = "src/util" }
garage_web = { version = "0.8.5", path = "src/web" }
k2v-client = { version = "0.0.4", path = "src/k2v-client" }
[profile.dev]
+2 -2
View File
@@ -421,7 +421,7 @@ Now we can write a simple script (eg `~/.local/bin/matrix-cache-gc`):
## CONFIGURATION ##
AWS_ACCESS_KEY_ID=GKxxx
AWS_SECRET_ACCESS_KEY=xxxx
S3_ENDPOINT=http://localhost:3900
AWS_ENDPOINT_URL=http://localhost:3900
S3_BUCKET=matrix
MEDIA_STORE=/var/lib/matrix-synapse/media
PG_USER=matrix
@@ -442,7 +442,7 @@ EOF
s3_media_upload update-db 1d
s3_media_upload --no-progress check-deleted $MEDIA_STORE
s3_media_upload --no-progress upload $MEDIA_STORE $S3_BUCKET --delete --endpoint-url $S3_ENDPOINT
s3_media_upload --no-progress upload $MEDIA_STORE $S3_BUCKET --delete --endpoint-url $AWS_ENDPOINT_URL
```
This script will list all the medias that were not accessed in the 24 hours according to your database.
+4 -3
View File
@@ -70,16 +70,17 @@ Then a file named `~/.aws/config` and put:
```toml
[default]
region=garage
endpoint_url=http://127.0.0.1:3900
```
Now, supposing Garage is listening on `http://127.0.0.1:3900`, you can list your buckets with:
```bash
aws --endpoint-url http://127.0.0.1:3900 s3 ls
aws s3 ls
```
Passing the `--endpoint-url` parameter to each command is annoying but AWS developers do not provide a corresponding configuration entry.
As a workaround, you can redefine the aws command by editing the file `~/.bashrc`:
If you're using awscli `<1.29.0` or `<2.13.0`, you need to pass `--endpoint-url` to each CLI invocation explicitly.
As a workaround, you can redefine the aws command by editing the file `~/.bashrc` in this case:
```
function aws { command aws --endpoint-url http://127.0.0.1:3900 $@ ; }
+4 -2
View File
@@ -269,12 +269,14 @@ named `~/.awsrc` with this content:
export AWS_ACCESS_KEY_ID=xxxx # put your Key ID here
export AWS_SECRET_ACCESS_KEY=xxxx # put your Secret key here
export AWS_DEFAULT_REGION='garage'
export AWS_ENDPOINT='http://localhost:3900'
export AWS_ENDPOINT_URL='http://localhost:3900'
function aws { command aws --endpoint-url $AWS_ENDPOINT $@ ; }
aws --version
```
Note you need to have at least `awscli` `>=1.29.0` or `>=2.13.0`, otherwise you
need to specify `--endpoint-url` explicitly on each `awscli` invocation.
Now, each time you want to use `awscli` on this target, run:
```bash
+26 -3
View File
@@ -276,7 +276,7 @@ Compression is done synchronously, setting a value too high will add latency to
This value can be different between nodes, compression is done by the node which receive the
API call.
### `rpc_secret`, `rpc_secret_file` or `GARAGE_RPC_SECRET` (env)
### `rpc_secret`, `rpc_secret_file` or `GARAGE_RPC_SECRET`, `GARAGE_RPC_SECRET_FILE` (env)
Garage uses a secret key, called an RPC secret, that is shared between all
nodes of the cluster in order to identify these nodes and allow them to
@@ -288,6 +288,9 @@ Since Garage `v0.8.2`, the RPC secret can also be stored in a file whose path is
given in the configuration variable `rpc_secret_file`, or specified as an
environment variable `GARAGE_RPC_SECRET`.
Since Garage `v0.9.0`, you can also specify the path of a file storing the secret
as the `GARAGE_RPC_SECRET_FILE` environment variable.
### `rpc_bind_addr`
The address and port on which to bind for inter-cluster communcations
@@ -320,6 +323,17 @@ be obtained by running `garage node id` and then included directly in the
key will be returned by `garage node id` and you will have to add the IP
yourself.
### `allow_world_readable_secrets`
Garage checks the permissions of your secret files to make sure they're not
world-readable. In some cases, the check might fail and consider your files as
world-readable even if they're not, for instance when using Posix ACLs.
Setting `allow_world_readable_secrets` to `true` bypass this
permission verification.
Alternatively, you can set the `GARAGE_ALLOW_WORLD_READABLE_SECRETS`
environment variable to `true` to bypass the permissions check.
## The `[consul_discovery]` section
@@ -410,6 +424,8 @@ manually.
The IP and port on which to bind for accepting S3 API calls.
This endpoint does not suport TLS: a reverse proxy should be used to provide it.
Alternatively, since `v0.8.5`, a path can be used to create a unix socket with 0222 mode.
### `s3_region`
Garage will accept S3 API calls that are targetted to the S3 region defined here.
@@ -439,6 +455,8 @@ The IP and port on which to bind for accepting HTTP requests to buckets configur
for website access.
This endpoint does not suport TLS: a reverse proxy should be used to provide it.
Alternatively, since `v0.8.5`, a path can be used to create a unix socket with 0222 mode.
### `root_domain`
The optional suffix appended to bucket names for the corresponding HTTP Host.
@@ -458,7 +476,10 @@ If specified, Garage will bind an HTTP server to this port and address, on
which it will listen to requests for administration features.
See [administration API reference](@/documentation/reference-manual/admin-api.md) to learn more about these features.
### `metrics_token`, `metrics_token_file` or `GARAGE_METRICS_TOKEN` (env)
Alternatively, since `v0.8.5`, a path can be used to create a unix socket. Note that for security reasons,
the socket will have 0220 mode. Make sure to set user and group permissions accordingly.
### `metrics_token`, `metrics_token_file` or `GARAGE_METRICS_TOKEN`, `GARAGE_METRICS_TOKEN_FILE` (env)
The token for accessing the Metrics endpoint. If this token is not set, the
Metrics endpoint can be accessed without access control.
@@ -468,8 +489,9 @@ You can use any random string for this value. We recommend generating a random t
`metrics_token` was introduced in Garage `v0.7.2`.
`metrics_token_file` and the `GARAGE_METRICS_TOKEN` environment variable are supported since Garage `v0.8.2`.
`GARAGE_METRICS_TOKEN_FILE` is supported since `v0.9.0`.
### `admin_token`, `admin_token_file` or `GARAGE_ADMIN_TOKEN` (env)
### `admin_token`, `admin_token_file` or `GARAGE_ADMIN_TOKEN`, `GARAGE_ADMIN_TOKEN_FILE` (env)
The token for accessing all of the other administration endpoints. If this
token is not set, access to these endpoints is disabled entirely.
@@ -479,6 +501,7 @@ You can use any random string for this value. We recommend generating a random t
`admin_token` was introduced in Garage `v0.7.2`.
`admin_token_file` and the `GARAGE_ADMIN_TOKEN` environment variable are supported since Garage `v0.8.2`.
`GARAGE_ADMIN_TOKEN_FILE` is supported since `v0.9.0`.
### `trace_sink`
+1 -1
View File
@@ -1,7 +1,7 @@
export AWS_ACCESS_KEY_ID=`cat /tmp/garage.s3 |cut -d' ' -f1`
export AWS_SECRET_ACCESS_KEY=`cat /tmp/garage.s3 |cut -d' ' -f2`
export AWS_DEFAULT_REGION='garage'
# FUTUREWORK: set AWS_ENDPOINT_URL instead, once nixpkgs bumps awscli to >=2.13.0.
function aws { command aws --endpoint-url http://127.0.0.1:3911 $@ ; }
aws --version
+1 -1
View File
@@ -21,4 +21,4 @@ version: 0.4.1
# incremented each time you make changes to the application. Versions are not expected to
# follow Semantic Versioning. They should reflect the version the application is using.
# It is recommended to use it with quotes.
appVersion: "v0.8.4"
appVersion: "v0.8.5"
+2 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "garage_api"
version = "0.8.4"
version = "0.8.5"
authors = ["Alex Auvolat <alex@adnab.me>"]
edition = "2018"
license = "AGPL-3.0"
@@ -45,6 +45,7 @@ http = "0.2"
httpdate = "1.0"
http-range = "0.1"
hyper = { version = "0.14", features = ["server", "http1", "runtime", "tcp", "stream"] }
hyperlocal = { version = "0.8.0", default-features = false, features = ["server"] }
multer = "2.0"
percent-encoding = "2.1.0"
roxmltree = "0.18"
+3 -3
View File
@@ -1,5 +1,4 @@
use std::collections::HashMap;
use std::net::SocketAddr;
use std::sync::Arc;
use async_trait::async_trait;
@@ -18,6 +17,7 @@ use prometheus::{Encoder, TextEncoder};
use garage_model::garage::Garage;
use garage_rpc::system::ClusterHealthStatus;
use garage_util::error::Error as GarageError;
use garage_util::socket_address::UnixOrTCPSocketAddress;
use crate::generic_server::*;
@@ -61,12 +61,12 @@ impl AdminApiServer {
pub async fn run(
self,
bind_addr: SocketAddr,
bind_addr: UnixOrTCPSocketAddress,
shutdown_signal: impl Future<Output = ()>,
) -> Result<(), GarageError> {
let region = self.garage.config.s3_api.s3_region.clone();
ApiServer::new(region, self)
.run_server(bind_addr, shutdown_signal)
.run_server(bind_addr, Some(0o220), shutdown_signal)
.await
}
+51 -9
View File
@@ -1,4 +1,5 @@
use std::net::SocketAddr;
use std::fs::{self, Permissions};
use std::os::unix::fs::PermissionsExt;
use std::sync::Arc;
use async_trait::async_trait;
@@ -11,6 +12,10 @@ use hyper::service::{make_service_fn, service_fn};
use hyper::{Body, Request, Response, Server};
use hyper::{HeaderMap, StatusCode};
use hyperlocal::UnixServerExt;
use tokio::net::UnixStream;
use opentelemetry::{
global,
metrics::{Counter, ValueRecorder},
@@ -21,6 +26,7 @@ use opentelemetry::{
use garage_util::error::Error as GarageError;
use garage_util::forwarded_headers;
use garage_util::metrics::{gen_trace_id, RecordDuration};
use garage_util::socket_address::UnixOrTCPSocketAddress;
pub(crate) trait ApiEndpoint: Send + Sync + 'static {
fn name(&self) -> &'static str;
@@ -91,10 +97,11 @@ impl<A: ApiHandler> ApiServer<A> {
pub async fn run_server(
self: Arc<Self>,
bind_addr: SocketAddr,
bind_addr: UnixOrTCPSocketAddress,
unix_bind_addr_mode: Option<u32>,
shutdown_signal: impl Future<Output = ()>,
) -> Result<(), GarageError> {
let service = make_service_fn(|conn: &AddrStream| {
let tcp_service = make_service_fn(|conn: &AddrStream| {
let this = self.clone();
let client_addr = conn.remote_addr();
@@ -102,28 +109,63 @@ impl<A: ApiHandler> ApiServer<A> {
Ok::<_, GarageError>(service_fn(move |req: Request<Body>| {
let this = this.clone();
this.handler(req, client_addr)
this.handler(req, client_addr.to_string())
}))
}
});
let server = Server::bind(&bind_addr).serve(service);
let unix_service = make_service_fn(|_: &UnixStream| {
let this = self.clone();
let path = bind_addr.to_string();
async move {
Ok::<_, GarageError>(service_fn(move |req: Request<Body>| {
let this = this.clone();
this.handler(req, path.clone())
}))
}
});
let graceful = server.with_graceful_shutdown(shutdown_signal);
info!(
"{} API server listening on http://{}",
"{} API server listening on {}",
A::API_NAME_DISPLAY,
bind_addr
);
graceful.await?;
match bind_addr {
UnixOrTCPSocketAddress::TCPSocket(addr) => {
Server::bind(&addr)
.serve(tcp_service)
.with_graceful_shutdown(shutdown_signal)
.await?
}
UnixOrTCPSocketAddress::UnixSocket(ref path) => {
if path.exists() {
fs::remove_file(path)?
}
let bound = Server::bind_unix(path)?;
fs::set_permissions(
path,
Permissions::from_mode(unix_bind_addr_mode.unwrap_or(0o222)),
)?;
bound
.serve(unix_service)
.with_graceful_shutdown(shutdown_signal)
.await?;
}
};
Ok(())
}
async fn handler(
self: Arc<Self>,
req: Request<Body>,
addr: SocketAddr,
addr: String,
) -> Result<Response<Body>, GarageError> {
let uri = req.uri().clone();
+3 -3
View File
@@ -1,4 +1,3 @@
use std::net::SocketAddr;
use std::sync::Arc;
use async_trait::async_trait;
@@ -9,6 +8,7 @@ use hyper::{Body, Method, Request, Response};
use opentelemetry::{trace::SpanRef, KeyValue};
use garage_util::error::Error as GarageError;
use garage_util::socket_address::UnixOrTCPSocketAddress;
use garage_model::garage::Garage;
@@ -37,12 +37,12 @@ pub(crate) struct K2VApiEndpoint {
impl K2VApiServer {
pub async fn run(
garage: Arc<Garage>,
bind_addr: SocketAddr,
bind_addr: UnixOrTCPSocketAddress,
s3_region: String,
shutdown_signal: impl Future<Output = ()>,
) -> Result<(), GarageError> {
ApiServer::new(s3_region, K2VApiServer { garage })
.run_server(bind_addr, shutdown_signal)
.run_server(bind_addr, None, shutdown_signal)
.await
}
}
+3 -3
View File
@@ -1,4 +1,3 @@
use std::net::SocketAddr;
use std::sync::Arc;
use async_trait::async_trait;
@@ -10,6 +9,7 @@ use hyper::{Body, Request, Response};
use opentelemetry::{trace::SpanRef, KeyValue};
use garage_util::error::Error as GarageError;
use garage_util::socket_address::UnixOrTCPSocketAddress;
use garage_model::garage::Garage;
use garage_model::key_table::Key;
@@ -44,12 +44,12 @@ pub(crate) struct S3ApiEndpoint {
impl S3ApiServer {
pub async fn run(
garage: Arc<Garage>,
addr: SocketAddr,
addr: UnixOrTCPSocketAddress,
s3_region: String,
shutdown_signal: impl Future<Output = ()>,
) -> Result<(), GarageError> {
ApiServer::new(s3_region, S3ApiServer { garage })
.run_server(addr, shutdown_signal)
.run_server(addr, None, shutdown_signal)
.await
}
+10 -3
View File
@@ -15,6 +15,7 @@ use serde::Deserialize;
use garage_model::garage::Garage;
use crate::s3::cors::*;
use crate::s3::error::*;
use crate::s3::put::{get_headers, save_stream};
use crate::s3::xml as s3_xml;
@@ -242,7 +243,7 @@ pub async fn handle_post_object(
let etag = format!("\"{}\"", md5);
let resp = if let Some(mut target) = params
let mut resp = if let Some(mut target) = params
.get("success_action_redirect")
.and_then(|h| h.to_str().ok())
.and_then(|u| url::Url::parse(u).ok())
@@ -262,8 +263,7 @@ pub async fn handle_post_object(
} else {
let path = head
.uri
.into_parts()
.path_and_query
.path_and_query()
.map(|paq| paq.path().to_string())
.unwrap_or_else(|| "/".to_string());
let authority = head
@@ -308,6 +308,13 @@ pub async fn handle_post_object(
}
};
let matching_cors_rule =
find_matching_cors_rule(&bucket, &Request::from_parts(head, Body::empty()))?;
if let Some(rule) = matching_cors_rule {
add_cors_headers(&mut resp, rule)
.ok_or_internal_error("Invalid bucket CORS configuration")?;
}
Ok(resp)
}
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "garage_block"
version = "0.8.4"
version = "0.8.5"
authors = ["Alex Auvolat <alex@adnab.me>"]
edition = "2018"
license = "AGPL-3.0"
+1 -6
View File
@@ -1,6 +1,6 @@
[package]
name = "garage_db"
version = "0.8.4"
version = "0.8.5"
authors = ["Alex Auvolat <alex@adnab.me>"]
edition = "2018"
license = "AGPL-3.0"
@@ -11,11 +11,6 @@ readme = "../../README.md"
[lib]
path = "lib.rs"
[[bin]]
name = "convert"
path = "bin/convert.rs"
required-features = ["cli"]
[dependencies]
err-derive = "0.3"
hexdump = "0.1"
+2 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "garage"
version = "0.8.4"
version = "0.8.5"
authors = ["Alex Auvolat <alex@adnab.me>"]
edition = "2018"
license = "AGPL-3.0"
@@ -67,6 +67,7 @@ chrono = "0.4"
http = "0.2"
hmac = "0.12"
hyper = { version = "0.14", features = ["client", "http1", "runtime"] }
mktemp = "0.5"
sha2 = "0.10"
static_init = "1.0"
@@ -1,39 +1,30 @@
use std::path::PathBuf;
use structopt::StructOpt;
use garage_db::*;
use clap::Parser;
/// K2V command line interface
#[derive(Parser, Debug)]
#[clap(author, version, about, long_about = None)]
struct Args {
/// Input DB path
#[clap(short = 'i')]
#[derive(StructOpt, Debug)]
pub struct ConvertDbOpt {
/// Input database path (not the same as metadata_dir, see
/// https://garagehq.deuxfleurs.fr/documentation/reference-manual/configuration/#db-engine-since-v0-8-0)
#[structopt(short = "i")]
input_path: PathBuf,
/// Input DB engine
#[clap(short = 'a')]
/// Input database engine (sled, lmdb or sqlite; limited by db engines
/// enabled in this build)
#[structopt(short = "a")]
input_engine: String,
/// Output DB path
#[clap(short = 'o')]
/// Output database path
#[structopt(short = "o")]
output_path: PathBuf,
/// Output DB engine
#[clap(short = 'b')]
/// Output database engine
#[structopt(short = "b")]
output_engine: String,
}
fn main() {
let args = Args::parse();
pretty_env_logger::init();
match do_conversion(args) {
Ok(()) => println!("Success!"),
Err(e) => eprintln!("Error: {}", e),
}
}
fn do_conversion(args: Args) -> Result<()> {
pub(crate) fn do_conversion(args: ConvertDbOpt) -> Result<()> {
let input = open_db(args.input_path, args.input_engine)?;
let output = open_db(args.output_path, args.output_engine)?;
output.import(&input)?;
@@ -42,14 +33,17 @@ fn do_conversion(args: Args) -> Result<()> {
fn open_db(path: PathBuf, engine: String) -> Result<Db> {
match engine.as_str() {
#[cfg(feature = "sled")]
"sled" => {
let db = sled_adapter::sled::Config::default().path(&path).open()?;
Ok(sled_adapter::SledDb::init(db))
}
#[cfg(feature = "sqlite")]
"sqlite" | "sqlite3" | "rusqlite" => {
let db = sqlite_adapter::rusqlite::Connection::open(&path)?;
Ok(sqlite_adapter::SqliteDb::init(db))
}
#[cfg(feature = "lmdb")]
"lmdb" | "heed" => {
std::fs::create_dir_all(&path).map_err(|e| {
Error(format!("Unable to create LMDB data directory: {}", e).into())
@@ -64,6 +58,8 @@ fn open_db(path: PathBuf, engine: String) -> Result<Db> {
.unwrap();
Ok(lmdb_adapter::LmdbDb::init(db))
}
e => Err(Error(format!("Invalid DB engine: {}", e).into())),
e => Err(Error(
format!("Invalid or unsupported DB engine: {}", e).into(),
)),
}
}
+2
View File
@@ -4,6 +4,8 @@ pub(crate) mod layout;
pub(crate) mod structs;
pub(crate) mod util;
pub(crate) mod convert_db;
pub(crate) use cmd::*;
pub(crate) use init::*;
pub(crate) use layout::*;
+6
View File
@@ -3,6 +3,8 @@ use structopt::StructOpt;
use garage_util::version::garage_version;
use crate::cli::convert_db;
#[derive(StructOpt, Debug)]
pub enum Command {
/// Run Garage server
@@ -54,6 +56,10 @@ pub enum Command {
/// Low-level debug operations on data blocks
#[structopt(name = "block", version = garage_version())]
Block(BlockOperation),
/// Convert metadata db between database engine formats
#[structopt(name = "convert-db", version = garage_version())]
ConvertDb(convert_db::ConvertDbOpt),
}
#[derive(StructOpt, Debug)]
+5 -32
View File
@@ -7,6 +7,7 @@ extern crate tracing;
mod admin;
mod cli;
mod repair;
mod secrets;
mod server;
#[cfg(feature = "telemetry-otlp")]
mod tracing_setup;
@@ -25,7 +26,6 @@ use structopt::StructOpt;
use netapp::util::parse_and_resolve_peer_addr;
use netapp::NetworkKey;
use garage_util::config::Config;
use garage_util::error::*;
use garage_rpc::system::*;
@@ -35,6 +35,7 @@ use garage_model::helper::error::Error as HelperError;
use admin::*;
use cli::*;
use secrets::Secrets;
#[derive(StructOpt, Debug)]
#[structopt(
@@ -63,24 +64,6 @@ struct Opt {
cmd: Command,
}
#[derive(StructOpt, Debug)]
pub struct Secrets {
/// RPC secret network key, used to replace rpc_secret in config.toml when running the
/// daemon or doing admin operations
#[structopt(short = "s", long = "rpc-secret", env = "GARAGE_RPC_SECRET")]
pub rpc_secret: Option<String>,
/// Metrics API authentication token, replaces admin.metrics_token in config.toml when
/// running the Garage daemon
#[structopt(long = "admin-token", env = "GARAGE_ADMIN_TOKEN")]
pub admin_token: Option<String>,
/// Metrics API authentication token, replaces admin.metrics_token in config.toml when
/// running the Garage daemon
#[structopt(long = "metrics-token", env = "GARAGE_METRICS_TOKEN")]
pub metrics_token: Option<String>,
}
#[tokio::main]
async fn main() {
// Initialize version and features info
@@ -173,6 +156,9 @@ async fn main() {
Command::OfflineRepair(repair_opt) => {
repair::offline::offline_repair(opt.config_file, opt.secrets, repair_opt).await
}
Command::ConvertDb(conv_opt) => {
cli::convert_db::do_conversion(conv_opt).map_err(From::from)
}
Command::Node(NodeOperation::NodeId(node_id_opt)) => {
node_id_command(opt.config_file, node_id_opt.quiet)
}
@@ -255,16 +241,3 @@ async fn cli_command(opt: Opt) -> Result<(), Error> {
Ok(x) => Ok(x),
}
}
fn fill_secrets(mut config: Config, secrets: Secrets) -> Config {
if secrets.rpc_secret.is_some() {
config.rpc_secret = secrets.rpc_secret;
}
if secrets.admin_token.is_some() {
config.admin.admin_token = secrets.admin_token;
}
if secrets.metrics_token.is_some() {
config.admin.metrics_token = secrets.metrics_token;
}
config
}
+2 -2
View File
@@ -6,7 +6,7 @@ use garage_util::error::*;
use garage_model::garage::Garage;
use crate::cli::structs::*;
use crate::{fill_secrets, Secrets};
use crate::secrets::{fill_secrets, Secrets};
pub async fn offline_repair(
config_file: PathBuf,
@@ -20,7 +20,7 @@ pub async fn offline_repair(
}
info!("Loading configuration...");
let config = fill_secrets(read_config(config_file)?, secrets);
let config = fill_secrets(read_config(config_file)?, secrets)?;
info!("Initializing Garage main data store...");
let garage = Garage::new(config)?;
+318
View File
@@ -0,0 +1,318 @@
use structopt::StructOpt;
use garage_util::config::Config;
use garage_util::error::Error;
/// Structure for secret values or paths that are passed as CLI arguments or environment
/// variables, instead of in the config file.
#[derive(StructOpt, Debug, Default, Clone)]
pub struct Secrets {
/// Skip permission check on files containing secrets
#[cfg(unix)]
#[structopt(
long = "allow-world-readable-secrets",
env = "GARAGE_ALLOW_WORLD_READABLE_SECRETS"
)]
pub allow_world_readable_secrets: Option<bool>,
/// RPC secret network key, used to replace rpc_secret in config.toml when running the
/// daemon or doing admin operations
#[structopt(short = "s", long = "rpc-secret", env = "GARAGE_RPC_SECRET")]
pub rpc_secret: Option<String>,
/// RPC secret network key, used to replace rpc_secret in config.toml and rpc-secret
/// when running the daemon or doing admin operations
#[structopt(long = "rpc-secret-file", env = "GARAGE_RPC_SECRET_FILE")]
pub rpc_secret_file: Option<String>,
/// Admin API authentication token, replaces admin.admin_token in config.toml when
/// running the Garage daemon
#[structopt(long = "admin-token", env = "GARAGE_ADMIN_TOKEN")]
pub admin_token: Option<String>,
/// Admin API authentication token file path, replaces admin.admin_token in config.toml
/// and admin-token when running the Garage daemon
#[structopt(long = "admin-token-file", env = "GARAGE_ADMIN_TOKEN_FILE")]
pub admin_token_file: Option<String>,
/// Metrics API authentication token, replaces admin.metrics_token in config.toml when
/// running the Garage daemon
#[structopt(long = "metrics-token", env = "GARAGE_METRICS_TOKEN")]
pub metrics_token: Option<String>,
/// Metrics API authentication token file path, replaces admin.metrics_token in config.toml
/// and metrics-token when running the Garage daemon
#[structopt(long = "metrics-token-file", env = "GARAGE_METRICS_TOKEN_FILE")]
pub metrics_token_file: Option<String>,
}
/// Single function to fill all secrets in the Config struct from their correct source (value
/// from config or CLI param or env variable or read from a file specified in config or CLI
/// param or env variable)
pub fn fill_secrets(mut config: Config, secrets: Secrets) -> Result<Config, Error> {
let allow_world_readable = secrets
.allow_world_readable_secrets
.unwrap_or(config.allow_world_readable_secrets);
fill_secret(
&mut config.rpc_secret,
&config.rpc_secret_file,
&secrets.rpc_secret,
&secrets.rpc_secret_file,
"rpc_secret",
allow_world_readable,
)?;
fill_secret(
&mut config.admin.admin_token,
&config.admin.admin_token_file,
&secrets.admin_token,
&secrets.admin_token_file,
"admin.admin_token",
allow_world_readable,
)?;
fill_secret(
&mut config.admin.metrics_token,
&config.admin.metrics_token_file,
&secrets.metrics_token,
&secrets.metrics_token_file,
"admin.metrics_token",
allow_world_readable,
)?;
Ok(config)
}
fn fill_secret(
config_secret: &mut Option<String>,
config_secret_file: &Option<String>,
cli_secret: &Option<String>,
cli_secret_file: &Option<String>,
name: &'static str,
allow_world_readable: bool,
) -> Result<(), Error> {
let cli_value = match (&cli_secret, &cli_secret_file) {
(Some(_), Some(_)) => {
return Err(format!("only one of `{}` and `{}_file` can be set", name, name).into());
}
(Some(secret), None) => Some(secret.to_string()),
(None, Some(file)) => Some(read_secret_file(file, allow_world_readable)?),
(None, None) => None,
};
if let Some(val) = cli_value {
if config_secret.is_some() || config_secret_file.is_some() {
debug!("Overriding secret `{}` using value specified using CLI argument or environnement variable.", name);
}
*config_secret = Some(val);
} else if let Some(file_path) = &config_secret_file {
if config_secret.is_some() {
return Err(format!("only one of `{}` and `{}_file` can be set", name, name).into());
}
*config_secret = Some(read_secret_file(file_path, allow_world_readable)?);
}
Ok(())
}
fn read_secret_file(file_path: &String, allow_world_readable: bool) -> Result<String, Error> {
if !allow_world_readable {
#[cfg(unix)]
{
use std::os::unix::fs::MetadataExt;
let metadata = std::fs::metadata(file_path)?;
if metadata.mode() & 0o077 != 0 {
return Err(format!("File {} is world-readable! (mode: 0{:o}, expected 0600)\nRefusing to start until this is fixed, or environment variable GARAGE_ALLOW_WORLD_READABLE_SECRETS is set to true.", file_path, metadata.mode()).into());
}
}
}
let secret_buf = std::fs::read_to_string(file_path)?;
// trim_end: allows for use case such as `echo "$(openssl rand -hex 32)" > somefile`.
// also editors sometimes add a trailing newline
Ok(String::from(secret_buf.trim_end()))
}
#[cfg(test)]
mod tests {
use std::fs::File;
use std::io::Write;
use garage_util::config::read_config;
use garage_util::error::Error;
use super::*;
#[test]
fn test_rpc_secret_file_works() -> Result<(), Error> {
let path_secret = mktemp::Temp::new_file()?;
let mut file_secret = File::create(path_secret.as_path())?;
writeln!(file_secret, "foo")?;
drop(file_secret);
let path_config = mktemp::Temp::new_file()?;
let mut file_config = File::create(path_config.as_path())?;
let path_secret_path = path_secret.as_path();
writeln!(
file_config,
r#"
metadata_dir = "/tmp/garage/meta"
data_dir = "/tmp/garage/data"
replication_mode = "3"
rpc_bind_addr = "[::]:3901"
rpc_secret_file = "{}"
[s3_api]
s3_region = "garage"
api_bind_addr = "[::]:3900"
"#,
path_secret_path.display()
)?;
drop(file_config);
// Second configuration file, same as previous one
// except it allows world-readable secrets.
let path_config_allow_world_readable = mktemp::Temp::new_file()?;
let mut file_config_allow_world_readable =
File::create(path_config_allow_world_readable.as_path())?;
writeln!(
file_config_allow_world_readable,
r#"
metadata_dir = "/tmp/garage/meta"
data_dir = "/tmp/garage/data"
replication_mode = "3"
rpc_bind_addr = "[::]:3901"
rpc_secret_file = "{}"
allow_world_readable_secrets = true
[s3_api]
s3_region = "garage"
api_bind_addr = "[::]:3900"
"#,
path_secret_path.display()
)?;
drop(file_config_allow_world_readable);
let config = read_config(path_config.to_path_buf())?;
let config = fill_secrets(config, Secrets::default())?;
assert_eq!("foo", config.rpc_secret.unwrap());
// ---- Check non world-readable secrets config ----
#[cfg(unix)]
{
let secrets_allow_world_readable = Secrets {
allow_world_readable_secrets: Some(true),
..Default::default()
};
let secrets_no_allow_world_readable = Secrets {
allow_world_readable_secrets: Some(false),
..Default::default()
};
use std::os::unix::fs::PermissionsExt;
let metadata = std::fs::metadata(&path_secret_path)?;
let mut perm = metadata.permissions();
perm.set_mode(0o660);
std::fs::set_permissions(&path_secret_path, perm)?;
// Config file that just specifies the path
let config = read_config(path_config.to_path_buf())?;
assert!(fill_secrets(config, Secrets::default()).is_err());
let config = read_config(path_config.to_path_buf())?;
assert!(fill_secrets(config, secrets_allow_world_readable.clone()).is_ok());
let config = read_config(path_config.to_path_buf())?;
assert!(fill_secrets(config, secrets_no_allow_world_readable.clone()).is_err());
// Config file that also specifies to allow world_readable_secrets
let config = read_config(path_config_allow_world_readable.to_path_buf())?;
assert!(fill_secrets(config, Secrets::default()).is_ok());
let config = read_config(path_config_allow_world_readable.to_path_buf())?;
assert!(fill_secrets(config, secrets_allow_world_readable).is_ok());
let config = read_config(path_config_allow_world_readable.to_path_buf())?;
assert!(fill_secrets(config, secrets_no_allow_world_readable).is_err());
}
// ---- Check alternative secrets specified on CLI ----
let path_secret2 = mktemp::Temp::new_file()?;
let mut file_secret2 = File::create(path_secret2.as_path())?;
writeln!(file_secret2, "bar")?;
drop(file_secret2);
let config = read_config(path_config.to_path_buf())?;
let config = fill_secrets(
config,
Secrets {
rpc_secret: Some("baz".into()),
..Default::default()
},
)?;
assert_eq!(config.rpc_secret.as_deref(), Some("baz"));
let config = read_config(path_config.to_path_buf())?;
let config = fill_secrets(
config,
Secrets {
rpc_secret_file: Some(path_secret2.display().to_string()),
..Default::default()
},
)?;
assert_eq!(config.rpc_secret.as_deref(), Some("bar"));
let config = read_config(path_config.to_path_buf())?;
assert!(fill_secrets(
config,
Secrets {
rpc_secret: Some("baz".into()),
rpc_secret_file: Some(path_secret2.display().to_string()),
..Default::default()
}
)
.is_err());
drop(path_secret);
drop(path_secret2);
drop(path_config);
drop(path_config_allow_world_readable);
Ok(())
}
#[test]
fn test_rcp_secret_and_rpc_secret_file_cannot_be_set_both() -> Result<(), Error> {
let path_config = mktemp::Temp::new_file()?;
let mut file_config = File::create(path_config.as_path())?;
writeln!(
file_config,
r#"
metadata_dir = "/tmp/garage/meta"
data_dir = "/tmp/garage/data"
replication_mode = "3"
rpc_bind_addr = "[::]:3901"
rpc_secret= "dummy"
rpc_secret_file = "dummy"
[s3_api]
s3_region = "garage"
api_bind_addr = "[::]:3900"
"#
)?;
let config = read_config(path_config.to_path_buf())?;
assert_eq!(
"only one of `rpc_secret` and `rpc_secret_file` can be set",
fill_secrets(config, Secrets::default())
.unwrap_err()
.to_string()
);
drop(path_config);
drop(file_config);
Ok(())
}
}
+16 -7
View File
@@ -15,9 +15,9 @@ use garage_web::WebServer;
use garage_api::k2v::api_server::K2VApiServer;
use crate::admin::*;
use crate::secrets::{fill_secrets, Secrets};
#[cfg(feature = "telemetry-otlp")]
use crate::tracing_setup::*;
use crate::{fill_secrets, Secrets};
async fn wait_from(mut chan: watch::Receiver<bool>) {
while !*chan.borrow() {
@@ -29,12 +29,19 @@ async fn wait_from(mut chan: watch::Receiver<bool>) {
pub async fn run_server(config_file: PathBuf, secrets: Secrets) -> Result<(), Error> {
info!("Loading configuration...");
let config = fill_secrets(read_config(config_file)?, secrets);
let config = fill_secrets(read_config(config_file)?, secrets)?;
// ---- Initialize Garage internals ----
#[cfg(feature = "metrics")]
let metrics_exporter = opentelemetry_prometheus::exporter().init();
let metrics_exporter = opentelemetry_prometheus::exporter()
.with_default_summary_quantiles(vec![0.25, 0.5, 0.75, 0.9, 0.95, 0.99])
.with_default_histogram_boundaries(vec![
0.001, 0.0015, 0.002, 0.003, 0.005, 0.007, 0.01, 0.015, 0.02, 0.03, 0.05, 0.07, 0.1,
0.15, 0.2, 0.3, 0.5, 0.7, 1., 1.5, 2., 3., 5., 7., 10., 15., 20., 30., 40., 50., 60.,
70., 100.,
])
.init();
info!("Initializing Garage main data store...");
let garage = Garage::new(config.clone())?;
@@ -79,7 +86,7 @@ pub async fn run_server(config_file: PathBuf, secrets: Secrets) -> Result<(), Er
"S3 API",
tokio::spawn(S3ApiServer::run(
garage.clone(),
*s3_bind_addr,
s3_bind_addr.clone(),
config.s3_api.s3_region.clone(),
wait_from(watch_cancel.clone()),
)),
@@ -94,7 +101,7 @@ pub async fn run_server(config_file: PathBuf, secrets: Secrets) -> Result<(), Er
"K2V API",
tokio::spawn(K2VApiServer::run(
garage.clone(),
config.k2v_api.as_ref().unwrap().api_bind_addr,
config.k2v_api.as_ref().unwrap().api_bind_addr.clone(),
config.s3_api.s3_region.clone(),
wait_from(watch_cancel.clone()),
)),
@@ -110,7 +117,7 @@ pub async fn run_server(config_file: PathBuf, secrets: Secrets) -> Result<(), Er
"Web",
tokio::spawn(WebServer::run(
garage.clone(),
web_config.bind_addr,
web_config.bind_addr.clone(),
web_config.root_domain.clone(),
wait_from(watch_cancel.clone()),
)),
@@ -121,7 +128,9 @@ pub async fn run_server(config_file: PathBuf, secrets: Secrets) -> Result<(), Er
info!("Launching Admin API server...");
servers.push((
"Admin",
tokio::spawn(admin_server.run(*admin_bind_addr, wait_from(watch_cancel.clone()))),
tokio::spawn(
admin_server.run(admin_bind_addr.clone(), wait_from(watch_cancel.clone())),
),
));
}
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "garage_model"
version = "0.8.4"
version = "0.8.5"
authors = ["Alex Auvolat <alex@adnab.me>"]
edition = "2018"
license = "AGPL-3.0"
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "garage_rpc"
version = "0.8.4"
version = "0.8.5"
authors = ["Alex Auvolat <alex@adnab.me>"]
edition = "2018"
license = "AGPL-3.0"
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "garage_table"
version = "0.8.4"
version = "0.8.5"
authors = ["Alex Auvolat <alex@adnab.me>"]
edition = "2018"
license = "AGPL-3.0"
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "garage_util"
version = "0.8.4"
version = "0.8.5"
authors = ["Alex Auvolat <alex@adnab.me>"]
edition = "2018"
license = "AGPL-3.0"
+17 -147
View File
@@ -1,12 +1,12 @@
//! Contains type and functions related to Garage configuration file
use std::convert::TryFrom;
use std::io::Read;
use std::net::SocketAddr;
use std::path::PathBuf;
use serde::{de, Deserialize};
use crate::error::Error;
use crate::socket_address::UnixOrTCPSocketAddress;
/// Represent the whole configuration
#[derive(Deserialize, Debug, Clone)]
@@ -37,11 +37,15 @@ pub struct Config {
)]
pub compression_level: Option<i32>,
/// Skip the permission check of secret files. Useful when
/// POSIX ACLs (or more complex chmods) are used.
#[serde(default)]
pub allow_world_readable_secrets: bool,
/// RPC secret key: 32 bytes hex encoded
pub rpc_secret: Option<String>,
/// Optional file where RPC secret key is read from
pub rpc_secret_file: Option<String>,
/// Address to bind for RPC
pub rpc_bind_addr: SocketAddr,
/// Public IP address of this node
@@ -102,7 +106,7 @@ pub struct Config {
#[derive(Deserialize, Debug, Clone)]
pub struct S3ApiConfig {
/// Address and port to bind for api serving
pub api_bind_addr: Option<SocketAddr>,
pub api_bind_addr: Option<UnixOrTCPSocketAddress>,
/// S3 region to use
pub s3_region: String,
/// Suffix to remove from domain name to find bucket. If None,
@@ -114,14 +118,14 @@ pub struct S3ApiConfig {
#[derive(Deserialize, Debug, Clone)]
pub struct K2VApiConfig {
/// Address and port to bind for api serving
pub api_bind_addr: SocketAddr,
pub api_bind_addr: UnixOrTCPSocketAddress,
}
/// Configuration for serving files as normal web server
#[derive(Deserialize, Debug, Clone)]
pub struct WebConfig {
/// Address and port to bind for web serving
pub bind_addr: SocketAddr,
pub bind_addr: UnixOrTCPSocketAddress,
/// Suffix to remove from domain name to find bucket
pub root_domain: String,
}
@@ -130,7 +134,7 @@ pub struct WebConfig {
#[derive(Deserialize, Debug, Clone, Default)]
pub struct AdminConfig {
/// Address and port to bind for admin API serving
pub api_bind_addr: Option<SocketAddr>,
pub api_bind_addr: Option<UnixOrTCPSocketAddress>,
/// Bearer token to use to scrape metrics
pub metrics_token: Option<String>,
@@ -193,6 +197,13 @@ pub struct KubernetesDiscoveryConfig {
pub skip_crd: bool,
}
/// Read and parse configuration
pub fn read_config(config_file: PathBuf) -> Result<Config, Error> {
let config = std::fs::read_to_string(config_file)?;
Ok(toml::from_str(&config)?)
}
fn default_db_engine() -> String {
"sled".into()
}
@@ -207,68 +218,6 @@ fn default_block_size() -> usize {
1048576
}
/// Read and parse configuration
pub fn read_config(config_file: PathBuf) -> Result<Config, Error> {
let mut file = std::fs::OpenOptions::new()
.read(true)
.open(config_file.as_path())?;
let mut config = String::new();
file.read_to_string(&mut config)?;
let mut parsed_config: Config = toml::from_str(&config)?;
secret_from_file(
&mut parsed_config.rpc_secret,
&parsed_config.rpc_secret_file,
"rpc_secret",
)?;
secret_from_file(
&mut parsed_config.admin.metrics_token,
&parsed_config.admin.metrics_token_file,
"admin.metrics_token",
)?;
secret_from_file(
&mut parsed_config.admin.admin_token,
&parsed_config.admin.admin_token_file,
"admin.admin_token",
)?;
Ok(parsed_config)
}
fn secret_from_file(
secret: &mut Option<String>,
secret_file: &Option<String>,
name: &'static str,
) -> Result<(), Error> {
match (&secret, &secret_file) {
(_, None) => {
// no-op
}
(Some(_), Some(_)) => {
return Err(format!("only one of `{}` and `{}_file` can be set", name, name).into());
}
(None, Some(file_path)) => {
#[cfg(unix)]
if std::env::var("GARAGE_ALLOW_WORLD_READABLE_SECRETS").as_deref() != Ok("true") {
use std::os::unix::fs::MetadataExt;
let metadata = std::fs::metadata(file_path)?;
if metadata.mode() & 0o077 != 0 {
return Err(format!("File {} is world-readable! (mode: 0{:o}, expected 0600)\nRefusing to start until this is fixed, or environment variable GARAGE_ALLOW_WORLD_READABLE_SECRETS is set to true.", file_path, metadata.mode()).into());
}
}
let mut file = std::fs::OpenOptions::new().read(true).open(file_path)?;
let mut secret_buf = String::new();
file.read_to_string(&mut secret_buf)?;
// trim_end: allows for use case such as `echo "$(openssl rand -hex 32)" > somefile`.
// also editors sometimes add a trailing newline
*secret = Some(String::from(secret_buf.trim_end()));
}
}
Ok(())
}
fn default_compression() -> Option<i32> {
Some(1)
}
@@ -397,83 +346,4 @@ mod tests {
Ok(())
}
#[test]
fn test_rpc_secret_file_works() -> Result<(), Error> {
let path_secret = mktemp::Temp::new_file()?;
let mut file_secret = File::create(path_secret.as_path())?;
writeln!(file_secret, "foo")?;
drop(file_secret);
let path_config = mktemp::Temp::new_file()?;
let mut file_config = File::create(path_config.as_path())?;
let path_secret_path = path_secret.as_path();
writeln!(
file_config,
r#"
metadata_dir = "/tmp/garage/meta"
data_dir = "/tmp/garage/data"
replication_mode = "3"
rpc_bind_addr = "[::]:3901"
rpc_secret_file = "{}"
[s3_api]
s3_region = "garage"
api_bind_addr = "[::]:3900"
"#,
path_secret_path.display()
)?;
let config = super::read_config(path_config.to_path_buf())?;
assert_eq!("foo", config.rpc_secret.unwrap());
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let metadata = std::fs::metadata(&path_secret_path)?;
let mut perm = metadata.permissions();
perm.set_mode(0o660);
std::fs::set_permissions(&path_secret_path, perm)?;
std::env::set_var("GARAGE_ALLOW_WORLD_READABLE_SECRETS", "false");
assert!(super::read_config(path_config.to_path_buf()).is_err());
std::env::set_var("GARAGE_ALLOW_WORLD_READABLE_SECRETS", "true");
assert!(super::read_config(path_config.to_path_buf()).is_ok());
}
drop(path_config);
drop(path_secret);
drop(file_config);
Ok(())
}
#[test]
fn test_rcp_secret_and_rpc_secret_file_cannot_be_set_both() -> Result<(), Error> {
let path_config = mktemp::Temp::new_file()?;
let mut file_config = File::create(path_config.as_path())?;
writeln!(
file_config,
r#"
metadata_dir = "/tmp/garage/meta"
data_dir = "/tmp/garage/data"
replication_mode = "3"
rpc_bind_addr = "[::]:3901"
rpc_secret= "dummy"
rpc_secret_file = "dummy"
[s3_api]
s3_region = "garage"
api_bind_addr = "[::]:3900"
"#
)?;
assert_eq!(
"only one of `rpc_secret` and `rpc_secret_file` can be set",
super::read_config(path_config.to_path_buf())
.unwrap_err()
.to_string()
);
drop(path_config);
drop(file_config);
Ok(())
}
}
+1
View File
@@ -14,6 +14,7 @@ pub mod forwarded_headers;
pub mod metrics;
pub mod migrate;
pub mod persister;
pub mod socket_address;
pub mod time;
pub mod tranquilizer;
pub mod version;
+44
View File
@@ -0,0 +1,44 @@
use std::fmt::{Debug, Display, Formatter};
use std::net::SocketAddr;
use std::path::PathBuf;
use std::str::FromStr;
use serde::de::Error;
use serde::{Deserialize, Deserializer};
#[derive(Debug, Clone)]
pub enum UnixOrTCPSocketAddress {
TCPSocket(SocketAddr),
UnixSocket(PathBuf),
}
impl Display for UnixOrTCPSocketAddress {
fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
match self {
UnixOrTCPSocketAddress::TCPSocket(address) => write!(formatter, "http://{}", address),
UnixOrTCPSocketAddress::UnixSocket(path) => {
write!(formatter, "http+unix://{}", path.to_string_lossy())
}
}
}
}
impl<'de> Deserialize<'de> for UnixOrTCPSocketAddress {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let string = String::deserialize(deserializer)?;
let string = string.as_str();
if string.starts_with("/") {
Ok(UnixOrTCPSocketAddress::UnixSocket(
PathBuf::from_str(string).map_err(Error::custom)?,
))
} else {
Ok(UnixOrTCPSocketAddress::TCPSocket(
SocketAddr::from_str(string).map_err(Error::custom)?,
))
}
}
}
+4 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "garage_web"
version = "0.8.4"
version = "0.8.5"
authors = ["Alex Auvolat <alex@adnab.me>", "Quentin Dufour <quentin@dufour.io>"]
edition = "2018"
license = "AGPL-3.0"
@@ -27,5 +27,8 @@ futures = "0.3"
http = "0.2"
hyper = { version = "0.14", features = ["server", "http1", "runtime", "tcp", "stream"] }
hyperlocal = { version = "0.8.0", default-features = false, features = ["server"] }
tokio = { version = "1.0", default-features = false, features = ["net"] }
opentelemetry = "0.17"
+49 -9
View File
@@ -1,4 +1,6 @@
use std::{convert::Infallible, net::SocketAddr, sync::Arc};
use std::fs::{self, Permissions};
use std::os::unix::prelude::PermissionsExt;
use std::{convert::Infallible, sync::Arc};
use futures::future::Future;
@@ -9,6 +11,10 @@ use hyper::{
Body, Method, Request, Response, Server, StatusCode,
};
use hyperlocal::UnixServerExt;
use tokio::net::UnixStream;
use opentelemetry::{
global,
metrics::{Counter, ValueRecorder},
@@ -32,6 +38,7 @@ use garage_util::data::Uuid;
use garage_util::error::Error as GarageError;
use garage_util::forwarded_headers;
use garage_util::metrics::{gen_trace_id, RecordDuration};
use garage_util::socket_address::UnixOrTCPSocketAddress;
struct WebMetrics {
request_counter: Counter<u64>,
@@ -69,7 +76,7 @@ impl WebServer {
/// Run a web server
pub async fn run(
garage: Arc<Garage>,
addr: SocketAddr,
addr: UnixOrTCPSocketAddress,
root_domain: String,
shutdown_signal: impl Future<Output = ()>,
) -> Result<(), GarageError> {
@@ -80,7 +87,7 @@ impl WebServer {
root_domain,
});
let service = make_service_fn(|conn: &AddrStream| {
let tcp_service = make_service_fn(|conn: &AddrStream| {
let web_server = web_server.clone();
let client_addr = conn.remote_addr();
@@ -88,23 +95,56 @@ impl WebServer {
Ok::<_, Error>(service_fn(move |req: Request<Body>| {
let web_server = web_server.clone();
web_server.handle_request(req, client_addr)
web_server.handle_request(req, client_addr.to_string())
}))
}
});
let server = Server::bind(&addr).serve(service);
let graceful = server.with_graceful_shutdown(shutdown_signal);
info!("Web server listening on http://{}", addr);
let unix_service = make_service_fn(|_: &UnixStream| {
let web_server = web_server.clone();
let path = addr.to_string();
async move {
Ok::<_, Error>(service_fn(move |req: Request<Body>| {
let web_server = web_server.clone();
web_server.handle_request(req, path.clone())
}))
}
});
info!("Web server listening on {}", addr);
match addr {
UnixOrTCPSocketAddress::TCPSocket(addr) => {
Server::bind(&addr)
.serve(tcp_service)
.with_graceful_shutdown(shutdown_signal)
.await?
}
UnixOrTCPSocketAddress::UnixSocket(ref path) => {
if path.exists() {
fs::remove_file(path)?
}
let bound = Server::bind_unix(path)?;
fs::set_permissions(path, Permissions::from_mode(0o222))?;
bound
.serve(unix_service)
.with_graceful_shutdown(shutdown_signal)
.await?;
}
};
graceful.await?;
Ok(())
}
async fn handle_request(
self: Arc<Self>,
req: Request<Body>,
addr: SocketAddr,
addr: String,
) -> Result<Response<Body>, Infallible> {
if let Ok(forwarded_for_ip_addr) =
forwarded_headers::handle_forwarded_for_headers(req.headers())