Skip to content

Commit

Permalink
Add command line option --escape-newlines
Browse files Browse the repository at this point in the history
Depending on the file type we're operating on, escaping newlines in
credentials is necessary (e.g., JSON strings).
  • Loading branch information
veehaitch committed Oct 29, 2024
1 parent 10aa66b commit 248d322
Show file tree
Hide file tree
Showing 4 changed files with 46 additions and 3 deletions.
4 changes: 2 additions & 2 deletions nixos/tests/nixos-test-mk-load-credential-option/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ nixosTest {
mkdir --mode 700 -p /run/secrets
echo "CLqLt9zrR5k92TqVgIUSNu+gV4pyCuNu8F9X3pEfA28=" > /run/secrets/a-key
echo "wurzelpfropf" > /run/secrets/a-name
echo -en "prometheus\n\n" > /run/secrets/a-password
echo -en "prome\ntheus\n\n" > /run/secrets/a-password
'';
};

Expand All @@ -51,7 +51,7 @@ nixosTest {
"maybeASecret": "kartoffelpuffer",
"secretKey": "CLqLt9zrR5k92TqVgIUSNu+gV4pyCuNu8F9X3pEfA28=",
"secretName": "wurzelpfropf",
"secretPassword": "prometheus"
"secretPassword": "prome\ntheus"
}
assert actual == expected, f"appsettings.json has unexpected content '{out}'"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ in
DynamicUser = true;

LoadCredential = pkgs.systemd-credsubst.lib.toLoadCredentialList cfg.settings;
ExecStartPre = [ "${pkgs.systemd-credsubst}/bin/systemd-credsubst -i ${configFile} -o appsettings.json" ];
ExecStartPre = [ "${pkgs.systemd-credsubst}/bin/systemd-credsubst --escape-newlines -i ${configFile} -o appsettings.json" ];
ExecStart = "${pkgs.pkgsStatic.busybox}/bin/tail -f -n +1 appsettings.json";

WorkingDirectory = "/run/systemd-credsubst-test/workdir";
Expand Down
11 changes: 11 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ struct Cli {
help = "Make parent directories of the output file as needed."
)]
make_parents: bool,
#[arg(short, long, help = "Escape newlines.")]
escape_newlines: bool,
}

fn validate_path_exists(path: &str) -> Result<PathBuf, String> {
Expand Down Expand Up @@ -155,6 +157,7 @@ fn substitute(
creds_dir: &str,
pattern: &Regex,
make_parents: bool,
escape_newlines: bool,
) -> Result<()> {
// Read input as string
let mut reader = input_reader(input)?;
Expand Down Expand Up @@ -182,6 +185,13 @@ fn substitute(
fs::read_to_string(secret_path)
.context(format!("Failed to open '{secret_path}' for reading"))
.map(|s| s.trim_end().to_string())
.map(|s| {
if escape_newlines {
s.replace("\n", "\\n")
} else {
s
}
})
},
)
},
Expand Down Expand Up @@ -212,6 +222,7 @@ fn main() -> Result<ExitCode> {
&creds_dir?,
&cli.pattern,
cli.make_parents,
cli.escape_newlines,
)?,
Err(err) => {
if cli.copy_if_no_creds {
Expand Down
32 changes: 32 additions & 0 deletions tests/credsubst.rs
Original file line number Diff line number Diff line change
Expand Up @@ -307,3 +307,35 @@ fn fails_no_make_parents() -> Result<()> {

Ok(())
}

#[test]
fn escape_newlines() -> Result<()> {
let credentials_dir = tempfile::tempdir()?;
let mut file = File::create(credentials_dir.path().join("yaxi-license"))?;
file.write_all(b"This\nis\na\nmulti-line\n\n\nlicense\n")?;

let mut cmd = Command::cargo_bin(crate_name!())?;
let assert = cmd
.env("CREDENTIALS_DIRECTORY", credentials_dir.path())
.arg("--escape-newlines")
.write_stdin(indoc! {r#"
{
"wurzel": "pfropf",
"license": "${yaxi-license}",
"wuff": "c://msdog",
"password": "secret-password:${/with-special-chars}"
}
"#})
.assert();

assert.success().stdout(indoc! {r#"
{
"wurzel": "pfropf",
"license": "This\nis\na\nmulti-line\n\n\nlicense",
"wuff": "c://msdog",
"password": "secret-password:${/with-special-chars}"
}
"#});

Ok(())
}

0 comments on commit 248d322

Please sign in to comment.