Skip to content

Commit

Permalink
Update ACL config format to support AND/OR logic between subjects (#1200
Browse files Browse the repository at this point in the history
)

* Add trie dependency

* Start replacing subject HashMap with TrieMap

* Complete SubjectTrieMap implementation

* Add new ACL config schema to zenoh config

* Add new ACL config parsing logic

* Fix empty subject lists edge-case for cartesian product of subjects

* Format code, apply clippy suggestions

* Fix edge-case where a subject-combination is repeated in config

* Update new transport ACL logic with subject-combinations support

* Make ACL config lists mandatory when ACL config is enabled

* Update ACL tests

* Update authentication tests

* Break ACL and authentication test into multiple tests that can run concurrently

* Fix entry_id value in error message

* Add policy entry rules/subjects id validation

* Update DEFAULT_CONFIG

* Fix missing port number in test client config

* Add ACL subject combination tests

* Empty commit to trigger CI

* Fix unsoundness in `SubjectMap`

This replaces the trie data structure with a vector and allows querying
any combination of subject properties in any order.

Moreover, undefined subject properties are now always interpreted as wildcards.

* Address review comments from original pull request

* Fix typos

* Fix clippy errors

* Minor edits

* Check for empty subject attributes

* Rename ACL config field actions to messages

* Rename ACL config field policy to policies

* Update DEFAULT_CONFIG

* Update ACL tests config

* Add warning when applying ACL on transport with multiple interfaces

* Improve ACL subject logs

* Improve ACL no matching subject log

* Separate empty ACL config logs

* Replace unwrap with expect

* Fix unmodified copy/pasted code

* Rename ACL config message 'get' to 'query'

* Rename ACL 'get' to 'query' in DEFAULT_CONFIG

* Rename 'get' to 'query' in tests

---------

Co-authored-by: Mahmoud Mazouz <[email protected]>
  • Loading branch information
oteffahi and fuzzypixelz authored Jul 23, 2024
1 parent cf4d3d3 commit cb9fc8a
Show file tree
Hide file tree
Showing 10 changed files with 1,422 additions and 472 deletions.
18 changes: 14 additions & 4 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ hmac = { version = "0.12.1", features = ["std"] }
home = "0.5.4"
http-types = "2.12.0"
humantime = "2.1.0"
itertools = "0.13.0"
json5 = "0.4.1"
jsonschema = { version = "0.18.0", default-features = false }
keyed-set = "1.0.0"
Expand Down
77 changes: 68 additions & 9 deletions DEFAULT_CONFIG.json5
Original file line number Diff line number Diff line change
Expand Up @@ -186,27 +186,52 @@
// },
// ],

// /// configure access control (ACL) rules
// /// Configure access control (ACL) rules
// access_control: {
// ///[true/false] acl will be activated only if this is set to true
// /// [true/false] acl will be activated only if this is set to true
// "enabled": false,
// ///[deny/allow] default permission is deny (even if this is left empty or not specified)
// /// [deny/allow] default permission is deny (even if this is left empty or not specified)
// "default_permission": "deny",
// ///rule set for permissions allowing or denying access to key-expressions
// /// Rule set for permissions allowing or denying access to key-expressions
// "rules":
// [
// {
// "actions": [
// "put", "get", "declare_subscriber", "declare_queryable"
// /// Id has to be unique within the rule set
// "id": "rule1",
// "messages": [
// "put", "query", "declare_subscriber", "declare_queryable"
// ],
// "flows":["egress","ingress"],
// "permission": "allow",
// "key_exprs": [
// "test/demo"
// ],
// },
// {
// "id": "rule2",
// "messages": [
// "put", "query", "declare_subscriber", "declare_queryable"
// ],
// "flows":["ingress"],
// "permission": "allow",
// "key_exprs": [
// "**"
// ],
// },
// ],
// /// List of combinations of subjects.
// ///
// /// If a subject property (i.e. username, certificate common name or interface) is empty
// /// it is interpreted as a wildcard. Moreover, a subject property cannot be an empty list.
// "subjects":
// [
// {
// /// Id has to be unique within the subjects list
// "id": "subject1",
// /// Subjects can be interfaces
// "interfaces": [
// "lo0"
// "lo0",
// "en0",
// ],
// /// Subjects can be cert_common_names when using TLS or Quic
// "cert_common_names": [
Expand All @@ -215,9 +240,43 @@
// /// Subjects can be usernames when using user/password authentication
// "usernames": [
// "zenoh-example"
// ]
// ],
// /// This instance translates internally to this filter:
// /// (interface="lo0" && cert_common_name="example.zenoh.io" && username="zenoh-example") ||
// /// (interface="en0" && cert_common_name="example.zenoh.io" && username="zenoh-example")
// },
// ]
// {
// "id": "subject2",
// "interfaces": [
// "lo0",
// "en0",
// ],
// "cert_common_names": [
// "example2.zenoh.io"
// ],
// /// This instance translates internally to this filter:
// /// (interface="lo0" && cert_common_name="example2.zenoh.io") ||
// /// (interface="en0" && cert_common_name="example2.zenoh.io")
// },
// {
// "id": "subject3",
// /// An empty subject combination is a wildcard
// },
// ],
// /// The policies list associates rules to subjects
// "policies":
// [
// /// Each policy associates one or multiple rules to one or multiple subject combinations
// {
// /// Rules and Subjects are identified with their unique IDs declared above
// "rules": ["rule1"],
// "subjects": ["subject1", "subject2"],
// },
// {
// "rules": ["rule2"],
// "subjects": ["subject3"],
// },
// ]
//},

/// Configure internal transport parameters
Expand Down
2 changes: 2 additions & 0 deletions commons/zenoh-config/src/defaults.rs
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,8 @@ impl Default for AclConfig {
enabled: false,
default_permission: Permission::Deny,
rules: None,
subjects: None,
policies: None,
}
}
}
Expand Down
70 changes: 51 additions & 19 deletions commons/zenoh-config/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,40 +104,70 @@ pub struct DownsamplingItemConf {
}

#[derive(Serialize, Debug, Deserialize, Clone)]
pub struct AclConfigRules {
pub interfaces: Option<Vec<String>>,
pub cert_common_names: Option<Vec<String>>,
pub usernames: Option<Vec<String>>,
pub struct AclConfigRule {
pub id: String,
pub key_exprs: Vec<String>,
pub actions: Vec<Action>,
pub messages: Vec<AclMessage>,
pub flows: Option<Vec<InterceptorFlow>>,
pub permission: Permission,
}

#[derive(Serialize, Debug, Deserialize, Clone)]
pub struct AclConfigSubjects {
pub id: String,
pub interfaces: Option<Vec<Interface>>,
pub cert_common_names: Option<Vec<CertCommonName>>,
pub usernames: Option<Vec<Username>>,
}

#[derive(Serialize, Debug, Deserialize, Clone, PartialEq, Eq, Hash)]
pub struct Interface(pub String);

impl std::fmt::Display for Interface {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Interface({})", self.0)
}
}

#[derive(Serialize, Debug, Deserialize, Clone, PartialEq, Eq, Hash)]
pub struct CertCommonName(pub String);

impl std::fmt::Display for CertCommonName {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "CertCommonName({})", self.0)
}
}

#[derive(Serialize, Debug, Deserialize, Clone, PartialEq, Eq, Hash)]
pub struct Username(pub String);

impl std::fmt::Display for Username {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Username({})", self.0)
}
}

#[derive(Serialize, Debug, Deserialize, Clone, PartialEq, Eq, Hash)]
pub struct AclConfigPolicyEntry {
pub rules: Vec<String>,
pub subjects: Vec<String>,
}

#[derive(Clone, Serialize, Debug, Deserialize)]
pub struct PolicyRule {
pub subject: Subject,
pub subject_id: usize,
pub key_expr: String,
pub action: Action,
pub message: AclMessage,
pub permission: Permission,
pub flow: InterceptorFlow,
}

#[derive(Serialize, Debug, Deserialize, Eq, PartialEq, Hash, Clone)]
#[serde(untagged)]
#[serde(rename_all = "snake_case")]
pub enum Subject {
Interface(String),
CertCommonName(String),
Username(String),
}

#[derive(Clone, Copy, Debug, Serialize, Deserialize, Eq, Hash, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum Action {
pub enum AclMessage {
Put,
DeclareSubscriber,
Get,
Query,
DeclareQueryable,
}

Expand Down Expand Up @@ -505,7 +535,9 @@ validated_struct::validator! {
pub access_control: AclConfig {
pub enabled: bool,
pub default_permission: Permission,
pub rules: Option<Vec<AclConfigRules>>
pub rules: Option<Vec<AclConfigRule>>,
pub subjects: Option<Vec<AclConfigSubjects>>,
pub policies: Option<Vec<AclConfigPolicyEntry>>,
},

/// A list of directories where plugins may be searched for if no `__path__` was specified for them.
Expand Down
1 change: 1 addition & 0 deletions zenoh/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ flume = { workspace = true }
form_urlencoded = { workspace = true }
futures = { workspace = true }
git-version = { workspace = true }
itertools = { workspace = true }
lazy_static = { workspace = true }
tracing = { workspace = true }
ordered-float = { workspace = true }
Expand Down
Loading

0 comments on commit cb9fc8a

Please sign in to comment.