Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Audit access policy implementation #12846

Merged
merged 2 commits into from
Jul 26, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
1 change: 1 addition & 0 deletions policy-controller/k8s/api/src/policy/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ pub struct ServerSpec {
pub selector: Selector,
pub port: Port,
pub proxy_protocol: Option<ProxyProtocol>,
pub access_policy: Option<String>,
}

#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, JsonSchema)]
Expand Down
58 changes: 39 additions & 19 deletions policy-controller/k8s/index/src/defaults.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ pub enum DefaultPolicy {

/// Indicates that all traffic is denied unless explicitly permitted by an authorization policy.
Deny,

/// Indicates that all traffic is let through, but gets audited
Audit,
}

// === impl DefaultPolicy ===
Expand All @@ -47,6 +50,7 @@ impl std::str::FromStr for DefaultPolicy {
cluster_only: true,
}),
"deny" => Ok(Self::Deny),
"audit" => Ok(Self::Audit),
s => Err(anyhow!("invalid mode: {:?}", s)),
}
}
Expand All @@ -72,6 +76,7 @@ impl DefaultPolicy {
cluster_only: true,
} => "cluster-unauthenticated",
Self::Deny => "deny",
Self::Audit => "audit",
}
}

Expand All @@ -80,34 +85,48 @@ impl DefaultPolicy {
config: &ClusterInfo,
) -> HashMap<AuthorizationRef, ClientAuthorization> {
let mut authzs = HashMap::default();
let auth_ref = AuthorizationRef::Default(self.as_str());

if let DefaultPolicy::Allow {
authenticated_only,
cluster_only,
} = self
{
let authentication = if authenticated_only {
ClientAuthentication::TlsAuthenticated(vec![IdentityMatch::Suffix(vec![])])
} else {
ClientAuthentication::Unauthenticated
};
let networks = if cluster_only {
config.networks.iter().copied().map(Into::into).collect()
} else {
vec![
"0.0.0.0/0".parse::<IpNet>().unwrap().into(),
"::/0".parse::<IpNet>().unwrap().into(),
]
};
authzs.insert(
AuthorizationRef::Default(self.as_str()),
ClientAuthorization {
authentication,
networks,
},
auth_ref,
Self::default_client_authz(config, authenticated_only, cluster_only),
);
};
} else if let DefaultPolicy::Audit = self {
authzs.insert(auth_ref, Self::default_client_authz(config, false, false));
}

authzs
}

fn default_client_authz(
config: &ClusterInfo,
authenticated_only: bool,
cluster_only: bool,
) -> ClientAuthorization {
let authentication = if authenticated_only {
ClientAuthentication::TlsAuthenticated(vec![IdentityMatch::Suffix(vec![])])
} else {
ClientAuthentication::Unauthenticated
};
let networks = if cluster_only {
config.networks.iter().copied().map(Into::into).collect()
} else {
vec![
"0.0.0.0/0".parse::<IpNet>().unwrap().into(),
"::/0".parse::<IpNet>().unwrap().into(),
]
};

ClientAuthorization {
authentication,
networks,
}
}
}

impl std::fmt::Display for DefaultPolicy {
Expand Down Expand Up @@ -140,6 +159,7 @@ mod test {
authenticated_only: false,
cluster_only: true,
},
DefaultPolicy::Audit,
] {
assert_eq!(
default.to_string().parse::<DefaultPolicy>().unwrap(),
Expand Down
4 changes: 4 additions & 0 deletions policy-controller/k8s/index/src/inbound/index.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1753,6 +1753,10 @@ impl PolicyIndex {
authzs.insert(reference, authz);
}

if let Some(p) = server.access_policy {
authzs.extend(p.default_authzs(&self.cluster_info));
}

authzs
}

Expand Down
4 changes: 3 additions & 1 deletion policy-controller/k8s/index/src/inbound/server.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::ClusterInfo;
use crate::{ClusterInfo, DefaultPolicy};
use linkerd_policy_controller_core::inbound::ProxyProtocol;
use linkerd_policy_controller_k8s_api::{
self as k8s, policy::server::Port, policy::server::Selector,
Expand All @@ -11,6 +11,7 @@ pub(crate) struct Server {
pub selector: Selector,
pub port_ref: Port,
pub protocol: ProxyProtocol,
pub access_policy: Option<DefaultPolicy>,
}

impl Server {
Expand All @@ -20,6 +21,7 @@ impl Server {
selector: srv.spec.selector,
port_ref: srv.spec.port,
protocol: proxy_protocol(srv.spec.proxy_protocol, cluster),
access_policy: srv.spec.access_policy.and_then(|p| p.parse().ok()),
}
}
}
Expand Down
9 changes: 8 additions & 1 deletion policy-controller/src/admission.rs
Original file line number Diff line number Diff line change
Expand Up @@ -319,7 +319,8 @@ impl Validate<MeshTLSAuthenticationSpec> for Admission {

#[async_trait::async_trait]
impl Validate<ServerSpec> for Admission {
/// Checks that `spec` doesn't select the same pod/ports as other existing Servers
/// Checks that `spec` doesn't select the same pod/ports as other existing Servers, and that
/// `accessPolicy` contains a valid value
//
// TODO(ver) this isn't rigorous about detecting servers that select the same port if one port
// specifies a numeric port and the other specifies the port's name.
Expand All @@ -346,6 +347,12 @@ impl Validate<ServerSpec> for Admission {
}
}

if let Some(policy) = spec.access_policy {
policy
.parse::<index::DefaultPolicy>()
.map_err(|err| anyhow!("Invalid 'accessPolicy' field: {err}"))?;
}

Ok(())
}
}
Expand Down
Loading