1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
// Copyright Materialize, Inc. and contributors. All rights reserved.
//
// Use of this software is governed by the Business Source License
// included in the LICENSE file.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0.
use anyhow::{bail, Error};
use async_trait::async_trait;
use mz_secrets::{SecretOp, SecretsController};
use std::fs;
use std::fs::OpenOptions;
use std::io::Write;
use std::os::unix::fs::OpenOptionsExt;
use std::path::PathBuf;
pub struct FilesystemSecretsController {
secrets_storage_path: PathBuf,
}
impl FilesystemSecretsController {
pub fn new(secrets_storage_path: PathBuf) -> Self {
Self {
secrets_storage_path,
}
}
}
#[async_trait]
impl SecretsController for FilesystemSecretsController {
async fn apply(&mut self, ops: Vec<SecretOp>) -> Result<(), Error> {
// The filesystem controller can not execute multiple FS operations in a single atomic txn.
// Deletes are a special case. They are being executed as a post-commit event and once
// the coordinator reaches that code, the secrets can no longer be reached by any reader.
// This behavior is atomic in nature and the FS operations do not have to be.
// On the other hand, Ensure has to be atomic, since a concurrent reader should not
// see torn FS transactions.
// Hence we enforce the limit of exactly 1 ensure (update/create) operation per txn
for op in ops.iter() {
match op {
SecretOp::Ensure { id, contents } => {
if ops.len() > 1 {
bail!("secrets controller does not support creating multiple secrets atomically")
}
let file_path = self.secrets_storage_path.join(format!("{}", id));
let mut file = OpenOptions::new()
.mode(0o600)
.create(true)
.write(true)
.truncate(true)
.open(file_path)?;
file.write_all(contents)?;
file.sync_all()?;
}
SecretOp::Delete { id } => {
fs::remove_file(self.secrets_storage_path.join(format!("{}", id)))?;
}
}
}
return Ok(());
}
}