mz_secrets/
lib.rs

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.
9
10//! Abstractions for secure management of user secrets.
11
12use std::collections::BTreeMap;
13use std::fmt::Debug;
14use std::sync::{Arc, Mutex};
15use std::time::Duration;
16
17use anyhow::Context;
18use async_trait::async_trait;
19use mz_repr::CatalogItemId;
20
21pub mod cache;
22
23/// 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.
28    async fn ensure(&self, id: CatalogItemId, contents: &[u8]) -> Result<(), anyhow::Error>;
29
30    /// Deletes the specified secret.
31    async fn delete(&self, id: CatalogItemId) -> Result<(), anyhow::Error>;
32
33    /// Lists known secrets. Unrecognized secret objects do not produce an error
34    /// and are ignored.
35    async fn list(&self) -> Result<Vec<CatalogItemId>, anyhow::Error>;
36
37    /// Returns a reader for the secrets managed by this controller.
38    fn reader(&self) -> Arc<dyn SecretsReader>;
39}
40
41#[derive(Debug)]
42pub struct CachingPolicy {
43    /// Whether or not caching is enabled.
44    pub enabled: bool,
45    /// "time to live" of records within the cache.
46    pub ttl: Duration,
47}
48
49/// 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.
55    async fn read(&self, id: CatalogItemId) -> Result<Vec<u8>, anyhow::Error>;
56
57    /// Returns the string contents of the specified secret.
58    ///
59    /// Returns an error if the secret's contents cannot be decoded as UTF-8.
60    async fn read_string(&self, id: CatalogItemId) -> Result<String, anyhow::Error> {
61        let contents = self.read(id).await?;
62        String::from_utf8(contents).context("converting secret value to string")
63    }
64}
65
66#[derive(Debug)]
67pub struct InMemorySecretsController {
68    data: Arc<Mutex<BTreeMap<CatalogItemId, Vec<u8>>>>,
69}
70
71impl InMemorySecretsController {
72    pub fn new() -> Self {
73        Self {
74            data: Arc::new(Mutex::new(BTreeMap::new())),
75        }
76    }
77}
78
79#[async_trait]
80impl SecretsController for InMemorySecretsController {
81    async fn ensure(&self, id: CatalogItemId, contents: &[u8]) -> Result<(), anyhow::Error> {
82        self.data.lock().unwrap().insert(id, contents.to_vec());
83        Ok(())
84    }
85
86    async fn delete(&self, id: CatalogItemId) -> Result<(), anyhow::Error> {
87        self.data.lock().unwrap().remove(&id);
88        Ok(())
89    }
90
91    async fn list(&self) -> Result<Vec<CatalogItemId>, anyhow::Error> {
92        Ok(self.data.lock().unwrap().keys().cloned().collect())
93    }
94
95    fn reader(&self) -> Arc<dyn SecretsReader> {
96        Arc::new(InMemorySecretsController {
97            data: Arc::clone(&self.data),
98        })
99    }
100}
101
102#[async_trait]
103impl SecretsReader for InMemorySecretsController {
104    async fn read(&self, id: CatalogItemId) -> Result<Vec<u8>, anyhow::Error> {
105        let contents = self.data.lock().unwrap().get(&id).cloned();
106        contents.ok_or_else(|| anyhow::anyhow!("secret does not exist"))
107    }
108}