From ac6c0edc8630c88a728bdd35206d965c3e2ff7cc Mon Sep 17 00:00:00 2001
From: brownz11 <74672092+brownz11@users.noreply.github.com>
Date: Fri, 16 Aug 2024 13:48:08 -0500
Subject: [PATCH] Add Context Aware Policy Functions and Example
---
.gitignore | 4 +-
.../HostCapabilitiesPolicy.csproj | 18 +++
example/HostCapabilitiesPolicy/Policy.cs | 16 +++
example/HostCapabilitiesPolicy/PolicyRules.cs | 81 ++++++++++++
.../HostCapabilitiesPolicy/PolicySettings.cs | 13 ++
example/HostCapabilitiesPolicy/metadata.yml | 23 ++++
src/KubewardenPolicySDK/Kubewarden.cs | 116 ++++++++++++++++++
.../KubewardenPolicySDK.csproj | 9 +-
8 files changed, 277 insertions(+), 3 deletions(-)
create mode 100644 example/HostCapabilitiesPolicy/HostCapabilitiesPolicy.csproj
create mode 100644 example/HostCapabilitiesPolicy/Policy.cs
create mode 100644 example/HostCapabilitiesPolicy/PolicyRules.cs
create mode 100644 example/HostCapabilitiesPolicy/PolicySettings.cs
create mode 100644 example/HostCapabilitiesPolicy/metadata.yml
diff --git a/.gitignore b/.gitignore
index faa1838..6863d36 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,4 @@
.vscode
-
+.vs
+**/bin
+**/obj
\ No newline at end of file
diff --git a/example/HostCapabilitiesPolicy/HostCapabilitiesPolicy.csproj b/example/HostCapabilitiesPolicy/HostCapabilitiesPolicy.csproj
new file mode 100644
index 0000000..a62dc01
--- /dev/null
+++ b/example/HostCapabilitiesPolicy/HostCapabilitiesPolicy.csproj
@@ -0,0 +1,18 @@
+
+
+
+ Exe
+ net8.0
+ enable
+ warnings
+
+
+
+
+
+
+
+
+
+
+
diff --git a/example/HostCapabilitiesPolicy/Policy.cs b/example/HostCapabilitiesPolicy/Policy.cs
new file mode 100644
index 0000000..28bb0c7
--- /dev/null
+++ b/example/HostCapabilitiesPolicy/Policy.cs
@@ -0,0 +1,16 @@
+using KubewardenPolicySDK;
+using WapcGuest;
+
+namespace Policy
+{
+ public class HostCapPolicy
+ {
+ public static void Main()
+ {
+ var wapc = new Wapc();
+ wapc.RegisterFunction("protocol_version", Kubewarden.ProtocolVersionGuest);
+ wapc.RegisterFunction("validate", PolicyRules.Validate);
+ wapc.RegisterFunction("validate_settings", PolicySettings.Validate);
+ }
+ }
+}
diff --git a/example/HostCapabilitiesPolicy/PolicyRules.cs b/example/HostCapabilitiesPolicy/PolicyRules.cs
new file mode 100644
index 0000000..3d304ff
--- /dev/null
+++ b/example/HostCapabilitiesPolicy/PolicyRules.cs
@@ -0,0 +1,81 @@
+
+using k8s.Models;
+using KubewardenPolicySDK;
+using System.Text.Json;
+
+
+namespace Policy
+{
+ public class PolicyRules
+ {
+ public static byte[] Validate(byte[] payload)
+ {
+ try
+ {
+ ValidationRequest? validationRequest = JsonSerializer.Deserialize(payload);
+
+ if (validationRequest is ValidationRequest req)
+ {
+ PolicySettings? policySettings = req.Settings.Deserialize();
+
+ return ProcessValidationRequest(ref req, policySettings);
+ }
+ else
+ {
+ return Kubewarden.RejectRequest("Invalid payload", 400, null, null);
+ }
+
+ }
+ catch (Exception e)
+ {
+ return Kubewarden.RejectRequest($"Internal errror: {e}", 500, null, null);
+ }
+ }
+
+
+ private static byte[] ProcessValidationRequest(ref ValidationRequest req, PolicySettings ps)
+ {
+ V1Pod maybePod = Kubewarden.GetResource(req.Request.Namespace, "iliketobealone", true);
+ if(maybePod != null)
+ {
+ return Kubewarden.RejectRequest("A Pod that wants to be alone already exists!", 400, null, null);
+ }
+
+ KubernetesList nses = Kubewarden.ListResourcesAll("", "");
+ if(nses == null)
+ {
+ return Kubewarden.RejectRequest($"Internal errror", 500, null, null);
+ }
+ foreach(var ns in nses.Items)
+ {
+ Console.WriteLine($"Looking at namespace: {ns.Name()}");
+ //Ideally you would GetResource the actual namespace instead of all of them, but for demo purposes lets look at them all
+ if (ns.Name() == req.Request.Namespace && ns.Labels()?.ContainsKey("nopodsplease") == true)
+ {
+ return Kubewarden.RejectRequest("The namespace doesn't want any pods!", 400, null, null);
+ }
+ }
+
+ KubernetesList configMaps = Kubewarden.ListResourcesAll("", "");
+ if(configMaps == null)
+ {
+ return Kubewarden.RejectRequest($"Internal errror", 500, null, null);
+ }
+ foreach(var cm in configMaps.Items)
+ {
+ Console.WriteLine($"Looking at configmap: {cm.Name()}");
+ if(cm.Name().Contains("nopodsplease") == true)
+ {
+ return Kubewarden.RejectRequest("The configmap told us no pods!", 400, null, null);
+ }
+ }
+
+
+
+ return Kubewarden.AcceptRequest();
+ }
+
+
+ }
+
+}
diff --git a/example/HostCapabilitiesPolicy/PolicySettings.cs b/example/HostCapabilitiesPolicy/PolicySettings.cs
new file mode 100644
index 0000000..3f8f599
--- /dev/null
+++ b/example/HostCapabilitiesPolicy/PolicySettings.cs
@@ -0,0 +1,13 @@
+namespace Policy;
+
+using KubewardenPolicySDK;
+
+public class PolicySettings
+{
+
+ public static byte[] Validate(byte[] payload)
+ {
+ return Kubewarden.AcceptSettings();
+ }
+}
+
diff --git a/example/HostCapabilitiesPolicy/metadata.yml b/example/HostCapabilitiesPolicy/metadata.yml
new file mode 100644
index 0000000..5f5032d
--- /dev/null
+++ b/example/HostCapabilitiesPolicy/metadata.yml
@@ -0,0 +1,23 @@
+rules:
+- apiGroups: [""]
+ apiVersions: ["v1"]
+ resources: ["Pods"]
+ operations: ["CREATE", "UPDATE"]
+mutating: true
+contextAwareResources:
+ - apiVersion: v1
+ kind: Pod
+ - apiVersion: v1
+ kind: Namespace
+ - apiVersion: v1
+ kind: ConfigMap
+annotations:
+ io.kubewarden.policy.title: host-capabilities-policy
+ io.kubewarden.policy.description: Demonstrate how to use host capabilities to examine cluster objects and reject them if they don't meet certain criteria.
+ io.kubewarden.policy.author: Zach Brown
+ io.kubewarden.policy.url: https://github.com/kubewarden
+ io.kubewarden.policy.source: https://github.com/kubewarden
+ io.kubewarden.policy.license: Apache-2.0
+ io.kubewarden.policy.usage: |
+ This policy demonstrates leveraging host capabilities/contextAware policies to deny select pods
+
\ No newline at end of file
diff --git a/src/KubewardenPolicySDK/Kubewarden.cs b/src/KubewardenPolicySDK/Kubewarden.cs
index d092ecf..586808d 100644
--- a/src/KubewardenPolicySDK/Kubewarden.cs
+++ b/src/KubewardenPolicySDK/Kubewarden.cs
@@ -1,7 +1,12 @@
namespace KubewardenPolicySDK;
+using k8s.Models;
+using k8s;
+using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
+using WapcGuest;
+using System.Reflection;
///
/// Set of helper methods used to write Kubewarden policies.
@@ -112,5 +117,116 @@ public static byte[] ProtocolVersionGuest(byte[] _payload)
string protocol_version = "v1";
return JsonSerializer.SerializeToUtf8Bytes(protocol_version);
}
+
+ ///
+ /// Invokes the `list_resources` capability of the host
+ ///
+ ///
+ ///
+ public static KubernetesList? ListResourcesAll(string labelSelector, string fieldSelector) where T : class, IKubernetesObject
+ {
+ var input = JsonSerializer.Serialize(new
+ {
+ api_version = ExtractAPIVersion(),
+ kind = ExtractKubeKind(),
+ label_selector = labelSelector,
+ field_selector = fieldSelector
+ });
+
+ var resp = Wapc.HostCall("kubewarden", "kubernetes", "list_resources_all", Encoding.UTF8.GetBytes(input));
+ var responseString = Encoding.UTF8.GetString(resp);
+ if (String.IsNullOrEmpty(responseString))
+ {
+ return null;
+ }
+ try
+ {
+ return JsonSerializer.Deserialize>(responseString);
+ }
+ catch (JsonException e)
+ {
+ throw new JsonException($"Error deserializing response: {resp}", e);
+ }
+ }
+
+ ///
+ /// Invokes the `list_resources_by_namespace` capability of the host
+ ///
+ ///
+ ///
+ ///
+ public static KubernetesList? ListResourcesByNamespace(string k8sNamespace, string labelSelector, string fieldSelector) where T : class, IKubernetesObject
+ {
+ var input = JsonSerializer.Serialize(new
+ {
+ api_version = ExtractAPIVersion(),
+ kind = ExtractKubeKind(),
+ label_selector = labelSelector,
+ field_selector = fieldSelector,
+ @namespace = k8sNamespace
+ });
+
+ var resp = Wapc.HostCall("kubewarden", "kubernetes", "list_resources", Encoding.UTF8.GetBytes(input));
+ var responseString = Encoding.UTF8.GetString(resp);
+ if (String.IsNullOrEmpty(responseString))
+ {
+ return null;
+ }
+ try
+ {
+ return JsonSerializer.Deserialize>(responseString);
+ }
+ catch (JsonException e)
+ {
+ throw new JsonException($"Error deserializing response: {resp}", e);
+ }
+ }
+
+ ///
+ /// Invokes the `list_resources_by_namespace` capability of the host
+ ///
+ ///
+ ///
+ ///
+ public static T? GetResource(string k8sNamespace, string name, bool disable_cache) where T : class, IKubernetesObject
+ {
+ var input = JsonSerializer.Serialize(new
+ {
+ api_version = ExtractAPIVersion(),
+ kind = ExtractKubeKind(),
+ @namespace = k8sNamespace,
+ name = name,
+ disable_cache = disable_cache
+ });
+
+ var resp = Wapc.HostCall("kubewarden", "kubernetes", "get_resource", Encoding.UTF8.GetBytes(input));
+ var responseString = Encoding.UTF8.GetString(resp);
+ if (String.IsNullOrEmpty(responseString))
+ {
+ return null;
+ }
+ try
+ {
+ return JsonSerializer.Deserialize(responseString);
+ }
+ catch (JsonException e)
+ {
+ throw new JsonException($"Error deserializing response: {resp}", e);
+ }
+ }
+
+ private static string ExtractKubeKind() where T : IKubernetesObject
+ {
+ var type = typeof(T);
+ var kea = type.GetCustomAttribute();
+ return kea?.Kind ?? type.Name;
+ }
+
+ private static string ExtractAPIVersion() where T : IKubernetesObject
+ {
+ var type = typeof(T);
+ var kea = type.GetCustomAttribute();
+ return kea?.ApiVersion ?? type.Name;
+ }
}
diff --git a/src/KubewardenPolicySDK/KubewardenPolicySDK.csproj b/src/KubewardenPolicySDK/KubewardenPolicySDK.csproj
index 29309d0..a2641ed 100644
--- a/src/KubewardenPolicySDK/KubewardenPolicySDK.csproj
+++ b/src/KubewardenPolicySDK/KubewardenPolicySDK.csproj
@@ -13,13 +13,18 @@
true
-
+
bin\Debug\net7.0\KubewardenPolicySDK.xml
-
+
+
+
+
+
+