Redact secrets from access statements in INFO
(#4100)
This commit is contained in:
parent
e98a56958b
commit
9962c56961
8 changed files with 217 additions and 10 deletions
core/src
lib/tests
|
@ -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,
|
||||
|
|
|
@ -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))]
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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?));
|
||||
|
|
|
@ -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: { } }"]
|
||||
];
|
||||
|
||||
|
|
|
@ -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}'",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 = [
|
||||
|
|
Loading…
Reference in a new issue