mirror of
https://github.com/deuxfleurs-org/garage.git
synced 2026-09-05 19:55:39 +00:00
Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 33d50666b5 | |||
| c85597fd18 | |||
| 05e294307e | |||
| 109fbd49b1 | |||
| d0176f8e30 | |||
| d9b1dba137 | |||
| c3c8af9a66 | |||
| ad78b9ee5c |
Generated
+1
@@ -1890,6 +1890,7 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2 0.10.9",
|
||||
"subtle",
|
||||
"thiserror 2.0.18",
|
||||
"tokio",
|
||||
"toml",
|
||||
|
||||
@@ -76,6 +76,7 @@ pnet_datalink = "0.35"
|
||||
rand = "0.9"
|
||||
sha1 = "0.10"
|
||||
sha2 = "0.10"
|
||||
subtle = "2.6.1"
|
||||
timeago = { version = "0.5", default-features = false }
|
||||
xxhash-rust = { version = "0.8", default-features = false, features = ["xxh3"] }
|
||||
|
||||
|
||||
@@ -133,12 +133,17 @@ Use the following command to launch the Garage server:
|
||||
garage server --single-node --default-bucket
|
||||
```
|
||||
|
||||
The `--single-node` flag instructs Garage to automatically configure a single-node cluster without data replication.
|
||||
The `--default-bucket` flag instructs Garage to create a default access key and a default bucket using the environment variables we defined above.
|
||||
Both flags are optional and can be omitted, in which case you will have to follow manual configuration steps described below.
|
||||
- the `--single-node` flag instructs Garage to automatically configure a
|
||||
single-node cluster without data replication;
|
||||
- the `--default-bucket` flag instructs Garage to create a default access key
|
||||
and a default bucket using the environment variables we defined above (it
|
||||
implies `--default-access-key`).
|
||||
|
||||
**For older versions of Garage (before v2.3.0):** automatic configuration using `--single-node` and `--default-bucket` is not available,
|
||||
you must follow the manual configuration steps.
|
||||
> You can refer to the [manual configuration
|
||||
> steps](#manual-configuration) if:
|
||||
>
|
||||
> - you decide to no use these optional flags;
|
||||
> - you are running an **older version of Garage (before v2.3.0)**.
|
||||
|
||||
Alternatively, if you cannot or do not wish to run the Garage binary directly,
|
||||
you may use Docker to run Garage in a container using the following command:
|
||||
@@ -292,7 +297,7 @@ An exhaustive list is maintained in the ["Integrations" > "Browsing tools" secti
|
||||
|
||||
|
||||
|
||||
## Manual configuration
|
||||
## Manual configuration {#manual-configuration}
|
||||
|
||||
This section provides instructions that are equivalent to using the
|
||||
`--single-node` and `--default-bucket` flags for automatic configuration. If
|
||||
|
||||
@@ -175,6 +175,9 @@ they do not exist in the configuration file:
|
||||
Garage daemon send its logs to `journald` (using the native protocol of `systemd-journald`)
|
||||
instead of printing to stderr.
|
||||
|
||||
- `NO_COLOR` (since `v2.4.0`): set this to `0` or `false` to disable
|
||||
ANSI color codes in Garage's logs.
|
||||
|
||||
The following environment variables can be used to override the corresponding
|
||||
values in the configuration file:
|
||||
|
||||
|
||||
@@ -8,12 +8,11 @@ which is an alternative storage API designed to help efficiently store
|
||||
many small values in buckets (in opposition to S3 which is more designed
|
||||
to store large blobs).
|
||||
|
||||
K2V is currently disabled at compile time in all builds, as the
|
||||
specification is still subject to changes. To build a Garage version with
|
||||
K2V, the Cargo feature flag `k2v` must be activated. Special builds with
|
||||
the `k2v` feature flag enabled can be obtained from our download page under
|
||||
"Extra builds": such builds can be identified easily as their tag name ends
|
||||
with `-k2v` (example: `v0.7.2-k2v`).
|
||||
K2V is included in release builds since version 0.8.0. Precompiled builds
|
||||
of earlier versions including `k2v` can be found in our download page under
|
||||
"Extra builds": they can be easily identified as their tag name ends with
|
||||
`-k2v` (example: `v0.7.2-k2v`). Otherwise, when compiling Garage, the Cargo
|
||||
feature flag `k2v` must be activated.
|
||||
|
||||
The specification of the K2V API can be found
|
||||
[here](https://git.deuxfleurs.fr/Deuxfleurs/garage/src/commit/f8be15c37db857e177d543de7be863692628d567/doc/drafts/k2v-spec.md).
|
||||
|
||||
@@ -18,7 +18,7 @@ fi
|
||||
|
||||
$GARAGE_BIN -c /tmp/config.1.toml bucket create eprouvette
|
||||
if [ "$GARAGE_OLDVER" = "v08" ]; then
|
||||
KEY_INFO=$($GARAGE_BIN -c /tmp/config.1.toml key create opérateur)
|
||||
KEY_INFO=$($GARAGE_BIN -c /tmp/config.1.toml key new --name opérateur)
|
||||
ACCESS_KEY=`echo $KEY_INFO|grep -Po 'GK[a-f0-9]+'`
|
||||
SECRET_KEY=`echo $KEY_INFO|grep -Po 'Secret key: [a-f0-9]+'|grep -Po '[a-f0-9]+$'`
|
||||
elif [ "$GARAGE_OLDVER" = "v1" ]; then
|
||||
|
||||
@@ -191,7 +191,7 @@ impl RequestHandler for GetCurrentAdminTokenInfoRequest {
|
||||
.admin
|
||||
.metrics_token
|
||||
.as_ref()
|
||||
.is_some_and(|s| s == &self.admin_token)
|
||||
.is_some_and(|s| s.eq_ct(&self.admin_token))
|
||||
{
|
||||
return Ok(GetCurrentAdminTokenInfoResponse(
|
||||
GetAdminTokenInfoResponse {
|
||||
@@ -210,7 +210,7 @@ impl RequestHandler for GetCurrentAdminTokenInfoRequest {
|
||||
.admin
|
||||
.admin_token
|
||||
.as_ref()
|
||||
.is_some_and(|s| s == &self.admin_token)
|
||||
.is_some_and(|s| s.eq_ct(&self.admin_token))
|
||||
{
|
||||
return Ok(GetCurrentAdminTokenInfoResponse(
|
||||
GetAdminTokenInfoResponse {
|
||||
|
||||
@@ -117,8 +117,14 @@ impl AdminApiServer {
|
||||
#[cfg(feature = "metrics")] exporter: PrometheusExporter,
|
||||
) -> Arc<Self> {
|
||||
let cfg = &garage.config.admin;
|
||||
let metrics_token = cfg.metrics_token.as_deref().map(hash_bearer_token);
|
||||
let admin_token = cfg.admin_token.as_deref().map(hash_bearer_token);
|
||||
let metrics_token = cfg
|
||||
.metrics_token
|
||||
.as_ref()
|
||||
.map(|token| hash_bearer_token(token.extract_secret()));
|
||||
let admin_token = cfg
|
||||
.admin_token
|
||||
.as_ref()
|
||||
.map(|token| hash_bearer_token(token.extract_secret()));
|
||||
let metrics_require_token = cfg.metrics_require_token;
|
||||
|
||||
let endpoint = garage.system.netapp.endpoint(ADMIN_RPC_PATH.into());
|
||||
|
||||
+228
-2
@@ -47,15 +47,26 @@ where
|
||||
HI: Iterator<Item = S>,
|
||||
S: AsRef<str>,
|
||||
{
|
||||
rule.allow_origins.iter().any(|x| x == "*" || x == origin)
|
||||
rule.allow_origins.iter().any(|x| wildcard_match(x, origin))
|
||||
&& rule.allow_methods.iter().any(|x| x == "*" || x == method)
|
||||
&& request_headers.all(|h| {
|
||||
rule.allow_headers
|
||||
.iter()
|
||||
.any(|x| x == "*" || x == h.as_ref())
|
||||
.any(|x| wildcard_match(x, h.as_ref()))
|
||||
})
|
||||
}
|
||||
|
||||
/// Checks whether `candidate` matches the pattern `allowed_wildcard`.
|
||||
#[inline]
|
||||
fn wildcard_match(allowed_wildcard: &String, candidate: &str) -> bool {
|
||||
if allowed_wildcard.contains("*") {
|
||||
let parts = allowed_wildcard.split("*").collect::<Vec<&str>>();
|
||||
parts.len() == 2 && candidate.starts_with(parts[0]) && candidate.ends_with(parts[1])
|
||||
} else {
|
||||
candidate == allowed_wildcard
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_cors_headers(
|
||||
resp: &mut Response<impl Body>,
|
||||
rule: &GarageCorsRule,
|
||||
@@ -190,6 +201,221 @@ pub fn handle_options_for_bucket<B>(
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn cors_rule(
|
||||
allow_origins: &[&str],
|
||||
allow_methods: &[&str],
|
||||
allow_headers: &[&str],
|
||||
) -> GarageCorsRule {
|
||||
GarageCorsRule {
|
||||
id: None,
|
||||
max_age_seconds: None,
|
||||
allow_origins: allow_origins.iter().map(|s| s.to_string()).collect(),
|
||||
allow_methods: allow_methods.iter().map(|s| s.to_string()).collect(),
|
||||
allow_headers: allow_headers.iter().map(|s| s.to_string()).collect(),
|
||||
expose_headers: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn matches_when_origin_method_and_headers_are_explicitly_allowed() {
|
||||
let rule = cors_rule(
|
||||
&["https://app.example.test"],
|
||||
&["GET", "PUT"],
|
||||
&["content-type", "x-custom"],
|
||||
);
|
||||
let headers = vec!["content-type", "x-custom"];
|
||||
|
||||
assert!(cors_rule_matches(
|
||||
&rule,
|
||||
"https://app.example.test",
|
||||
"PUT",
|
||||
headers.iter(),
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn does_not_match_when_origin_is_not_allowed() {
|
||||
let rule = cors_rule(&["https://app.example.test"], &["GET"], &["*"]);
|
||||
|
||||
assert!(!cors_rule_matches(
|
||||
&rule,
|
||||
"https://evil.example.test",
|
||||
"GET",
|
||||
std::iter::empty::<&str>(),
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn does_not_match_when_method_is_not_allowed() {
|
||||
let rule = cors_rule(&["*"], &["GET"], &["*"]);
|
||||
|
||||
assert!(!cors_rule_matches(
|
||||
&rule,
|
||||
"https://app.example.test",
|
||||
"DELETE",
|
||||
std::iter::empty::<&str>(),
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn does_not_match_when_a_requested_header_is_not_allowed() {
|
||||
let rule = cors_rule(&["*"], &["GET"], &["content-type"]);
|
||||
let headers = vec!["content-type", "x-not-allowed"];
|
||||
|
||||
assert!(!cors_rule_matches(
|
||||
&rule,
|
||||
"https://app.example.test",
|
||||
"GET",
|
||||
headers.iter(),
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wildcard_origin_method_and_headers_match_anything() {
|
||||
let rule = cors_rule(&["*"], &["*"], &["*"]);
|
||||
let headers = vec!["x-anything"];
|
||||
|
||||
assert!(cors_rule_matches(
|
||||
&rule,
|
||||
"https://app.example.test",
|
||||
"DELETE",
|
||||
headers.iter(),
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wildcard_origin_regex() {
|
||||
let rule = cors_rule(&["https://*.localhost.com"], &["*"], &["*"]);
|
||||
let headers = vec!["x-anything"];
|
||||
|
||||
assert!(cors_rule_matches(
|
||||
&rule,
|
||||
"https://s3.localhost.com",
|
||||
"DELETE",
|
||||
headers.iter(),
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn origin_matching_cases() {
|
||||
// (allow_origins, origin, expect_match)
|
||||
let cases: &[(&[&str], &str, bool)] = &[
|
||||
// exact match
|
||||
(
|
||||
&["https://app.example.test"],
|
||||
"https://app.example.test",
|
||||
true,
|
||||
),
|
||||
(
|
||||
&["https://app.example.test"],
|
||||
"https://other.example.test",
|
||||
false,
|
||||
),
|
||||
// full wildcard
|
||||
(&["*"], "https://anything.example.test", true),
|
||||
// subdomain glob
|
||||
(
|
||||
&["https://*.example.test"],
|
||||
"https://foo.example.test",
|
||||
true,
|
||||
),
|
||||
(&["https://*.example.test"], "https://example.test", false),
|
||||
(
|
||||
&["https://*.example.test"],
|
||||
"http://foo.example.test",
|
||||
false,
|
||||
),
|
||||
// multiple allowed origins, at least one should match
|
||||
(
|
||||
&["https://a.example.test", "https://b.example.test"],
|
||||
"https://b.example.test",
|
||||
true,
|
||||
),
|
||||
// match multiple origins
|
||||
(
|
||||
&["https://a*.example.test", "https://ab*.example.test"],
|
||||
"https://abc.example.test",
|
||||
true,
|
||||
),
|
||||
(
|
||||
&["https://a.example.test", "https://b.example.test"],
|
||||
"https://c.example.test",
|
||||
false,
|
||||
),
|
||||
// at most one '*' in a pattern is allowed
|
||||
(&["https://*.example.*"], "https://a.example.test", false),
|
||||
// domain changed with wildcard
|
||||
(
|
||||
&["https://*example.test"],
|
||||
"https://garageexample.test",
|
||||
true,
|
||||
),
|
||||
// trailing '*' matches any suffix, including the empty string,
|
||||
// so this also matches origins with anything (or nothing) after
|
||||
// "example."
|
||||
(&["https://example.*"], "https://example.test", true),
|
||||
(&["https://*example.test"], "https://example.test", true),
|
||||
(&["https://example.*"], "https://example.", true),
|
||||
];
|
||||
|
||||
for (allow_origins, origin, expect_match) in cases {
|
||||
let rule = cors_rule(allow_origins, &["GET"], &["*"]);
|
||||
let got = cors_rule_matches(&rule, origin, "GET", std::iter::empty::<&str>());
|
||||
assert_eq!(
|
||||
got, *expect_match,
|
||||
"allow_origins={allow_origins:?}, origin={origin:?}: expected match={expect_match}, got {got}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn header_matching_cases() {
|
||||
// (allow_headers, requested_headers, expect_match)
|
||||
let cases: &[(&[&str], &[&str], bool)] = &[
|
||||
// exact match
|
||||
(&["content-type"], &["content-type"], true),
|
||||
(&["content-type"], &["x-custom"], false),
|
||||
// full wildcard
|
||||
(&["*"], &["x-anything"], true),
|
||||
// no headers requested always matches, regardless of allow_headers
|
||||
(&["content-type"], &[], true),
|
||||
(&[], &[], true),
|
||||
// prefix glob
|
||||
(&["x-amz-*"], &["x-amz-meta-foo"], true),
|
||||
(&["x-amz-*"], &["x-amz-"], true),
|
||||
(&["x-amz-*"], &["x-other"], false),
|
||||
// suffix glob
|
||||
(&["*-meta"], &["foo-meta"], true),
|
||||
(&["*-meta"], &["-meta"], true),
|
||||
(&["*-meta"], &["foo-meta-bar"], false),
|
||||
// multiple allowed headers, at least one should match per requested header
|
||||
(
|
||||
&["content-type", "x-amz-*"],
|
||||
&["content-type", "x-amz-meta-foo"],
|
||||
true,
|
||||
),
|
||||
(&["content-type", "x-amz-*"], &["x-other"], false),
|
||||
// all requested headers must be covered
|
||||
(&["content-type"], &["content-type", "x-custom"], false),
|
||||
// at most one '*' in a pattern is allowed
|
||||
(&["x-*-*"], &["x-a-b"], false),
|
||||
];
|
||||
|
||||
for (allow_headers, requested_headers, expect_match) in cases {
|
||||
let rule = cors_rule(&["*"], &["GET"], allow_headers);
|
||||
let got = cors_rule_matches(
|
||||
&rule,
|
||||
"https://app.example.test",
|
||||
"GET",
|
||||
requested_headers.iter(),
|
||||
);
|
||||
assert_eq!(
|
||||
got, *expect_match,
|
||||
"allow_headers={allow_headers:?}, requested_headers={requested_headers:?}: expected match={expect_match}, got {got}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn bucket_params_with_rule(allow_origins: Vec<&str>) -> BucketParams {
|
||||
let mut bucket_params = BucketParams::default();
|
||||
bucket_params.cors_config.update(
|
||||
|
||||
+7
-1
@@ -276,6 +276,11 @@ fn init_logging(opt: &Opt) {
|
||||
tracing_subscriber::fmt()
|
||||
.with_writer(std::io::stderr)
|
||||
.with_env_filter(env_filter)
|
||||
.with_ansi(
|
||||
std::env::var("NO_COLOR")
|
||||
.map(|x| x != "0" && !x.eq_ignore_ascii_case("false"))
|
||||
.unwrap_or(true),
|
||||
)
|
||||
.init();
|
||||
}
|
||||
|
||||
@@ -302,7 +307,8 @@ async fn cli_command(opt: Opt) -> Result<(), Error> {
|
||||
|
||||
let net_key_hex_str = rpc_secret.ok_or("No RPC secret provided")?;
|
||||
let network_key = NetworkKey::from_slice(
|
||||
&hex::decode(&net_key_hex_str).err_context("Invalid RPC secret key (bad hex)")?[..],
|
||||
&hex::decode(net_key_hex_str.extract_secret())
|
||||
.err_context("Invalid RPC secret key (bad hex)")?[..],
|
||||
)
|
||||
.ok_or("Invalid RPC secret provided (wrong length)")?;
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ use std::path::PathBuf;
|
||||
|
||||
use structopt::StructOpt;
|
||||
|
||||
use garage_util::config::Config;
|
||||
use garage_util::config::{Config, Secret};
|
||||
use garage_util::error::Error;
|
||||
|
||||
/// Structure for secret values or paths that are passed as CLI arguments or environment
|
||||
@@ -99,7 +99,7 @@ pub fn fill_secrets(mut config: Config, secrets: Secrets) -> Result<Config, Erro
|
||||
}
|
||||
|
||||
pub(crate) fn fill_secret(
|
||||
config_secret: &mut Option<String>,
|
||||
config_secret: &mut Option<Secret<String>>,
|
||||
config_secret_file: &Option<PathBuf>,
|
||||
cli_secret: &Option<String>,
|
||||
cli_secret_file: &Option<PathBuf>,
|
||||
@@ -110,7 +110,7 @@ pub(crate) fn fill_secret(
|
||||
(Some(_), Some(_)) => {
|
||||
return Err(format!("only one of `{}` and `{}_file` can be set", name, name).into());
|
||||
}
|
||||
(Some(secret), None) => Some(secret.to_string()),
|
||||
(Some(secret), None) => Some(Secret::new(secret.to_string())),
|
||||
(None, Some(file)) => Some(read_secret_file(file, allow_world_readable)?),
|
||||
(None, None) => None,
|
||||
};
|
||||
@@ -132,7 +132,10 @@ pub(crate) fn fill_secret(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn read_secret_file(file_path: &PathBuf, allow_world_readable: bool) -> Result<String, Error> {
|
||||
fn read_secret_file(
|
||||
file_path: &PathBuf,
|
||||
allow_world_readable: bool,
|
||||
) -> Result<Secret<String>, Error> {
|
||||
if !allow_world_readable {
|
||||
#[cfg(unix)]
|
||||
{
|
||||
@@ -152,7 +155,7 @@ fn read_secret_file(file_path: &PathBuf, allow_world_readable: bool) -> Result<S
|
||||
|
||||
// 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()))
|
||||
Ok(Secret::new(String::from(secret_buf.trim_end())))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -152,6 +152,14 @@ pub async fn run_server(
|
||||
}
|
||||
}
|
||||
|
||||
// Deregister from Consul (if enabled) in the background, in parallel with the
|
||||
// rest of the shutdown sequence, so that it doesn't add to shutdown latency.
|
||||
#[cfg(feature = "consul-discovery")]
|
||||
let deregister_consul_task = tokio::spawn({
|
||||
let system = garage.system.clone();
|
||||
async move { system.deregister_from_discovery().await }
|
||||
});
|
||||
|
||||
// Remove RPC handlers for system to break reference cycles
|
||||
info!("Deregistering RPC handlers for shutdown...");
|
||||
garage.system.netapp.drop_all_handlers();
|
||||
@@ -168,6 +176,12 @@ pub async fn run_server(
|
||||
// Await for all background tasks to end
|
||||
await_background_done.await?;
|
||||
|
||||
// Await for Consul deregistration to end, if it hasn't already
|
||||
#[cfg(feature = "consul-discovery")]
|
||||
if let Err(e) = deregister_consul_task.await {
|
||||
error!("Error while joining Consul deregistration task: {}", e);
|
||||
}
|
||||
|
||||
info!("Cleaning up...");
|
||||
|
||||
Ok(())
|
||||
|
||||
+1
-1
@@ -137,7 +137,7 @@ impl Garage {
|
||||
info!("Initializing RPC...");
|
||||
let network_key = hex::decode(config.rpc_secret.as_ref().ok_or_message(
|
||||
"rpc_secret value is missing, not present in config file or in environment",
|
||||
)?)
|
||||
)?.extract_secret())
|
||||
.ok()
|
||||
.and_then(|x| NetworkKey::from_slice(&x))
|
||||
.ok_or_message("Invalid RPC secret key: expected 32 bytes of random hex, please check the documentation for requirements")?;
|
||||
|
||||
+31
-1
@@ -115,7 +115,7 @@ impl ConsulDiscovery {
|
||||
let mut headers = reqwest::header::HeaderMap::new();
|
||||
headers.insert(
|
||||
"x-consul-token",
|
||||
reqwest::header::HeaderValue::from_str(token)?,
|
||||
reqwest::header::HeaderValue::from_str(token.extract_secret())?,
|
||||
);
|
||||
builder = builder.default_headers(headers);
|
||||
}
|
||||
@@ -183,6 +183,36 @@ impl ConsulDiscovery {
|
||||
}
|
||||
// ---- PUBLISHING TO CONSUL CATALOG ----
|
||||
|
||||
#[cfg(feature = "consul-discovery")]
|
||||
pub async fn deregister_consul_service(&self, node_id: NodeID) -> Result<(), ConsulError> {
|
||||
let node = format!("garage:{}", hex::encode(&node_id[..8]));
|
||||
let url = format!(
|
||||
"{}/v1/{}",
|
||||
self.config.consul_http_addr,
|
||||
(match &self.config.api {
|
||||
ConsulDiscoveryAPI::Catalog => format!("catalog/deregister"),
|
||||
ConsulDiscoveryAPI::Agent => format!("agent/service/deregister/{}", node),
|
||||
})
|
||||
);
|
||||
|
||||
let req = self.client.put(&url);
|
||||
|
||||
let http = if matches!(&self.config.api, ConsulDiscoveryAPI::Catalog) {
|
||||
let deregister_request = serde_json::json!({
|
||||
"Node": node,
|
||||
"ServiceID": node,
|
||||
});
|
||||
let req = req.json(&deregister_request);
|
||||
req.send().await?
|
||||
} else {
|
||||
req.send().await?
|
||||
};
|
||||
http.error_for_status()?;
|
||||
|
||||
debug!("Deregistered service {} from Consul", node);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn publish_consul_service(
|
||||
&self,
|
||||
node_id: NodeID,
|
||||
|
||||
+10
-1
@@ -358,7 +358,7 @@ impl System {
|
||||
);
|
||||
}
|
||||
|
||||
pub fn cleanup(&self) {
|
||||
pub fn cleanup(self: &Arc<Self>) {
|
||||
// Break reference cycle
|
||||
self.metrics.store(None);
|
||||
}
|
||||
@@ -650,6 +650,15 @@ impl System {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "consul-discovery")]
|
||||
pub async fn deregister_from_discovery(self: &Arc<Self>) {
|
||||
if let Some(c) = &self.consul_discovery {
|
||||
if let Err(e) = c.deregister_consul_service(self.netapp.id).await {
|
||||
error!("Error while deregistering from Consul: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn discovery_loop(self: &Arc<Self>, mut stop_signal: watch::Receiver<bool>) {
|
||||
while !*stop_signal.borrow() {
|
||||
let peers_up = self
|
||||
|
||||
@@ -32,6 +32,7 @@ lazy_static.workspace = true
|
||||
tracing.workspace = true
|
||||
rand.workspace = true
|
||||
sha2.workspace = true
|
||||
subtle.workspace = true
|
||||
|
||||
chrono.workspace = true
|
||||
rmp-serde.workspace = true
|
||||
|
||||
+35
-4
@@ -90,7 +90,7 @@ pub struct Config {
|
||||
pub allow_world_readable_secrets: bool,
|
||||
|
||||
/// RPC secret key: 32 bytes hex encoded
|
||||
pub rpc_secret: Option<String>,
|
||||
pub rpc_secret: Option<Secret<String>>,
|
||||
/// Optional file where RPC secret key is read from
|
||||
pub rpc_secret_file: Option<PathBuf>,
|
||||
/// Address to bind for RPC
|
||||
@@ -205,6 +205,37 @@ pub struct WebConfig {
|
||||
pub add_host_to_metrics: bool,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Clone)]
|
||||
#[serde(transparent)]
|
||||
pub struct Secret<T>(T);
|
||||
|
||||
impl<T> Secret<T> {
|
||||
pub fn new(secret: T) -> Self {
|
||||
Secret(secret)
|
||||
}
|
||||
|
||||
pub fn extract_secret(&self) -> &T {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: std::ops::Deref<Target = str>> Secret<T> {
|
||||
pub fn eq_ct(&self, other: &T) -> bool {
|
||||
use subtle::ConstantTimeEq;
|
||||
self.0
|
||||
.deref()
|
||||
.as_bytes()
|
||||
.ct_eq(other.deref().as_bytes())
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> std::fmt::Debug for Secret<T> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("Secret").finish_non_exhaustive()
|
||||
}
|
||||
}
|
||||
|
||||
/// Configuration for the admin and monitoring HTTP API
|
||||
#[derive(Deserialize, Debug, Clone, Default)]
|
||||
pub struct AdminConfig {
|
||||
@@ -212,7 +243,7 @@ pub struct AdminConfig {
|
||||
pub api_bind_addr: Option<UnixOrTCPSocketAddress>,
|
||||
|
||||
/// Bearer token to use to scrape metrics
|
||||
pub metrics_token: Option<String>,
|
||||
pub metrics_token: Option<Secret<String>>,
|
||||
/// File to read metrics token from
|
||||
pub metrics_token_file: Option<PathBuf>,
|
||||
/// Whether to require an access token for accessing the metrics endpoint
|
||||
@@ -220,7 +251,7 @@ pub struct AdminConfig {
|
||||
pub metrics_require_token: bool,
|
||||
|
||||
/// Bearer token to use to access Admin API endpoints
|
||||
pub admin_token: Option<String>,
|
||||
pub admin_token: Option<Secret<String>>,
|
||||
/// File to read admin token from
|
||||
pub admin_token_file: Option<PathBuf>,
|
||||
|
||||
@@ -252,7 +283,7 @@ pub struct ConsulDiscoveryConfig {
|
||||
/// Client TLS key to use when connecting to Consul
|
||||
pub client_key: Option<String>,
|
||||
/// /// Token to use for connecting to consul
|
||||
pub token: Option<String>,
|
||||
pub token: Option<Secret<String>>,
|
||||
/// Skip TLS hostname verification
|
||||
#[serde(default)]
|
||||
pub tls_skip_verify: bool,
|
||||
|
||||
Reference in New Issue
Block a user