use crate::compose::types::ServiceSecretRef;
use super::Section;
pub(super) fn secret_field(value: &str) -> String {
value
.chars()
.filter(|c| !c.is_control() && *c != ',' && *c != '=')
.collect()
}
pub(super) fn render_secret(secret: &ServiceSecretRef) -> String {
match secret {
ServiceSecretRef::Short(name) => secret_field(name),
ServiceSecretRef::Long {
source,
target,
uid,
gid,
mode,
} => {
let mut s = secret_field(source);
if let Some(t) = target {
s.push_str(&format!(",target={}", secret_field(t)));
}
if let Some(u) = uid {
s.push_str(&format!(",uid={}", secret_field(u)));
}
if let Some(g) = gid {
s.push_str(&format!(",gid={}", secret_field(g)));
}
if let Some(m) = mode {
s.push_str(&format!(",mode={m:o}"));
}
s
}
}
}
pub(super) fn map_security_opt(
opt: &str,
container: &mut Section,
name: &str,
warnings: &mut Vec<String>,
) {
if let Some(rest) = opt.strip_prefix("no-new-privileges") {
let val = rest.trim_start_matches([':', '=']);
let enabled = val.is_empty() || val == "true";
container.add("NoNewPrivileges", enabled.to_string());
} else if let Some(profile) = opt.strip_prefix("seccomp=") {
container.add("SeccompProfile", profile.to_string());
} else if let Some(profile) = opt
.strip_prefix("apparmor=")
.or_else(|| opt.strip_prefix("apparmor:"))
{
container.add("PodmanArgs", format!("--security-opt apparmor={profile}"));
} else if let Some(label) = opt.strip_prefix("label=") {
if label == "disable" {
container.add("SecurityLabelDisable", "true".to_string());
} else if label == "nested" {
container.add("SecurityLabelNested", "true".to_string());
} else if let Some(t) = label.strip_prefix("type:") {
container.add("SecurityLabelType", t.to_string());
} else if let Some(l) = label.strip_prefix("level:") {
container.add("SecurityLabelLevel", l.to_string());
} else if let Some(f) = label.strip_prefix("filetype:") {
container.add("SecurityLabelFileType", f.to_string());
} else {
warnings.push(format!(
"{name}: security_opt 'label={label}' has no Quadlet key and is skipped"
));
}
} else {
warnings.push(format!(
"{name}: security_opt '{opt}' has no Quadlet mapping and is skipped"
));
}
}
#[cfg(test)]
mod tests {
use super::{render_secret, secret_field};
use crate::compose::types::ServiceSecretRef;
#[test]
fn secret_field_strips_separators_and_controls() {
assert_eq!(secret_field("a,b=c\nd"), "abcd");
assert_eq!(secret_field("plain"), "plain");
}
#[test]
fn render_secret_cannot_inject_extra_options() {
let s = ServiceSecretRef::Long {
source: "tok".into(),
target: Some("/run/x,uid=0".into()),
uid: None,
gid: None,
mode: None,
};
let out = render_secret(&s);
assert_eq!(out.matches(',').count(), 1);
assert_eq!(out, "tok,target=/run/xuid0");
}
}