Skip to content

Commit

Permalink
Update dependencies and add kubernetes-python to dockerfile
Browse files Browse the repository at this point in the history
  • Loading branch information
swoehrl-mw committed Aug 31, 2022
1 parent efdd546 commit a70db30
Show file tree
Hide file tree
Showing 15 changed files with 169 additions and 163 deletions.
158 changes: 94 additions & 64 deletions Cargo.lock

Large diffs are not rendered by default.

24 changes: 12 additions & 12 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,23 @@ edition = "2018"

[dependencies]
log = "0.4.17"
simple_logger = {version = "1.16.0", default-features = false }
kube = {version = "0.73.0", features = ["derive", "admission", "runtime"]}
simple_logger = {version = "2.3.0", default-features = false }
kube = {version = "0.74.0", features = ["derive", "admission", "runtime"]}
k8s-openapi = { version = "0.15.0", default-features = false, features = ["v1_19"] }
schemars = "0.8.10"
serde = "1.0.137"
serde_derive = "1.0.137"
serde_json = "1.0.81"
serde_yaml = "0.8.24"
tokio = { version = "1.18.2", features = ["rt-multi-thread", "macros", "sync"]}
futures = "0.3.21"
serde = "1.0.144"
serde_derive = "1.0.144"
serde_json = "1.0.85"
serde_yaml = "0.9.10"
tokio = { version = "1.20.1", features = ["rt-multi-thread", "macros", "sync"]}
futures = "0.3.23"
rocket = {version = "0.5.0-rc.2", features = ["tls", "json"]}
rustls = "0.20.5"
pyo3 = "0.16.5"
pythonize = "0.16.0"
rcgen = "0.9.2"
pyo3 = "0.17.1"
pythonize = "0.17.0"
rcgen = "0.9.3"
base64 = "0.13.0"
argh = "0.1.7"
argh = "0.1.8"
rust-embed = "6.4.0"
lazy_static = "1.4.0"
prometheus = {version = "0.13.1", features = ["process"]}
Expand Down
7 changes: 4 additions & 3 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM rust:1.60-alpine as builder
FROM rust:1.63-alpine as builder
RUN mkdir /build
RUN apk add --no-cache musl-dev python3 python3-dev openssl openssl-dev
ADD Cargo.toml /build/
Expand All @@ -11,6 +11,7 @@ COPY manifests /build/manifests
RUN touch src/main.rs && cargo build --release
RUN strip /build/target/release/bridgekeeper

FROM alpine:3.15
RUN apk add --no-cache python3 openssl libgcc
FROM alpine:3.16
RUN apk add --no-cache python3 openssl libgcc py3-pip
RUN pip install kubernetes==24.2.0
COPY --from=builder /build/target/release/bridgekeeper /usr/local/bin/
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ If the python code is not valid, raises an exception or the return value does no

If the code returns a mutated object bridekeeper will calculate the diff between the input and output objects and return it with the admission response so that Kubernetes can apply the patch to the object.

You can use the entire python feature set and standard library in your script (so stuff like `import re` is possible). Using threads, accessing the filesystem or using the network (e.g. via sockets) should not be done and might be prohibited in the future.
You can use the entire python feature set and standard library in your script (so stuff like `import re` is possible). Using threads or accessing the filesystem should not be done and might be prohibited in the future. The official bridgekeeper docker image includes the [python kubernetes library](https://github.com/kubernetes-client/python) so you can use that in policies to query the Kubernetes API. Depending on which resources you want to access you might need to assign bridgekeeper an additional ClusterRole (can be done via the helm chart by setting `serviceAccount.extraClusterRole`).

You can find a more useful example under [example/policy.yaml](example/policy.yaml) that denies deployments that use a docker image with a `latest` tag. Try it out using the following steps:

Expand Down
17 changes: 17 additions & 0 deletions charts/bridgekeeper/templates/rbac.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -106,3 +106,20 @@ subjects:
- kind: ServiceAccount
name: {{ include "bridgekeeper.serviceAccountName" . }}
namespace: "{{ .Release.Namespace }}"
---
{{- if .Values.serviceAccount.extraClusterRole }}
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
labels:
{{- include "bridgekeeper.labels" . | nindent 4 }}
name: bridgekeeper-extrarole
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: {{ .Values.serviceAccount.extraClusterRole }}
subjects:
- kind: ServiceAccount
name: {{ include "bridgekeeper.serviceAccountName" . }}
namespace: "{{ .Release.Namespace }}"
{{- end }}
2 changes: 2 additions & 0 deletions charts/bridgekeeper/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ serviceAccount:
# The name of the service account to use.
# If not set and create is true, a name is generated using the fullname template
name: ""
# The name of a ClusterRole (e.g. cluster-admin) to bind to the bridgekeeper serviceaccount, might be needed for audit mode or if a policy needs to query other resources vor validation
extraClusterRole: ""

podAnnotations: {}

Expand Down
10 changes: 6 additions & 4 deletions src/api.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::crd::Policy;
use crate::evaluator::{PolicyEvaluatorRef, EvaluationResult, validate_policy};
use crate::evaluator::{validate_policy, EvaluationResult, PolicyEvaluatorRef};
use crate::util::cert::CertKeyPair;
use kube::{
api::DynamicObject,
Expand Down Expand Up @@ -53,9 +53,11 @@ async fn admission_mutate(
reason,
warnings,
patch,
} = tokio::task::spawn_blocking(move || {
evaluator.evaluate_policies(admission_request)
}).await.map_err(|err| ApiError::ProcessingFailure(format!("Error evaluating policies: {}", err)))?;
} = tokio::task::spawn_blocking(move || evaluator.evaluate_policies(admission_request))
.await
.map_err(|err| {
ApiError::ProcessingFailure(format!("Error evaluating policies: {}", err))
})?;

response.allowed = allowed;
if !warnings.is_empty() {
Expand Down
18 changes: 4 additions & 14 deletions src/audit.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::policy::{PolicyInfo, PolicyStore, PolicyStoreRef};
use crate::crd::{Policy, PolicyStatus, Violation};
use crate::events::init_event_watcher;
use crate::manager::Manager;
use crate::policy::{PolicyInfo, PolicyStore, PolicyStoreRef};
use crate::util::error::{kube_err, BridgekeeperError, Result};
use crate::util::k8s_client::{list_with_retry, patch_status_with_retry};
use argh::FromArgs;
Expand Down Expand Up @@ -83,18 +83,11 @@ impl Auditor {
}
}

pub async fn audit_policies(
&self,
print_violations: bool,
update_status: bool,
) -> Result<()> {
pub async fn audit_policies(&self, print_violations: bool, update_status: bool) -> Result<()> {
let mut policies = Vec::new();
// While holding the lock only collect the policies, directly auditing them would make the future of the method not implement Send which breaks the task spawn
{
let policy_store = self
.policies
.lock()
.expect("lock failed. Cannot continue");
let policy_store = self.policies.lock().expect("lock failed. Cannot continue");
for policy in policy_store.policies.values() {
if policy.policy.audit.unwrap_or(false) {
policies.push(policy.clone());
Expand Down Expand Up @@ -191,10 +184,7 @@ impl Auditor {
Some(reason) => format!(": {}", reason),
None => String::new(),
};
println!(
"{} violates policy '{}'{}",
object, policy.name, message
);
println!("{} violates policy '{}'{}", object, policy.name, message);
}
}
let num_violations = results.len();
Expand Down
45 changes: 13 additions & 32 deletions src/evaluator.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::{
policy::{PolicyInfo, PolicyStoreRef},
crd::Policy,
events::{PolicyEvent, PolicyEventData, EventSender},
events::{EventSender, PolicyEvent, PolicyEventData},
policy::{PolicyInfo, PolicyStoreRef},
};
use kube::core::{
admission::{self, Operation},
Expand Down Expand Up @@ -82,10 +82,7 @@ pub struct EvaluationResult {
pub type PolicyEvaluatorRef = Arc<PolicyEvaluator>;

impl PolicyEvaluator {
pub fn new(
policies: PolicyStoreRef,
event_sender: EventSender,
) -> PolicyEvaluatorRef {
pub fn new(policies: PolicyStoreRef, event_sender: EventSender) -> PolicyEvaluatorRef {
let evaluator = PolicyEvaluator {
policies: policies,
event_sender,
Expand Down Expand Up @@ -113,10 +110,7 @@ impl PolicyEvaluator {
}
}
};
let policies = self
.policies
.lock()
.expect("lock failed. Cannot continue");
let policies = self.policies.lock().expect("lock failed. Cannot continue");

let mut matching_policies = Vec::new();

Expand Down Expand Up @@ -198,7 +192,6 @@ impl PolicyEvaluator {
} else {
warnings.push(reason);
}

}
}
EvaluationResult {
Expand All @@ -210,9 +203,7 @@ impl PolicyEvaluator {
}
}

pub fn validate_policy(
request: &admission::AdmissionRequest<Policy>,
) -> (bool, Option<String>) {
pub fn validate_policy(request: &admission::AdmissionRequest<Policy>) -> (bool, Option<String>) {
if let Some(policy) = request.object.as_ref() {
let python_code = policy.spec.rule.python.clone();
Python::with_gil(|py| {
Expand All @@ -232,7 +223,6 @@ pub fn validate_policy(
}
}


fn evaluate_policy(
policy: &PolicyInfo,
request: &ValidationRequest,
Expand All @@ -243,12 +233,9 @@ fn evaluate_policy(
Ok(obj) => obj,
Err(err) => return fail(name, &format!("Failed to initialize python: {}", err)),
};
if let Ok(rule_code) = PyModule::from_code(
py,
&policy.policy.rule.python,
"rule.py",
"bridgekeeper",
) {
if let Ok(rule_code) =
PyModule::from_code(py, &policy.policy.rule.python, "rule.py", "bridgekeeper")
{
if let Ok(validation_function) = rule_code.getattr("validate") {
match validation_function.call1((obj,)) {
Ok(result) => extract_result(name, request, result),
Expand Down Expand Up @@ -301,9 +288,7 @@ fn extract_result(
}

fn fail(name: &str, reason: &str) -> (bool, Option<String>, Option<json_patch::Patch>) {
POLICY_EVALUATIONS_ERROR
.with_label_values(&[name])
.inc();
POLICY_EVALUATIONS_ERROR.with_label_values(&[name]).inc();
(false, Some(reason.to_string()), None)
}

Expand Down Expand Up @@ -332,8 +317,7 @@ def validate(request):
return True
"#;
let policy_spec = PolicySpec::from_python(python.to_string());
let policy =
PolicyInfo::new("test".to_string(), policy_spec, Default::default());
let policy = PolicyInfo::new("test".to_string(), policy_spec, Default::default());

let object = DynamicObject {
types: None,
Expand All @@ -359,8 +343,7 @@ def validate(request):
return False, "foobar"
"#;
let policy_spec = PolicySpec::from_python(python.to_string());
let policy =
PolicyInfo::new("test".to_string(), policy_spec, Default::default());
let policy = PolicyInfo::new("test".to_string(), policy_spec, Default::default());

let object = DynamicObject {
types: None,
Expand All @@ -387,8 +370,7 @@ def validate(request):
return false, "foobar"
"#;
let policy_spec = PolicySpec::from_python(python.to_string());
let policy =
PolicyInfo::new("test".to_string(), policy_spec, Default::default());
let policy = PolicyInfo::new("test".to_string(), policy_spec, Default::default());

let object = DynamicObject {
types: None,
Expand Down Expand Up @@ -420,8 +402,7 @@ def validate(request):
return True, None, object
"#;
let policy_spec = PolicySpec::from_python(python.to_string());
let policy =
PolicyInfo::new("test".to_string(), policy_spec, Default::default());
let policy = PolicyInfo::new("test".to_string(), policy_spec, Default::default());

let data = serde_json::from_str(r#"{"a": 1, "b": "1"}"#).unwrap();
let object = DynamicObject {
Expand Down
5 changes: 2 additions & 3 deletions src/helper/gencrd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ pub struct Args {
}

pub fn run(args: Args) {
let data = serde_yaml::to_string(&Policy::crd())
.expect("Could not generate yaml from CRD definition");
let data =
serde_yaml::to_string(&Policy::crd()).expect("Could not generate yaml from CRD definition");
let filepath = args.file.unwrap_or_else(|| CRD_FILEPATH.to_string());
let wrapped_data = "{{- if .Values.installCRDs }}\n".to_string() + &data + "{{- end }}\n";
if filepath == "-" {
Expand All @@ -32,5 +32,4 @@ pub fn run(args: Args) {
)
.expect("Unable to write crd yaml");
}

}
3 changes: 1 addition & 2 deletions src/helper/init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,7 @@ async fn generate_and_store_certificates(
args: &Args,
client: &Client,
) -> CertKeyPair {
let cert =
crate::util::cert::gen_cert(SERVICE_NAME.to_string(), namespace, args.local.clone());
let cert = crate::util::cert::gen_cert(SERVICE_NAME.to_string(), namespace, args.local.clone());
if args.local.is_some() {
let _ = create_dir(LOCAL_CERTS_DIR);
let mut cert_file = File::create(Path::new(LOCAL_CERTS_DIR).join(CERT_FILENAME))
Expand Down
4 changes: 2 additions & 2 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@ use argh::FromArgs;
mod api;
mod audit;
mod constants;
mod policy;
mod crd;
mod evaluator;
mod events;
mod helper;
mod manager;
mod policy;
mod server;
mod util;

Expand All @@ -29,7 +29,7 @@ enum CommandEnum {
GenCRD(helper::gencrd::Args),
}

#[tokio::main(flavor="multi_thread")]
#[tokio::main(flavor = "multi_thread")]
async fn main() {
let args: MainArgs = argh::from_env();
let log_level = match args.command {
Expand Down
15 changes: 4 additions & 11 deletions src/manager.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use crate::util::error::{kube_err, Result};
use crate::{
policy::PolicyStoreRef,
crd::Policy,
events::{PolicyEvent, PolicyEventData, EventSender},
events::{EventSender, PolicyEvent, PolicyEventData},
policy::PolicyStoreRef,
};
use futures::StreamExt;
use kube::runtime::{watcher, watcher::Event};
Expand All @@ -19,11 +19,7 @@ pub struct Manager {
}

impl Manager {
pub fn new(
client: Client,
policies: PolicyStoreRef,
event_sender: EventSender,
) -> Manager {
pub fn new(client: Client, policies: PolicyStoreRef, event_sender: EventSender) -> Manager {
Manager {
k8s_client: client,
policies,
Expand All @@ -38,10 +34,7 @@ impl Manager {
.await
.map_err(kube_err)?;
{
let mut policies = self
.policies
.lock()
.expect("lock failed. Cannot continue");
let mut policies = self.policies.lock().expect("lock failed. Cannot continue");
for policy in res {
if let Some(ref_info) = policies.add_policy(policy) {
self.event_sender
Expand Down
Loading

0 comments on commit a70db30

Please sign in to comment.