mirror of
https://github.com/moghtech/komodo.git
synced 2026-04-28 11:49:39 -05:00
write files potentially containing secrets as 0600
This commit is contained in:
12
Cargo.lock
generated
12
Cargo.lock
generated
@@ -1755,7 +1755,7 @@ dependencies = [
|
||||
"anyhow",
|
||||
"formatting",
|
||||
"komodo_client",
|
||||
"tokio",
|
||||
"secret_file",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2871,6 +2871,7 @@ dependencies = [
|
||||
"resolver_api",
|
||||
"response",
|
||||
"rustls 0.23.32",
|
||||
"secret_file",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_yaml_ng",
|
||||
@@ -2926,6 +2927,7 @@ dependencies = [
|
||||
"response",
|
||||
"run_command",
|
||||
"rustls 0.23.32",
|
||||
"secret_file",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_yaml_ng",
|
||||
@@ -3297,6 +3299,7 @@ dependencies = [
|
||||
"komodo_client",
|
||||
"pem-rfc7468",
|
||||
"pkcs8",
|
||||
"secret_file",
|
||||
"serde_json",
|
||||
"snow",
|
||||
"spki",
|
||||
@@ -4661,6 +4664,13 @@ dependencies = [
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "secret_file"
|
||||
version = "2.0.0-dev-42"
|
||||
dependencies = [
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "security-framework"
|
||||
version = "2.11.1"
|
||||
|
||||
@@ -25,6 +25,7 @@ periphery_client = { path = "client/periphery/rs" }
|
||||
environment_file = { path = "lib/environment_file" }
|
||||
environment = { path = "lib/environment" }
|
||||
interpolate = { path = "lib/interpolate" }
|
||||
secret_file = { path = "lib/secret_file" }
|
||||
formatting = { path = "lib/formatting" }
|
||||
transport = { path = "lib/transport" }
|
||||
database = { path = "lib/database" }
|
||||
|
||||
@@ -19,6 +19,7 @@ komodo_client = { workspace = true, features = ["mongo"] }
|
||||
periphery_client.workspace = true
|
||||
environment_file.workspace = true
|
||||
interpolate.workspace = true
|
||||
secret_file.workspace = true
|
||||
formatting.workspace = true
|
||||
transport.workspace = true
|
||||
database.workspace = true
|
||||
|
||||
@@ -142,15 +142,11 @@ impl Resolve<ExecuteArgs> for RunAction {
|
||||
let file = format!("{}.ts", random_string(10));
|
||||
let path = core_config().action_directory.join(&file);
|
||||
|
||||
if let Some(parent) = path.parent() {
|
||||
fs::create_dir_all(parent)
|
||||
.await
|
||||
.with_context(|| format!("Failed to initialize Action file parent directory {parent:?}"))?;
|
||||
}
|
||||
|
||||
fs::write(&path, contents).await.with_context(|| {
|
||||
format!("Failed to write action file to {path:?}")
|
||||
})?;
|
||||
secret_file::write_async(&path, contents)
|
||||
.await
|
||||
.with_context(|| {
|
||||
format!("Failed to write action file to {path:?}")
|
||||
})?;
|
||||
|
||||
let CoreConfig { ssl_enabled, .. } = core_config();
|
||||
|
||||
|
||||
@@ -272,8 +272,9 @@ async fn write_dockerfile_contents_git(
|
||||
return Ok(update);
|
||||
}
|
||||
|
||||
if let Err(e) =
|
||||
fs::write(&full_path, &contents).await.with_context(|| {
|
||||
if let Err(e) = secret_file::write_async(&full_path, &contents)
|
||||
.await
|
||||
.with_context(|| {
|
||||
format!("Failed to write dockerfile contents to {full_path:?}")
|
||||
})
|
||||
{
|
||||
|
||||
@@ -206,15 +206,7 @@ async fn write_sync_file_contents_on_host(
|
||||
.context("Invalid resource path")?;
|
||||
let full_path = root.join(&resource_path).join(&file_path);
|
||||
|
||||
if let Some(parent) = full_path.parent() {
|
||||
tokio::fs::create_dir_all(parent).await.with_context(|| {
|
||||
format!(
|
||||
"Failed to initialize resource file parent directory {parent:?}"
|
||||
)
|
||||
})?;
|
||||
}
|
||||
|
||||
if let Err(e) = tokio::fs::write(&full_path, &contents)
|
||||
if let Err(e) = secret_file::write_async(&full_path, &contents)
|
||||
.await
|
||||
.with_context(|| {
|
||||
format!(
|
||||
@@ -491,12 +483,7 @@ impl Resolve<WriteArgs> for CommitSync {
|
||||
.sync_directory
|
||||
.join(to_path_compatible_name(&sync.name))
|
||||
.join(&resource_path);
|
||||
if let Some(parent) = file_path.parent() {
|
||||
tokio::fs::create_dir_all(parent)
|
||||
.await
|
||||
.with_context(|| format!("Failed to initialize resource file parent directory {parent:?}"))?;
|
||||
};
|
||||
if let Err(e) = tokio::fs::write(&file_path, &res.toml)
|
||||
if let Err(e) = secret_file::write_async(&file_path, &res.toml)
|
||||
.await
|
||||
.with_context(|| {
|
||||
format!("Failed to write resource file to {file_path:?}",)
|
||||
|
||||
@@ -20,6 +20,7 @@ periphery_client.workspace = true
|
||||
environment_file.workspace = true
|
||||
environment.workspace = true
|
||||
interpolate.workspace = true
|
||||
secret_file.workspace = true
|
||||
formatting.workspace = true
|
||||
transport.workspace = true
|
||||
response.workspace = true
|
||||
|
||||
@@ -26,14 +26,7 @@ pub async fn write_dockerfile(
|
||||
.components()
|
||||
.collect::<PathBuf>();
|
||||
|
||||
// Ensure parent directory exists
|
||||
if let Some(parent) = full_dockerfile_path.parent() && !parent.exists() {
|
||||
tokio::fs::create_dir_all(parent)
|
||||
.await
|
||||
.with_context(|| format!("Failed to initialize dockerfile parent directory {parent:?}"))?;
|
||||
}
|
||||
|
||||
tokio::fs::write(&full_dockerfile_path, dockerfile).await.with_context(|| {
|
||||
secret_file::write_async(&full_dockerfile_path, dockerfile).await.with_context(|| {
|
||||
format!(
|
||||
"Failed to write dockerfile contents to {full_dockerfile_path:?}"
|
||||
)
|
||||
@@ -86,12 +79,14 @@ pub async fn parse_secret_args(
|
||||
}
|
||||
// Write the value to file to mount
|
||||
let path = build_dir.join(variable);
|
||||
tokio::fs::write(&path, value).await.with_context(|| {
|
||||
format!(
|
||||
"Failed to write build secret {variable} to {}",
|
||||
path.display()
|
||||
)
|
||||
})?;
|
||||
secret_file::write_async(&path, value).await.with_context(
|
||||
|| {
|
||||
format!(
|
||||
"Failed to write build secret {variable} to {}",
|
||||
path.display()
|
||||
)
|
||||
},
|
||||
)?;
|
||||
// Extend the command
|
||||
write!(
|
||||
&mut res,
|
||||
|
||||
@@ -106,9 +106,13 @@ impl Resolve<super::Args> for WriteDockerfileContentsToHost {
|
||||
.await
|
||||
.with_context(|| format!("Failed to initialize dockerfile parent directory {parent:?}"))?;
|
||||
}
|
||||
fs::write(&full_path, contents).await.with_context(|| {
|
||||
format!("Failed to write dockerfile contents to {full_path:?}")
|
||||
})?;
|
||||
secret_file::write_async(&full_path, contents)
|
||||
.await
|
||||
.with_context(|| {
|
||||
format!(
|
||||
"Failed to write dockerfile contents to {full_path:?}"
|
||||
)
|
||||
})?;
|
||||
Ok(Log::simple(
|
||||
"Write dockerfile to host",
|
||||
format!("dockerfile contents written to {full_path:?}"),
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
use std::{borrow::Cow, path::PathBuf};
|
||||
|
||||
use anyhow::{Context, anyhow};
|
||||
use command::{
|
||||
run_komodo_command, run_komodo_command_with_sanitization,
|
||||
@@ -18,8 +20,6 @@ use periphery_client::api::compose::*;
|
||||
use resolver_api::Resolve;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use shell_escape::unix::escape;
|
||||
use std::{borrow::Cow, path::PathBuf};
|
||||
use tokio::fs;
|
||||
|
||||
use crate::{
|
||||
config::periphery_config,
|
||||
@@ -160,12 +160,6 @@ impl Resolve<super::Args> for GetComposeContentsOnHost {
|
||||
let run_directory =
|
||||
root.join(&run_directory).components().collect::<PathBuf>();
|
||||
|
||||
if !run_directory.exists() {
|
||||
fs::create_dir_all(&run_directory)
|
||||
.await
|
||||
.context("Failed to initialize run directory")?;
|
||||
}
|
||||
|
||||
let mut res = GetComposeContentsOnHostResponse::default();
|
||||
|
||||
for file in file_paths {
|
||||
@@ -173,11 +167,13 @@ impl Resolve<super::Args> for GetComposeContentsOnHost {
|
||||
.join(&file.path)
|
||||
.components()
|
||||
.collect::<PathBuf>();
|
||||
match fs::read_to_string(&full_path).await.with_context(|| {
|
||||
format!(
|
||||
"Failed to read compose file contents at {full_path:?}"
|
||||
)
|
||||
}) {
|
||||
match tokio::fs::read_to_string(&full_path).await.with_context(
|
||||
|| {
|
||||
format!(
|
||||
"Failed to read compose file contents at {full_path:?}"
|
||||
)
|
||||
},
|
||||
) {
|
||||
Ok(contents) => {
|
||||
// The path we store here has to be the same as incoming file path in the array,
|
||||
// in order for WriteComposeContentsToHost to write to the correct path.
|
||||
@@ -227,17 +223,13 @@ impl Resolve<super::Args> for WriteComposeContentsToHost {
|
||||
.join(file_path)
|
||||
.components()
|
||||
.collect::<PathBuf>();
|
||||
// Ensure parent directory exists
|
||||
if let Some(parent) = file_path.parent() {
|
||||
fs::create_dir_all(&parent)
|
||||
.await
|
||||
.with_context(|| format!("Failed to initialize compose file parent directory {parent:?}"))?;
|
||||
}
|
||||
fs::write(&file_path, contents).await.with_context(|| {
|
||||
format!(
|
||||
"Failed to write compose file contents to {file_path:?}"
|
||||
)
|
||||
})?;
|
||||
secret_file::write_async(&file_path, contents)
|
||||
.await
|
||||
.with_context(|| {
|
||||
format!(
|
||||
"Failed to write compose file contents to {file_path:?}"
|
||||
)
|
||||
})?;
|
||||
Ok(Log::simple(
|
||||
"Write contents to host",
|
||||
format!("File contents written to {file_path:?}"),
|
||||
|
||||
@@ -354,7 +354,7 @@ async fn write_stack_ui_defined(
|
||||
.components()
|
||||
.collect::<PathBuf>();
|
||||
|
||||
fs::write(&file_path, &stack.config.file_contents)
|
||||
secret_file::write_async(&file_path, &stack.config.file_contents)
|
||||
.await
|
||||
.with_context(|| {
|
||||
format!("Failed to write compose file to {file_path:?}")
|
||||
|
||||
@@ -60,7 +60,7 @@ impl Resolve<super::Args> for RotateCorePublicKey {
|
||||
};
|
||||
|
||||
SpkiPublicKey::from(self.public_key)
|
||||
.write_pem(core_public_key_path)?;
|
||||
.write_pem_sync(core_public_key_path)?;
|
||||
|
||||
core_public_keys().refresh();
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ homepage.workspace = true
|
||||
|
||||
[dependencies]
|
||||
komodo_client.workspace = true
|
||||
secret_file.workspace = true
|
||||
formatting.workspace = true
|
||||
#
|
||||
anyhow.workspace = true
|
||||
tokio.workspace = true
|
||||
anyhow.workspace = true
|
||||
@@ -32,19 +32,7 @@ pub async fn write_env_file(
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n");
|
||||
|
||||
if let Some(parent) = env_file_path.parent()
|
||||
&& let Err(e) = tokio::fs::create_dir_all(parent)
|
||||
.await
|
||||
.with_context(|| format!("Failed to initialize environment file parent directory {parent:?}"))
|
||||
{
|
||||
logs.push(Log::error(
|
||||
"Write Environment File",
|
||||
format_serror(&e.into()),
|
||||
));
|
||||
return None;
|
||||
}
|
||||
|
||||
if let Err(e) = tokio::fs::write(&env_file_path, contents)
|
||||
if let Err(e) = secret_file::write_async(&env_file_path, contents)
|
||||
.await
|
||||
.with_context(|| {
|
||||
format!("Failed to write environment file to {env_file_path:?}")
|
||||
|
||||
@@ -7,7 +7,6 @@ use komodo_client::entities::{
|
||||
RepoExecutionResponse, all_logs_success, update::Log,
|
||||
};
|
||||
use run_command::async_run_command;
|
||||
use tokio::fs;
|
||||
|
||||
use crate::get_commit_hash_log;
|
||||
|
||||
@@ -35,12 +34,12 @@ pub async fn write_commit_file(
|
||||
.collect::<PathBuf>();
|
||||
|
||||
if let Some(parent) = full_file_path.parent() {
|
||||
fs::create_dir_all(parent).await.with_context(|| {
|
||||
tokio::fs::create_dir_all(parent).await.with_context(|| {
|
||||
format!("Failed to initialize file parent directory {parent:?}")
|
||||
})?;
|
||||
}
|
||||
|
||||
fs::write(&full_file_path, contents)
|
||||
tokio::fs::write(&full_file_path, contents)
|
||||
.await
|
||||
.with_context(|| {
|
||||
format!("Failed to write contents to {full_file_path:?}")
|
||||
|
||||
@@ -9,6 +9,7 @@ homepage.workspace = true
|
||||
|
||||
[dependencies]
|
||||
komodo_client.workspace = true
|
||||
secret_file.workspace = true
|
||||
#
|
||||
pem-rfc7468.workspace = true
|
||||
serde_json.workspace = true
|
||||
|
||||
@@ -73,7 +73,7 @@ pub fn generate_write_keys(
|
||||
let path = path.as_ref();
|
||||
// Generate and write pems to path
|
||||
let keys = EncodedKeyPair::generate()?;
|
||||
keys.private.write_pem(path)?;
|
||||
keys.public.write_pem(path.with_extension("pub"))?;
|
||||
keys.private.write_pem_sync(path)?;
|
||||
keys.public.write_pem_sync(path.with_extension("pub"))?;
|
||||
Ok(keys)
|
||||
}
|
||||
|
||||
@@ -42,25 +42,32 @@ impl Pkcs8PrivateKey {
|
||||
)
|
||||
}
|
||||
|
||||
pub fn write_pem<P: AsRef<Path>>(
|
||||
pub fn write_pem_sync(
|
||||
&self,
|
||||
path: P,
|
||||
path: impl AsRef<Path>,
|
||||
) -> anyhow::Result<()> {
|
||||
let path = path.as_ref();
|
||||
// Ensure the parent directory exists
|
||||
if let Some(parent) = path.parent() {
|
||||
std::fs::create_dir_all(parent).with_context(|| {
|
||||
format!(
|
||||
"Failed to create private key parent directory {parent:?}"
|
||||
)
|
||||
})?;
|
||||
}
|
||||
tracing::info!("Writing private key to {path:?}");
|
||||
std::fs::write(path, self.as_pem()).with_context(|| {
|
||||
secret_file::write_sync(path, self.as_pem()).with_context(|| {
|
||||
format!("Failed to write private key pem to {path:?}")
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn write_pem_async(
|
||||
&self,
|
||||
path: impl AsRef<Path>,
|
||||
) -> anyhow::Result<()> {
|
||||
let path = path.as_ref();
|
||||
// Ensure the parent directory exists
|
||||
tracing::info!("Writing private key to {path:?}");
|
||||
secret_file::write_async(path, self.as_pem())
|
||||
.await
|
||||
.with_context(|| {
|
||||
format!("Failed to write private key pem to {path:?}")
|
||||
})
|
||||
}
|
||||
|
||||
/// - For raw bytes: converts to pkcs8 and returns
|
||||
/// - For pkcs8 base64: clones and returns
|
||||
/// - For pkcs8 base64 pem: unwraps and returns
|
||||
|
||||
@@ -43,25 +43,30 @@ impl SpkiPublicKey {
|
||||
)
|
||||
}
|
||||
|
||||
pub fn write_pem<P: AsRef<Path>>(
|
||||
pub fn write_pem_sync(
|
||||
&self,
|
||||
path: P,
|
||||
path: impl AsRef<Path>,
|
||||
) -> anyhow::Result<()> {
|
||||
let path = path.as_ref();
|
||||
// Ensure the parent directory exists
|
||||
if let Some(parent) = path.parent() {
|
||||
std::fs::create_dir_all(parent).with_context(|| {
|
||||
format!(
|
||||
"Failed to create private key parent directory {parent:?}"
|
||||
)
|
||||
})?;
|
||||
}
|
||||
tracing::info!("Writing public key to {path:?}");
|
||||
std::fs::write(path, self.as_pem()).with_context(|| {
|
||||
secret_file::write_sync(path, self.as_pem()).with_context(|| {
|
||||
format!("Failed to write private key pem to {path:?}")
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn write_pem_async(
|
||||
&self,
|
||||
path: impl AsRef<Path>,
|
||||
) -> anyhow::Result<()> {
|
||||
let path = path.as_ref();
|
||||
tracing::info!("Writing public key to {path:?}");
|
||||
secret_file::write_async(path, self.as_pem())
|
||||
.await
|
||||
.with_context(|| {
|
||||
format!("Failed to write private key pem to {path:?}")
|
||||
})
|
||||
}
|
||||
|
||||
/// Accepts pem rfc7468 (openssl) or base64 der (second line of rfc7468 pem).
|
||||
pub fn from_maybe_pem(
|
||||
public_key_maybe_pem: &str,
|
||||
|
||||
11
lib/secret_file/Cargo.toml
Normal file
11
lib/secret_file/Cargo.toml
Normal file
@@ -0,0 +1,11 @@
|
||||
[package]
|
||||
name = "secret_file"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
authors.workspace = true
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
homepage.workspace = true
|
||||
|
||||
[dependencies]
|
||||
tokio.workspace = true
|
||||
64
lib/secret_file/src/lib.rs
Normal file
64
lib/secret_file/src/lib.rs
Normal file
@@ -0,0 +1,64 @@
|
||||
use std::path::Path;
|
||||
|
||||
/// Writes data to path, setting permissions to 0600.
|
||||
/// `std::fs` sync version.
|
||||
///
|
||||
/// Also ensures parent directory exists.
|
||||
pub fn write_sync(
|
||||
path: impl AsRef<Path>,
|
||||
contents: impl AsRef<[u8]>,
|
||||
) -> std::io::Result<()> {
|
||||
use std::{io::Write, os::unix::fs::OpenOptionsExt};
|
||||
|
||||
let path = path.as_ref();
|
||||
|
||||
if let Some(parent) = path.parent() {
|
||||
std::fs::create_dir_all(parent)?;
|
||||
}
|
||||
|
||||
let mut file = std::fs::OpenOptions::new()
|
||||
.write(true)
|
||||
.create(true)
|
||||
.truncate(true)
|
||||
// Only sets mode if file is created.
|
||||
// This leaves existing permissions intact.
|
||||
.mode(0o600)
|
||||
.open(path)?;
|
||||
|
||||
file.write_all(contents.as_ref())?;
|
||||
file.flush()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Writes data to path, setting permissions to 0600.
|
||||
/// `tokio::fs` async version.
|
||||
///
|
||||
/// Also ensures parent directory exists.
|
||||
pub async fn write_async(
|
||||
path: impl AsRef<Path>,
|
||||
contents: impl AsRef<[u8]>,
|
||||
) -> std::io::Result<()> {
|
||||
use tokio::io::AsyncWriteExt;
|
||||
|
||||
let path = path.as_ref();
|
||||
|
||||
if let Some(parent) = path.parent() {
|
||||
tokio::fs::create_dir_all(parent).await?;
|
||||
}
|
||||
|
||||
let mut file = tokio::fs::OpenOptions::new()
|
||||
.write(true)
|
||||
.create(true)
|
||||
.truncate(true)
|
||||
// Only sets mode if file is created.
|
||||
// This leaves existing permissions intact.
|
||||
.mode(0o600)
|
||||
.open(path)
|
||||
.await?;
|
||||
|
||||
file.write_all(contents.as_ref()).await?;
|
||||
file.flush().await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Reference in New Issue
Block a user