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 - + + + + + +