1// Copyright Materialize, Inc. and contributors. All rights reserved.
2//
3// Use of this software is governed by the Business Source License
4// included in the LICENSE file.
5//
6// As of the Change Date specified in that file, in accordance with
7// the Business Source License, use of this software will be governed
8// by the Apache License, Version 2.0.
910//! Abstractions for secure management of user secrets.
1112use std::collections::BTreeMap;
13use std::fmt::Debug;
14use std::sync::{Arc, Mutex};
15use std::time::Duration;
1617use anyhow::Context;
18use async_trait::async_trait;
19use mz_repr::CatalogItemId;
2021pub mod cache;
2223/// Securely manages user secrets.
24#[async_trait]
25pub trait SecretsController: Debug + Send + Sync {
26/// Creates or updates the specified secret with the specified binary
27 /// contents.
28async fn ensure(&self, id: CatalogItemId, contents: &[u8]) -> Result<(), anyhow::Error>;
2930/// Deletes the specified secret.
31async fn delete(&self, id: CatalogItemId) -> Result<(), anyhow::Error>;
3233/// Lists known secrets. Unrecognized secret objects do not produce an error
34 /// and are ignored.
35async fn list(&self) -> Result<Vec<CatalogItemId>, anyhow::Error>;
3637/// Returns a reader for the secrets managed by this controller.
38fn reader(&self) -> Arc<dyn SecretsReader>;
39}
4041#[derive(Debug)]
42pub struct CachingPolicy {
43/// Whether or not caching is enabled.
44pub enabled: bool,
45/// "time to live" of records within the cache.
46pub ttl: Duration,
47}
4849/// Securely reads secrets that are managed by a [`SecretsController`].
50///
51/// Does not provide access to create, update, or delete the secrets within.
52#[async_trait]
53pub trait SecretsReader: Debug + Send + Sync {
54/// Returns the binary contents of the specified secret.
55async fn read(&self, id: CatalogItemId) -> Result<Vec<u8>, anyhow::Error>;
5657/// Returns the string contents of the specified secret.
58 ///
59 /// Returns an error if the secret's contents cannot be decoded as UTF-8.
60async fn read_string(&self, id: CatalogItemId) -> Result<String, anyhow::Error> {
61let contents = self.read(id).await?;
62 String::from_utf8(contents).context("converting secret value to string")
63 }
64}
6566#[derive(Debug)]
67pub struct InMemorySecretsController {
68 data: Arc<Mutex<BTreeMap<CatalogItemId, Vec<u8>>>>,
69}
7071impl InMemorySecretsController {
72pub fn new() -> Self {
73Self {
74 data: Arc::new(Mutex::new(BTreeMap::new())),
75 }
76 }
77}
7879#[async_trait]
80impl SecretsController for InMemorySecretsController {
81async fn ensure(&self, id: CatalogItemId, contents: &[u8]) -> Result<(), anyhow::Error> {
82self.data.lock().unwrap().insert(id, contents.to_vec());
83Ok(())
84 }
8586async fn delete(&self, id: CatalogItemId) -> Result<(), anyhow::Error> {
87self.data.lock().unwrap().remove(&id);
88Ok(())
89 }
9091async fn list(&self) -> Result<Vec<CatalogItemId>, anyhow::Error> {
92Ok(self.data.lock().unwrap().keys().cloned().collect())
93 }
9495fn reader(&self) -> Arc<dyn SecretsReader> {
96 Arc::new(InMemorySecretsController {
97 data: Arc::clone(&self.data),
98 })
99 }
100}
101102#[async_trait]
103impl SecretsReader for InMemorySecretsController {
104async fn read(&self, id: CatalogItemId) -> Result<Vec<u8>, anyhow::Error> {
105let contents = self.data.lock().unwrap().get(&id).cloned();
106 contents.ok_or_else(|| anyhow::anyhow!("secret does not exist"))
107 }
108}