Skip to content

Commit 6dda6aa

Browse files
Martin Kyselphilip-stoev
Martin Kysel
andauthored
coord: Add support for FilesystemSecretsController (MaterializeInc#11628)
Continuation of the SECRETS work as defined in its design. This PR adds a configurable Local Filesystem secrets controller. The controller stores the contents of the secret on disk inside of the mzdata/secrets directory. Each secret is stored in a file that matches its GlobalId. The user can select which secret controller they want to use via --secrets-controller the two options are local-file-system and kubernetes. The default is the FilesystemSecretsController. This PR also adds the necessary files for the Kubernetes controller, yet the controller does nothing. Motivation This PR adds a known-desirable feature. Co-authored-by: Philip Stoev <[email protected]>
1 parent 6f3f8a8 commit 6dda6aa

File tree

20 files changed

+399
-10
lines changed

20 files changed

+399
-10
lines changed

Cargo.lock

Lines changed: 30 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,9 @@ members = [
4242
"src/repr-test-util",
4343
"src/repr",
4444
"src/s3-datagen",
45+
"src/secrets",
46+
"src/secrets-filesystem",
47+
"src/secrets-kubernetes",
4548
"src/sql-parser",
4649
"src/sql",
4750
"src/sqllogictest",

ci/nightly/pipeline.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ steps:
3434
- { value: feature-benchmark-persistence }
3535
- { value: aws-config }
3636
- { value: zippy }
37+
- { value: secrets }
3738
- { value: persistence-failpoints }
3839
- { value: catalog-compat }
3940
multiple: true
@@ -209,6 +210,13 @@ steps:
209210
- ./ci/plugins/mzcompose:
210211
composition: zippy
211212

213+
- id: secrets
214+
label: "Secrets"
215+
timeout_in_minutes: 30
216+
plugins:
217+
- ./ci/plugins/mzcompose:
218+
composition: secrets
219+
212220
- id: persistence-failpoints
213221
label: Persistence failpoints
214222
depends_on: build-x86_64

misc/python/materialize/mzcompose/__init__.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -601,12 +601,18 @@ def exec(
601601
stdin: read STDIN from a string.
602602
"""
603603

604+
entrypoint = (
605+
self.compose["services"][service]["entrypoint"]
606+
if "entrypoint" in self.compose["services"][service]
607+
else None
608+
)
609+
604610
return self.invoke(
605611
"exec",
606612
*(["--detach"] if detach else []),
607613
"-T",
608614
service,
609-
*self.compose["services"][service]["entrypoint"],
615+
*([entrypoint] if entrypoint else []),
610616
*args,
611617
stdin=stdin,
612618
)

src/coord/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ mz-stash = { path = "../stash" }
3838
mz-sql = { path = "../sql" }
3939
mz-sql-parser = { path = "../sql-parser" }
4040
mz-transform = { path = "../transform" }
41+
mz-secrets = { path = "../secrets"}
4142
postgres-types = { git = "https://github.com/MaterializeInc/rust-postgres", branch = "mz-0.7.2" }
4243
prometheus = { version = "0.13.0", default-features = false }
4344
prost = "0.9.0"

src/coord/src/coord.rs

Lines changed: 52 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ use derivative::Derivative;
7878
use differential_dataflow::lattice::Lattice;
7979
use futures::future::{FutureExt, TryFutureExt};
8080
use futures::stream::StreamExt;
81+
use itertools::Itertools;
8182
use rand::Rng;
8283
use timely::order::PartialOrder;
8384
use timely::progress::frontier::MutableAntichain;
@@ -104,8 +105,8 @@ use mz_dataflow_types::{
104105
Update,
105106
};
106107
use mz_expr::{
107-
permutation_for_arrangement, CollectionPlan, EvalError, ExprHumanizer, GlobalId,
108-
MirRelationExpr, MirScalarExpr, OptimizedMirRelationExpr, RowSetFinishing,
108+
permutation_for_arrangement, CollectionPlan, ExprHumanizer, GlobalId, MirRelationExpr,
109+
MirScalarExpr, OptimizedMirRelationExpr, RowSetFinishing,
109110
};
110111
use mz_ore::metrics::MetricsRegistry;
111112
use mz_ore::now::{to_datetime, EpochMillis, NowFn};
@@ -116,6 +117,7 @@ use mz_ore::thread::JoinHandleExt;
116117
use mz_repr::adt::interval::Interval;
117118
use mz_repr::adt::numeric::{Numeric, NumericMaxScale};
118119
use mz_repr::{Datum, Diff, RelationDesc, RelationType, Row, RowArena, ScalarType, Timestamp};
120+
use mz_secrets::{SecretOp, SecretsController};
119121
use mz_sql::ast::display::AstDisplay;
120122
use mz_sql::ast::{
121123
CreateIndexStatement, CreateSinkStatement, CreateSourceStatement, ExplainStage, FetchStatement,
@@ -257,6 +259,7 @@ pub struct Config {
257259
pub metrics_registry: MetricsRegistry,
258260
pub persister: PersisterWithConfig,
259261
pub now: NowFn,
262+
pub secrets_controller: Box<dyn SecretsController>,
260263
}
261264

262265
struct PendingPeek {
@@ -328,6 +331,10 @@ pub struct Coordinator {
328331
write_lock: Arc<tokio::sync::Mutex<()>>,
329332
/// Holds plans deferred due to write lock.
330333
write_lock_wait_group: VecDeque<DeferredPlan>,
334+
335+
/// Handle to secret manager that can create and delete secrets from
336+
/// an arbitrary secret storage engine.
337+
secrets_controller: Box<dyn SecretsController>,
331338
}
332339

333340
/// Metadata about an active connection.
@@ -2118,18 +2125,22 @@ impl Coordinator {
21182125
let evaled = secret.secret_as.eval(&[], &temp_storage)?;
21192126

21202127
if evaled == Datum::Null {
2121-
return Err(CoordError::Eval(EvalError::NullCharacterNotPermitted));
2128+
coord_bail!("secret value can not be null");
21222129
}
21232130

2124-
// TODO martin: hook the payload into a secrets backend
2125-
let _payload = evaled.unwrap_bytes();
2131+
let payload = evaled.unwrap_bytes();
21262132

21272133
let id = self.catalog.allocate_user_id()?;
21282134
let oid = self.catalog.allocate_oid()?;
21292135
let secret = catalog::Secret {
21302136
create_sql: format!("CREATE SECRET {} AS '********'", full_name),
21312137
};
21322138

2139+
self.secrets_controller.apply(vec![SecretOp::Ensure {
2140+
id,
2141+
contents: Vec::from(payload),
2142+
}])?;
2143+
21332144
let ops = vec![catalog::Op::CreateItem {
21342145
id,
21352146
oid,
@@ -2143,7 +2154,18 @@ impl Coordinator {
21432154
kind: catalog::ErrorKind::ItemAlreadyExists(_),
21442155
..
21452156
})) if if_not_exists => Ok(ExecuteResponse::CreatedSecret { existed: true }),
2146-
Err(err) => Err(err),
2157+
Err(err) => {
2158+
match self.secrets_controller.apply(vec![SecretOp::Delete { id }]) {
2159+
Ok(_) => {}
2160+
Err(e) => {
2161+
warn!(
2162+
"Dropping newly created secrets has encountered an error: {}",
2163+
e
2164+
);
2165+
}
2166+
}
2167+
Err(err)
2168+
}
21472169
}
21482170
}
21492171

@@ -4353,6 +4375,7 @@ impl Coordinator {
43534375
let mut sinks_to_drop = vec![];
43544376
let mut indexes_to_drop = vec![];
43554377
let mut replication_slots_to_drop: HashMap<String, Vec<String>> = HashMap::new();
4378+
let mut secrets_to_drop = vec![];
43564379

43574380
for op in &ops {
43584381
if let catalog::Op::DropItem(id) = op {
@@ -4390,6 +4413,9 @@ impl Coordinator {
43904413
}) => {
43914414
indexes_to_drop.push((*compute_instance, *id));
43924415
}
4416+
CatalogItem::Secret(_) => {
4417+
secrets_to_drop.push(*id);
4418+
}
43934419
_ => (),
43944420
}
43954421
}
@@ -4438,6 +4464,9 @@ impl Coordinator {
44384464
if !indexes_to_drop.is_empty() {
44394465
self.drop_indexes(indexes_to_drop).await;
44404466
}
4467+
if !secrets_to_drop.is_empty() {
4468+
self.drop_secrets(secrets_to_drop).await;
4469+
}
44414470

44424471
// We don't want to block the coordinator on an external postgres server, so
44434472
// move the drop slots to a separate task. This does mean that a failed drop
@@ -4613,6 +4642,20 @@ impl Coordinator {
46134642
Ok(())
46144643
}
46154644

4645+
async fn drop_secrets(&mut self, secrets: Vec<GlobalId>) {
4646+
let ops = secrets
4647+
.into_iter()
4648+
.map(|id| SecretOp::Delete { id })
4649+
.collect_vec();
4650+
4651+
match self.secrets_controller.apply(ops) {
4652+
Ok(_) => {}
4653+
Err(e) => {
4654+
warn!("Dropping secrets has encountered an error: {}", e);
4655+
}
4656+
}
4657+
}
4658+
46164659
/// Finalizes a dataflow and then broadcasts it to all workers.
46174660
/// Utility method for the more general [Self::ship_dataflows]
46184661
async fn ship_dataflow(&mut self, dataflow: DataflowDesc, instance: ComputeInstanceId) {
@@ -4839,6 +4882,7 @@ pub async fn serve(
48394882
metrics_registry,
48404883
persister,
48414884
now,
4885+
secrets_controller,
48424886
}: Config,
48434887
) -> Result<(Handle, Client), CoordError> {
48444888
let (cmd_tx, cmd_rx) = mpsc::unbounded_channel();
@@ -4885,6 +4929,7 @@ pub async fn serve(
48854929
// for bootstrap completion before proceeding.
48864930
let (bootstrap_tx, bootstrap_rx) = oneshot::channel();
48874931
let handle = TokioHandle::current();
4932+
48884933
let thread = thread::Builder::new()
48894934
.name("coordinator".to_string())
48904935
.spawn(move || {
@@ -4908,6 +4953,7 @@ pub async fn serve(
49084953
pending_tails: HashMap::new(),
49094954
write_lock: Arc::new(tokio::sync::Mutex::new(())),
49104955
write_lock_wait_group: VecDeque::new(),
4956+
secrets_controller,
49114957
};
49124958
let bootstrap = handle.block_on(coord.bootstrap(builtin_table_updates));
49134959
let ok = bootstrap.is_ok();

src/materialized/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,9 @@ mz-pgwire = { path = "../pgwire" }
6666
mz-pid-file = { path = "../pid-file" }
6767
mz-prof = { path = "../prof" }
6868
mz-repr = { path = "../repr" }
69+
mz-secrets = { path = "../secrets" }
70+
mz-secrets-filesystem = { path = "../secrets-filesystem" }
71+
mz-secrets-kubernetes = { path = "../secrets-kubernetes" }
6972
mz-sql = { path = "../sql" }
7073
nix = "0.23.1"
7174
num_cpus = "1.13.1"

src/materialized/src/bin/materialized/main.rs

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,10 @@ use lazy_static::lazy_static;
4444
use sysinfo::{ProcessorExt, SystemExt};
4545
use uuid::Uuid;
4646

47-
use materialized::{OrchestratorConfig, RemoteStorageConfig, StorageConfig, TlsConfig, TlsMode};
47+
use materialized::{
48+
OrchestratorConfig, RemoteStorageConfig, SecretsControllerConfig, StorageConfig, TlsConfig,
49+
TlsMode,
50+
};
4851
use mz_coord::{PersistConfig, PersistFileStorage, PersistStorage};
4952
use mz_dataflow_types::sources::AwsExternalId;
5053
use mz_frontegg_auth::{FronteggAuthentication, FronteggConfig};
@@ -193,6 +196,11 @@ pub struct Args {
193196
#[structopt(long, hide = true, required_if_eq("orchestrator", "kubernetes"))]
194197
dataflowd_image: Option<String>,
195198

199+
// === Secrets Controller options. ===
200+
/// The secrets controller implementation to use
201+
#[structopt(long, hide = true, arg_enum)]
202+
secrets_controller: Option<SecretsController>,
203+
196204
// === Timely worker configuration. ===
197205
/// Number of dataflow worker threads.
198206
#[clap(short, long, env = "MZ_WORKERS", value_name = "N", default_value_t)]
@@ -460,6 +468,12 @@ enum Orchestrator {
460468
Kubernetes,
461469
}
462470

471+
#[derive(ArgEnum, Debug, Clone)]
472+
enum SecretsController {
473+
LocalFileSystem,
474+
Kubernetes,
475+
}
476+
463477
#[derive(Debug)]
464478
struct OrchestratorLabel {
465479
key: String,
@@ -654,7 +668,7 @@ fn run(args: Args) -> Result<(), anyhow::Error> {
654668
}
655669
Some(Orchestrator::Kubernetes) => Some(OrchestratorConfig::Kubernetes {
656670
config: KubernetesOrchestratorConfig {
657-
context: args.kubernetes_context,
671+
context: args.kubernetes_context.clone(),
658672
service_labels: args
659673
.orchestrator_service_label
660674
.into_iter()
@@ -665,6 +679,15 @@ fn run(args: Args) -> Result<(), anyhow::Error> {
665679
}),
666680
};
667681

682+
// Configure secrets controller.
683+
let secrets_controller = match args.secrets_controller {
684+
None => None,
685+
Some(SecretsController::LocalFileSystem) => Some(SecretsControllerConfig::LocalFileSystem),
686+
Some(SecretsController::Kubernetes) => Some(SecretsControllerConfig::Kubernetes {
687+
context: args.kubernetes_context,
688+
}),
689+
};
690+
668691
// Configure storage.
669692
let data_directory = args.data_directory;
670693
fs::create_dir_all(&data_directory)
@@ -871,6 +894,7 @@ max log level: {max_log_level}",
871894
cors_allowed_origins: args.cors_allowed_origin,
872895
data_directory,
873896
orchestrator,
897+
secrets_controller,
874898
storage,
875899
experimental_mode: args.experimental,
876900
disable_user_indexes: args.disable_user_indexes,

0 commit comments

Comments
 (0)