Skip to content

Commit 082159a

Browse files
Merge pull request #2481 from oasisprotocol/martin/feature/rofl-public-config
Support non-encrypted variables in ROFL manifest yaml
2 parents 471d7cd + b88002f commit 082159a

3 files changed

Lines changed: 110 additions & 11 deletions

File tree

rofl-containers/src/containers.rs

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,32 @@ pub async fn start() -> Result<()> {
8787
Ok(())
8888
}
8989

90+
/// Extract environment variables from deployment metadata.
91+
pub fn env_from_metadata(metadata: &BTreeMap<String, String>) -> BTreeMap<String, String> {
92+
metadata
93+
.iter()
94+
.filter_map(|(key, value)| {
95+
parse_env_metadata_key(key).map(|name| (name.to_string(), value.clone()))
96+
})
97+
.collect()
98+
}
99+
100+
/// Parse a metadata key that follows the environment variable convention.
101+
///
102+
/// The canonical format is `env.<VAR>`, where `<VAR>` environment variable
103+
/// consists of a non-empty ASCII alphanumeric characters or `_`.
104+
///
105+
/// Dotted forms are intentionally rejected so `env.<SERVICE>.<VAR>` can be added
106+
/// later without being breaking.
107+
fn parse_env_metadata_key(key: &str) -> Option<&str> {
108+
let name = key.strip_prefix("env.")?;
109+
if name.is_empty() || !name.chars().all(|c| c.is_ascii_alphanumeric() || c == '_') {
110+
return None;
111+
}
112+
113+
Some(name)
114+
}
115+
90116
static GLOBAL_ENVIRONMENT: LazyLock<Environment> = LazyLock::new(Environment::new);
91117

92118
/// Management of environment variables to expose to the compose file.
@@ -117,3 +143,63 @@ impl Environment {
117143
vars.clone()
118144
}
119145
}
146+
147+
#[cfg(test)]
148+
mod test {
149+
use super::*;
150+
151+
#[test]
152+
fn test_parse_env_metadata_key() {
153+
let tcs = vec![
154+
("foo", None),
155+
("env.", None),
156+
("env.MY_VAR", Some("MY_VAR")),
157+
("env.MY_VAR_1", Some("MY_VAR_1")),
158+
("env.service_A.MY_VAR", None),
159+
("env.MY-VAR", None),
160+
("env.MY VAR", None),
161+
("env.my_var", Some("my_var")),
162+
];
163+
for tc in tcs {
164+
assert_eq!(parse_env_metadata_key(tc.0), tc.1);
165+
}
166+
}
167+
168+
#[test]
169+
fn test_env_from_empty_metadata() {
170+
assert_eq!(env_from_metadata(&BTreeMap::new()), BTreeMap::new());
171+
}
172+
173+
#[test]
174+
fn test_env_from_metadata_with_non_env_keys() {
175+
let metadata = BTreeMap::from([
176+
(".env".to_string(), "ignored".to_string()),
177+
("env.MY_VAR".to_string(), "my value".to_string()),
178+
]);
179+
180+
assert_eq!(
181+
env_from_metadata(&metadata),
182+
BTreeMap::from([("MY_VAR".to_string(), "my value".to_string())])
183+
);
184+
}
185+
186+
#[test]
187+
fn test_env_from_metadata() {
188+
let metadata = BTreeMap::from([
189+
("env.".to_string(), "ignored".to_string()),
190+
("env.MY_VAR".to_string(), "my value".to_string()),
191+
("env.OTHER_VAR".to_string(), "other value".to_string()),
192+
("env.service_A.MY_VAR".to_string(), "reserved".to_string()),
193+
("env.MY-VAR".to_string(), "ignored".to_string()),
194+
("net.oasis.foo".to_string(), "ignored".to_string()),
195+
]);
196+
197+
assert_eq!(
198+
env_from_metadata(&metadata),
199+
BTreeMap::from([
200+
("MY_VAR".to_string(), "my value".to_string()),
201+
("OTHER_VAR".to_string(), "other value".to_string()),
202+
])
203+
);
204+
}
205+
}

rofl-containers/src/main.rs

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -158,9 +158,25 @@ impl App for ContainersApp {
158158
}
159159
}
160160

161-
// Initialize secrets.
161+
// Fetch app config.
162+
let app_cfg = match env.client().app_cfg().await {
163+
Ok(cfg) => cfg,
164+
Err(err) => {
165+
slog::error!(logger, "failed to fetch app config"; "err" => ?err);
166+
process::abort();
167+
}
168+
};
169+
170+
// Initialize environment variables from deployment metadata (env.* keys).
171+
slog::info!(logger, "initializing container environment variables");
172+
for (name, value) in containers::env_from_metadata(&app_cfg.metadata) {
173+
containers::env().set(&name, &value);
174+
slog::info!(logger, "provisioned environment variable"; "name" => name);
175+
}
176+
177+
// Initialize secrets (runs after env vars so secrets take precedence on collision).
162178
slog::info!(logger, "initializing container secrets");
163-
if let Err(err) = secrets::init(env.clone(), kms.clone()).await {
179+
if let Err(err) = secrets::init(&app_cfg.secrets, kms.clone()).await {
164180
slog::error!(logger, "failed to initialize container secrets"; "err" => ?err);
165181
process::abort();
166182
}

rofl-containers/src/secrets.rs

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use std::sync::Arc;
1+
use std::{collections::BTreeMap, sync::Arc};
22

33
use anyhow::Result;
44
use cmd_lib::run_cmd;
@@ -10,24 +10,21 @@ use rofl_appd::services::{self, kms::OpenSecretRequest};
1010
use crate::containers;
1111

1212
/// Initialize secrets available to containers.
13-
pub async fn init<A: App>(
14-
env: Environment<A>,
13+
pub async fn init(
14+
encrypted_secrets: &BTreeMap<String, Vec<u8>>,
1515
kms: Arc<dyn services::kms::KmsService>,
1616
) -> Result<()> {
1717
let logger = get_logger("secrets");
1818

19-
// Query own app cfg to get encrypted secrets.
20-
let encrypted_secrets = env.client().app_cfg().await?.secrets;
21-
2219
// Ensure all secrets are removed.
2320
run_cmd!(podman secret rm --all)?;
2421
// Create all requested secrets.
25-
for (pub_name, value) in encrypted_secrets {
22+
for (pub_name, encrypted_value) in encrypted_secrets {
2623
// Decrypt and authenticate secret. In case of failures, the secret is skipped.
2724
let (name, value) = match kms
2825
.open_secret(&OpenSecretRequest {
29-
name: &pub_name,
30-
value: &value,
26+
name: pub_name,
27+
value: encrypted_value,
3128
context: None,
3229
})
3330
.await

0 commit comments

Comments
 (0)