Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 106 additions & 5 deletions proxy_agent/src/key_keeper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ use self::local_rules::{
use crate::common::error::{Error, KeyErrorType};
use crate::common::result::Result;
use crate::common::{constants, helpers, logger};
use crate::key_keeper::key::{AuthorizationRules, KeyStatus};
use crate::key_keeper::key::{AuthorizationItem, AuthorizationRules, KeyStatus};
use crate::provision;
use crate::proxy::authorization_rules::{AuthorizationRulesForLogging, ComputedAuthorizationRules};
use crate::shared_state::access_control_wrapper::AccessControlSharedState;
Expand Down Expand Up @@ -107,6 +107,25 @@ enum WakeReason {
TimerElapsed,
}

/// Determines whether soft audit should be enabled based on the WireServer and IMDS rules and their descriptors.
/// return true when any of the host service is in Audit mode and its “enableSoftAudit” is set to true
/// return false otherwise
fn should_enable_soft_audit(
wireserver_rules: &Option<AuthorizationItem>,
wireserver_rule_id_descriptor: &key::RuleIdDescriptor,
imds_rules: &Option<AuthorizationItem>,
imds_rule_id_descriptor: &key::RuleIdDescriptor,
) -> bool {
(wireserver_rule_id_descriptor.enable_soft_audit
&& wireserver_rules
.as_ref()
.is_some_and(|rules| rules.mode.eq_ignore_ascii_case("audit")))
|| (imds_rule_id_descriptor.enable_soft_audit
&& imds_rules
.as_ref()
.is_some_and(|rules| rules.mode.eq_ignore_ascii_case("audit")))
}

impl KeyKeeper {
pub fn new(
host: String,
Expand Down Expand Up @@ -537,23 +556,49 @@ impl KeyKeeper {
}
}

let remote_wire_server_rules = status.get_wireserver_rules();
let wire_server_rule_id_descriptor = key::parse_rule_id_descriptor(
remote_wire_server_rules
.as_ref()
.map(|item| item.id.as_str()),
);
let (wireserver_rules, wireserver_local_state_changed) = resolve_effective_rules(
&self.rules_dir,
status.get_wireserver_rules(),
remote_wire_server_rules,
&wire_server_rule_id_descriptor,
LocalRuleTarget::WireServer,
&mut local_rule_state_tracker.wireserver,
wireserver_rule_id_changed,
)
.await;

let remote_imds_rules = status.get_imds_rules();
let imds_rule_id_descriptor =
key::parse_rule_id_descriptor(remote_imds_rules.as_ref().map(|item| item.id.as_str()));
let (imds_rules, imds_local_state_changed) = resolve_effective_rules(
&self.rules_dir,
status.get_imds_rules(),
remote_imds_rules,
&imds_rule_id_descriptor,
LocalRuleTarget::Imds,
&mut local_rule_state_tracker.imds,
imds_rule_id_changed,
)
.await;

let soft_audit_mode_enabled = should_enable_soft_audit(
&wireserver_rules,
&wire_server_rule_id_descriptor,
&imds_rules,
&imds_rule_id_descriptor,
);
if let Err(e) = self
.agent_status_shared_state
.set_soft_audit_mode(soft_audit_mode_enabled)
.await
{
logger::write_warning(format!("Failed to set soft audit mode: {e}"));
}

if wireserver_rule_id_changed || wireserver_local_state_changed {
if let Err(e) = self
.access_control_shared_state
Expand Down Expand Up @@ -1097,17 +1142,73 @@ impl KeyKeeper {

#[cfg(test)]
mod tests {
use super::key::Key;
use super::key::{AuthorizationItem, Key, RuleIdDescriptor};
use super::local_rules;
use crate::key_keeper;
use crate::key_keeper::KeyKeeper;
use crate::key_keeper::{self, should_enable_soft_audit};
use proxy_agent_shared::misc_helpers;
use proxy_agent_shared::server_mock;
use std::env;
use std::fs;
use std::time::Duration;
use tokio_util::sync::CancellationToken;

fn authorization_item(mode: &str) -> Option<AuthorizationItem> {
Some(AuthorizationItem {
defaultAccess: "deny".to_string(),
mode: mode.to_string(),
id: "test-rule-id".to_string(),
rules: None,
})
}

fn rule_id_descriptor(enable_soft_audit: bool) -> RuleIdDescriptor {
RuleIdDescriptor {
logical_id: "test-rule-id".to_string(),
use_local_file_rules: false,
enable_soft_audit,
}
}

#[test]
fn should_enable_soft_audit_test() {
let enabled_descriptor = rule_id_descriptor(true);
let disabled_descriptor = rule_id_descriptor(false);
let audit_rules = authorization_item("Audit");
let enforce_rules = authorization_item("enforce");

assert!(should_enable_soft_audit(
&audit_rules,
&enabled_descriptor,
&enforce_rules,
&disabled_descriptor,
));
assert!(should_enable_soft_audit(
&enforce_rules,
&disabled_descriptor,
&audit_rules,
&enabled_descriptor,
));
assert!(!should_enable_soft_audit(
&audit_rules,
&disabled_descriptor,
&audit_rules,
&disabled_descriptor,
));
assert!(!should_enable_soft_audit(
&enforce_rules,
&enabled_descriptor,
&audit_rules,
&disabled_descriptor,
));
assert!(!should_enable_soft_audit(
&None,
&enabled_descriptor,
&None,
&enabled_descriptor,
));
}

#[tokio::test]
async fn check_local_key_test() {
let mut temp_test_path = env::temp_dir();
Expand Down
79 changes: 79 additions & 0 deletions proxy_agent/src/key_keeper/key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ use crate::{
},
proxy::{proxy_connection::ConnectionLogger, Claims},
};
use base64::{engine::general_purpose, Engine as _};
use http::{Method, StatusCode};
use hyper::Uri;
use proxy_agent_shared::hyper_client;
Expand Down Expand Up @@ -772,6 +773,68 @@ impl Display for KeyAction {
}
}

#[derive(Default)]
pub(crate) struct RuleIdDescriptor {
pub(crate) logical_id: String,
pub(crate) use_local_file_rules: bool,
pub(crate) enable_soft_audit: bool,
}

impl RuleIdDescriptor {
pub(crate) fn is_empty(&self) -> bool {
self.logical_id.is_empty()
}

pub(crate) fn display_id(&self) -> String {
if self.logical_id.is_empty() {
"unknown".to_string()
} else {
format!(
"{}-useLocalFileRules-{}",
self.logical_id, self.use_local_file_rules
)
}
}
}

#[derive(Deserialize)]
#[allow(non_snake_case)]
pub(crate) struct EncodedRuleId {
#[serde(default)]
pub(crate) id: String,
#[serde(default)]
pub(crate) useLocalFileRules: bool,
#[serde(default)]
pub(crate) enableSoftAudit: bool,
}

/// Parse the rule ID descriptor from the raw rule ID string.
/// The raw rule ID can be either a plain logical ID or
/// a base64-encoded JSON string containing the logical ID and whether to use local file rules.
pub(crate) fn parse_rule_id_descriptor(raw_rule_id: Option<&str>) -> RuleIdDescriptor {
let raw_rule_id = raw_rule_id.unwrap_or_default().trim();
if raw_rule_id.is_empty() {
return RuleIdDescriptor::default();
}

if let Ok(decoded) = general_purpose::STANDARD.decode(raw_rule_id) {
if let Ok(contract) = serde_json::from_slice::<EncodedRuleId>(&decoded) {
return RuleIdDescriptor {
logical_id: contract.id,
use_local_file_rules: contract.useLocalFileRules,
enable_soft_audit: contract.enableSoftAudit,
};
}
}

// If parsing fails, treat the raw rule ID as the logical ID and do not use local file rules.
RuleIdDescriptor {
logical_id: raw_rule_id.to_string(),
use_local_file_rules: false,
enable_soft_audit: false,
}
}

const STATUS_URL: &str = "/secure-channel/status";
const KEY_URL: &str = "/secure-channel/key";
const HOST_DATE_TIME_DRIFT_MAX_AGE: Duration = Duration::from_secs(60 * 15);
Expand Down Expand Up @@ -914,9 +977,12 @@ mod tests {

use super::Key;
use super::KeyStatus;
use crate::key_keeper::key::parse_rule_id_descriptor;
use crate::key_keeper::key::Identity;
use crate::key_keeper::key::Privilege;
use crate::proxy::proxy_connection::ConnectionLogger;
use base64::engine::general_purpose;
use base64::Engine;
use hyper::Uri;
use proxy_agent_shared::hyper_client;
use serde_json::json;
Expand Down Expand Up @@ -1808,4 +1874,17 @@ mod tests {
"process name after lossy conversion should be equal to replacement char"
);
}

#[test]
fn parse_rule_id_descriptor_test() {
let legacy = parse_rule_id_descriptor(Some("legacy-id"));
assert_eq!(legacy.logical_id, "legacy-id");
assert!(!legacy.use_local_file_rules);

let encoded = general_purpose::STANDARD
.encode(r#"{"id":"sig-resource-id","useLocalFileRules":true}"#);
let descriptor = parse_rule_id_descriptor(Some(&encoded));
assert_eq!(descriptor.logical_id, "sig-resource-id");
assert!(descriptor.use_local_file_rules);
}
}
Loading
Loading