diff --git a/.github/workflows/pipeline.yml b/.github/workflows/pipeline.yml index 87fb636a..efde6d36 100644 --- a/.github/workflows/pipeline.yml +++ b/.github/workflows/pipeline.yml @@ -255,7 +255,7 @@ jobs: - name: Setup .NET SDK uses: actions/setup-dotnet@87b7050bc53ea08284295505d98d2aa94301e852 # v4.2.0 with: - dotnet-version: 8.0.x + dotnet-version: 10.0.x - name: Execute Functional Tests run: | set -xe diff --git a/Dockerfile b/Dockerfile index ade67582..6146a786 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ # Contrast Security, Inc licenses this file to you under the Apache 2.0 License. # See the LICENSE file in the project root for more information. -FROM mcr.microsoft.com/dotnet/aspnet:8.0.22-bookworm-slim AS base +FROM mcr.microsoft.com/dotnet/aspnet:10.0.0-noble AS base # To aid in debugging. RUN set -xe \ @@ -9,7 +9,7 @@ RUN set -xe \ && apt-get install -y --no-install-recommends curl jq \ && apt-get clean && rm -rf /var/lib/apt/lists/* -FROM mcr.microsoft.com/dotnet/sdk:8.0.416-bookworm-slim AS build +FROM mcr.microsoft.com/dotnet/sdk:10.0.100-noble AS build WORKDIR /source # Restore @@ -33,8 +33,8 @@ FROM base AS final WORKDIR /app RUN set -xe \ - && addgroup --gid 1000 operator-group \ - && useradd -G operator-group --uid 1000 operator-user + && groupadd --gid 1001 operator-group \ + && useradd -G operator-group --uid 1001 operator-user COPY src/get-info.sh /get-info.sh COPY --from=build /app . @@ -43,7 +43,7 @@ RUN set -xe \ && chown operator-user:operator-group -R . \ && chmod +x /get-info.sh -USER 1000 +USER 1001 ENV ASPNETCORE_URLS=https://+:5001 \ ASPNETCORE_ENVIRONMENT=Production \ diff --git a/src/Contrast.K8s.AgentOperator/Contrast.K8s.AgentOperator.csproj b/src/Contrast.K8s.AgentOperator/Contrast.K8s.AgentOperator.csproj index 85b6d4f2..69fea798 100644 --- a/src/Contrast.K8s.AgentOperator/Contrast.K8s.AgentOperator.csproj +++ b/src/Contrast.K8s.AgentOperator/Contrast.K8s.AgentOperator.csproj @@ -2,7 +2,7 @@ Exe - net8.0 + net10.0 11.0 enable false @@ -37,14 +37,11 @@ - - + + all runtime; build; native; contentfiles; analyzers - - - diff --git a/src/Contrast.K8s.AgentOperator/Core/HashHelper.cs b/src/Contrast.K8s.AgentOperator/Core/HashHelper.cs new file mode 100644 index 00000000..f6823c2b --- /dev/null +++ b/src/Contrast.K8s.AgentOperator/Core/HashHelper.cs @@ -0,0 +1,33 @@ +// Contrast Security, Inc licenses this file to you under the Apache 2.0 License. +// See the LICENSE file in the project root for more information. + +using System; +using System.Security.Cryptography; +using System.Text; + +namespace Contrast.K8s.AgentOperator.Core; + +public static class HashHelper +{ + public static string GetShortHash(string text) + { + using var sha256 = SHA256.Create(); + var bytes = Encoding.UTF8.GetBytes(text); + var hash = sha256.ComputeHash(bytes); + return Convert.ToHexStringLower(hash, 0, 8); + } + + public static string Sha256(string text) + { + using var sha256 = SHA256.Create(); + var bytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(text)); + return Convert.ToHexStringLower(bytes); + } + + public static string Sha256(byte[] data) + { + using var sha256 = SHA256.Create(); + var bytes = sha256.ComputeHash(data); + return Convert.ToHexStringLower(bytes); + } +} diff --git a/src/Contrast.K8s.AgentOperator/Core/HexConverter.cs b/src/Contrast.K8s.AgentOperator/Core/HexConverter.cs deleted file mode 100644 index 9b50699c..00000000 --- a/src/Contrast.K8s.AgentOperator/Core/HexConverter.cs +++ /dev/null @@ -1,30 +0,0 @@ -// Contrast Security, Inc licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information. - -using System.Text; -using HexMate; - -namespace Contrast.K8s.AgentOperator.Core; - -public static class HexConverter -{ - public static string ToLowerHex(byte[] bytes) - { - return Convert.ToHexString(bytes, HexFormattingOptions.Lowercase); - } - - public static string ToLowerHex(byte[] bytes, int length) - { - // This returns null bytes for some reason... - //return Convert.ToHexString(bytes, 0, length, HexFormattingOptions.Lowercase); - - var hashString = new StringBuilder(); - for (var index = 0; index < bytes.Length && hashString.Length <= length; index++) - { - var x = bytes[index]; - hashString.Append($"{x:x2}"); - } - - return hashString.ToString(); - } -} diff --git a/src/Contrast.K8s.AgentOperator/Core/Kube/CustomEntityDefinitionExtensions.cs b/src/Contrast.K8s.AgentOperator/Core/Kube/CustomEntityDefinitionExtensions.cs deleted file mode 100644 index bc882715..00000000 --- a/src/Contrast.K8s.AgentOperator/Core/Kube/CustomEntityDefinitionExtensions.cs +++ /dev/null @@ -1,103 +0,0 @@ -// Contrast Security, Inc licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information. - -using k8s.Models; -using k8s; -using KubeOps.Abstractions.Entities.Attributes; -using System; -using System.Reflection; -using KubeOps.Abstractions.Entities; - -namespace Contrast.K8s.AgentOperator.Core.Kube; - -//Pulled from https://github.com/buehler/dotnet-kubernetes-client/blob/master/src/DotnetKubernetesClient/Entities/CustomEntityDefinitionExtensions.cs -//We no longer use the custom k8s client but these extensions are still helpful -public static class CustomEntityDefinitionExtensions -{ - /// - /// Create a custom entity definition. - /// - /// The resource that is used as the type. - /// A . - public static CustomEntityDefinition CreateResourceDefinition( - this IKubernetesObject resource) => - CreateResourceDefinition(resource.GetType()); - - /// - /// Create a custom entity definition. - /// - /// The concrete type of the resource. - /// A . - public static CustomEntityDefinition CreateResourceDefinition() - where TResource : IKubernetesObject => - CreateResourceDefinition(typeof(TResource)); - - /// - /// Create a custom entity definition. - /// - /// A type to construct the definition from. - /// - /// When the type of the resource does not contain a . - /// - /// A . - public static CustomEntityDefinition CreateResourceDefinition(this Type resourceType) - { - var attribute = resourceType.GetCustomAttribute(); - if (attribute == null) - { - throw new ArgumentException($"The Type {resourceType} does not have the kubernetes entity attribute."); - } - - var scopeAttribute = resourceType.GetCustomAttribute(); - var kind = string.IsNullOrWhiteSpace(attribute.Kind) ? resourceType.Name : attribute.Kind; - - return new CustomEntityDefinition( - kind, - $"{kind}List", - attribute.Group, - attribute.ApiVersion, - kind.ToLower(), - string.IsNullOrWhiteSpace(attribute.PluralName) ? $"{kind.ToLower()}s" : attribute.PluralName, - scopeAttribute?.Scope ?? default); - } -} - -/// -/// Custom entity ("resource") definition. This is not a full CRD (custom resource definition) of -/// Kubernetes, but all parts that regard the resource. This is used to construct a CRD out of a type -/// of kubernetes entities/resources. -/// -public readonly struct CustomEntityDefinition -{ - public readonly string Kind; - - public readonly string ListKind; - - public readonly string Group; - - public readonly string Version; - - public readonly string Singular; - - public readonly string Plural; - - public readonly EntityScope Scope; - - public CustomEntityDefinition( - string kind, - string listKind, - string @group, - string version, - string singular, - string plural, - EntityScope scope) - { - Kind = kind; - ListKind = listKind; - Group = @group; - Version = version; - Singular = singular; - Plural = plural; - Scope = scope; - } -} diff --git a/src/Contrast.K8s.AgentOperator/Core/Kube/EntityMetadataCache.cs b/src/Contrast.K8s.AgentOperator/Core/Kube/EntityMetadataCache.cs new file mode 100644 index 00000000..3cac2dbb --- /dev/null +++ b/src/Contrast.K8s.AgentOperator/Core/Kube/EntityMetadataCache.cs @@ -0,0 +1,39 @@ +// Contrast Security, Inc licenses this file to you under the Apache 2.0 License. +// See the LICENSE file in the project root for more information. + +using k8s.Models; +using KubeOps.Abstractions.Entities; +using System; +using System.Collections.Concurrent; +using System.Reflection; + +namespace Contrast.K8s.AgentOperator.Core.Kube; + +public static class EntityMetadataCache +{ + private static readonly ConcurrentDictionary MetadataCache = new(); + + public static EntityMetadata GetMetadata() + { + var type = typeof(TEntity); + return MetadataCache.GetOrAdd(type, CreateMetadata); + } + + private static EntityMetadata CreateMetadata(Type resourceType) + { + var attribute = resourceType.GetCustomAttribute(); + if (attribute == null) + { + throw new ArgumentException($"The Type {resourceType} does not have the kubernetes entity attribute."); + } + + var kind = string.IsNullOrWhiteSpace(attribute.Kind) ? resourceType.Name : attribute.Kind; + var version = string.IsNullOrWhiteSpace(attribute.ApiVersion) ? "v1" : attribute.ApiVersion; + + return new EntityMetadata( + kind, + version, + attribute.Group, + attribute.PluralName); + } +} diff --git a/src/Contrast.K8s.AgentOperator/Core/Kube/KubernetesClientExtensions.cs b/src/Contrast.K8s.AgentOperator/Core/Kube/KubernetesClientExtensions.cs index e5901477..81bc615f 100644 --- a/src/Contrast.K8s.AgentOperator/Core/Kube/KubernetesClientExtensions.cs +++ b/src/Contrast.K8s.AgentOperator/Core/Kube/KubernetesClientExtensions.cs @@ -1,25 +1,24 @@ // Contrast Security, Inc licenses this file to you under the Apache 2.0 License. // See the LICENSE file in the project root for more information. +using k8s; +using k8s.Models; +using KubeOps.KubernetesClient; using System; using System.Collections.Generic; -using System.Text.Json.Nodes; using System.Text.Json.Serialization; using System.Threading.Tasks; -using k8s; -using k8s.Models; -using KubeOps.KubernetesClient; namespace Contrast.K8s.AgentOperator.Core.Kube; public static class KubernetesClientExtensions { - public static async ValueTask Patch(this IKubernetesClient client, T resource, JsonNode patchDocument, string fieldManager) + public static async ValueTask PatchEntity(this IKubernetesClient client, T resource, string patch, string fieldManager) where T : IKubernetesObject { var apiClient = client.ApiClient; - var crd = resource.CreateResourceDefinition(); - var crPatch = new V1Patch(patchDocument.ToJsonString(), V1Patch.PatchType.JsonPatch); + var crd = EntityMetadataCache.GetMetadata(); + var crPatch = new V1Patch(patch, V1Patch.PatchType.JsonPatch); if (string.IsNullOrWhiteSpace(resource.Metadata.NamespaceProperty)) { @@ -46,38 +45,6 @@ public static async ValueTask Patch(this IKubernetesClient client, T resource } } - public static async ValueTask PatchStatus(this IKubernetesClient client, T resource, JsonNode patchDocument, string fieldManager) - where T : IKubernetesObject - { - var apiClient = client.ApiClient; - var crd = resource.CreateResourceDefinition(); - var crPatch = new V1Patch(patchDocument.ToString(), V1Patch.PatchType.JsonPatch); - - if (string.IsNullOrWhiteSpace(resource.Metadata.NamespaceProperty)) - { - using var result = await apiClient.CustomObjects.PatchClusterCustomObjectStatusWithHttpMessagesAsync( - crPatch, - crd.Group, - crd.Version, - crd.Plural, - resource.Metadata.Name, - fieldManager: fieldManager - ); - } - else - { - using var result = await apiClient.CustomObjects.PatchNamespacedCustomObjectStatusWithHttpMessagesAsync( - crPatch, - crd.Group, - crd.Version, - resource.Metadata.NamespaceProperty, - crd.Plural, - resource.Metadata.Name, - fieldManager: fieldManager - ); - } - } - public static async ValueTask PatchStatus(this IKubernetesClient client, string name, string? @namespace, @@ -86,24 +53,7 @@ public static async ValueTask PatchStatus(this IKubernetesClient client, where T : IKubernetesObject { var apiClient = client.ApiClient; - var crd = CustomEntityDefinitionExtensions.CreateResourceDefinition(); - - //var headers = new Dictionary>(); - //if (typeof(T).Assembly == typeof(V1Pod).Assembly) - //{ - // headers.Add("content-type", new List - // { - // "application/strategic-merge-patch+json" - // }); - //} - //else - //{ - // // Strategic merge patch is only support for core types. - // headers.Add("content-type", new List - // { - // "application/merge-patch+json" - // }); - //} + var crd = EntityMetadataCache.GetMetadata(); var body = new StrategicMergePatchBase { diff --git a/src/Contrast.K8s.AgentOperator/Core/Kube/KubernetesJsonSerializer.cs b/src/Contrast.K8s.AgentOperator/Core/Kube/KubernetesJsonSerializer.cs index 8ef9cf8b..789528be 100644 --- a/src/Contrast.K8s.AgentOperator/Core/Kube/KubernetesJsonSerializer.cs +++ b/src/Contrast.K8s.AgentOperator/Core/Kube/KubernetesJsonSerializer.cs @@ -1,8 +1,10 @@ // Contrast Security, Inc licenses this file to you under the Apache 2.0 License. // See the LICENSE file in the project root for more information. -using System.Text.Json.Nodes; +using Json.More; +using Json.Patch; using k8s; +using System.Text.Json.Nodes; namespace Contrast.K8s.AgentOperator.Core.Kube; @@ -23,6 +25,11 @@ public T DeserializeObject(string json) return JsonNode.Parse(SerializeObject(entity)); } + public string ToJsonString(JsonPatch patch) + { + return patch.ToJsonDocument().RootElement.GetRawText(); + } + public T DeepClone(T entity) { return DeserializeObject(SerializeObject(entity)); diff --git a/src/Contrast.K8s.AgentOperator/Core/Kube/ResourcePatcher.cs b/src/Contrast.K8s.AgentOperator/Core/Kube/ResourcePatcher.cs index 6d0c82ae..eb52257e 100644 --- a/src/Contrast.K8s.AgentOperator/Core/Kube/ResourcePatcher.cs +++ b/src/Contrast.K8s.AgentOperator/Core/Kube/ResourcePatcher.cs @@ -3,12 +3,10 @@ using System; using System.Diagnostics; -using System.Linq; using System.Net; -using System.Text.Json.JsonDiffPatch; -using System.Text.Json.JsonDiffPatch.Diffs.Formatters; using System.Threading.Tasks; using Contrast.K8s.AgentOperator.Options; +using Json.Patch; using k8s; using k8s.Autorest; using k8s.Models; @@ -27,14 +25,12 @@ public class ResourcePatcher : IResourcePatcher { private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); - private readonly JsonPatchDeltaFormatter _jsonFormatter; private readonly IKubernetesClient _client; private readonly KubernetesJsonSerializer _jsonSerializer; private readonly OperatorOptions _operatorOptions; - public ResourcePatcher(JsonPatchDeltaFormatter jsonFormatter, IKubernetesClient client, KubernetesJsonSerializer jsonSerializer, OperatorOptions operatorOptions) + public ResourcePatcher(IKubernetesClient client, KubernetesJsonSerializer jsonSerializer, OperatorOptions operatorOptions) { - _jsonFormatter = jsonFormatter; _client = client; _jsonSerializer = jsonSerializer; _operatorOptions = operatorOptions; @@ -74,15 +70,18 @@ private async ValueTask Patch(T entity, Action mutator) where T : IKuberne mutator.Invoke(entityCopy); var nextVersion = _jsonSerializer.ToJsonNode(entityCopy); - var diff = currentVersion.Diff(nextVersion, _jsonFormatter); - if (diff != null && diff.AsArray().Any()) + var patch = currentVersion.CreatePatch(nextVersion); + + if (patch.Operations.Count > 0) { + var patchJson = _jsonSerializer.ToJsonString(patch); + Logger.Trace(() => $"Preparing to patch '{entity.Namespace()}/{entity.Name()}' ('{entity.Kind}/{entity.ApiVersion}') " - + $"with '{diff.ToJsonString()}'."); + + $"with '{patchJson}'."); try { - await _client.Patch(entity, diff, _operatorOptions.FieldManagerName); + await _client.PatchEntity(entity, patchJson, _operatorOptions.FieldManagerName); } catch (HttpOperationException e) when (e.Response.StatusCode == HttpStatusCode.NotFound) { @@ -107,7 +106,7 @@ public async ValueTask PatchStatus(string name, string? @namespace, Gen var stopwatch = Stopwatch.StartNew(); - var crd = CustomEntityDefinitionExtensions.CreateResourceDefinition(); + var crd = EntityMetadataCache.GetMetadata(); Logger.Trace(() => $"Preparing to patch status '{@namespace}/{name}' ('{crd.Kind}/{crd.Version}') " diff --git a/src/Contrast.K8s.AgentOperator/Core/Reactions/Defaults/Base/BaseUniqueSyncingHandler.cs b/src/Contrast.K8s.AgentOperator/Core/Reactions/Defaults/Base/BaseUniqueSyncingHandler.cs index d5d92c61..353eedbd 100644 --- a/src/Contrast.K8s.AgentOperator/Core/Reactions/Defaults/Base/BaseUniqueSyncingHandler.cs +++ b/src/Contrast.K8s.AgentOperator/Core/Reactions/Defaults/Base/BaseUniqueSyncingHandler.cs @@ -9,7 +9,6 @@ using Contrast.K8s.AgentOperator.Options; using k8s; using k8s.Models; -using KubeOps.Abstractions.Events; using KubeOps.KubernetesClient; using System; using System.Collections.Generic; diff --git a/src/Contrast.K8s.AgentOperator/Core/Reactions/Defaults/ClusterAgentConfigurationSyncingHandler.cs b/src/Contrast.K8s.AgentOperator/Core/Reactions/Defaults/ClusterAgentConfigurationSyncingHandler.cs index d62af565..6a08a727 100644 --- a/src/Contrast.K8s.AgentOperator/Core/Reactions/Defaults/ClusterAgentConfigurationSyncingHandler.cs +++ b/src/Contrast.K8s.AgentOperator/Core/Reactions/Defaults/ClusterAgentConfigurationSyncingHandler.cs @@ -58,7 +58,7 @@ public ClusterAgentConfigurationSyncingHandler(IStateContainer state, return ValueTask.FromResult(new V1Beta1AgentConfiguration { - Metadata = new V1ObjectMeta(name: targetName, namespaceProperty: targetNamespace), + Metadata = new V1ObjectMeta { Name = targetName, NamespaceProperty = targetNamespace }, Spec = new V1Beta1AgentConfiguration.AgentConfigurationSpec { Yaml = yaml, diff --git a/src/Contrast.K8s.AgentOperator/Core/Reactions/Defaults/ClusterAgentConnectionSecretSyncingHandler.cs b/src/Contrast.K8s.AgentOperator/Core/Reactions/Defaults/ClusterAgentConnectionSecretSyncingHandler.cs index a70eee61..40a52cd4 100644 --- a/src/Contrast.K8s.AgentOperator/Core/Reactions/Defaults/ClusterAgentConnectionSecretSyncingHandler.cs +++ b/src/Contrast.K8s.AgentOperator/Core/Reactions/Defaults/ClusterAgentConnectionSecretSyncingHandler.cs @@ -133,14 +133,15 @@ public ClusterAgentConnectionSecretSyncingHandler(IStateContainer state, } } - return new V1Secret( - metadata: new V1ObjectMeta + return new V1Secret + { + Metadata = new V1ObjectMeta { Name = targetName, NamespaceProperty = targetNamespace }, - data: data - ); + Data = data + }; } protected override string GetTargetEntityName(string targetNamespace) diff --git a/src/Contrast.K8s.AgentOperator/Core/Reactions/Defaults/ClusterAgentConnectionSyncingHandler.cs b/src/Contrast.K8s.AgentOperator/Core/Reactions/Defaults/ClusterAgentConnectionSyncingHandler.cs index 96158c76..83309d45 100644 --- a/src/Contrast.K8s.AgentOperator/Core/Reactions/Defaults/ClusterAgentConnectionSyncingHandler.cs +++ b/src/Contrast.K8s.AgentOperator/Core/Reactions/Defaults/ClusterAgentConnectionSyncingHandler.cs @@ -84,7 +84,7 @@ public ClusterAgentConnectionSyncingHandler(IStateContainer state, return ValueTask.FromResult(new V1Beta1AgentConnection { - Metadata = new V1ObjectMeta(name: targetName, namespaceProperty: targetNamespace), + Metadata = new V1ObjectMeta { Name = targetName, NamespaceProperty = targetNamespace }, Spec = spec })!; } diff --git a/src/Contrast.K8s.AgentOperator/Core/Reactions/Defaults/ClusterAgentInjectorPullSecretSyncingHandler.cs b/src/Contrast.K8s.AgentOperator/Core/Reactions/Defaults/ClusterAgentInjectorPullSecretSyncingHandler.cs index 54b485c8..343d7ca5 100644 --- a/src/Contrast.K8s.AgentOperator/Core/Reactions/Defaults/ClusterAgentInjectorPullSecretSyncingHandler.cs +++ b/src/Contrast.K8s.AgentOperator/Core/Reactions/Defaults/ClusterAgentInjectorPullSecretSyncingHandler.cs @@ -78,15 +78,16 @@ public ClusterAgentInjectorPullSecretSyncingHandler(IStateContainer state, return null; } - return new V1Secret( - metadata: new V1ObjectMeta + return new V1Secret + { + Metadata = new V1ObjectMeta { Name = targetName, NamespaceProperty = targetNamespace, }, - data: new Dictionary { { ".dockerconfigjson", pullSecretData } }, - type: "kubernetes.io/dockerconfigjson" - ); + Data = new Dictionary { { ".dockerconfigjson", pullSecretData } }, + Type = "kubernetes.io/dockerconfigjson" + }; } protected override string GetTargetEntityName(string targetNamespace, AgentInjectionType agentType) diff --git a/src/Contrast.K8s.AgentOperator/Core/Reactions/Defaults/ClusterAgentInjectorSyncingHandler.cs b/src/Contrast.K8s.AgentOperator/Core/Reactions/Defaults/ClusterAgentInjectorSyncingHandler.cs index a429d86e..865944eb 100644 --- a/src/Contrast.K8s.AgentOperator/Core/Reactions/Defaults/ClusterAgentInjectorSyncingHandler.cs +++ b/src/Contrast.K8s.AgentOperator/Core/Reactions/Defaults/ClusterAgentInjectorSyncingHandler.cs @@ -102,7 +102,7 @@ public ClusterAgentInjectorSyncingHandler(IStateContainer state, return ValueTask.FromResult(new V1Beta1AgentInjector { - Metadata = new V1ObjectMeta(name: targetName, namespaceProperty: targetNamespace), + Metadata = new V1ObjectMeta { Name = targetName, NamespaceProperty = targetNamespace }, Spec = spec })!; } diff --git a/src/Contrast.K8s.AgentOperator/Core/Reactions/Defaults/ClusterDefaults.cs b/src/Contrast.K8s.AgentOperator/Core/Reactions/Defaults/ClusterDefaults.cs index 6760626e..2584423d 100644 --- a/src/Contrast.K8s.AgentOperator/Core/Reactions/Defaults/ClusterDefaults.cs +++ b/src/Contrast.K8s.AgentOperator/Core/Reactions/Defaults/ClusterDefaults.cs @@ -3,13 +3,10 @@ using System; using System.Collections.Generic; -using System.Security.Cryptography; -using System.Text; using System.Threading; using System.Threading.Tasks; using Contrast.K8s.AgentOperator.Core.State; using Contrast.K8s.AgentOperator.Core.State.Resources; -using Contrast.K8s.AgentOperator.Core.State.Resources.Interfaces; using Contrast.K8s.AgentOperator.Core.State.Resources.Primitives; namespace Contrast.K8s.AgentOperator.Core.Reactions.Defaults; @@ -27,29 +24,29 @@ public ClusterDefaults(IStateContainer state, AgentInjectionTypeConverter typeCo public string GetDefaultAgentConfigurationName(string targetNamespace) { - return "default-agent-configuration-" + GetShortHash(targetNamespace); + return "default-agent-configuration-" + HashHelper.GetShortHash(targetNamespace); } public string GetDefaultAgentConnectionName(string targetNamespace) { - return "default-agent-connection-" + GetShortHash(targetNamespace); + return "default-agent-connection-" + HashHelper.GetShortHash(targetNamespace); } public string GetDefaultAgentConnectionSecretName(string targetNamespace) { - return "default-agent-connection-secret-" + GetShortHash(targetNamespace); + return "default-agent-connection-secret-" + HashHelper.GetShortHash(targetNamespace); } public string GetDefaultPullSecretName(string targetNamespace, AgentInjectionType agentType) { var type = _typeConverter.GetStringFromType(agentType); - return $"default-agent-injector-pullsecret-{type}-{GetShortHash(targetNamespace)}"; + return $"default-agent-injector-pullsecret-{type}-{HashHelper.GetShortHash(targetNamespace)}"; } public string GetDefaultAgentInjectorName(string targetNamespace, AgentInjectionType agentType) { var type = _typeConverter.GetStringFromType(agentType); - return $"default-agent-injector-{type}-{GetShortHash(targetNamespace)}"; + return $"default-agent-injector-{type}-{HashHelper.GetShortHash(targetNamespace)}"; } public async ValueTask> GetAllNamespaces(CancellationToken cancellationToken = default) @@ -82,14 +79,4 @@ public IReadOnlyCollection GetSystemNamespaces() { return new HashSet { "kube-system", "kube-node-lease", "kube-public", "gatekeeper-system" }; } - - private static string GetShortHash(string text) - { - using var sha256 = SHA256.Create(); - var bytes = Encoding.UTF8.GetBytes(text); - var hash = sha256.ComputeHash(bytes); - return HexConverter.ToLowerHex(hash, 8); - } - - } diff --git a/src/Contrast.K8s.AgentOperator/Core/Reactions/Injecting/Patching/Agents/DotNetPatcher.cs b/src/Contrast.K8s.AgentOperator/Core/Reactions/Injecting/Patching/Agents/DotNetPatcher.cs index 914e91b8..bafff967 100644 --- a/src/Contrast.K8s.AgentOperator/Core/Reactions/Injecting/Patching/Agents/DotNetPatcher.cs +++ b/src/Contrast.K8s.AgentOperator/Core/Reactions/Injecting/Patching/Agents/DotNetPatcher.cs @@ -4,6 +4,7 @@ using Contrast.K8s.AgentOperator.Core.Reactions.Injecting.Patching.Utility; using Contrast.K8s.AgentOperator.Core.State.Resources.Primitives; using Contrast.K8s.AgentOperator.Options; +using JetBrains.Annotations; using k8s.Models; using NLog; using System; @@ -11,6 +12,7 @@ namespace Contrast.K8s.AgentOperator.Core.Reactions.Injecting.Patching.Agents; +[UsedImplicitly] public class DotNetAgentPatcher : IAgentPatcher { private readonly InjectorOptions _injectorOptions; @@ -27,22 +29,22 @@ public IEnumerable GenerateEnvVars(PatchingContext context) { if (_injectorOptions.EnableEarlyChaining) { - yield return new V1EnvVar("LD_PRELOAD", GetAgentPreloadPath(context)); + yield return new V1EnvVar { Name = "LD_PRELOAD", Value = GetAgentPreloadPath(context) }; } else { - yield return new V1EnvVar("CORECLR_PROFILER", "{8B2CE134-0948-48CA-A4B2-80DDAD9F5791}"); - yield return new V1EnvVar("CORECLR_PROFILER_PATH", $"{context.AgentMountPath}/runtimes/linux-x64/native/ContrastProfiler.so"); - yield return new V1EnvVar("CORECLR_PROFILER_PATH_ARM64", $"{context.AgentMountPath}/runtimes/linux-arm64/native/ContrastProfiler.so"); - yield return new V1EnvVar("CORECLR_ENABLE_PROFILING", "1"); + yield return new V1EnvVar { Name = "CORECLR_PROFILER", Value = "{8B2CE134-0948-48CA-A4B2-80DDAD9F5791}" }; + yield return new V1EnvVar { Name = "CORECLR_PROFILER_PATH", Value = $"{context.AgentMountPath}/runtimes/linux-x64/native/ContrastProfiler.so" }; + yield return new V1EnvVar { Name = "CORECLR_PROFILER_PATH_ARM64", Value = $"{context.AgentMountPath}/runtimes/linux-arm64/native/ContrastProfiler.so" }; + yield return new V1EnvVar { Name = "CORECLR_ENABLE_PROFILING", Value = "1" }; } - yield return new V1EnvVar("CONTRAST_INSTALL_SOURCE", "kubernetes-operator"); //For backwards compatibility - yield return new V1EnvVar("CONTRAST_INSTALLATION_TOOL", "KUBERNETES_OPERATOR"); - yield return new V1EnvVar("CONTRAST_CORECLR_INSTALL_DIRECTORY", context.AgentMountPath); - yield return new V1EnvVar("CONTRAST_CORECLR_DATA_DIRECTORY", context.WritableMountPath); - yield return new V1EnvVar("CONTRAST_CORECLR_LOGS_DIRECTORY", $"{context.WritableMountPath}/logs"); - yield return new V1EnvVar("CONTRAST__AGENT__DOTNET__ENABLE_FILE_WATCHING", "false"); + yield return new V1EnvVar { Name = "CONTRAST_INSTALL_SOURCE", Value = "kubernetes-operator" }; //For backwards compatibility + yield return new V1EnvVar { Name = "CONTRAST_INSTALLATION_TOOL", Value = "KUBERNETES_OPERATOR" }; + yield return new V1EnvVar { Name = "CONTRAST_CORECLR_INSTALL_DIRECTORY", Value = context.AgentMountPath }; + yield return new V1EnvVar { Name = "CONTRAST_CORECLR_DATA_DIRECTORY", Value = context.WritableMountPath }; + yield return new V1EnvVar { Name = "CONTRAST_CORECLR_LOGS_DIRECTORY", Value = $"{context.WritableMountPath}/logs" }; + yield return new V1EnvVar { Name = "CONTRAST__AGENT__DOTNET__ENABLE_FILE_WATCHING", Value = "false" }; } public void PatchContainer(V1Container container, PatchingContext context) @@ -71,9 +73,8 @@ public void PatchContainer(V1Container container, PatchingContext context) && !currentLdPreloadValue.Contains("ContrastChainLoader.so", StringComparison.OrdinalIgnoreCase) && container.Env.FirstOrDefault("CONTRAST_EXISTING_LD_PRELOAD") is null) { - container.Env.AddOrUpdate(new V1EnvVar("CONTRAST_EXISTING_LD_PRELOAD", currentLdPreloadValue)); - container.Env.AddOrUpdate(new V1EnvVar("LD_PRELOAD", - $"{GetAgentPreloadPath(context)}:{currentLdPreloadValue}")); + container.Env.AddOrUpdate(new V1EnvVar { Name = "CONTRAST_EXISTING_LD_PRELOAD", Value = currentLdPreloadValue }); + container.Env.AddOrUpdate(new V1EnvVar { Name = "LD_PRELOAD", Value = $"{GetAgentPreloadPath(context)}:{currentLdPreloadValue}" }); } } diff --git a/src/Contrast.K8s.AgentOperator/Core/Reactions/Injecting/Patching/Agents/FlexAgentPatcher.cs b/src/Contrast.K8s.AgentOperator/Core/Reactions/Injecting/Patching/Agents/FlexAgentPatcher.cs index 51b266aa..34b7c8a7 100644 --- a/src/Contrast.K8s.AgentOperator/Core/Reactions/Injecting/Patching/Agents/FlexAgentPatcher.cs +++ b/src/Contrast.K8s.AgentOperator/Core/Reactions/Injecting/Patching/Agents/FlexAgentPatcher.cs @@ -5,22 +5,24 @@ using System.Collections.Generic; using Contrast.K8s.AgentOperator.Core.Reactions.Injecting.Patching.Utility; using Contrast.K8s.AgentOperator.Core.State.Resources.Primitives; +using JetBrains.Annotations; using k8s.Models; namespace Contrast.K8s.AgentOperator.Core.Reactions.Injecting.Patching.Agents; +[UsedImplicitly] public class FlexAgentPatcher : IAgentPatcher { public AgentInjectionType Type => AgentInjectionType.Flex; public IEnumerable GenerateEnvVars(PatchingContext context) { - yield return new V1EnvVar("LD_PRELOAD", GetInjectorPreloadPath(context)); - yield return new V1EnvVar("CONTRAST_INSTALLATION_TOOL", "KUBERNETES_OPERATOR"); + yield return new V1EnvVar { Name = "LD_PRELOAD", Value = GetInjectorPreloadPath(context) }; + yield return new V1EnvVar { Name = "CONTRAST_INSTALLATION_TOOL", Value = "KUBERNETES_OPERATOR" }; - yield return new V1EnvVar("CONTRAST_FLEX_AGENTS_PARENT_DIR", context.AgentMountPath); - yield return new V1EnvVar("CONTRAST_FLEX_COMMS_PARENT_DIR", context.AgentMountPath); - yield return new V1EnvVar("CONTRAST_FLEX_WRITABLE_DIR", context.WritableMountPath); + yield return new V1EnvVar { Name = "CONTRAST_FLEX_AGENTS_PARENT_DIR", Value = context.AgentMountPath }; + yield return new V1EnvVar { Name = "CONTRAST_FLEX_COMMS_PARENT_DIR", Value = context.AgentMountPath }; + yield return new V1EnvVar { Name = "CONTRAST_FLEX_WRITABLE_DIR", Value = context.WritableMountPath }; } public void PatchContainer(V1Container container, PatchingContext context) @@ -31,9 +33,8 @@ public void PatchContainer(V1Container container, PatchingContext context) && !currentLdPreloadValue.Contains("agent_injector.so", StringComparison.OrdinalIgnoreCase) && container.Env.FirstOrDefault("CONTRAST_EXISTING_LD_PRELOAD") is null) { - container.Env.AddOrUpdate(new V1EnvVar("CONTRAST_EXISTING_LD_PRELOAD", currentLdPreloadValue)); - container.Env.AddOrUpdate(new V1EnvVar("LD_PRELOAD", - $"{GetInjectorPreloadPath(context)}:{currentLdPreloadValue}")); + container.Env.AddOrUpdate(new V1EnvVar { Name = "CONTRAST_EXISTING_LD_PRELOAD", Value = currentLdPreloadValue }); + container.Env.AddOrUpdate(new V1EnvVar { Name = "LD_PRELOAD", Value = $"{GetInjectorPreloadPath(context)}:{currentLdPreloadValue}" }); } } diff --git a/src/Contrast.K8s.AgentOperator/Core/Reactions/Injecting/Patching/Agents/JavaAgentPatcher.cs b/src/Contrast.K8s.AgentOperator/Core/Reactions/Injecting/Patching/Agents/JavaAgentPatcher.cs index 22271aab..00653f29 100644 --- a/src/Contrast.K8s.AgentOperator/Core/Reactions/Injecting/Patching/Agents/JavaAgentPatcher.cs +++ b/src/Contrast.K8s.AgentOperator/Core/Reactions/Injecting/Patching/Agents/JavaAgentPatcher.cs @@ -1,16 +1,18 @@ // Contrast Security, Inc licenses this file to you under the Apache 2.0 License. // See the LICENSE file in the project root for more information. -using System; -using System.Collections.Generic; -using System.Linq; using Contrast.K8s.AgentOperator.Core.Reactions.Injecting.Patching.Utility; using Contrast.K8s.AgentOperator.Core.State.Resources.Primitives; +using JetBrains.Annotations; using k8s.Models; using NLog; +using System; +using System.Collections.Generic; +using System.Linq; namespace Contrast.K8s.AgentOperator.Core.Reactions.Injecting.Patching.Agents; +[UsedImplicitly] public class JavaAgentPatcher : IAgentPatcher { private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); @@ -19,13 +21,13 @@ public class JavaAgentPatcher : IAgentPatcher public IEnumerable GenerateEnvVars(PatchingContext context) { - yield return new V1EnvVar("JAVA_TOOL_OPTIONS", GetContrastAgentArgument(context)); - yield return new V1EnvVar("CONTRAST__AGENT__CONTRAST_WORKING_DIR", context.WritableMountPath); - yield return new V1EnvVar("CONTRAST__AGENT__LOGGER__PATH", $"{context.WritableMountPath}/logs/contrast_agent.log"); - yield return new V1EnvVar("CONTRAST_INSTALLATION_TOOL", "KUBERNETES_OPERATOR"); + yield return new V1EnvVar { Name = "JAVA_TOOL_OPTIONS", Value = GetContrastAgentArgument(context) }; + yield return new V1EnvVar { Name = "CONTRAST__AGENT__CONTRAST_WORKING_DIR", Value = context.WritableMountPath }; + yield return new V1EnvVar { Name = "CONTRAST__AGENT__LOGGER__PATH", Value = $"{context.WritableMountPath}/logs/contrast_agent.log" }; + yield return new V1EnvVar { Name = "CONTRAST_INSTALLATION_TOOL", Value = "KUBERNETES_OPERATOR" }; //Disable hierarchy cache since we are in containers - yield return new V1EnvVar("CONTRAST__ASSESS__CACHE__HIERARCHY_ENABLE", "false"); + yield return new V1EnvVar { Name = "CONTRAST__ASSESS__CACHE__HIERARCHY_ENABLE", Value = "false" }; } public void PatchContainer(V1Container container, PatchingContext context) @@ -38,7 +40,7 @@ public void PatchContainer(V1Container container, PatchingContext context) var contrastAgentArgument = GetContrastAgentArgument(context); //Parse and patch the existing JAVA_TOOL_OPTIONS - container.Env.AddOrUpdate(new V1EnvVar("CONTRAST_EXISTING_JAVA_TOOL_OPTIONS", currentJavaToolOptions)); + container.Env.AddOrUpdate(new V1EnvVar { Name = "CONTRAST_EXISTING_JAVA_TOOL_OPTIONS", Value = currentJavaToolOptions }); try { @@ -56,7 +58,7 @@ public void PatchContainer(V1Container container, PatchingContext context) options.Insert(0, contrastAgentArgument); } - container.Env.AddOrUpdate(new V1EnvVar("JAVA_TOOL_OPTIONS", string.Join(' ', options))); + container.Env.AddOrUpdate(new V1EnvVar { Name = "JAVA_TOOL_OPTIONS", Value = string.Join(' ', options) }); } catch (Exception e) { diff --git a/src/Contrast.K8s.AgentOperator/Core/Reactions/Injecting/Patching/Agents/NodeJsAgentPatcher.cs b/src/Contrast.K8s.AgentOperator/Core/Reactions/Injecting/Patching/Agents/NodeJsAgentPatcher.cs index 7c7023a4..14ba610d 100644 --- a/src/Contrast.K8s.AgentOperator/Core/Reactions/Injecting/Patching/Agents/NodeJsAgentPatcher.cs +++ b/src/Contrast.K8s.AgentOperator/Core/Reactions/Injecting/Patching/Agents/NodeJsAgentPatcher.cs @@ -3,10 +3,12 @@ using System.Collections.Generic; using Contrast.K8s.AgentOperator.Core.State.Resources.Primitives; +using JetBrains.Annotations; using k8s.Models; namespace Contrast.K8s.AgentOperator.Core.Reactions.Injecting.Patching.Agents; +[UsedImplicitly] public class NodeJsAgentPatcher : IAgentPatcher { public AgentInjectionType Type => AgentInjectionType.NodeJs; @@ -14,10 +16,10 @@ public class NodeJsAgentPatcher : IAgentPatcher public IEnumerable GenerateEnvVars(PatchingContext context) { // https://nodejs.org/api/cli.html#node_optionsoptions - yield return new V1EnvVar("NODE_OPTIONS", $"--import {context.AgentMountPath}/node_modules/@contrast/agent/lib/esm-loader.mjs"); - yield return new V1EnvVar("CONTRAST__AGENT__LOGGER__PATH", $"{context.WritableMountPath}/logs/contrast_agent.log"); - yield return new V1EnvVar("CONTRAST__AGENT__SECURITY_LOGGER__PATH", $"{context.WritableMountPath}/logs/contrast_agent_cef.log"); - yield return new V1EnvVar("CONTRAST__AGENT__NODE__REWRITE__CACHE__PATH", $"{context.WritableMountPath}/cache"); - yield return new V1EnvVar("CONTRAST_INSTALLATION_TOOL", "KUBERNETES_OPERATOR"); + yield return new V1EnvVar { Name = "NODE_OPTIONS", Value = $"--import {context.AgentMountPath}/node_modules/@contrast/agent/lib/esm-loader.mjs" }; + yield return new V1EnvVar { Name = "CONTRAST__AGENT__LOGGER__PATH", Value = $"{context.WritableMountPath}/logs/contrast_agent.log" }; + yield return new V1EnvVar { Name = "CONTRAST__AGENT__SECURITY_LOGGER__PATH", Value = $"{context.WritableMountPath}/logs/contrast_agent_cef.log" }; + yield return new V1EnvVar { Name = "CONTRAST__AGENT__NODE__REWRITE__CACHE__PATH", Value = $"{context.WritableMountPath}/cache" }; + yield return new V1EnvVar { Name = "CONTRAST_INSTALLATION_TOOL", Value = "KUBERNETES_OPERATOR" }; } } diff --git a/src/Contrast.K8s.AgentOperator/Core/Reactions/Injecting/Patching/Agents/NodeJsEsmAgentPatcher.cs b/src/Contrast.K8s.AgentOperator/Core/Reactions/Injecting/Patching/Agents/NodeJsEsmAgentPatcher.cs index fd76aa4e..825c030b 100644 --- a/src/Contrast.K8s.AgentOperator/Core/Reactions/Injecting/Patching/Agents/NodeJsEsmAgentPatcher.cs +++ b/src/Contrast.K8s.AgentOperator/Core/Reactions/Injecting/Patching/Agents/NodeJsEsmAgentPatcher.cs @@ -3,10 +3,12 @@ using System.Collections.Generic; using Contrast.K8s.AgentOperator.Core.State.Resources.Primitives; +using JetBrains.Annotations; using k8s.Models; namespace Contrast.K8s.AgentOperator.Core.Reactions.Injecting.Patching.Agents; +[UsedImplicitly] public class NodeJsEsmAgentPatcher : IAgentPatcher { public bool Deprecated => true; @@ -17,10 +19,10 @@ public class NodeJsEsmAgentPatcher : IAgentPatcher public IEnumerable GenerateEnvVars(PatchingContext context) { // https://nodejs.org/api/cli.html#node_optionsoptions - yield return new V1EnvVar("NODE_OPTIONS", $"--import {context.AgentMountPath}/node_modules/@contrast/agent/lib/esm-loader.mjs"); - yield return new V1EnvVar("CONTRAST__AGENT__LOGGER__PATH", $"{context.WritableMountPath}/logs/contrast_agent.log"); - yield return new V1EnvVar("CONTRAST__AGENT__SECURITY_LOGGER__PATH", $"{context.WritableMountPath}/logs/contrast_agent_cef.log"); - yield return new V1EnvVar("CONTRAST__AGENT__NODE__REWRITE__CACHE__PATH", $"{context.WritableMountPath}/cache"); - yield return new V1EnvVar("CONTRAST_INSTALLATION_TOOL", "KUBERNETES_OPERATOR"); + yield return new V1EnvVar { Name = "NODE_OPTIONS", Value = $"--import {context.AgentMountPath}/node_modules/@contrast/agent/lib/esm-loader.mjs" }; + yield return new V1EnvVar { Name = "CONTRAST__AGENT__LOGGER__PATH", Value = $"{context.WritableMountPath}/logs/contrast_agent.log" }; + yield return new V1EnvVar { Name = "CONTRAST__AGENT__SECURITY_LOGGER__PATH", Value = $"{context.WritableMountPath}/logs/contrast_agent_cef.log" }; + yield return new V1EnvVar { Name = "CONTRAST__AGENT__NODE__REWRITE__CACHE__PATH", Value = $"{context.WritableMountPath}/cache" }; + yield return new V1EnvVar { Name = "CONTRAST_INSTALLATION_TOOL", Value = "KUBERNETES_OPERATOR" }; } } diff --git a/src/Contrast.K8s.AgentOperator/Core/Reactions/Injecting/Patching/Agents/NodeJsLegacyAgentPatcher.cs b/src/Contrast.K8s.AgentOperator/Core/Reactions/Injecting/Patching/Agents/NodeJsLegacyAgentPatcher.cs index 98f33be0..c1fec5f5 100644 --- a/src/Contrast.K8s.AgentOperator/Core/Reactions/Injecting/Patching/Agents/NodeJsLegacyAgentPatcher.cs +++ b/src/Contrast.K8s.AgentOperator/Core/Reactions/Injecting/Patching/Agents/NodeJsLegacyAgentPatcher.cs @@ -3,10 +3,12 @@ using System.Collections.Generic; using Contrast.K8s.AgentOperator.Core.State.Resources.Primitives; +using JetBrains.Annotations; using k8s.Models; namespace Contrast.K8s.AgentOperator.Core.Reactions.Injecting.Patching.Agents; +[UsedImplicitly] public class NodeJsLegacyAgentPatcher : IAgentPatcher { public AgentInjectionType Type => AgentInjectionType.NodeJsLegacy; @@ -14,10 +16,10 @@ public class NodeJsLegacyAgentPatcher : IAgentPatcher public IEnumerable GenerateEnvVars(PatchingContext context) { // https://nodejs.org/api/cli.html#node_optionsoptions - yield return new V1EnvVar("NODE_OPTIONS", $"--require {context.AgentMountPath}/node_modules/@contrast/agent"); - yield return new V1EnvVar("CONTRAST__AGENT__LOGGER__PATH", $"{context.WritableMountPath}/logs/contrast_agent.log"); - yield return new V1EnvVar("CONTRAST__AGENT__SECURITY_LOGGER__PATH", $"{context.WritableMountPath}/logs/contrast_agent_cef.log"); - yield return new V1EnvVar("CONTRAST__AGENT__NODE__REWRITE__CACHE__PATH", $"{context.WritableMountPath}/cache"); - yield return new V1EnvVar("CONTRAST_INSTALLATION_TOOL", "KUBERNETES_OPERATOR"); + yield return new V1EnvVar { Name = "NODE_OPTIONS", Value = $"--require {context.AgentMountPath}/node_modules/@contrast/agent" }; + yield return new V1EnvVar { Name = "CONTRAST__AGENT__LOGGER__PATH", Value = $"{context.WritableMountPath}/logs/contrast_agent.log" }; + yield return new V1EnvVar { Name = "CONTRAST__AGENT__SECURITY_LOGGER__PATH", Value = $"{context.WritableMountPath}/logs/contrast_agent_cef.log" }; + yield return new V1EnvVar { Name = "CONTRAST__AGENT__NODE__REWRITE__CACHE__PATH", Value = $"{context.WritableMountPath}/cache" }; + yield return new V1EnvVar { Name = "CONTRAST_INSTALLATION_TOOL", Value = "KUBERNETES_OPERATOR" }; } } diff --git a/src/Contrast.K8s.AgentOperator/Core/Reactions/Injecting/Patching/Agents/PhpAgentPatcher.cs b/src/Contrast.K8s.AgentOperator/Core/Reactions/Injecting/Patching/Agents/PhpAgentPatcher.cs index 966cf801..cdf44f20 100644 --- a/src/Contrast.K8s.AgentOperator/Core/Reactions/Injecting/Patching/Agents/PhpAgentPatcher.cs +++ b/src/Contrast.K8s.AgentOperator/Core/Reactions/Injecting/Patching/Agents/PhpAgentPatcher.cs @@ -3,19 +3,21 @@ using System.Collections.Generic; using Contrast.K8s.AgentOperator.Core.State.Resources.Primitives; +using JetBrains.Annotations; using k8s.Models; namespace Contrast.K8s.AgentOperator.Core.Reactions.Injecting.Patching.Agents; +[UsedImplicitly] public class PhpAgentPatcher : IAgentPatcher { public AgentInjectionType Type => AgentInjectionType.Php; public IEnumerable GenerateEnvVars(PatchingContext context) { - yield return new V1EnvVar("PHP_INI_SCAN_DIR", $":{context.AgentMountPath}/ini/"); - yield return new V1EnvVar("CONTRAST__AGENT__LOGGER__PATH", $"{context.WritableMountPath}/logs/contrast_agent.log"); - yield return new V1EnvVar("CONTRAST_INSTALLATION_TOOL", "KUBERNETES_OPERATOR"); + yield return new V1EnvVar { Name = "PHP_INI_SCAN_DIR", Value = $":{context.AgentMountPath}/ini/" }; + yield return new V1EnvVar { Name = "CONTRAST__AGENT__LOGGER__PATH", Value = $"{context.WritableMountPath}/logs/contrast_agent.log" }; + yield return new V1EnvVar { Name = "CONTRAST_INSTALLATION_TOOL", Value = "KUBERNETES_OPERATOR" }; } public string GetOverrideAgentMountPath() => "/usr/local/lib/contrast/php"; diff --git a/src/Contrast.K8s.AgentOperator/Core/Reactions/Injecting/Patching/Agents/PythonAgentPatcher.cs b/src/Contrast.K8s.AgentOperator/Core/Reactions/Injecting/Patching/Agents/PythonAgentPatcher.cs index 3661b278..9be242ba 100644 --- a/src/Contrast.K8s.AgentOperator/Core/Reactions/Injecting/Patching/Agents/PythonAgentPatcher.cs +++ b/src/Contrast.K8s.AgentOperator/Core/Reactions/Injecting/Patching/Agents/PythonAgentPatcher.cs @@ -4,6 +4,7 @@ using Contrast.K8s.AgentOperator.Core.Reactions.Injecting.Patching.Utility; using Contrast.K8s.AgentOperator.Core.State.Resources.Primitives; using Contrast.K8s.AgentOperator.Options; +using JetBrains.Annotations; using k8s.Models; using System; using System.Collections.Generic; @@ -11,6 +12,7 @@ namespace Contrast.K8s.AgentOperator.Core.Reactions.Injecting.Patching.Agents; +[UsedImplicitly] public class PythonAgentPatcher : IAgentPatcher { private readonly InjectorOptions _injectorOptions; @@ -23,15 +25,15 @@ public PythonAgentPatcher(InjectorOptions injectorOptions) public IEnumerable GenerateEnvVars(PatchingContext context) { - yield return new V1EnvVar("PYTHONPATH", GetAgentPythonPath(context)); + yield return new V1EnvVar { Name = "PYTHONPATH", Value = GetAgentPythonPath(context) }; if (_injectorOptions.EnablePythonRewriter) { - yield return new V1EnvVar("CONTRAST__AGENT__PYTHON__REWRITE", "true"); + yield return new V1EnvVar { Name = "CONTRAST__AGENT__PYTHON__REWRITE", Value = "true" }; } - yield return new V1EnvVar("__CONTRAST_USING_RUNNER", "true"); - yield return new V1EnvVar("CONTRAST__AGENT__LOGGER__PATH", $"{context.WritableMountPath}/logs/contrast_agent.log"); - yield return new V1EnvVar("CONTRAST_INSTALLATION_TOOL", "KUBERNETES_OPERATOR"); + yield return new V1EnvVar { Name = "__CONTRAST_USING_RUNNER", Value = "true" }; + yield return new V1EnvVar { Name = "CONTRAST__AGENT__LOGGER__PATH", Value = $"{context.WritableMountPath}/logs/contrast_agent.log" }; + yield return new V1EnvVar { Name = "CONTRAST_INSTALLATION_TOOL", Value = "KUBERNETES_OPERATOR" }; } public void PatchContainer(V1Container container, PatchingContext context) @@ -42,15 +44,14 @@ public void PatchContainer(V1Container container, PatchingContext context) && !currentPath.EndsWith("contrast/loader", StringComparison.OrdinalIgnoreCase) && container.Env.FirstOrDefault("CONTRAST_EXISTING_PYTHONPATH") is null) { - container.Env.AddOrUpdate(new V1EnvVar("CONTRAST_EXISTING_PYTHONPATH", currentPath)); + container.Env.AddOrUpdate(new V1EnvVar { Name = "CONTRAST_EXISTING_PYTHONPATH", Value = currentPath }); //Some operators add the existing PYTHONPATH to the middle of the new PYTHONPATH so remove our existing one and prefix it var splitPath = currentPath.Split(':').ToList(); splitPath.Remove(context.AgentMountPath); splitPath.Remove($"{context.AgentMountPath}/contrast/loader"); - container.Env.AddOrUpdate(new V1EnvVar("PYTHONPATH", - $"{GetAgentPythonPath(context)}:{string.Join(':', splitPath)}")); + container.Env.AddOrUpdate(new V1EnvVar { Name = "PYTHONPATH", Value = $"{GetAgentPythonPath(context)}:{string.Join(':', splitPath)}" }); } } diff --git a/src/Contrast.K8s.AgentOperator/Core/Reactions/Injecting/Patching/PodPatcher.cs b/src/Contrast.K8s.AgentOperator/Core/Reactions/Injecting/Patching/PodPatcher.cs index 1cdb0a02..1f83b6e2 100644 --- a/src/Contrast.K8s.AgentOperator/Core/Reactions/Injecting/Patching/PodPatcher.cs +++ b/src/Contrast.K8s.AgentOperator/Core/Reactions/Injecting/Patching/PodPatcher.cs @@ -1,13 +1,6 @@ // Contrast Security, Inc licenses this file to you under the Apache 2.0 License. // See the LICENSE file in the project root for more information. -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text.RegularExpressions; -using System.Threading; -using System.Threading.Tasks; using Contrast.K8s.AgentOperator.Core.Reactions.Injecting.Patching.Agents; using Contrast.K8s.AgentOperator.Core.Reactions.Matching; using Contrast.K8s.AgentOperator.Core.Reactions.Secrets; @@ -15,6 +8,13 @@ using Contrast.K8s.AgentOperator.Options; using k8s.Models; using NLog; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text.RegularExpressions; +using System.Threading; +using System.Threading.Tasks; namespace Contrast.K8s.AgentOperator.Core.Reactions.Injecting.Patching; @@ -82,22 +82,25 @@ private void ApplyPatches(PatchingContext context, V1Pod pod, IAgentPatcher? age // Volumes. pod.Spec.Volumes ??= new List(); - var agentVolume = new V1Volume("contrast-agent") + var agentVolume = new V1Volume { + Name = "contrast-agent", EmptyDir = new V1EmptyDirVolumeSource() }; pod.Spec.Volumes.AddOrUpdate(agentVolume.Name, agentVolume); - var writableVolume = new V1Volume("contrast-writable") + var writableVolume = new V1Volume { + Name = "contrast-writable", EmptyDir = new V1EmptyDirVolumeSource() }; pod.Spec.Volumes.AddOrUpdate(writableVolume.Name, writableVolume); var connectionSecretVolumeName = "contrast-connection"; if (context.Connection.MountAsVolume == true) { - var secretVolume = new V1Volume(connectionSecretVolumeName) + var secretVolume = new V1Volume { + Name = connectionSecretVolumeName, Secret = new V1SecretVolumeSource { SecretName = VolumeSecrets.GetConnectionVolumeSecretName(context.Injector.ConnectionReference.Name), @@ -124,7 +127,7 @@ private void ApplyPatches(PatchingContext context, V1Pod pod, IAgentPatcher? age pod.Spec.ImagePullSecrets ??= new List(); pod.Spec.ImagePullSecrets.AddOrUpdate( x => string.Equals(x.Name, pullSecret.Name, StringComparison.Ordinal), - new V1LocalObjectReference(pullSecret.Name) + new V1LocalObjectReference{ Name = pullSecret.Name } ); } @@ -133,16 +136,30 @@ private void ApplyPatches(PatchingContext context, V1Pod pod, IAgentPatcher? age { container.VolumeMounts ??= new List(); - var agentVolumeMount = new V1VolumeMount(context.AgentMountPath, agentVolume.Name, readOnlyProperty: true); + var agentVolumeMount = new V1VolumeMount + { + Name = agentVolume.Name, + MountPath = context.AgentMountPath, + ReadOnlyProperty = true + }; container.VolumeMounts.AddOrUpdate(agentVolumeMount.Name, agentVolumeMount); - var writableVolumeMount = - new V1VolumeMount(context.WritableMountPath, writableVolume.Name, readOnlyProperty: false); + var writableVolumeMount = new V1VolumeMount + { + Name = writableVolume.Name, + MountPath = context.WritableMountPath, + ReadOnlyProperty = false + }; container.VolumeMounts.AddOrUpdate(writableVolumeMount.Name, writableVolumeMount); if (context.Connection.MountAsVolume == true) { - var connectionSecretVolumeMount = new V1VolumeMount(context.ConnectionSecretMountPath, connectionSecretVolumeName, readOnlyProperty: true); + var connectionSecretVolumeMount = new V1VolumeMount + { + Name = connectionSecretVolumeName, + MountPath = context.ConnectionSecretMountPath, + ReadOnlyProperty = true + }; container.VolumeMounts.AddOrUpdate(connectionSecretVolumeMount.Name, connectionSecretVolumeMount); } @@ -230,20 +247,21 @@ private V1Container CreateInitContainer(PatchingContext context, resources.Limits.TryAdd("memory", new ResourceQuantity(_initOptions.MemoryLimit)); resources.Limits.TryAdd("ephemeral-storage", new ResourceQuantity(_initOptions.EphemeralStorageLimit)); - var initContainer = new V1Container("contrast-init") + var initContainer = new V1Container { + Name = "contrast-init", Image = context.Injector.Image.GetFullyQualifiedContainerImageName(), VolumeMounts = new List { - new(initAgentMountPath, agentVolume.Name), - new(initWritableMountPath, writableVolume.Name), + new() { MountPath = initAgentMountPath, Name = agentVolume.Name }, + new() { MountPath = initWritableMountPath, Name = writableVolume.Name }, }, ImagePullPolicy = context.Injector.ImagePullPolicy, Env = new List { - new("CONTRAST_MOUNT_PATH", initAgentMountPath), - new("CONTRAST_MOUNT_AGENT_PATH", initAgentMountPath), - new("CONTRAST_MOUNT_WRITABLE_PATH", initWritableMountPath), + new() { Name = "CONTRAST_MOUNT_PATH", Value = initAgentMountPath }, + new() { Name = "CONTRAST_MOUNT_AGENT_PATH", Value = initAgentMountPath }, + new() { Name = "CONTRAST_MOUNT_WRITABLE_PATH", Value = initWritableMountPath }, }, Resources = resources, SecurityContext = securityContent @@ -252,7 +270,7 @@ private V1Container CreateInitContainer(PatchingContext context, // This is for our CS team to aid in debugging, but also for our functional tests. if (securityContextTainted) { - initContainer.Env.Add(new V1EnvVar("CONTRAST_DEBUGGING_SECURITY_CONTEXT_TAINTED", true.ToString())); + initContainer.Env.Add(new V1EnvVar { Name = "CONTRAST_DEBUGGING_SECURITY_CONTEXT_TAINTED", Value = "true" }); } return initContainer; @@ -294,61 +312,85 @@ private IEnumerable GenerateEnvVars(PatchingContext context, V1Pod pod var (workloadName, workloadNamespace, _, connection, configuration, agentMountPath, writableMountPath, secretMountPath) = context; // This isn't used in modern agent images, but is still used in older images. - yield return new V1EnvVar("CONTRAST_MOUNT_PATH", agentMountPath); - yield return new V1EnvVar("CONTRAST_MOUNT_AGENT_PATH", agentMountPath); - yield return new V1EnvVar("CONTRAST_MOUNT_WRITABLE_PATH", writableMountPath); + yield return new V1EnvVar { Name = "CONTRAST_MOUNT_PATH", Value = agentMountPath }; + yield return new V1EnvVar { Name = "CONTRAST_MOUNT_AGENT_PATH", Value = agentMountPath }; + yield return new V1EnvVar { Name = "CONTRAST_MOUNT_WRITABLE_PATH", Value = writableMountPath }; if (connection.TeamServerUri != null) { - yield return new V1EnvVar("CONTRAST__API__URL", connection.TeamServerUri); + yield return new V1EnvVar { Name = "CONTRAST__API__URL", Value = connection.TeamServerUri }; } if (context.Connection.MountAsVolume == true) { - yield return new V1EnvVar("CONTRAST_CONFIG_PATH", Path.Join(secretMountPath, "contrast_security.yaml")); + yield return new V1EnvVar { Name = "CONTRAST_CONFIG_PATH", Value = Path.Join(secretMountPath, "contrast_security.yaml") }; } else { // New auth method if (connection.Token != null) { - yield return new V1EnvVar( - "CONTRAST__API__TOKEN", - valueFrom: new V1EnvVarSource( - secretKeyRef: new V1SecretKeySelector(connection.Token.Key, connection.Token.Name) - ) - ); + yield return new V1EnvVar + { + Name = "CONTRAST__API__TOKEN", + ValueFrom = new V1EnvVarSource + { + SecretKeyRef = new V1SecretKeySelector + { + Key = connection.Token.Key, + Name = connection.Token.Name + } + } + }; } // Legacy auth method if (connection.ApiKey != null) { - yield return new V1EnvVar( - "CONTRAST__API__API_KEY", - valueFrom: new V1EnvVarSource( - secretKeyRef: new V1SecretKeySelector(connection.ApiKey.Key, connection.ApiKey.Name) - ) - ); + yield return new V1EnvVar + { + Name = "CONTRAST__API__API_KEY", + ValueFrom = new V1EnvVarSource + { + SecretKeyRef = new V1SecretKeySelector + { + Key = connection.ApiKey.Key, + Name = connection.ApiKey.Name + } + } + }; } if (connection.ServiceKey != null) { - yield return new V1EnvVar( - "CONTRAST__API__SERVICE_KEY", - valueFrom: new V1EnvVarSource( - secretKeyRef: new V1SecretKeySelector(connection.ServiceKey.Key, connection.ServiceKey.Name) - ) - ); + yield return new V1EnvVar + { + Name = "CONTRAST__API__SERVICE_KEY", + ValueFrom = new V1EnvVarSource + { + SecretKeyRef = new V1SecretKeySelector + { + Key = connection.ServiceKey.Key, + Name = connection.ServiceKey.Name + } + } + }; } if (connection.UserName != null) { - yield return new V1EnvVar( - "CONTRAST__API__USER_NAME", - valueFrom: new V1EnvVarSource( - secretKeyRef: new V1SecretKeySelector(connection.UserName.Key, connection.UserName.Name) - ) - ); + yield return new V1EnvVar + { + Name = "CONTRAST__API__USER_NAME", + ValueFrom = new V1EnvVarSource + { + SecretKeyRef = new V1SecretKeySelector + { + Key = connection.UserName.Key, + Name = connection.UserName.Name + } + } + }; } } @@ -370,13 +412,12 @@ private IEnumerable GenerateEnvVars(PatchingContext context, V1Pod pod yield return envVar; } - yield return new V1EnvVar($"CONTRAST__{key.Replace(".", "__").ToUpperInvariant()}", - replacement.Value); + yield return new V1EnvVar { Name = $"CONTRAST__{key.Replace(".", "__").ToUpperInvariant()}", Value = replacement.Value }; } } else { - yield return new V1EnvVar($"CONTRAST__{key.Replace(".", "__").ToUpperInvariant()}", value); + yield return new V1EnvVar { Name = $"CONTRAST__{key.Replace(".", "__").ToUpperInvariant()}", Value = value }; } } } @@ -385,24 +426,24 @@ private IEnumerable GenerateEnvVars(PatchingContext context, V1Pod pod // Order does matter here, YamlKeys values take precedent. if (_operatorOptions.EnableAgentStdout) { - yield return new V1EnvVar("CONTRAST__AGENT__LOGGER__STDOUT", "true"); + yield return new V1EnvVar { Name = "CONTRAST__AGENT__LOGGER__STDOUT", Value = "true" }; } if (configuration?.SuppressDefaultServerName != true && !string.IsNullOrWhiteSpace(workloadNamespace)) { - yield return new V1EnvVar("CONTRAST__SERVER__NAME", $"kubernetes-{workloadNamespace}"); + yield return new V1EnvVar { Name = "CONTRAST__SERVER__NAME", Value = $"kubernetes-{workloadNamespace}" }; } if (configuration?.SuppressDefaultApplicationName != true && !string.IsNullOrWhiteSpace(workloadName)) { - yield return new V1EnvVar("CONTRAST__APPLICATION__NAME", workloadName); + yield return new V1EnvVar { Name = "CONTRAST__APPLICATION__NAME", Value = workloadName }; } if (_clusterIdState.GetClusterId() is { } clusterId) { - yield return new V1EnvVar("CONTRAST_CLUSTER_ID", clusterId.Guid.ToString("D")); + yield return new V1EnvVar { Name = "CONTRAST_CLUSTER_ID", Value = clusterId.Guid.ToString("D") }; } } @@ -425,11 +466,15 @@ private IEnumerable GenerateEnvVars(PatchingContext context, V1Pod pod var key = match.Groups[1].Value; if (key.Equals("namespace", StringComparison.OrdinalIgnoreCase)) { - additionalVars.Add(new V1EnvVar( - "CONTRAST_VAR_POD_NAMESPACE", - valueFrom: new V1EnvVarSource( - fieldRef: new V1ObjectFieldSelector("metadata.namespace") - ))); + additionalVars.Add(new V1EnvVar + { + Name = "CONTRAST_VAR_POD_NAMESPACE", + ValueFrom = new V1EnvVarSource + { + FieldRef = new V1ObjectFieldSelector { FieldPath = "metadata.namespace" } + } + }); + value = value.Replace("%namespace%", "$(CONTRAST_VAR_POD_NAMESPACE)"); } else if (key.StartsWith("labels", StringComparison.OrdinalIgnoreCase)) @@ -443,11 +488,15 @@ private IEnumerable GenerateEnvVars(PatchingContext context, V1Pod pod var labelKey = key.Substring("labels.".Length); var envKey = labelKey.Replace("/", "").Replace("-", "").Replace(".", "").ToUpper(); var envVariableName = $"CONTRAST_VAR_LABEL_{envKey}"; - additionalVars.Add(new V1EnvVar( - envVariableName, - valueFrom: new V1EnvVarSource( - fieldRef: new V1ObjectFieldSelector($"metadata.labels['{labelKey}']") - ))); + + additionalVars.Add(new V1EnvVar + { + Name = envVariableName, + ValueFrom = new V1EnvVarSource + { + FieldRef = new V1ObjectFieldSelector { FieldPath = $"metadata.labels['{labelKey}']" } + } + }); value = value.Replace($"%{key}%", $"$({envVariableName})"); } @@ -462,11 +511,15 @@ private IEnumerable GenerateEnvVars(PatchingContext context, V1Pod pod var annotationKey = key.Substring("annotations.".Length); var envKey = annotationKey.Replace("/", "").Replace("-", "").Replace(".", "").ToUpper(); var envVariableName = $"CONTRAST_VAR_ANNOTATION_{envKey}"; - additionalVars.Add(new V1EnvVar( - envVariableName, - valueFrom: new V1EnvVarSource( - fieldRef: new V1ObjectFieldSelector($"metadata.annotations['{annotationKey}']") - ))); + + additionalVars.Add(new V1EnvVar + { + Name = envVariableName, + ValueFrom = new V1EnvVarSource + { + FieldRef = new V1ObjectFieldSelector { FieldPath = $"metadata.annotations['{annotationKey}']" } + } + }); value = value.Replace($"%{key}%", $"$({envVariableName})"); } diff --git a/src/Contrast.K8s.AgentOperator/Core/Reactions/Injecting/PodTemplateInjectionHandler.cs b/src/Contrast.K8s.AgentOperator/Core/Reactions/Injecting/PodTemplateInjectionHandler.cs index 068b5d2e..1696ccc3 100644 --- a/src/Contrast.K8s.AgentOperator/Core/Reactions/Injecting/PodTemplateInjectionHandler.cs +++ b/src/Contrast.K8s.AgentOperator/Core/Reactions/Injecting/PodTemplateInjectionHandler.cs @@ -113,7 +113,7 @@ private async ValueTask PatchToDesiredStateDeployment(DesiredState desiredState, private async ValueTask PatchToDesiredStateRollout(DesiredState desiredState, NamespacedResourceIdentity identity) { await _state.MarkAsDirty(identity); - await _patcher.Patch(identity.Name, identity.Namespace, o => { PatchAnnotations(desiredState, o.Spec.Template); }); + await _patcher.Patch(identity.Name, identity.Namespace, o => { PatchAnnotations(desiredState, o.Spec.Template!); }); } private async ValueTask PatchToDesiredStateDeploymentConfig(DesiredState desiredState, NamespacedResourceIdentity identity) diff --git a/src/Contrast.K8s.AgentOperator/Core/Reactions/Secrets/AgentConnectionVolumeSecretHandler.cs b/src/Contrast.K8s.AgentOperator/Core/Reactions/Secrets/AgentConnectionVolumeSecretHandler.cs index ce8446e2..02687c0d 100644 --- a/src/Contrast.K8s.AgentOperator/Core/Reactions/Secrets/AgentConnectionVolumeSecretHandler.cs +++ b/src/Contrast.K8s.AgentOperator/Core/Reactions/Secrets/AgentConnectionVolumeSecretHandler.cs @@ -90,17 +90,18 @@ public async Task Handle(DeferredStateModified notification, CancellationToken c private V1Secret CreateEntity(byte[] config, string name, string ns) { - return new V1Secret( - metadata: new V1ObjectMeta + return new V1Secret + { + Metadata = new V1ObjectMeta { Name = name, NamespaceProperty = ns }, - data: new Dictionary + Data = new Dictionary { { ConfigFilename, config } } - ); + }; } private async Task CreateDerivedSecretHash(ResourceIdentityPair connectionResource) @@ -148,7 +149,7 @@ private async Task CreateDerivedSecretHash(ResourceIdentityPair CreateLiveConfig(ResourceIdentityPair connectionResource) diff --git a/src/Contrast.K8s.AgentOperator/Core/Reactions/Secrets/VolumeSecrets.cs b/src/Contrast.K8s.AgentOperator/Core/Reactions/Secrets/VolumeSecrets.cs index 7dc86b98..b06dce66 100644 --- a/src/Contrast.K8s.AgentOperator/Core/Reactions/Secrets/VolumeSecrets.cs +++ b/src/Contrast.K8s.AgentOperator/Core/Reactions/Secrets/VolumeSecrets.cs @@ -1,23 +1,12 @@ // Contrast Security, Inc licenses this file to you under the Apache 2.0 License. // See the LICENSE file in the project root for more information. -using System.Security.Cryptography; -using System.Text; - namespace Contrast.K8s.AgentOperator.Core.Reactions.Secrets; public class VolumeSecrets { public static string GetConnectionVolumeSecretName(string agentConnection) { - return "agent-connection-volume-secret-" + GetShortHash(agentConnection); - } - - private static string GetShortHash(string text) - { - using var sha256 = SHA256.Create(); - var bytes = Encoding.UTF8.GetBytes(text); - var hash = sha256.ComputeHash(bytes); - return HexConverter.ToLowerHex(hash, 8); + return "agent-connection-volume-secret-" + HashHelper.GetShortHash(agentConnection); } } diff --git a/src/Contrast.K8s.AgentOperator/Core/State/ResourceHasher.cs b/src/Contrast.K8s.AgentOperator/Core/State/ResourceHasher.cs index 8aa81152..28fdbe24 100644 --- a/src/Contrast.K8s.AgentOperator/Core/State/ResourceHasher.cs +++ b/src/Contrast.K8s.AgentOperator/Core/State/ResourceHasher.cs @@ -2,7 +2,6 @@ // See the LICENSE file in the project root for more information. using System.Collections.Generic; -using System.Security.Cryptography; using System.Text; using Contrast.K8s.AgentOperator.Core.Kube; using Contrast.K8s.AgentOperator.Core.State.Resources; @@ -42,19 +41,14 @@ private string GetHashImpl(params object?[] objects) builder.Append(GetHashImpl(o)); } - return Sha256(builder.ToString()); + return HashHelper.Sha256(builder.ToString()); } private string GetHashImpl(object? o) { var json = _jsonSerializer.SerializeObject(o ?? ""); - return Sha256(json); + return HashHelper.Sha256(json); } - private static string Sha256(string text) - { - using var sha256 = SHA256.Create(); - var bytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(text)); - return HexConverter.ToLowerHex(bytes); - } + } diff --git a/src/Contrast.K8s.AgentOperator/Core/State/Resources/Primitives/SecretKeyValue.cs b/src/Contrast.K8s.AgentOperator/Core/State/Resources/Primitives/SecretKeyValue.cs index e7c99442..867d75a1 100644 --- a/src/Contrast.K8s.AgentOperator/Core/State/Resources/Primitives/SecretKeyValue.cs +++ b/src/Contrast.K8s.AgentOperator/Core/State/Resources/Primitives/SecretKeyValue.cs @@ -1,20 +1,13 @@ // Contrast Security, Inc licenses this file to you under the Apache 2.0 License. // See the LICENSE file in the project root for more information. -using System.Security.Cryptography; - namespace Contrast.K8s.AgentOperator.Core.State.Resources.Primitives; public record SecretKeyValue(string Key, string DataHash) { public static SecretKeyValue Create(string key, byte[] value) { - return new SecretKeyValue(key, Sha256(value)); - } - private static string Sha256(byte[] data) - { - using var sha256 = SHA256.Create(); - var bytes = sha256.ComputeHash(data); - return HexConverter.ToLowerHex(bytes); + return new SecretKeyValue(key, HashHelper.Sha256(value)); } + } diff --git a/src/Contrast.K8s.AgentOperator/Core/Telemetry/Cluster/ClusterIdWriter.cs b/src/Contrast.K8s.AgentOperator/Core/Telemetry/Cluster/ClusterIdWriter.cs index d02f6497..a007e7ee 100644 --- a/src/Contrast.K8s.AgentOperator/Core/Telemetry/Cluster/ClusterIdWriter.cs +++ b/src/Contrast.K8s.AgentOperator/Core/Telemetry/Cluster/ClusterIdWriter.cs @@ -93,10 +93,11 @@ public async Task SetId(ClusterId clusterId) await _client.SaveAsync(new V1Secret { - Metadata = new V1ObjectMeta( - name: _options.ClusterIdSecretName, - namespaceProperty: _options.ClusterIdSecretNamespace - ), + Metadata = new V1ObjectMeta + { + Name = _options.ClusterIdSecretName, + NamespaceProperty = _options.ClusterIdSecretNamespace + }, Data = new Dictionary { { PayloadKey, bytes } diff --git a/src/Contrast.K8s.AgentOperator/Core/Tls/TlsCertificateChainConverter.cs b/src/Contrast.K8s.AgentOperator/Core/Tls/TlsCertificateChainConverter.cs index 201b9fd4..c521b3f2 100644 --- a/src/Contrast.K8s.AgentOperator/Core/Tls/TlsCertificateChainConverter.cs +++ b/src/Contrast.K8s.AgentOperator/Core/Tls/TlsCertificateChainConverter.cs @@ -31,8 +31,8 @@ public TlsCertificateChainExport Export(TlsCertificateChain chain) public TlsCertificateChain Import(TlsCertificateChainExport export) { - var caCertificate = new X509Certificate2(export.CaCertificatePfx, (string?)null, X509KeyStorageFlags.Exportable); - var serverCertificate = new X509Certificate2(export.ServerCertificatePfx, (string?)null, X509KeyStorageFlags.Exportable); + var caCertificate = X509CertificateLoader.LoadPkcs12(export.CaCertificatePfx, null, X509KeyStorageFlags.Exportable); + var serverCertificate = X509CertificateLoader.LoadPkcs12(export.ServerCertificatePfx, null, X509KeyStorageFlags.Exportable); return new TlsCertificateChain(caCertificate, serverCertificate, export.SansHash, export.Version); } diff --git a/src/Contrast.K8s.AgentOperator/Entities/Argo/V1Alpha1Rollout.cs b/src/Contrast.K8s.AgentOperator/Entities/Argo/V1Alpha1Rollout.cs index 561440df..de2478be 100644 --- a/src/Contrast.K8s.AgentOperator/Entities/Argo/V1Alpha1Rollout.cs +++ b/src/Contrast.K8s.AgentOperator/Entities/Argo/V1Alpha1Rollout.cs @@ -6,6 +6,7 @@ using KubeOps.Abstractions.Entities; using KubeOps.Abstractions.Entities.Attributes; using KubeOps.Abstractions.Rbac; +using System.Text.Json.Serialization; namespace Contrast.K8s.AgentOperator.Entities.Argo; @@ -15,7 +16,12 @@ namespace Contrast.K8s.AgentOperator.Entities.Argo; public partial class V1Alpha1Rollout : CustomKubernetesEntity { //Drop-in replacement for Deployment (with additional fields for the rollout info) - public class RolloutSpec : V1DeploymentSpec + public class RolloutSpec { + [JsonPropertyName("selector")] + public V1LabelSelector? Selector { get; set; } + + [JsonPropertyName("template")] + public V1PodTemplateSpec? Template { get; set; } } } diff --git a/src/Contrast.K8s.AgentOperator/Startup.cs b/src/Contrast.K8s.AgentOperator/Startup.cs index 87f2a259..59b0a951 100644 --- a/src/Contrast.K8s.AgentOperator/Startup.cs +++ b/src/Contrast.K8s.AgentOperator/Startup.cs @@ -5,6 +5,7 @@ using Contrast.K8s.AgentOperator.Core; using Contrast.K8s.AgentOperator.Extensions; using JetBrains.Annotations; +using KubeOps.Abstractions.Builder; using KubeOps.Operator; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Diagnostics.HealthChecks; @@ -27,7 +28,7 @@ public void ConfigureServices(IServiceCollection services) { services.AddKubernetesOperator(settings => { - settings.EnableLeaderElection = true; + settings.LeaderElectionType = LeaderElectionType.Single; }).RegisterEntities(); services.AddHealthChecks() diff --git a/tests/Contrast.K8s.AgentOperator.FunctionalTests/Contrast.K8s.AgentOperator.FunctionalTests.csproj b/tests/Contrast.K8s.AgentOperator.FunctionalTests/Contrast.K8s.AgentOperator.FunctionalTests.csproj index 74647318..9a1b77b4 100644 --- a/tests/Contrast.K8s.AgentOperator.FunctionalTests/Contrast.K8s.AgentOperator.FunctionalTests.csproj +++ b/tests/Contrast.K8s.AgentOperator.FunctionalTests/Contrast.K8s.AgentOperator.FunctionalTests.csproj @@ -1,7 +1,7 @@ - net8.0 + net10.0 11.0 enable false @@ -25,7 +25,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/tests/Contrast.K8s.AgentOperator.FunctionalTests/Scenarios/Injection/StandardInjectionTests.cs b/tests/Contrast.K8s.AgentOperator.FunctionalTests/Scenarios/Injection/StandardInjectionTests.cs index d69e1d83..17f0909b 100644 --- a/tests/Contrast.K8s.AgentOperator.FunctionalTests/Scenarios/Injection/StandardInjectionTests.cs +++ b/tests/Contrast.K8s.AgentOperator.FunctionalTests/Scenarios/Injection/StandardInjectionTests.cs @@ -215,10 +215,10 @@ public async Task When_init_container_is_created_then_default_resource_limits_sh var resources = container.Resources; resources.Should().NotBeNull(); - resources.Limits.Should().ContainKey("cpu").WhoseValue.Value.Should().Be("100m"); - resources.Limits.Should().ContainKey("memory").WhoseValue.Value.Should().Be("256Mi"); - resources.Requests.Should().ContainKey("cpu").WhoseValue.Value.Should().Be("100m"); - resources.Requests.Should().ContainKey("memory").WhoseValue.Value.Should().Be("64Mi"); + resources.Limits.Should().ContainKey("cpu").WhoseValue.ToString().Should().Be("100m"); + resources.Limits.Should().ContainKey("memory").WhoseValue.ToString().Should().Be("256Mi"); + resources.Requests.Should().ContainKey("cpu").WhoseValue.ToString().Should().Be("100m"); + resources.Requests.Should().ContainKey("memory").WhoseValue.ToString().Should().Be("64Mi"); } } } diff --git a/tests/Contrast.K8s.AgentOperator.Tests/Contrast.K8s.AgentOperator.Tests.csproj b/tests/Contrast.K8s.AgentOperator.Tests/Contrast.K8s.AgentOperator.Tests.csproj index c215a5e3..978050b8 100644 --- a/tests/Contrast.K8s.AgentOperator.Tests/Contrast.K8s.AgentOperator.Tests.csproj +++ b/tests/Contrast.K8s.AgentOperator.Tests/Contrast.K8s.AgentOperator.Tests.csproj @@ -1,7 +1,7 @@  - net8.0 + net10.0 11.0 enable false diff --git a/tests/Contrast.K8s.AgentOperator.Tests/Core/Reactions/Injecting/Patching/Agents/DotNetAgentPatcherTests.cs b/tests/Contrast.K8s.AgentOperator.Tests/Core/Reactions/Injecting/Patching/Agents/DotNetAgentPatcherTests.cs index fce949c1..b9c27c18 100644 --- a/tests/Contrast.K8s.AgentOperator.Tests/Core/Reactions/Injecting/Patching/Agents/DotNetAgentPatcherTests.cs +++ b/tests/Contrast.K8s.AgentOperator.Tests/Core/Reactions/Injecting/Patching/Agents/DotNetAgentPatcherTests.cs @@ -75,7 +75,8 @@ public void PatchContainer_should_add_ld_preload_if_already_set() var context = AutoFixture.Create(); var existingPreload = AutoFixture.Create(); var container = AutoFixture.Build() - .With(x => x.Env, new List { new("LD_PRELOAD", existingPreload) }).Create(); + .Without(x => x.Resources) + .With(x => x.Env, new List { new() { Name = "LD_PRELOAD", Value = existingPreload }}).Create(); // Act patcher.PatchContainer(container, context); @@ -96,8 +97,9 @@ public void PatchContainer_should_not_do_anything_if_chaining_disabled() { var patcher = new DotNetAgentPatcher(new InjectorOptions(false, false)); var context = AutoFixture.Create(); - var container = AutoFixture.Build().With(x => x.Env, - new List { new("CONTRAST__AGENT__DOTNET__ENABLE_CHAINING", "false") }).Create(); + var container = AutoFixture.Build() + .Without(x => x.Resources) + .With(x => x.Env, new List { new() { Name = "CONTRAST__AGENT__DOTNET__ENABLE_CHAINING", Value = "false" }}).Create(); // Act patcher.PatchContainer(container, context); @@ -117,7 +119,8 @@ public void PatchContainer_should_not_do_anything_if_we_already_injected_early() var context = AutoFixture.Create(); var existingPreload = AutoFixture.Create() + "ContrastChainLoader.so"; var container = AutoFixture.Build() - .With(x => x.Env, new List { new("LD_PRELOAD", existingPreload) }).Create(); + .Without(x => x.Resources) + .With(x => x.Env, new List { new() { Name = "LD_PRELOAD", Value = existingPreload } }).Create(); // Act patcher.PatchContainer(container, context); diff --git a/tests/Contrast.K8s.AgentOperator.Tests/Core/Reactions/Injecting/Patching/Agents/JavaAgentPatcherTests.cs b/tests/Contrast.K8s.AgentOperator.Tests/Core/Reactions/Injecting/Patching/Agents/JavaAgentPatcherTests.cs index 592969e9..5edc2868 100644 --- a/tests/Contrast.K8s.AgentOperator.Tests/Core/Reactions/Injecting/Patching/Agents/JavaAgentPatcherTests.cs +++ b/tests/Contrast.K8s.AgentOperator.Tests/Core/Reactions/Injecting/Patching/Agents/JavaAgentPatcherTests.cs @@ -53,11 +53,11 @@ public void GenerateEnvVars_should_return_correct_env_vars() // Assert result.Should().BeEquivalentTo(new List { - new("JAVA_TOOL_OPTIONS", $"-javaagent:{contextFake.AgentMountPath}/contrast-agent.jar"), - new("CONTRAST__AGENT__CONTRAST_WORKING_DIR", contextFake.WritableMountPath), - new("CONTRAST__AGENT__LOGGER__PATH", $"{contextFake.WritableMountPath}/logs/contrast_agent.log"), - new("CONTRAST_INSTALLATION_TOOL", "KUBERNETES_OPERATOR"), - new("CONTRAST__ASSESS__CACHE__HIERARCHY_ENABLE", "false"), + new() { Name = "JAVA_TOOL_OPTIONS", Value = $"-javaagent:{contextFake.AgentMountPath}/contrast-agent.jar" }, + new() { Name = "CONTRAST__AGENT__CONTRAST_WORKING_DIR", Value = contextFake.WritableMountPath }, + new() { Name = "CONTRAST__AGENT__LOGGER__PATH", Value = $"{contextFake.WritableMountPath}/logs/contrast_agent.log" }, + new() { Name = "CONTRAST_INSTALLATION_TOOL", Value = "KUBERNETES_OPERATOR" }, + new() { Name = "CONTRAST__ASSESS__CACHE__HIERARCHY_ENABLE", Value = "false" }, }); } @@ -68,7 +68,8 @@ public void PatchContainer_should_not_do_anything_if_we_already_injected() var context = AutoFixture.Create(); var existingToolOptions = "-javaagent:/somepath/contrast-agent.jar"; var container = AutoFixture.Build() - .With(x => x.Env, new List { new("JAVA_TOOL_OPTIONS", existingToolOptions) }).Create(); + .Without(x => x.Resources) + .With(x => x.Env, new List { new() { Name = "JAVA_TOOL_OPTIONS", Value = existingToolOptions }}).Create(); // Act patcher.PatchContainer(container, context); @@ -88,7 +89,8 @@ public void PatchContainer_should_handle_existing_javaagent() var context = AutoFixture.Create(); var existingToolOptions = "-javaagent:/somepath/some-tool.jar -Dcontrast.dir=/tmp"; var container = AutoFixture.Build() - .With(x => x.Env, new List { new("JAVA_TOOL_OPTIONS", existingToolOptions) }).Create(); + .Without(x => x.Resources) + .With(x => x.Env, new List { new() { Name = "JAVA_TOOL_OPTIONS", Value = existingToolOptions } }).Create(); // Act patcher.PatchContainer(container, context); @@ -111,7 +113,8 @@ public void PatchContainer_should_handle_existing_correct_contrast_javaagent() var context = AutoFixture.Create(); var existingToolOptions = "-javaagent:/somepath/contrast-agent.jar -Dcontrast.dir=/tmp"; var container = AutoFixture.Build() - .With(x => x.Env, new List { new("JAVA_TOOL_OPTIONS", existingToolOptions) }).Create(); + .Without(x => x.Resources) + .With(x => x.Env, new List { new() { Name = "JAVA_TOOL_OPTIONS", Value = existingToolOptions } }).Create(); // Act patcher.PatchContainer(container, context); @@ -134,7 +137,8 @@ public void PatchContainer_should_handle_no_javaagent() var context = AutoFixture.Create(); var existingToolOptions = "-Dcontrast.dir=/tmp"; var container = AutoFixture.Build() - .With(x => x.Env, new List { new("JAVA_TOOL_OPTIONS", existingToolOptions) }).Create(); + .Without(x => x.Resources) + .With(x => x.Env, new List { new() { Name = "JAVA_TOOL_OPTIONS", Value = existingToolOptions } }).Create(); // Act patcher.PatchContainer(container, context); @@ -157,7 +161,8 @@ public void PatchContainer_should_not_patch_on_malformed_options() var context = AutoFixture.Create(); var existingToolOptions = "-Dcontrast.dir='/tmp"; var container = AutoFixture.Build() - .With(x => x.Env, new List { new("JAVA_TOOL_OPTIONS", existingToolOptions) }).Create(); + .Without(x => x.Resources) + .With(x => x.Env, new List { new() { Name = "JAVA_TOOL_OPTIONS", Value = existingToolOptions } }).Create(); // Act patcher.PatchContainer(container, context); diff --git a/tests/Contrast.K8s.AgentOperator.Tests/Core/Reactions/Injecting/Patching/Agents/PythonAgentPatcherTests.cs b/tests/Contrast.K8s.AgentOperator.Tests/Core/Reactions/Injecting/Patching/Agents/PythonAgentPatcherTests.cs index 711ba8be..7b0c22a1 100644 --- a/tests/Contrast.K8s.AgentOperator.Tests/Core/Reactions/Injecting/Patching/Agents/PythonAgentPatcherTests.cs +++ b/tests/Contrast.K8s.AgentOperator.Tests/Core/Reactions/Injecting/Patching/Agents/PythonAgentPatcherTests.cs @@ -67,7 +67,8 @@ public void PatchContainer_should_add_pythonpath_if_already_set() var context = AutoFixture.Create(); var existingPath = AutoFixture.Create(); var container = AutoFixture.Build() - .With(x => x.Env, new List { new("PYTHONPATH", existingPath) }).Create(); + .Without(x => x.Resources) + .With(x => x.Env, new List { new() { Name = "PYTHONPATH", Value = existingPath }}).Create(); // Act patcher.PatchContainer(container, context); @@ -90,7 +91,8 @@ public void PatchContainer_should_not_do_anything_if_we_already_injected() var context = AutoFixture.Create(); var existingPath = AutoFixture.Create() + "/contrast/loader"; var container = AutoFixture.Build() - .With(x => x.Env, new List { new("PYTHONPATH", existingPath) }).Create(); + .Without(x => x.Resources) + .With(x => x.Env, new List { new() { Name = "PYTHONPATH", Value = existingPath } }).Create(); // Act patcher.PatchContainer(container, context); @@ -110,7 +112,8 @@ public void PatchContainer_should_move_contrast_injection_to_the_start() var context = AutoFixture.Create(); var existingPath = $"bleh:{context.AgentMountPath}:{context.AgentMountPath}/contrast/loader:bleh2"; var container = AutoFixture.Build() - .With(x => x.Env, new List { new("PYTHONPATH", existingPath) }).Create(); + .Without(x => x.Resources) + .With(x => x.Env, new List { new() { Name = "PYTHONPATH", Value = existingPath } }).Create(); // Act patcher.PatchContainer(container, context); diff --git a/tests/Contrast.K8s.AgentOperator.Tests/Core/Tls/TlsHelperTests.cs b/tests/Contrast.K8s.AgentOperator.Tests/Core/Tls/TlsHelperTests.cs index 1e1a99e2..0e4816a6 100644 --- a/tests/Contrast.K8s.AgentOperator.Tests/Core/Tls/TlsHelperTests.cs +++ b/tests/Contrast.K8s.AgentOperator.Tests/Core/Tls/TlsHelperTests.cs @@ -26,7 +26,7 @@ public void Hashes_generated_should_be_stable() var result = TlsHelper.GenerateSansHash(fake.OrderBy(_ => Random.Shared.Next())); // Assert - HexMate.Convert.ToHexString(result).Should().Be("0C323EACC9F32E549476C7CC3E1ACE33A5DB39870BD19238921FB4CDCB44911D"); + Convert.ToHexString(result).Should().Be("0C323EACC9F32E549476C7CC3E1ACE33A5DB39870BD19238921FB4CDCB44911D"); } [Fact] @@ -43,7 +43,7 @@ public void Hashes_should_be_case_insensitive() var result = TlsHelper.GenerateSansHash(fake.OrderBy(_ => Random.Shared.Next())); // Assert - HexMate.Convert.ToHexString(result).Should().Be("0C323EACC9F32E549476C7CC3E1ACE33A5DB39870BD19238921FB4CDCB44911D"); + Convert.ToHexString(result).Should().Be("0C323EACC9F32E549476C7CC3E1ACE33A5DB39870BD19238921FB4CDCB44911D"); } [Fact] @@ -62,7 +62,7 @@ public void Hashes_should_ignore_duplicates() var result = TlsHelper.GenerateSansHash(fake.OrderBy(_ => Random.Shared.Next())); // Assert - HexMate.Convert.ToHexString(result).Should().Be("0C323EACC9F32E549476C7CC3E1ACE33A5DB39870BD19238921FB4CDCB44911D"); + Convert.ToHexString(result).Should().Be("0C323EACC9F32E549476C7CC3E1ACE33A5DB39870BD19238921FB4CDCB44911D"); } } } diff --git a/tests/Contrast.K8s.AgentOperator.Tests/Core/Tls/WebHookSecretParserTests.cs b/tests/Contrast.K8s.AgentOperator.Tests/Core/Tls/WebHookSecretParserTests.cs index b757504b..849cffe7 100644 --- a/tests/Contrast.K8s.AgentOperator.Tests/Core/Tls/WebHookSecretParserTests.cs +++ b/tests/Contrast.K8s.AgentOperator.Tests/Core/Tls/WebHookSecretParserTests.cs @@ -40,7 +40,7 @@ public void When_secret_is_valid_then_TryGetWebHookCertificateSecret_should_retu using var chainFake = FakeCertificates(); var secretFake = new V1Secret { - Metadata = new V1ObjectMeta(name: optionsFake.SecretName, namespaceProperty: optionsFake.SecretNamespace), + Metadata = new V1ObjectMeta { Name = optionsFake.SecretName, NamespaceProperty = optionsFake.SecretNamespace }, Data = new Dictionary { { optionsFake.ServerCertificateName, exportFake.ServerCertificatePfx }, diff --git a/tests/performance-tests/Contrast.K8s.AgentOperator.Performance.ClusterFaker/Contrast.K8s.AgentOperator.Performance.ClusterFaker.csproj b/tests/performance-tests/Contrast.K8s.AgentOperator.Performance.ClusterFaker/Contrast.K8s.AgentOperator.Performance.ClusterFaker.csproj index e6e35768..f30a47b8 100644 --- a/tests/performance-tests/Contrast.K8s.AgentOperator.Performance.ClusterFaker/Contrast.K8s.AgentOperator.Performance.ClusterFaker.csproj +++ b/tests/performance-tests/Contrast.K8s.AgentOperator.Performance.ClusterFaker/Contrast.K8s.AgentOperator.Performance.ClusterFaker.csproj @@ -2,7 +2,7 @@ Exe - net8.0 + net10.0 enable @@ -10,7 +10,7 @@ - +