Redact secrets from access statements in INFO ()

This commit is contained in:
Gerard Guillemas Martos 2024-05-28 17:29:11 +02:00 committed by GitHub
parent e98a56958b
commit 9962c56961
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 217 additions and 10 deletions

View file

@ -1464,6 +1464,16 @@ impl Transaction {
})
}
/// Retrieve all namespace access method definitions in redacted form.
pub async fn all_ns_accesses_redacted(
&mut self,
ns: &str,
) -> Result<Arc<[DefineAccessStatement]>, Error> {
let accesses = self.all_ns_accesses(ns).await?;
let redacted: Vec<_> = accesses.iter().map(|statement| statement.redacted()).collect();
Ok(Arc::from(redacted))
}
/// Retrieve all database definitions for a specific namespace.
pub async fn all_db(&mut self, ns: &str) -> Result<Arc<[DefineDatabaseStatement]>, Error> {
let key = crate::key::namespace::db::prefix(ns);
@ -1529,6 +1539,17 @@ impl Transaction {
})
}
/// Retrieve all database access method definitions in redacted form.
pub async fn all_db_accesses_redacted(
&mut self,
ns: &str,
db: &str,
) -> Result<Arc<[DefineAccessStatement]>, Error> {
let accesses = self.all_db_accesses(ns, db).await?;
let redacted: Vec<_> = accesses.iter().map(|statement| statement.redacted()).collect();
Ok(Arc::from(redacted))
}
/// Retrieve all analyzer definitions for a specific database.
pub async fn all_db_analyzers(
&mut self,

View file

@ -59,6 +59,31 @@ impl Default for JwtAccess {
}
}
impl JwtAccess {
pub(crate) fn redacted(&self) -> JwtAccess {
let mut jwt = self.clone();
jwt.verify = match jwt.verify {
JwtAccessVerify::Key(mut key) => {
// If algorithm is symmetric, the verification key is a secret
if key.alg.is_symmetric() {
key.key = "[REDACTED]".to_string();
}
JwtAccessVerify::Key(key)
}
// No secrets in JWK
JwtAccessVerify::Jwks(jwks) => JwtAccessVerify::Jwks(jwks),
};
jwt.issue = match jwt.issue {
Some(mut issue) => {
issue.key = "[REDACTED]".to_string();
Some(issue)
}
None => None,
};
jwt
}
}
#[revisioned(revision = 1)]
#[derive(Debug, Serialize, Deserialize, Hash, Clone, Eq, PartialEq, PartialOrd)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]

View file

@ -25,7 +25,7 @@ pub enum Algorithm {
}
impl Algorithm {
// Does the algorithm us the same key for signing and verification?
// Does the algorithm use the same key for signing and verification?
pub(crate) fn is_symmetric(self) -> bool {
matches!(self, Algorithm::Hs256 | Algorithm::Hs384 | Algorithm::Hs512)
}

View file

@ -32,6 +32,21 @@ impl DefineAccessStatement {
pub(crate) fn random_key() -> String {
rand::thread_rng().sample_iter(&Alphanumeric).take(128).map(char::from).collect::<String>()
}
/// Returns a version of the statement where potential secrets are redacted
/// This function should be used when displaying the statement to datastore users
/// This function should NOT be used when displaying the statement for export purposes
pub fn redacted(&self) -> DefineAccessStatement {
let mut das = self.clone();
das.kind = match das.kind {
AccessType::Jwt(ac) => AccessType::Jwt(ac.redacted()),
AccessType::Record(mut ac) => {
ac.jwt = ac.jwt.redacted();
AccessType::Record(ac)
}
};
das
}
}
impl DefineAccessStatement {

View file

@ -115,7 +115,7 @@ impl InfoStatement {
res.insert("users".to_owned(), tmp.into());
// Process the accesses
let mut tmp = Object::default();
for v in run.all_ns_accesses(opt.ns()).await?.iter() {
for v in run.all_ns_accesses_redacted(opt.ns()).await?.iter() {
tmp.insert(v.name.to_string(), v.to_string().into());
}
res.insert("accesses".to_owned(), tmp.into());
@ -155,7 +155,7 @@ impl InfoStatement {
res.insert("params".to_owned(), tmp.into());
// Process the accesses
let mut tmp = Object::default();
for v in run.all_db_accesses(opt.ns(), opt.db()).await?.iter() {
for v in run.all_db_accesses_redacted(opt.ns(), opt.db()).await?.iter() {
tmp.insert(v.name.to_string(), v.to_string().into());
}
res.insert("accesses".to_owned(), tmp.into());
@ -259,7 +259,7 @@ impl InfoStatement {
// Process the accesses
res.insert(
"accesses".to_owned(),
process_arr(run.all_ns_accesses(opt.ns()).await?),
process_arr(run.all_ns_accesses_redacted(opt.ns()).await?),
);
// Ok all good
Value::from(res).ok()
@ -299,7 +299,7 @@ impl InfoStatement {
// Process the accesses
res.insert(
"accesses".to_owned(),
process_arr(run.all_db_accesses(opt.ns(), opt.db()).await?),
process_arr(run.all_db_accesses_redacted(opt.ns(), opt.db()).await?),
);
// Process the tables
res.insert("tables".to_owned(), process_arr(run.all_tb(opt.ns(), opt.db()).await?));

View file

@ -1704,7 +1704,7 @@ async fn permissions_checks_define_access_ns() {
// Define the expected results for the check statement when the test statement succeeded and when it failed
let check_results = [
vec!["{ accesses: { access: \"DEFINE ACCESS access ON NAMESPACE TYPE JWT ALGORITHM HS512 KEY 'secret' WITH ISSUER KEY 'secret' DURATION 1h\" }, databases: { }, users: { } }"],
vec!["{ accesses: { access: \"DEFINE ACCESS access ON NAMESPACE TYPE JWT ALGORITHM HS512 KEY '[REDACTED]' WITH ISSUER KEY '[REDACTED]' DURATION 1h\" }, databases: { }, users: { } }"],
vec!["{ accesses: { }, databases: { }, users: { } }"]
];
@ -1746,7 +1746,7 @@ async fn permissions_checks_define_access_db() {
// Define the expected results for the check statement when the test statement succeeded and when it failed
let check_results = [
vec!["{ accesses: { access: \"DEFINE ACCESS access ON DATABASE TYPE JWT ALGORITHM HS512 KEY 'secret' WITH ISSUER KEY 'secret' DURATION 1h\" }, analyzers: { }, functions: { }, models: { }, params: { }, tables: { }, users: { } }"],
vec!["{ accesses: { access: \"DEFINE ACCESS access ON DATABASE TYPE JWT ALGORITHM HS512 KEY '[REDACTED]' WITH ISSUER KEY '[REDACTED]' DURATION 1h\" }, analyzers: { }, functions: { }, models: { }, params: { }, tables: { }, users: { } }"],
vec!["{ accesses: { }, analyzers: { }, functions: { }, models: { }, params: { }, tables: { }, users: { } }"]
];
@ -1914,7 +1914,7 @@ async fn permissions_checks_define_access_record() {
// Define the expected results for the check statement when the test statement succeeded and when it failed
let check_results = [
vec!["{ accesses: { account: \"DEFINE ACCESS account ON DATABASE TYPE RECORD DURATION 1h WITH JWT ALGORITHM HS512 KEY 'secret' WITH ISSUER KEY 'secret' DURATION 1h\" }, analyzers: { }, functions: { }, models: { }, params: { }, tables: { }, users: { } }"],
vec!["{ accesses: { account: \"DEFINE ACCESS account ON DATABASE TYPE RECORD DURATION 1h WITH JWT ALGORITHM HS512 KEY '[REDACTED]' WITH ISSUER KEY '[REDACTED]' DURATION 1h\" }, analyzers: { }, functions: { }, models: { }, params: { }, tables: { }, users: { } }"],
vec!["{ accesses: { }, analyzers: { }, functions: { }, models: { }, params: { }, tables: { }, users: { } }"]
];

View file

@ -485,3 +485,149 @@ async fn permissions_checks_info_user_db() {
let res = iam_check_cases(test_cases.iter(), &scenario, check_results).await;
assert!(res.is_ok(), "{}", res.unwrap_err());
}
#[tokio::test]
async fn access_info_redacted() {
// Symmetric
{
let sql = r#"
DEFINE ACCESS access ON NS TYPE JWT ALGORITHM HS512 KEY 'secret' WITH ISSUER KEY 'secret';
INFO FOR NS
"#;
let dbs = new_ds().await.unwrap();
let ses = Session::owner().with_ns("ns");
let mut res = dbs.execute(sql, &ses, None).await.unwrap();
assert_eq!(res.len(), 2);
let out = res.pop().unwrap().output();
assert!(out.is_ok(), "Unexpected error: {:?}", out);
let out_expected =
r#"{ accesses: { access: "DEFINE ACCESS access ON NAMESPACE TYPE JWT ALGORITHM HS512 KEY '[REDACTED]' WITH ISSUER KEY '[REDACTED]' DURATION 1h" }, databases: { }, users: { } }"#.to_string();
let out_str = out.unwrap().to_string();
assert_eq!(
out_str, out_expected,
"Output '{out_str}' doesn't match expected output '{out_expected}'",
);
}
// Asymmetric
{
let sql = r#"
DEFINE ACCESS access ON NS TYPE JWT ALGORITHM PS512 KEY 'public' WITH ISSUER KEY 'private';
INFO FOR NS
"#;
let dbs = new_ds().await.unwrap();
let ses = Session::owner().with_ns("ns");
let mut res = dbs.execute(sql, &ses, None).await.unwrap();
assert_eq!(res.len(), 2);
let out = res.pop().unwrap().output();
assert!(out.is_ok(), "Unexpected error: {:?}", out);
let out_expected =
r#"{ accesses: { access: "DEFINE ACCESS access ON NAMESPACE TYPE JWT ALGORITHM PS512 KEY 'public' WITH ISSUER KEY '[REDACTED]' DURATION 1h" }, databases: { }, users: { } }"#.to_string();
let out_str = out.unwrap().to_string();
assert_eq!(
out_str, out_expected,
"Output '{out_str}' doesn't match expected output '{out_expected}'",
);
}
// Record
{
let sql = r#"
DEFINE ACCESS access ON NS TYPE RECORD WITH JWT ALGORITHM HS512 KEY 'secret' WITH ISSUER KEY 'secret';
INFO FOR NS
"#;
let dbs = new_ds().await.unwrap();
let ses = Session::owner().with_ns("ns");
let mut res = dbs.execute(sql, &ses, None).await.unwrap();
assert_eq!(res.len(), 2);
let out = res.pop().unwrap().output();
assert!(out.is_ok(), "Unexpected error: {:?}", out);
let out_expected =
r#"{ accesses: { access: "DEFINE ACCESS access ON NAMESPACE TYPE RECORD DURATION 1h WITH JWT ALGORITHM HS512 KEY '[REDACTED]' WITH ISSUER KEY '[REDACTED]' DURATION 1h" }, databases: { }, users: { } }"#.to_string();
let out_str = out.unwrap().to_string();
assert_eq!(
out_str, out_expected,
"Output '{out_str}' doesn't match expected output '{out_expected}'",
);
}
}
#[tokio::test]
async fn access_info_redacted_structure() {
// Symmetric
{
let sql = r#"
DEFINE ACCESS access ON NS TYPE JWT ALGORITHM HS512 KEY 'secret';
INFO FOR NS STRUCTURE
"#;
let dbs = new_ds().await.unwrap();
let ses = Session::owner().with_ns("ns");
let mut res = dbs.execute(sql, &ses, None).await.unwrap();
assert_eq!(res.len(), 2);
let out = res.pop().unwrap().output();
assert!(out.is_ok(), "Unexpected error: {:?}", out);
let out_expected =
r#"{ accesses: [{ base: 'NAMESPACE', kind: { jwt: { alg: 'HS512', issuer: "{ alg: 'HS512', duration: 1h, key: '[REDACTED]' }", key: '[REDACTED]' }, kind: 'JWT' }, name: 'access' }], databases: [], users: [] }"#.to_string();
let out_str = out.unwrap().to_string();
assert_eq!(
out_str, out_expected,
"Output '{out_str}' doesn't match expected output '{out_expected}'",
);
}
// Asymmetric
{
let sql = r#"
DEFINE ACCESS access ON NS TYPE JWT ALGORITHM PS512 KEY 'public' WITH ISSUER KEY 'private';
INFO FOR NS STRUCTURE
"#;
let dbs = new_ds().await.unwrap();
let ses = Session::owner().with_ns("ns");
let mut res = dbs.execute(sql, &ses, None).await.unwrap();
assert_eq!(res.len(), 2);
let out = res.pop().unwrap().output();
assert!(out.is_ok(), "Unexpected error: {:?}", out);
let out_expected =
r#"{ accesses: [{ base: 'NAMESPACE', kind: { jwt: { alg: 'PS512', issuer: "{ alg: 'PS512', duration: 1h, key: '[REDACTED]' }", key: 'public' }, kind: 'JWT' }, name: 'access' }], databases: [], users: [] }"#.to_string();
let out_str = out.unwrap().to_string();
assert_eq!(
out_str, out_expected,
"Output '{out_str}' doesn't match expected output '{out_expected}'",
);
}
// Record
{
let sql = r#"
DEFINE ACCESS access ON NS TYPE RECORD WITH JWT ALGORITHM HS512 KEY 'secret';
INFO FOR NS STRUCTURE
"#;
let dbs = new_ds().await.unwrap();
let ses = Session::owner().with_ns("ns");
let mut res = dbs.execute(sql, &ses, None).await.unwrap();
assert_eq!(res.len(), 2);
let out = res.pop().unwrap().output();
assert!(out.is_ok(), "Unexpected error: {:?}", out);
let out_expected =
r#"{ accesses: [{ base: 'NAMESPACE', kind: { duration: 1h, jwt: { alg: 'HS512', issuer: "{ alg: 'HS512', duration: 1h, key: '[REDACTED]' }", key: '[REDACTED]' }, kind: 'RECORD' }, name: 'access' }], databases: [], users: [] }"#.to_string();
let out_str = out.unwrap().to_string();
assert_eq!(
out_str, out_expected,
"Output '{out_str}' doesn't match expected output '{out_expected}'",
);
}
}

View file

@ -662,7 +662,7 @@ async fn permissions_checks_remove_ns_access() {
// Define the expected results for the check statement when the test statement succeeded and when it failed
let check_results = [
vec!["{ accesses: { }, databases: { }, users: { } }"],
vec!["{ accesses: { access: \"DEFINE ACCESS access ON NAMESPACE TYPE JWT ALGORITHM HS512 KEY 'secret' WITH ISSUER KEY 'secret' DURATION 1h\" }, databases: { }, users: { } }"],
vec!["{ accesses: { access: \"DEFINE ACCESS access ON NAMESPACE TYPE JWT ALGORITHM HS512 KEY '[REDACTED]' WITH ISSUER KEY '[REDACTED]' DURATION 1h\" }, databases: { }, users: { } }"],
];
let test_cases = [
@ -704,7 +704,7 @@ async fn permissions_checks_remove_db_access() {
// Define the expected results for the check statement when the test statement succeeded and when it failed
let check_results = [
vec!["{ accesses: { }, analyzers: { }, functions: { }, models: { }, params: { }, tables: { }, users: { } }"],
vec!["{ accesses: { access: \"DEFINE ACCESS access ON DATABASE TYPE JWT ALGORITHM HS512 KEY 'secret' WITH ISSUER KEY 'secret' DURATION 1h\" }, analyzers: { }, functions: { }, models: { }, params: { }, tables: { }, users: { } }"],
vec!["{ accesses: { access: \"DEFINE ACCESS access ON DATABASE TYPE JWT ALGORITHM HS512 KEY '[REDACTED]' WITH ISSUER KEY '[REDACTED]' DURATION 1h\" }, analyzers: { }, functions: { }, models: { }, params: { }, tables: { }, users: { } }"],
];
let test_cases = [