Skip to content

Commit d8f9558

Browse files
authored
Merge pull request #54 from stackql/feature/stackql-deploy-updates
Feature/stackql deploy updates
2 parents a615730 + ccccb99 commit d8f9558

16 files changed

Lines changed: 444 additions & 31 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "stackql-deploy"
3-
version = "2.0.9"
3+
version = "2.1.0"
44
edition = "2021"
55
rust-version = "1.75"
66
description = "Infrastructure-as-code framework for declarative cloud resource management using StackQL"

ci-scripts/get-contributors.iql

Lines changed: 25 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,25 @@
1-
SELECT login FROM
2-
(
3-
SELECT login, SUM(contributions) total_contributions FROM
4-
(SELECT login, contributions
5-
FROM github.repos.contributors
6-
WHERE owner = 'stackql'
7-
AND repo = 'stackql'
8-
UNION
9-
SELECT login, contributions
10-
FROM github.repos.contributors
11-
WHERE owner = 'stackql'
12-
AND repo = 'stackql-deploy'
13-
UNION
14-
SELECT login, contributions
15-
FROM github.repos.contributors
16-
WHERE owner = 'stackql'
17-
AND repo = 'stackql-deploy-rs') t
18-
GROUP BY login
19-
ORDER BY total_contributions DESC
20-
) t1
1+
-- Contributors across the stackql, stackql-deploy and stackql-deploy-rs repos,
2+
-- ordered by total contributions. Non-human contributors are excluded:
3+
-- GitHub-flagged bot accounts (type = 'Bot') and AI agent user accounts.
4+
SELECT login FROM
5+
(
6+
SELECT login, SUM(contributions) total_contributions FROM
7+
(SELECT login, type, contributions
8+
FROM github.repos.contributors
9+
WHERE owner = 'stackql'
10+
AND repo = 'stackql'
11+
UNION
12+
SELECT login, type, contributions
13+
FROM github.repos.contributors
14+
WHERE owner = 'stackql'
15+
AND repo = 'stackql-deploy'
16+
UNION
17+
SELECT login, type, contributions
18+
FROM github.repos.contributors
19+
WHERE owner = 'stackql'
20+
AND repo = 'stackql-deploy-rs') t
21+
WHERE type <> 'Bot'
22+
AND login NOT IN ('claude')
23+
GROUP BY login
24+
ORDER BY total_contributions DESC
25+
) t1

docs/exports.md

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,30 @@ Sensitive values can be masked in log output by listing them under
127127
```
128128

129129
The actual value is still stored in the context and usable by templates;
130-
only the log messages are masked.
130+
only the log messages are masked. Protected export values are masked
131+
everywhere they appear in log output, including rendered queries shown
132+
using `--dry-run` or `--show-queries` and `DEBUG` level logging.
133+
134+
## Protected inputs (globals and props)
135+
136+
To mask sensitive input values (rather than exported values), set
137+
`protected: true` on a global variable or resource property:
138+
139+
```yaml
140+
globals:
141+
- name: postgres_master_password
142+
value: "{{ POSTGRES_MASTER_PASSWORD }}"
143+
protected: true
144+
resources:
145+
- name: operational_db
146+
props:
147+
- name: master_user_password
148+
value: "{{ postgres_master_password }}"
149+
protected: true
150+
```
151+
152+
The rendered value is masked (shown as `********`) in all log output; the
153+
real value is still sent to the provider in queries.
131154

132155
## Stack-level exports
133156

src/commands/base.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1271,6 +1271,8 @@ impl CommandRunner {
12711271
);
12721272
println!("{}", sep);
12731273
for (name, val) in &rows {
1274+
// Mask protected values in the displayed table (files keep real values)
1275+
let val = crate::core::secrets::redact(val);
12741276
let display_val = if val.len() > max_val_len {
12751277
format!("{}...", &val[..max_val_len - 3])
12761278
} else {

src/core/config.rs

Lines changed: 125 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,9 @@ pub fn render_globals(
182182
}
183183

184184
let sql_compat = to_sql_compatible_json(&rendered);
185+
if global_var.protected {
186+
crate::core::secrets::register_secret(&sql_compat);
187+
}
185188
debug!(
186189
"Setting global variable [{}] to {}",
187190
global_var.name, sql_compat
@@ -210,6 +213,9 @@ pub fn render_properties(
210213
if let Some(ref value) = prop.value {
211214
let rendered = render_value(engine, value, &resource_context);
212215
let sql_compat = to_sql_compatible_json(&rendered);
216+
if prop.protected {
217+
crate::core::secrets::register_secret(&sql_compat);
218+
}
213219
debug!("Setting property [{}] to {}", prop.name, sql_compat);
214220
prop_context.insert(prop.name.clone(), sql_compat.clone());
215221
resource_context.insert(prop.name.clone(), sql_compat);
@@ -219,6 +225,9 @@ pub fn render_properties(
219225
if let Some(env_val) = values.get(stack_env) {
220226
let rendered = render_value(engine, &env_val.value, &resource_context);
221227
let sql_compat = to_sql_compatible_json(&rendered);
228+
if prop.protected {
229+
crate::core::secrets::register_secret(&sql_compat);
230+
}
222231
debug!(
223232
"Setting property [{}] using env-specific value to {}",
224233
prop.name, sql_compat
@@ -293,6 +302,9 @@ pub fn render_properties(
293302

294303
if let Some(merged_val) = base_value {
295304
let processed = serde_json::to_string(&merged_val).unwrap_or_default();
305+
if prop.protected {
306+
crate::core::secrets::register_secret(&processed);
307+
}
296308
prop_context.insert(prop.name.clone(), processed.clone());
297309
resource_context.insert(prop.name.clone(), processed);
298310
}
@@ -437,7 +449,7 @@ pub fn is_json(s: &str) -> bool {
437449
#[cfg(test)]
438450
mod tests {
439451
use super::*;
440-
use crate::resource::manifest::{Property, Resource};
452+
use crate::resource::manifest::{Property, PropertyValue, Resource};
441453

442454
/// Helper to create a minimal Resource for testing.
443455
fn make_resource(name: &str, props: Vec<Property>) -> Resource {
@@ -466,6 +478,7 @@ mod tests {
466478
values: None,
467479
description: String::new(),
468480
merge: None,
481+
protected: false,
469482
}
470483
}
471484

@@ -654,4 +667,115 @@ mod tests {
654667

655668
assert_eq!(ctx.get("client_token").unwrap(), token);
656669
}
670+
671+
// ------------------------------------------------------------------
672+
// protected (secret) value tests
673+
// ------------------------------------------------------------------
674+
675+
#[test]
676+
fn test_protected_prop_registered_for_redaction() {
677+
let engine = TemplateEngine::new();
678+
let global_context = HashMap::new();
679+
680+
let mut prop = make_prop("master_user_password", "Cfg-Prop-S3cret-Value-1");
681+
prop.protected = true;
682+
683+
let ctx = render_properties(&engine, &[prop], &global_context, "dev");
684+
685+
// Value is stored unmasked in the context (real queries need it)
686+
assert_eq!(
687+
ctx.get("master_user_password").unwrap(),
688+
"Cfg-Prop-S3cret-Value-1"
689+
);
690+
// But the log scrubber masks it wherever it appears
691+
let redacted =
692+
crate::core::secrets::redact("INSERT ... SELECT 'Cfg-Prop-S3cret-Value-1', ...");
693+
assert!(
694+
!redacted.contains("Cfg-Prop-S3cret-Value-1"),
695+
"protected prop value leaked: {}",
696+
redacted
697+
);
698+
}
699+
700+
#[test]
701+
fn test_protected_prop_env_specific_value_registered_for_redaction() {
702+
let engine = TemplateEngine::new();
703+
let global_context = HashMap::new();
704+
705+
let mut values = HashMap::new();
706+
values.insert(
707+
"dev".to_string(),
708+
PropertyValue {
709+
value: serde_yaml::Value::String("Cfg-EnvProp-S3cret-Value-2".to_string()),
710+
},
711+
);
712+
let prop = Property {
713+
name: "api_key".to_string(),
714+
value: None,
715+
values: Some(values),
716+
description: String::new(),
717+
merge: None,
718+
protected: true,
719+
};
720+
721+
let ctx = render_properties(&engine, &[prop], &global_context, "dev");
722+
723+
assert_eq!(ctx.get("api_key").unwrap(), "Cfg-EnvProp-S3cret-Value-2");
724+
let redacted = crate::core::secrets::redact("key = 'Cfg-EnvProp-S3cret-Value-2'");
725+
assert!(!redacted.contains("Cfg-EnvProp-S3cret-Value-2"));
726+
}
727+
728+
#[test]
729+
fn test_protected_global_registered_for_redaction() {
730+
let engine = TemplateEngine::new();
731+
let mut vars = HashMap::new();
732+
vars.insert(
733+
"DB_PASSWORD".to_string(),
734+
"Cfg-Global-S3cret-Value-3".to_string(),
735+
);
736+
737+
let manifest: Manifest = serde_yaml::from_str(
738+
r#"
739+
version: 1
740+
name: test-stack
741+
providers:
742+
- aws
743+
globals:
744+
- name: db_password
745+
value: "{{ DB_PASSWORD }}"
746+
protected: true
747+
- name: region
748+
value: us-east-1
749+
"#,
750+
)
751+
.unwrap();
752+
753+
let ctx = render_globals(&engine, &vars, &manifest, "dev", "test-stack");
754+
755+
// Stored unmasked
756+
assert_eq!(ctx.get("db_password").unwrap(), "Cfg-Global-S3cret-Value-3");
757+
// Masked in log output
758+
let redacted = crate::core::secrets::redact("password = 'Cfg-Global-S3cret-Value-3'");
759+
assert!(!redacted.contains("Cfg-Global-S3cret-Value-3"));
760+
// Non-protected global is not masked
761+
let not_redacted = crate::core::secrets::redact("region = 'us-east-1'");
762+
assert!(not_redacted.contains("us-east-1"));
763+
}
764+
765+
#[test]
766+
fn test_unprotected_prop_not_registered() {
767+
let engine = TemplateEngine::new();
768+
let global_context = HashMap::new();
769+
770+
let prop = make_prop("instance_class", "Cfg-Plain-Value-Not-Secret-4");
771+
772+
let ctx = render_properties(&engine, &[prop], &global_context, "dev");
773+
774+
assert_eq!(
775+
ctx.get("instance_class").unwrap(),
776+
"Cfg-Plain-Value-Not-Secret-4"
777+
);
778+
let out = crate::core::secrets::redact("class = 'Cfg-Plain-Value-Not-Secret-4'");
779+
assert!(out.contains("Cfg-Plain-Value-Not-Secret-4"));
780+
}
657781
}

src/core/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,6 @@
88
pub mod config;
99
pub mod env;
1010
pub mod errors;
11+
pub mod secrets;
1112
pub mod templating;
1213
pub mod utils;

0 commit comments

Comments
 (0)