Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions bin/core/src/api/execute/stack.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use database::mungos::mongodb::bson::{
doc, oid::ObjectId, to_bson, to_document,
};
use formatting::format_serror;
use interpolate::Interpolator;
use interpolate::{Interpolator, stack_environment_secret_replacers};
use komodo_client::{
api::{execute::*, write::RefreshStackCache},
entities::{
Expand Down Expand Up @@ -182,7 +182,7 @@ impl Resolve<ExecuteArgs> for DeployStack {

interpolator.secret_replacers
} else {
Default::default()
stack_environment_secret_replacers(&stack.config.environment)?
};

let DeployStackResponse {
Expand Down Expand Up @@ -886,7 +886,7 @@ pub async fn pull_stack_inner(
}
interpolator.secret_replacers
} else {
Default::default()
stack_environment_secret_replacers(&stack.config.environment)?
};

let res = periphery_client(server)
Expand Down Expand Up @@ -1376,7 +1376,7 @@ impl Resolve<ExecuteArgs> for RunStackService {

interpolator.secret_replacers
} else {
Default::default()
stack_environment_secret_replacers(&stack.config.environment)?
};

let log = periphery_client(&server)
Expand Down
33 changes: 31 additions & 2 deletions bin/core/src/api/write/stack.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,6 @@ impl Resolve<WriteArgs> for CreateStack {
fields(
operator = user.id,
stack = self.name,
config = serde_json::to_string(&self.config).unwrap(),
)
)]
async fn resolve(
Expand Down Expand Up @@ -127,7 +126,6 @@ impl Resolve<WriteArgs> for UpdateStack {
fields(
operator = user.id,
stack = self.id,
update = serde_json::to_string(&self.config).unwrap(),
)
)]
async fn resolve(
Expand Down Expand Up @@ -1139,3 +1137,34 @@ impl Resolve<WriteArgs> for BatchCheckStackForUpdate {
Ok(res)
}
}

#[cfg(test)]
mod tests {
fn resolve_attribute<'a>(
source: &'a str,
implementation: &str,
) -> &'a str {
let implementation = source
.split_once(implementation)
.expect("missing Stack write implementation")
.1;
implementation
.split_once("async fn resolve")
.expect("missing Stack write resolver")
.0
}

#[test]
fn stack_write_spans_do_not_serialize_config() {
let source = include_str!("stack.rs");
for implementation in [
"impl Resolve<WriteArgs> for CreateStack",
"impl Resolve<WriteArgs> for UpdateStack",
] {
assert!(
!resolve_attribute(source, implementation)
.contains("self.config")
);
}
}
}
41 changes: 37 additions & 4 deletions bin/core/src/api/write/variable.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use anyhow::{Context, anyhow};
use database::mungos::mongodb::bson::doc;
use interpolate::REDACTED;
use komodo_client::{
api::write::*,
entities::{Operation, ResourceTarget, variable::Variable},
Expand All @@ -19,6 +20,17 @@ use crate::{

use super::WriteArgs;

fn variable_record(variable: &Variable) -> String {
if variable.is_secret {
format!(
"Variable {{\n name: {:?},\n value: {REDACTED:?},\n description: {:?},\n is_secret: true,\n}}",
variable.name, variable.description
)
} else {
format!("{variable:#?}")
}
}

impl Resolve<WriteArgs> for CreateVariable {
#[instrument(
"CreateVariable",
Expand Down Expand Up @@ -73,7 +85,7 @@ impl Resolve<WriteArgs> for CreateVariable {
);

update
.push_simple_log("Create Variable", format!("{variable:#?}"));
.push_simple_log("Create Variable", variable_record(&variable));

update.finalize();

Expand Down Expand Up @@ -133,8 +145,7 @@ impl Resolve<WriteArgs> for UpdateVariableValue {

let log = if variable.is_secret {
format!(
"<span class=\"text-muted-foreground\">variable</span>: '{name}'\n<span class=\"text-muted-foreground\">from</span>: <span class=\"text-red-500\">{}</span>\n<span class=\"text-muted-foreground\">to</span>: <span class=\"text-green-500\">{value}</span>",
variable.value.replace(|_| true, "#")
"<span class=\"text-muted-foreground\">variable</span>: '{name}'\n<span class=\"text-muted-foreground\">from</span>: <span class=\"text-red-500\">{REDACTED}</span>\n<span class=\"text-muted-foreground\">to</span>: <span class=\"text-green-500\">{REDACTED}</span>"
)
} else {
format!(
Expand Down Expand Up @@ -255,11 +266,33 @@ impl Resolve<WriteArgs> for DeleteVariable {
);

update
.push_simple_log("Delete Variable", format!("{variable:#?}"));
.push_simple_log("Delete Variable", variable_record(&variable));
update.finalize();

add_update(update).await?;

Ok(variable)
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn secret_variable_record_omits_synthetic_marker() {
const MARKER: &str = "komodo-redaction-marker-4d9c1d48f7b84a9e";
let variable = Variable {
name: "SYNTHETIC_SECRET".to_string(),
value: MARKER.to_string(),
description: "redaction acceptance".to_string(),
is_secret: true,
};

let record = variable_record(&variable);

assert!(!record.contains(MARKER));
assert!(record.contains(REDACTED));
assert!(record.contains("SYNTHETIC_SECRET"));
}
}
15 changes: 15 additions & 0 deletions bin/core/src/resource/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,15 @@ pub trait KomodoResource {
update: &mut Update,
) -> anyhow::Result<()>;

/// Values which must be removed from persisted create/update records for
/// this resource. Most resources rely only on Komodo Secret interpolation;
/// resource types with direct secret-bearing config can override this.
fn update_secret_replacers(
_resource: &Resource<Self::Config, Self::Info>,
) -> anyhow::Result<Vec<(String, String)>> {
Ok(Vec::new())
}

// =======
// RENAME
// =======
Expand Down Expand Up @@ -717,6 +726,9 @@ pub async fn create<T: KomodoResource>(

T::post_create(&resource, &mut update).await?;

let replacers = T::update_secret_replacers(&resource)?;
update.sanitize(&replacers);

refresh_all_resources_cache().await;

update.finalize();
Expand All @@ -740,6 +752,7 @@ pub async fn update<T: KomodoResource>(
PermissionLevel::Write.into(),
)
.await?;
let mut replacers = T::update_secret_replacers(&resource)?;

if T::busy(&resource.id).await? {
return Err(anyhow!("{} busy", T::resource_type()));
Expand Down Expand Up @@ -812,7 +825,9 @@ pub async fn update<T: KomodoResource>(

let updated = get::<T>(id_or_name).await?;

replacers.extend(T::update_secret_replacers(&updated)?);
T::post_update(&updated, &mut update).await?;
update.sanitize(&replacers);

refresh_all_resources_cache().await;

Expand Down
11 changes: 11 additions & 0 deletions bin/core/src/resource/stack.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use anyhow::Context;
use database::mungos::mongodb::Collection;
use formatting::format_serror;
use indexmap::IndexSet;
use interpolate::stack_environment_secret_replacers;
use komodo_client::{
api::write::RefreshStackCache,
entities::{
Expand Down Expand Up @@ -345,6 +346,16 @@ impl super::KomodoResource for Stack {
Self::post_create(updated, update).await
}

fn update_secret_replacers(
stack: &Resource<Self::Config, Self::Info>,
) -> anyhow::Result<Vec<(String, String)>> {
Ok(
stack_environment_secret_replacers(&stack.config.environment)?
.into_iter()
.collect(),
)
}

// RENAME

fn rename_operation() -> Operation {
Expand Down
40 changes: 40 additions & 0 deletions client/core/rs/src/entities/update.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,22 @@ impl Update {
self.end_ts = Some(komodo_timestamp());
self.status = UpdateStatus::Complete;
}

/// Removes secret values from every persisted free-form field on an
/// update. This is intentionally broader than [Log::sanitize] so resource
/// audit snapshots cannot bypass the same redaction seam.
#[cfg(feature = "svi")]
pub fn sanitize(&mut self, replacers: &Vec<(String, String)>) {
for log in &mut self.logs {
log.sanitize(replacers);
}
self.other_data =
svi::replace_in_string(&self.other_data, replacers);
self.prev_toml =
svi::replace_in_string(&self.prev_toml, replacers);
self.current_toml =
svi::replace_in_string(&self.current_toml, replacers);
}
}

/// Minimal representation of an action performed by Komodo.
Expand Down Expand Up @@ -212,6 +228,30 @@ impl Log {
}
}

#[cfg(all(test, feature = "svi"))]
mod tests {
use super::*;

#[test]
fn update_sanitizes_logs_and_audit_snapshots() {
const MARKER: &str = "komodo-redaction-marker-4d9c1d48f7b84a9e";
let mut update = Update {
logs: vec![Log::simple("Synthetic", MARKER.to_string())],
other_data: MARKER.to_string(),
prev_toml: format!("environment = 'SECRET={MARKER}'"),
current_toml: format!("environment = 'SECRET={MARKER}'"),
..Default::default()
};

update
.sanitize(&vec![(MARKER.to_string(), "SECRET".to_string())]);
let serialized = serde_json::to_string(&update).unwrap();

assert!(!serialized.contains(MARKER));
assert!(serialized.contains("<SECRET>"));
}
}

/// An update's status
#[typeshare]
#[derive(
Expand Down
Loading