Skip to content

Commit

Permalink
Refactor SELinux policy
Browse files Browse the repository at this point in the history
Default all containers besides the Pod if they have an empty security
context or if they have empty SELinuxOptions
  • Loading branch information
ereslibre committed Dec 17, 2021
1 parent e2e6e64 commit ebfc5ad
Show file tree
Hide file tree
Showing 2 changed files with 141 additions and 101 deletions.
231 changes: 133 additions & 98 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,88 +46,98 @@ fn validate(payload: &[u8]) -> CallResult {
}

fn do_validate(pod: apicore::Pod, settings: settings::Settings) -> Result<PolicyResponse> {
let pod_spec = pod.spec.ok_or_else(|| anyhow!("invalid pod spec"))?;

let orig_pod = pod.clone();
match settings {
Settings::MustRunAs(expected_selinux_options) => {
let pod_with_defaulted_selinux_options = apicore::Pod {
spec: Some(apicore::PodSpec {
security_context: Some(apicore::PodSecurityContext {
se_linux_options: Some(expected_selinux_options.clone().into()),
..apicore::PodSecurityContext::default()
}),
..pod_spec.clone()
}),
..apicore::Pod::default()
};

// If the pod has no SELinuxOptions, we default it to the expected value. We still have
// to check that all containers meet the SELinux requirements set by the configuration
// because individual containers might override the default PodSecurityContext SELinux
// options

let mut has_to_mutate = false;
let pod_selinux_options = if let Some(pod_security_context) = pod_spec.security_context
{
if let Some(selinux_options) = pod_security_context.se_linux_options {
selinux_options
let mut pod_spec = pod.spec.ok_or_else(|| anyhow!("invalid pod spec"))?;

if let Some(ref security_context) = pod_spec.security_context {
if let Some(ref selinux_options) = security_context.se_linux_options {
if !is_selinux_compliant(selinux_options, &expected_selinux_options) {
return Ok(PolicyResponse::Reject(
"SELinux validation failed".to_string(),
));
}
} else {
has_to_mutate = true;
expected_selinux_options.clone().into()
pod_spec.security_context = Some(apicore::PodSecurityContext {
se_linux_options: Some(expected_selinux_options.clone().into()),
..security_context.clone()
})
}
} else {
has_to_mutate = true;
expected_selinux_options.clone().into()
pod_spec.security_context = Some(apicore::PodSecurityContext {
se_linux_options: Some(expected_selinux_options.clone().into()),
..apicore::PodSecurityContext::default()
})
}

let default_container = |container: &mut apicore::Container| {
if let Some(ref security_context) = container.security_context {
if let Some(ref selinux_options) = security_context.se_linux_options {
if !is_selinux_compliant(selinux_options, &expected_selinux_options) {
return Some(PolicyResponse::Reject(
"SELinux validation failed".to_string(),
));
}
} else {
container.security_context = Some(apicore::SecurityContext {
se_linux_options: Some(expected_selinux_options.clone().into()),
..security_context.clone()
});
}
} else {
container.security_context = Some(apicore::SecurityContext {
se_linux_options: Some(expected_selinux_options.clone().into()),
..apicore::SecurityContext::default()
});
};
None
};

let all_compliant_containers = pod_spec.containers.into_iter().all(|container| {
is_selinux_compliant(
&container.security_context,
expected_selinux_options.clone(),
&pod_selinux_options,
)
});

let all_compliant_init_containers = pod_spec
.init_containers
.unwrap_or_else(Vec::new)
.into_iter()
.all(|container| {
is_selinux_compliant(
&container.security_context,
expected_selinux_options.clone(),
&pod_selinux_options,
)
});

let all_compliant_ephemeral_containers = pod_spec
.ephemeral_containers
.unwrap_or_else(Vec::new)
.into_iter()
.all(|container| {
is_selinux_compliant(
&container.security_context,
expected_selinux_options.clone(),
&pod_selinux_options,
)
});

if !all_compliant_containers
|| !all_compliant_init_containers
|| !all_compliant_ephemeral_containers
{
return Ok(PolicyResponse::Reject(
"SELinux validation failed".to_string(),
));
for container in pod_spec.containers.iter_mut() {
if let Some(early_response) = default_container(container) {
return Ok(early_response);
}
}

if let Some(ref mut init_containers) = pod_spec.init_containers {
for container in init_containers.iter_mut() {
if let Some(early_response) = default_container(container) {
return Ok(early_response);
}
}
}

// Mutating is the last step -- if needed. If we are defaulting the SELinux options of
// the pod security context, at this point we have to have already confirmed that _all_
// containers meet the desired SELinux options that we are defaulting to.
if let Some(ref mut ephemeral_containers) = pod_spec.ephemeral_containers {
for container in ephemeral_containers.iter_mut() {
if let Some(ref security_context) = container.security_context {
if let Some(ref selinux_options) = security_context.se_linux_options {
if !is_selinux_compliant(selinux_options, &expected_selinux_options) {
return Ok(PolicyResponse::Reject(
"SELinux validation failed".to_string(),
));
}
} else {
container.security_context = Some(apicore::SecurityContext {
se_linux_options: Some(expected_selinux_options.clone().into()),
..security_context.clone()
});
}
} else {
container.security_context = Some(apicore::SecurityContext {
se_linux_options: Some(expected_selinux_options.clone().into()),
..apicore::SecurityContext::default()
});
};
}
}

if has_to_mutate {
if orig_pod.spec != Some(pod_spec.clone()) {
return Ok(PolicyResponse::Mutate(serde_json::to_value(
pod_with_defaulted_selinux_options,
apicore::Pod {
spec: Some(pod_spec),
..orig_pod
},
)?));
}

Expand All @@ -138,32 +148,23 @@ fn do_validate(pod: apicore::Pod, settings: settings::Settings) -> Result<Policy
}

fn is_selinux_compliant(
security_context: &Option<apicore::SecurityContext>,
expected_selinux_options: SELinuxOptions,
pod_selinux_options: &apicore::SELinuxOptions,
selinux_options: &apicore::SELinuxOptions,
expected_selinux_options: &SELinuxOptions,
) -> bool {
let selinux_options = match security_context {
Some(security_context) => security_context
.se_linux_options
.clone()
.unwrap_or_else(|| pod_selinux_options.clone()),
None => pod_selinux_options.clone(),
};

if let Some(level) = selinux_options.level {
if let Ok(level) = SELinuxLevel::new(level) {
SELinuxOptions {
user: selinux_options.user,
role: selinux_options.role,
type_: selinux_options.type_,
level: Some(level),
} == expected_selinux_options
} else {
false
if let Some(ref expected_level) = expected_selinux_options.level {
if let Some(ref level) = selinux_options.level {
if let Ok(ref level) = SELinuxLevel::new(level.clone()) {
if level != expected_level {
return false;
}
} else {
return false;
}
}
} else {
false
}
selinux_options.role == expected_selinux_options.role
&& selinux_options.type_ == expected_selinux_options.type_
&& selinux_options.user == expected_selinux_options.user
}

#[cfg(test)]
Expand Down Expand Up @@ -568,7 +569,13 @@ mod tests {
apicore::Pod {
spec: Some({
apicore::PodSpec {
containers: vec![apicore::Container::default()],
containers: vec![apicore::Container {
security_context: Some(apicore::SecurityContext {
se_linux_options: Some(selinux_options.clone().into()),
..apicore::SecurityContext::default()
}),
..apicore::Container::default()
}],
security_context: Some(apicore::PodSecurityContext {
se_linux_options: Some(selinux_options.clone().into()),
..apicore::PodSecurityContext::default()
Expand Down Expand Up @@ -604,7 +611,13 @@ mod tests {
apicore::Pod {
spec: Some({
apicore::PodSpec {
containers: vec![apicore::Container::default()],
containers: vec![apicore::Container {
security_context: Some(apicore::SecurityContext {
se_linux_options: Some(selinux_options.clone().into()),
..apicore::SecurityContext::default()
}),
..apicore::Container::default()
}],
security_context: Some(apicore::PodSecurityContext {
se_linux_options: Some(selinux_options.clone().into()),
..apicore::PodSecurityContext::default()
Expand Down Expand Up @@ -653,7 +666,18 @@ mod tests {
)?,
PolicyResponse::Mutate(serde_json::to_value(apicore::Pod {
spec: Some(apicore::PodSpec {
containers: vec![apicore::Container::default()],
containers: vec![apicore::Container {
security_context: Some(apicore::SecurityContext {
se_linux_options: Some(apicore::SELinuxOptions {
user: Some("user".to_string()),
role: Some("role".to_string()),
level: Some("s0:c7,c1".to_string()),
type_: Some("type".to_string()),
}),
..apicore::SecurityContext::default()
}),
..apicore::Container::default()
}],
security_context: Some(apicore::PodSecurityContext {
se_linux_options: Some(apicore::SELinuxOptions {
user: Some("user".to_string()),
Expand Down Expand Up @@ -703,7 +727,18 @@ mod tests {
)?,
PolicyResponse::Mutate(serde_json::to_value(apicore::Pod {
spec: Some(apicore::PodSpec {
containers: vec![apicore::Container::default()],
containers: vec![apicore::Container {
security_context: Some(apicore::SecurityContext {
se_linux_options: Some(apicore::SELinuxOptions {
user: Some("user".to_string()),
role: Some("role".to_string()),
level: Some("s0:c7,c1".to_string()),
type_: Some("type".to_string()),
}),
..apicore::SecurityContext::default()
}),
..apicore::Container::default()
}],
security_context: Some(apicore::PodSecurityContext {
se_linux_options: Some(apicore::SELinuxOptions {
user: Some("user".to_string()),
Expand Down
11 changes: 8 additions & 3 deletions src/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ use anyhow::Result;
use serde::{Deserialize, Serialize};
use std::{
cmp::PartialEq,
collections::HashSet,
convert::{TryFrom, TryInto},
iter::FromIterator,
};

use k8s_openapi::api::core::v1 as apicore;
Expand Down Expand Up @@ -57,14 +59,16 @@ pub(crate) struct SELinuxLevel {
level: String,
sensitivity: String,
categories: Vec<String>,
categories_hashset: HashSet<String>,
}

impl PartialEq for SELinuxLevel {
fn eq(&self, selinux_level: &SELinuxLevel) -> bool {
if self.level == selinux_level.level {
return true;
}
self.sensitivity == selinux_level.sensitivity && self.categories == selinux_level.categories
self.sensitivity == selinux_level.sensitivity
&& self.categories_hashset == selinux_level.categories_hashset
}
}

Expand All @@ -81,16 +85,17 @@ impl SELinuxLevel {
splitted_level.next().unwrap(),
splitted_level.next().unwrap(),
);
let mut splitted_categories: Vec<String> = categories
let splitted_categories: Vec<String> = categories
.split(',')
.into_iter()
.map(String::from)
.collect();
splitted_categories.sort();
let categories_hashset = HashSet::from_iter(splitted_categories.clone().into_iter());
Ok(SELinuxLevel {
level: level.clone(),
sensitivity: sensitivity.to_string(),
categories: splitted_categories,
categories_hashset,
})
}
}
Expand Down

0 comments on commit ebfc5ad

Please sign in to comment.