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

genpolicy: improved ExecProcessRequest input validation #276

Closed
wants to merge 7 commits into from
Closed
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
2 changes: 1 addition & 1 deletion src/agent/samples/policy/yaml/configmap/pod-cm1.yaml

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/agent/samples/policy/yaml/configmap/pod-cm2.yaml

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/agent/samples/policy/yaml/configmap/pod-cm3.yaml

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/agent/samples/policy/yaml/job/test-job.yaml

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/agent/samples/policy/yaml/job/test-job2.yaml

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/agent/samples/policy/yaml/pod/pod-exec.yaml

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/agent/samples/policy/yaml/pod/pod-lifecycle.yaml

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/agent/samples/policy/yaml/pod/pod-many-layers.yaml

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/agent/samples/policy/yaml/pod/pod-one-container.yaml

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/agent/samples/policy/yaml/pod/pod-spark.yaml

Large diffs are not rendered by default.

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/agent/samples/policy/yaml/pod/pod-ubuntu.yaml

Large diffs are not rendered by default.

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/agent/samples/policy/yaml/replica-set/replica2.yaml

Large diffs are not rendered by default.

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/agent/samples/policy/yaml/stateful-set/web.yaml

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/agent/samples/policy/yaml/stateful-set/web2.yaml

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/agent/samples/policy/yaml/webhook/webhook-pod1.yaml

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/agent/samples/policy/yaml/webhook/webhook-pod2.yaml

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/agent/samples/policy/yaml/webhook/webhook-pod3.yaml

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/agent/samples/policy/yaml/webhook/webhook-pod4.yaml

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/agent/samples/policy/yaml/webhook/webhook-pod5.yaml

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/agent/samples/policy/yaml/webhook/webhook-pod6.yaml

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/agent/samples/policy/yaml/webhook/webhook-pod7.yaml

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/agent/samples/policy/yaml/webhook2/webhook-pod8.yaml

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/agent/samples/policy/yaml/webhook2/webhook-pod9.yaml

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/agent/samples/policy/yaml/webhook3/dns-test.yaml

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/agent/samples/policy/yaml/webhook3/many-layers.yaml

Large diffs are not rendered by default.

33 changes: 17 additions & 16 deletions src/agent/src/policy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ impl AgentPolicy {
/// Ask regorus if an API call should be allowed or not.
async fn allow_request(&mut self, ep: &str, ep_input: &str) -> Result<(bool, String)> {
debug!(sl!(), "policy check: {ep}");
self.log_eval_input(ep, ep_input).await;
self.log_request(ep, ep_input).await;

let query = format!("data.agent_policy.{ep}");
self.engine.set_input_json(ep_input)?;
Expand All @@ -174,16 +174,19 @@ impl AgentPolicy {
}
};

if !allow && self.allow_failures {
warn!(sl!(), "policy: ignoring error for {ep}");
allow = true;
}

let prints = match self.engine.take_prints() {
Ok(p) => p.join(" "),
Err(e) => format!("Failed to get policy log: {e}"),
};

if !allow {
self.log_request(ep, &prints).await;
if self.allow_failures {
warn!(sl!(), "policy: ignoring error for {ep}");
allow = true;
}
}

Ok((allow, prints))
}

Expand All @@ -197,24 +200,22 @@ impl AgentPolicy {
Ok(())
}

async fn log_eval_input(&mut self, ep: &str, input: &str) {
async fn log_request(&mut self, ep: &str, input: &str) {
if let Some(log_file) = &mut self.log_file {
match ep {
"StatsContainerRequest" | "ReadStreamRequest" | "SetPolicyRequest" => {
// - StatsContainerRequest and ReadStreamRequest are called
// relatively often, so we're not logging them, to avoid
// growing this log file too much.
// - Confidential Containers Policy documents are relatively
// large, so we're not logging them here, for SetPolicyRequest.
// The Policy text can be obtained directly from the pod YAML.
"StatsContainerRequest"
| "ReadStreamRequest"
| "SetPolicyRequest"
| "AllowRequestsFailingPolicy" => {
// Logging these request types would create too much unnecessary output.
}
_ => {
let log_entry = format!("[\"ep\":\"{ep}\",{input}],\n\n");

if let Err(e) = log_file.write_all(log_entry.as_bytes()).await {
warn!(sl!(), "policy: log_eval_input: write_all failed: {}", e);
warn!(sl!(), "policy: log_request: write_all failed: {}", e);
} else if let Err(e) = log_file.flush().await {
warn!(sl!(), "policy: log_eval_input: flush failed: {}", e);
warn!(sl!(), "policy: log_request: flush failed: {}", e);
}
}
}
Expand Down
3 changes: 2 additions & 1 deletion src/tools/genpolicy/genpolicy-settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -317,7 +317,8 @@
"^AZURE_CLIENT_ID=[A-Fa-f0-9-]*$",
"^AZURE_TENANT_ID=[A-Fa-f0-9-]*$",
"^AZURE_FEDERATED_TOKEN_FILE=/var/run/secrets/azure/tokens/azure-identity-token$",
"^AZURE_AUTHORITY_HOST=https://login\\.microsoftonline\\.com/$"
"^AZURE_AUTHORITY_HOST=https://login\\.microsoftonline\\.com/$",
"^TERM=xterm$"
]
},
"CopyFileRequest": [
Expand Down
128 changes: 97 additions & 31 deletions src/tools/genpolicy/rules.rego
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ default WriteStreamRequest := false
# them and inspect OPA logs for the root cause of a failure.
default AllowRequestsFailingPolicy := false

# Constants
S_NAME_KEY = "io.kubernetes.cri.sandbox-name"
S_NAMESPACE_KEY = "io.kubernetes.cri.sandbox-namespace"

CreateContainerRequest {
# Check if the input request should be rejected even before checking the
# policy_data.containers information.
Expand Down Expand Up @@ -155,16 +159,14 @@ allow_anno_key(i_key, p_oci) {
print("allow_anno_key 2: true")
}

# Get the value of the "io.kubernetes.cri.sandbox-name" annotation and
# Get the value of the S_NAME_KEY annotation and
# correlate it with other annotations and process fields.
allow_by_anno(p_oci, i_oci, p_storages, i_storages) {
print("allow_by_anno 1: start")

s_name := "io.kubernetes.cri.sandbox-name"

not p_oci.Annotations[s_name]
not p_oci.Annotations[S_NAME_KEY]

i_s_name := i_oci.Annotations[s_name]
i_s_name := i_oci.Annotations[S_NAME_KEY]
print("allow_by_anno 1: i_s_name =", i_s_name)

allow_by_sandbox_name(p_oci, i_oci, p_storages, i_storages, i_s_name)
Expand All @@ -174,10 +176,8 @@ allow_by_anno(p_oci, i_oci, p_storages, i_storages) {
allow_by_anno(p_oci, i_oci, p_storages, i_storages) {
print("allow_by_anno 2: start")

s_name := "io.kubernetes.cri.sandbox-name"

p_s_name := p_oci.Annotations[s_name]
i_s_name := i_oci.Annotations[s_name]
p_s_name := p_oci.Annotations[S_NAME_KEY]
i_s_name := i_oci.Annotations[S_NAME_KEY]
print("allow_by_anno 2: i_s_name =", i_s_name, "p_s_name =", p_s_name)

allow_sandbox_name(p_s_name, i_s_name)
Expand All @@ -189,16 +189,14 @@ allow_by_anno(p_oci, i_oci, p_storages, i_storages) {
allow_by_sandbox_name(p_oci, i_oci, p_storages, i_storages, s_name) {
print("allow_by_sandbox_name: start")

s_namespace := "io.kubernetes.cri.sandbox-namespace"

p_namespace := p_oci.Annotations[s_namespace]
i_namespace := i_oci.Annotations[s_namespace]
p_namespace := p_oci.Annotations[S_NAMESPACE_KEY]
i_namespace := i_oci.Annotations[S_NAMESPACE_KEY]
print("allow_by_sandbox_name: p_namespace =", p_namespace, "i_namespace =", i_namespace)
p_namespace == i_namespace

allow_by_container_types(p_oci, i_oci, s_name, p_namespace)
allow_by_bundle_or_sandbox_id(p_oci, i_oci, p_storages, i_storages)
allow_process(p_oci, i_oci, s_name)
allow_process(p_oci.Process, i_oci.Process, s_name)

print("allow_by_sandbox_name: true")
}
Expand Down Expand Up @@ -489,27 +487,56 @@ allow_by_bundle_or_sandbox_id(p_oci, i_oci, p_storages, i_storages) {
print("allow_by_bundle_or_sandbox_id: true")
}

allow_process(p_oci, i_oci, s_name) {
p_process := p_oci.Process
i_process := i_oci.Process

print("allow_process: i terminal =", i_process.Terminal, "p terminal =", p_process.Terminal)
p_process.Terminal == i_process.Terminal
allow_process_common(p_process, i_process, s_name) {
print("allow_process_common: p_process =", p_process)
print("allow_process_common: i_process = ", i_process)
print("allow_process_common: s_name =", s_name)

print("allow_process: i cwd =", i_process.Cwd, "i cwd =", p_process.Cwd)
p_process.Cwd == i_process.Cwd

print("allow_process: i noNewPrivileges =", i_process.NoNewPrivileges, "p noNewPrivileges =", p_process.NoNewPrivileges)
p_process.NoNewPrivileges == i_process.NoNewPrivileges

allow_caps(p_process.Capabilities, i_process.Capabilities)
allow_user(p_process, i_process)
allow_args(p_process, i_process, s_name)
allow_env(p_process, i_process, s_name)

print("allow_process_common: true")
}

# Compare the OCI Process field of a policy container with the input OCI Process from a CreateContainerRequest
allow_process(p_process, i_process, s_name) {
print("allow_process: start")

allow_args(p_process, i_process, s_name)
allow_process_common(p_process, i_process, s_name)
allow_caps(p_process.Capabilities, i_process.Capabilities)
p_process.Terminal == i_process.Terminal

print("allow_process: true")
}

# Compare the OCI Process field of a policy container with the input process field from ExecProcessRequest
allow_interactive_process(p_process, i_process, s_name) {
print("allow_interactive_process: start")

allow_process_common(p_process, i_process, s_name)
allow_exec_caps(i_process.Capabilities)

# These are commands enabled using ExecProcessRequest commands and/or regex from the settings file.
# They can be executed interactively so allow them to use any value for i_process.Terminal.

print("allow_interactive_process: true")
}

# Compare the OCI Process field of a policy container with the input process field from ExecProcessRequest
allow_probe_process(p_process, i_process, s_name) {
print("allow_probe_process: start")

allow_process_common(p_process, i_process, s_name)
allow_exec_caps(i_process.Capabilities)
p_process.Terminal == i_process.Terminal

print("allow_probe_process: true")
}

allow_user(p_process, i_process) {
p_user := p_process.User
i_user := i_process.User
Expand Down Expand Up @@ -1079,7 +1106,16 @@ allow_direct_vol_driver(p_storage, i_storage) {
print("allow_direct_vol_driver 2: true")
}

# process.Capabilities
# ExecProcessRequest.process.Capabilities
allow_exec_caps(i_caps) {
not i_caps.Ambient
not i_caps.Bounding
not i_caps.Effective
not i_caps.Inheritable
not i_caps.Permitted
}

# OCI.Process.Capabilities
allow_caps(p_caps, i_caps) {
print("allow_caps: policy Ambient =", p_caps.Ambient)
print("allow_caps: input Ambient =", i_caps.Ambient)
Expand Down Expand Up @@ -1206,6 +1242,27 @@ CreateSandboxRequest {
allow_sandbox_storages(input.storages)
}

allow_exec(p_container, i_process) {
print("allow_exec: start")

p_oci = p_container.OCI
p_s_name = p_oci.Annotations[S_NAME_KEY]
allow_probe_process(p_oci.Process, i_process, p_s_name)

print("allow_exec: true")
}

allow_interactive_exec(p_container, i_process) {
print("allow_interactive_exec: start")

p_oci = p_container.OCI
p_s_name = p_oci.Annotations[S_NAME_KEY]
allow_interactive_process(p_oci.Process, i_process, p_s_name)

print("allow_interactive_exec: true")
}

# TODO: should other ExecProcessRequest input data fields be validated as well?
ExecProcessRequest {
print("ExecProcessRequest 1: input =", input)

Expand All @@ -1216,22 +1273,27 @@ ExecProcessRequest {
print("ExecProcessRequest 1: p_command =", p_command)
p_command == i_command

# TODO: match p_container's ID with the input container_id.
some p_container in policy_data.containers
allow_interactive_exec(p_container, input.process)

print("ExecProcessRequest 1: true")
}
ExecProcessRequest {
print("ExecProcessRequest 2: input =", input)

# TODO: match input container ID with its corresponding container.exec_commands.
i_command = concat(" ", input.process.Args)
print("ExecProcessRequest 3: i_command =", i_command)
print("ExecProcessRequest 2: i_command =", i_command)

some container in policy_data.containers
some p_command in container.exec_commands
# TODO: match p_container's ID with the input container_id.
some p_container in policy_data.containers
some p_command in p_container.exec_commands
print("ExecProcessRequest 2: p_command =", p_command)

# TODO: should other input data fields be validated as well?
p_command == i_command

allow_exec(p_container, input.process)

print("ExecProcessRequest 2: true")
}
ExecProcessRequest {
Expand All @@ -1245,6 +1307,10 @@ ExecProcessRequest {

regex.match(p_regex, i_command)

# TODO: match p_container's ID with the input container_id.
some p_container in policy_data.containers
allow_interactive_exec(p_container, input.process)

print("ExecProcessRequest 3: true")
}

Expand Down
Loading