diff --git a/manifests/examples/testing/scenarios/namespaced/injection-dotnetchaining.yaml b/manifests/examples/testing/scenarios/namespaced/chaining-dotnet.yaml similarity index 75% rename from manifests/examples/testing/scenarios/namespaced/injection-dotnetchaining.yaml rename to manifests/examples/testing/scenarios/namespaced/chaining-dotnet.yaml index 7996b71a..d9fb0a2e 100644 --- a/manifests/examples/testing/scenarios/namespaced/injection-dotnetchaining.yaml +++ b/manifests/examples/testing/scenarios/namespaced/chaining-dotnet.yaml @@ -1,14 +1,14 @@ apiVersion: agents.contrastsecurity.com/v1beta1 kind: AgentInjector metadata: - name: injection-dotnetchaining + name: chaining-dotnet spec: enabled: true type: dotnet-core selector: labels: - name: app - value: injection-dotnetchaining + value: chaining-dotnet image: pullPolicy: Never connection: @@ -19,20 +19,20 @@ spec: apiVersion: apps/v1 kind: Deployment metadata: - name: injection-dotnetchaining + name: chaining-dotnet labels: - app: injection-dotnetchaining + app: chaining-dotnet spec: replicas: 1 selector: matchLabels: - app: injection-dotnetchaining + app: chaining-dotnet strategy: type: Recreate template: metadata: labels: - app: injection-dotnetchaining + app: chaining-dotnet spec: containers: - image: k8s.gcr.io/pause:3.3 diff --git a/manifests/examples/testing/scenarios/namespaced/injection-flexchaining.yaml b/manifests/examples/testing/scenarios/namespaced/chaining-flex.yaml similarity index 75% rename from manifests/examples/testing/scenarios/namespaced/injection-flexchaining.yaml rename to manifests/examples/testing/scenarios/namespaced/chaining-flex.yaml index da1e9b80..be4b2f18 100644 --- a/manifests/examples/testing/scenarios/namespaced/injection-flexchaining.yaml +++ b/manifests/examples/testing/scenarios/namespaced/chaining-flex.yaml @@ -1,14 +1,14 @@ apiVersion: agents.contrastsecurity.com/v1beta1 kind: AgentInjector metadata: - name: injection-flexchaining + name: chaining-flex spec: enabled: true type: flex selector: labels: - name: app - value: injection-flexchaining + value: chaining-flex image: pullPolicy: Never connection: @@ -19,20 +19,20 @@ spec: apiVersion: apps/v1 kind: Deployment metadata: - name: injection-flexchaining + name: chaining-flex labels: - app: injection-flexchaining + app: chaining-flex spec: replicas: 1 selector: matchLabels: - app: injection-flexchaining + app: chaining-flex strategy: type: Recreate template: metadata: labels: - app: injection-flexchaining + app: chaining-flex spec: containers: - image: k8s.gcr.io/pause:3.3 diff --git a/manifests/examples/testing/scenarios/namespaced/injection-javatooloptions.yaml b/manifests/examples/testing/scenarios/namespaced/chaining-java.yaml similarity index 74% rename from manifests/examples/testing/scenarios/namespaced/injection-javatooloptions.yaml rename to manifests/examples/testing/scenarios/namespaced/chaining-java.yaml index 49553994..bb8bac4b 100644 --- a/manifests/examples/testing/scenarios/namespaced/injection-javatooloptions.yaml +++ b/manifests/examples/testing/scenarios/namespaced/chaining-java.yaml @@ -1,14 +1,14 @@ apiVersion: agents.contrastsecurity.com/v1beta1 kind: AgentInjector metadata: - name: injection-javatooloptions + name: chaining-java spec: enabled: true type: java selector: labels: - name: app - value: injection-javatooloptions + value: chaining-java image: pullPolicy: Never connection: @@ -19,20 +19,20 @@ spec: apiVersion: apps/v1 kind: Deployment metadata: - name: injection-javatooloptions + name: chaining-java labels: - app: injection-javatooloptions + app: chaining-java spec: replicas: 1 selector: matchLabels: - app: injection-javatooloptions + app: chaining-java strategy: type: Recreate template: metadata: labels: - app: injection-javatooloptions + app: chaining-java spec: containers: - image: k8s.gcr.io/pause:3.3 diff --git a/manifests/examples/testing/scenarios/namespaced/chaining-python.yaml b/manifests/examples/testing/scenarios/namespaced/chaining-python.yaml new file mode 100644 index 00000000..ccf0dd56 --- /dev/null +++ b/manifests/examples/testing/scenarios/namespaced/chaining-python.yaml @@ -0,0 +1,42 @@ +apiVersion: agents.contrastsecurity.com/v1beta1 +kind: AgentInjector +metadata: + name: chaining-python +spec: + enabled: true + type: python + selector: + labels: + - name: app + value: chaining-python + image: + pullPolicy: Never + connection: + name: testing-agent-connection + configuration: + name: testing-agent-configuration +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: chaining-python + labels: + app: chaining-python +spec: + replicas: 1 + selector: + matchLabels: + app: chaining-python + strategy: + type: Recreate + template: + metadata: + labels: + app: chaining-python + spec: + containers: + - image: k8s.gcr.io/pause:3.3 + name: pause + env: + - name: PYTHONPATH + value: something diff --git a/manifests/examples/testing/scenarios/namespaced/kustomization.yaml b/manifests/examples/testing/scenarios/namespaced/kustomization.yaml index 18923769..e672bc56 100644 --- a/manifests/examples/testing/scenarios/namespaced/kustomization.yaml +++ b/manifests/examples/testing/scenarios/namespaced/kustomization.yaml @@ -11,16 +11,13 @@ resources: - ./glob-selecting.yaml - ./init-container-overrides.yaml - ./injection-dotnet.yaml - - ./injection-dotnetchaining.yaml - ./injection-dummy.yaml - ./injection-java.yaml - - ./injection-javatooloptions.yaml - ./injection-nodejs-require.yaml - ./injection-nodejs-import.yaml - ./injection-php.yaml - ./injection-python.yaml - ./injection-flex.yaml - - ./injection-flexchaining.yaml - ./missing-deps.yaml - ./multiple-images.yaml - ./namespace.yaml @@ -28,3 +25,7 @@ resources: - ./type-deployment.yaml - ./type-statefulset.yaml - ./unmatched.yaml + - ./chaining-dotnet.yaml + - ./chaining-flex.yaml + - ./chaining-java.yaml + - ./chaining-python.yaml diff --git a/manifests/helm/values.yaml b/manifests/helm/values.yaml index eecb954b..436a7e8d 100644 --- a/manifests/helm/values.yaml +++ b/manifests/helm/values.yaml @@ -43,7 +43,7 @@ operator: # The name of the webhook to patch after certificate generation occurs. # Should not normally need to change. webhookConfiguration: contrast-web-hook-configuration - # Enable early chaining. Should normally be disabled unless DynaKube is used in classicStack mode. + # Enable early chaining. Should only be enabled if you are using a dotnet-core AgentInjector and DynaKube is used in classicStack mode. enableEarlyChaining: false # Metadata for the operator deployment. labels: {} diff --git a/src/Contrast.K8s.AgentOperator/Core/Chaining/DynaKubeHandler.cs b/src/Contrast.K8s.AgentOperator/Core/Chaining/DynaKubeHandler.cs index 6670415b..282a42e4 100644 --- a/src/Contrast.K8s.AgentOperator/Core/Chaining/DynaKubeHandler.cs +++ b/src/Contrast.K8s.AgentOperator/Core/Chaining/DynaKubeHandler.cs @@ -31,7 +31,7 @@ public Task Handle(EntityReconciled notification, CancellationT if (oneAgentSpec?.ClassicFullStack != null) { Logger.Warn("Dynatrace Operator is present and in classicFullStack mode. " - + "Please set the environment variable 'CONTRAST_ENABLE_EARLY_CHAINING=true' on the operator and restart the affected pods."); + + "If you are using a 'dotnet-core' AgentInjector, please set the environment variable 'CONTRAST_ENABLE_EARLY_CHAINING=true' on the agent-operator and restart the affected pods."); } } 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 d609b0f5..db70393b 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 @@ -3,7 +3,7 @@ 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 Contrast.K8s.AgentOperator.Options; using k8s.Models; @@ -24,8 +24,7 @@ public IEnumerable GenerateEnvVars(PatchingContext context) { if (_injectorOptions.EnableEarlyChaining) { - yield return new V1EnvVar("LD_PRELOAD", - $"{context.AgentMountPath}/runtimes/linux/native/ContrastChainLoader.so"); + yield return new V1EnvVar("LD_PRELOAD", GetAgentPreloadPath(context)); } else { @@ -49,26 +48,24 @@ public void PatchContainer(V1Container container, PatchingContext context) // Either the users sets this on the pod manually, or we set it from our config file. // We also assume the default is true. var chainingEnabled = !string.Equals( - GetFirstOrDefaultEnvVar(container.Env, "CONTRAST__AGENT__DOTNET__ENABLE_CHAINING")?.Value, + container.Env.FirstOrDefault("CONTRAST__AGENT__DOTNET__ENABLE_CHAINING")?.Value, "false", StringComparison.OrdinalIgnoreCase ); // Only modify this if CONTRAST_EXISTING_LD_PRELOAD isn't already set, or we are not already set from early chaining. This is to prevent infinite loops. if (chainingEnabled - && GetFirstOrDefaultEnvVar(container.Env, "LD_PRELOAD") is { Value: { } currentLdPreloadValue } + && container.Env.FirstOrDefault("LD_PRELOAD") is { Value: { } currentLdPreloadValue } && !string.IsNullOrWhiteSpace(currentLdPreloadValue) && !currentLdPreloadValue.Contains("ContrastChainLoader.so", StringComparison.OrdinalIgnoreCase) - && GetFirstOrDefaultEnvVar(container.Env, "CONTRAST_EXISTING_LD_PRELOAD") is null) + && 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", - $"{context.AgentMountPath}/runtimes/linux/native/ContrastChainLoader.so:{currentLdPreloadValue}")); + $"{GetAgentPreloadPath(context)}:{currentLdPreloadValue}")); } } - private static V1EnvVar? GetFirstOrDefaultEnvVar(IEnumerable collection, string name) - { - return collection.FirstOrDefault(x => string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase)); - } + private static string GetAgentPreloadPath(PatchingContext context) => $"{context.AgentMountPath}/runtimes/linux/native/ContrastChainLoader.so"; + } 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 339c97f5..51b266aa 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 @@ -3,7 +3,7 @@ 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 k8s.Models; @@ -15,7 +15,7 @@ public class FlexAgentPatcher : IAgentPatcher public IEnumerable GenerateEnvVars(PatchingContext context) { - yield return new V1EnvVar("LD_PRELOAD", $"{context.AgentMountPath}/injector/agent_injector.so"); + yield return new V1EnvVar("LD_PRELOAD", GetInjectorPreloadPath(context)); yield return new V1EnvVar("CONTRAST_INSTALLATION_TOOL", "KUBERNETES_OPERATOR"); yield return new V1EnvVar("CONTRAST_FLEX_AGENTS_PARENT_DIR", context.AgentMountPath); @@ -26,19 +26,17 @@ public IEnumerable GenerateEnvVars(PatchingContext context) public void PatchContainer(V1Container container, PatchingContext context) { // Only modify this if CONTRAST_EXISTING_LD_PRELOAD isn't already set. This is to prevent infinite loops. - if (GetFirstOrDefaultEnvVar(container.Env, "LD_PRELOAD") is { Value: { } currentLdPreloadValue } + if (container.Env.FirstOrDefault("LD_PRELOAD") is { Value: { } currentLdPreloadValue } && !string.IsNullOrWhiteSpace(currentLdPreloadValue) && !currentLdPreloadValue.Contains("agent_injector.so", StringComparison.OrdinalIgnoreCase) - && GetFirstOrDefaultEnvVar(container.Env, "CONTRAST_EXISTING_LD_PRELOAD") is null) + && 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", - $"{context.AgentMountPath}/injector/agent_injector.so:{currentLdPreloadValue}")); + $"{GetInjectorPreloadPath(context)}:{currentLdPreloadValue}")); } } - private static V1EnvVar? GetFirstOrDefaultEnvVar(IEnumerable collection, string name) - { - return collection.FirstOrDefault(x => string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase)); - } + private static string GetInjectorPreloadPath(PatchingContext context) => $"{context.AgentMountPath}/injector/agent_injector.so"; + } 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 3bc19b19..22271aab 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 @@ -30,10 +30,10 @@ public IEnumerable GenerateEnvVars(PatchingContext context) public void PatchContainer(V1Container container, PatchingContext context) { - if (GetFirstOrDefaultEnvVar(container.Env, "JAVA_TOOL_OPTIONS") is { Value: { } currentJavaToolOptions } + if (container.Env.FirstOrDefault("JAVA_TOOL_OPTIONS") is { Value: { } currentJavaToolOptions } && !string.IsNullOrWhiteSpace(currentJavaToolOptions) && !currentJavaToolOptions.EndsWith("contrast-agent.jar", StringComparison.OrdinalIgnoreCase) - && GetFirstOrDefaultEnvVar(container.Env, "CONTRAST_EXISTING_JAVA_TOOL_OPTIONS") is null) + && container.Env.FirstOrDefault("CONTRAST_EXISTING_JAVA_TOOL_OPTIONS") is null) { var contrastAgentArgument = GetContrastAgentArgument(context); @@ -65,11 +65,6 @@ public void PatchContainer(V1Container container, PatchingContext context) } } - private static V1EnvVar? GetFirstOrDefaultEnvVar(IEnumerable collection, string name) - { - return collection.FirstOrDefault(x => string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase)); - } - private static string GetContrastAgentArgument(PatchingContext context) => $"-javaagent:{context.AgentMountPath}/contrast-agent.jar"; public string GetOverrideAgentMountPath() => "/opt/contrast"; 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 6d85c3d4..f8771321 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 @@ -1,10 +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.Collections.Generic; +using Contrast.K8s.AgentOperator.Core.Reactions.Injecting.Patching.Utility; using Contrast.K8s.AgentOperator.Core.State.Resources.Primitives; using Contrast.K8s.AgentOperator.Options; using k8s.Models; +using System; +using System.Collections.Generic; namespace Contrast.K8s.AgentOperator.Core.Reactions.Injecting.Patching.Agents; @@ -20,7 +22,7 @@ public PythonAgentPatcher(InjectorOptions injectorOptions) public IEnumerable GenerateEnvVars(PatchingContext context) { - yield return new V1EnvVar("PYTHONPATH", $"{context.AgentMountPath}:{context.AgentMountPath}/contrast/loader"); + yield return new V1EnvVar("PYTHONPATH", GetAgentPythonPath(context)); if (_injectorOptions.EnablePythonRewriter) { yield return new V1EnvVar("CONTRAST__AGENT__PYTHON__REWRITE", "true"); @@ -29,4 +31,21 @@ public IEnumerable GenerateEnvVars(PatchingContext context) yield return new V1EnvVar("CONTRAST__AGENT__LOGGER__PATH", $"{context.WritableMountPath}/logs/contrast_agent.log"); yield return new V1EnvVar("CONTRAST_INSTALLATION_TOOL", "KUBERNETES_OPERATOR"); } + + public void PatchContainer(V1Container container, PatchingContext context) + { + // Only modify this if CONTRAST_EXISTING_PYTHONPATH isn't already set. This is to prevent infinite loops. + if (container.Env.FirstOrDefault("PYTHONPATH") is { Value: { } currentPath } + && !string.IsNullOrWhiteSpace(currentPath) + && !currentPath.Contains("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("PYTHONPATH", + $"{GetAgentPythonPath(context)}:{currentPath}")); + } + } + + private static string GetAgentPythonPath(PatchingContext context) => $"{context.AgentMountPath}:{context.AgentMountPath}/contrast/loader"; + } diff --git a/src/Contrast.K8s.AgentOperator/Core/Reactions/Injecting/Patching/Utility/PatchingExtensions.cs b/src/Contrast.K8s.AgentOperator/Core/Reactions/Injecting/Patching/Utility/PatchingExtensions.cs new file mode 100644 index 00000000..7315171d --- /dev/null +++ b/src/Contrast.K8s.AgentOperator/Core/Reactions/Injecting/Patching/Utility/PatchingExtensions.cs @@ -0,0 +1,17 @@ +// 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 System; +using System.Collections.Generic; +using System.Linq; + +namespace Contrast.K8s.AgentOperator.Core.Reactions.Injecting.Patching.Utility; + +public static class PatchingExtensions +{ + public static V1EnvVar? FirstOrDefault(this IEnumerable collection, string name) + { + return collection.FirstOrDefault(x => string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase)); + } +} diff --git a/tests/Contrast.K8s.AgentOperator.FunctionalTests/Scenarios/Injection/Agents/DotnetChainingInjectionTests.cs b/tests/Contrast.K8s.AgentOperator.FunctionalTests/Scenarios/Injection/Chaining/DotnetChainingInjectionTests.cs similarity index 94% rename from tests/Contrast.K8s.AgentOperator.FunctionalTests/Scenarios/Injection/Agents/DotnetChainingInjectionTests.cs rename to tests/Contrast.K8s.AgentOperator.FunctionalTests/Scenarios/Injection/Chaining/DotnetChainingInjectionTests.cs index ccb0e917..ba8f2fcf 100644 --- a/tests/Contrast.K8s.AgentOperator.FunctionalTests/Scenarios/Injection/Agents/DotnetChainingInjectionTests.cs +++ b/tests/Contrast.K8s.AgentOperator.FunctionalTests/Scenarios/Injection/Chaining/DotnetChainingInjectionTests.cs @@ -8,11 +8,11 @@ using Xunit; using Xunit.Abstractions; -namespace Contrast.K8s.AgentOperator.FunctionalTests.Scenarios.Injection.Agents; +namespace Contrast.K8s.AgentOperator.FunctionalTests.Scenarios.Injection.Chaining; public class DotnetChainingInjectionTests : IClassFixture { - private const string ScenarioName = "injection-dotnetchaining"; + private const string ScenarioName = "chaining-dotnet"; private readonly TestingContext _context; diff --git a/tests/Contrast.K8s.AgentOperator.FunctionalTests/Scenarios/Injection/Agents/FlexChainingInjectionTests.cs b/tests/Contrast.K8s.AgentOperator.FunctionalTests/Scenarios/Injection/Chaining/FlexChainingInjectionTests.cs similarity index 94% rename from tests/Contrast.K8s.AgentOperator.FunctionalTests/Scenarios/Injection/Agents/FlexChainingInjectionTests.cs rename to tests/Contrast.K8s.AgentOperator.FunctionalTests/Scenarios/Injection/Chaining/FlexChainingInjectionTests.cs index dc56fa53..10cd4121 100644 --- a/tests/Contrast.K8s.AgentOperator.FunctionalTests/Scenarios/Injection/Agents/FlexChainingInjectionTests.cs +++ b/tests/Contrast.K8s.AgentOperator.FunctionalTests/Scenarios/Injection/Chaining/FlexChainingInjectionTests.cs @@ -8,11 +8,11 @@ using Xunit; using Xunit.Abstractions; -namespace Contrast.K8s.AgentOperator.FunctionalTests.Scenarios.Injection.Agents; +namespace Contrast.K8s.AgentOperator.FunctionalTests.Scenarios.Injection.Chaining; public class FlexChainingInjectionTests : IClassFixture { - private const string ScenarioName = "injection-flexchaining"; + private const string ScenarioName = "chaining-flex"; private readonly TestingContext _context; diff --git a/tests/Contrast.K8s.AgentOperator.FunctionalTests/Scenarios/Injection/Agents/JavaToolOptionsInjectionTests.cs b/tests/Contrast.K8s.AgentOperator.FunctionalTests/Scenarios/Injection/Chaining/JavaChainingInjectionTests.cs similarity index 83% rename from tests/Contrast.K8s.AgentOperator.FunctionalTests/Scenarios/Injection/Agents/JavaToolOptionsInjectionTests.cs rename to tests/Contrast.K8s.AgentOperator.FunctionalTests/Scenarios/Injection/Chaining/JavaChainingInjectionTests.cs index c4f23e4c..466a2acb 100644 --- a/tests/Contrast.K8s.AgentOperator.FunctionalTests/Scenarios/Injection/Agents/JavaToolOptionsInjectionTests.cs +++ b/tests/Contrast.K8s.AgentOperator.FunctionalTests/Scenarios/Injection/Chaining/JavaChainingInjectionTests.cs @@ -8,15 +8,15 @@ using Xunit; using Xunit.Abstractions; -namespace Contrast.K8s.AgentOperator.FunctionalTests.Scenarios.Injection.Agents; +namespace Contrast.K8s.AgentOperator.FunctionalTests.Scenarios.Injection.Chaining; -public class JavaToolOptionsInjectionTests : IClassFixture +public class JavaChainingInjectionTests : IClassFixture { - private const string ScenarioName = "injection-javatooloptions"; + private const string ScenarioName = "chaining-java"; private readonly TestingContext _context; - public JavaToolOptionsInjectionTests(TestingContext context, ITestOutputHelper outputHelper) + public JavaChainingInjectionTests(TestingContext context, ITestOutputHelper outputHelper) { _context = context; _context.RegisterOutput(outputHelper); diff --git a/tests/Contrast.K8s.AgentOperator.FunctionalTests/Scenarios/Injection/Chaining/PythonChainingInjectionTests.cs b/tests/Contrast.K8s.AgentOperator.FunctionalTests/Scenarios/Injection/Chaining/PythonChainingInjectionTests.cs new file mode 100644 index 00000000..775a357c --- /dev/null +++ b/tests/Contrast.K8s.AgentOperator.FunctionalTests/Scenarios/Injection/Chaining/PythonChainingInjectionTests.cs @@ -0,0 +1,44 @@ +// 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.Threading.Tasks; +using Contrast.K8s.AgentOperator.FunctionalTests.Fixtures; +using FluentAssertions; +using FluentAssertions.Execution; +using Xunit; +using Xunit.Abstractions; + +namespace Contrast.K8s.AgentOperator.FunctionalTests.Scenarios.Injection.Chaining; + +public class PythonChainingInjectionTests : IClassFixture +{ + private const string ScenarioName = "chaining-python"; + + private readonly TestingContext _context; + + public PythonChainingInjectionTests(TestingContext context, ITestOutputHelper outputHelper) + { + _context = context; + _context.RegisterOutput(outputHelper); + } + + [Fact] + public async Task When_injected_then_pod_should_have_agent_injection_chaining_environment_variables() + { + var client = await _context.GetClient(); + + // Act + var result = await client.GetInjectedPodByPrefix(ScenarioName); + + // Assert + using (new AssertionScope()) + { + var container = result.Spec.Containers.Should().ContainSingle().Subject; + + container.Env.Should().Contain(x => x.Name == "PYTHONPATH") + .Which.Value.Should().Be("/contrast/agent:/contrast/agent/contrast/loader:something"); + container.Env.Should().Contain(x => x.Name == "CONTRAST_EXISTING_PYTHONPATH") + .Which.Value.Should().Be("something"); + } + } +} diff --git a/tests/Contrast.K8s.AgentOperator.Tests/Core/Reactions/Injecting/Patching/Agents/DotNetPatcherTests.cs b/tests/Contrast.K8s.AgentOperator.Tests/Core/Reactions/Injecting/Patching/Agents/DotNetAgentPatcherTests.cs similarity index 99% rename from tests/Contrast.K8s.AgentOperator.Tests/Core/Reactions/Injecting/Patching/Agents/DotNetPatcherTests.cs rename to tests/Contrast.K8s.AgentOperator.Tests/Core/Reactions/Injecting/Patching/Agents/DotNetAgentPatcherTests.cs index 00b5bb71..fce949c1 100644 --- a/tests/Contrast.K8s.AgentOperator.Tests/Core/Reactions/Injecting/Patching/Agents/DotNetPatcherTests.cs +++ b/tests/Contrast.K8s.AgentOperator.Tests/Core/Reactions/Injecting/Patching/Agents/DotNetAgentPatcherTests.cs @@ -14,7 +14,7 @@ namespace Contrast.K8s.AgentOperator.Tests.Core.Reactions.Injecting.Patching.Agents { - public class DotNetPatcherTests + public class DotNetAgentPatcherTests { private static readonly Fixture AutoFixture = new(); diff --git a/tests/Contrast.K8s.AgentOperator.Tests/Core/Reactions/Injecting/Patching/Agents/PythonPatcherTests.cs b/tests/Contrast.K8s.AgentOperator.Tests/Core/Reactions/Injecting/Patching/Agents/PythonAgentPatcherTests.cs similarity index 51% rename from tests/Contrast.K8s.AgentOperator.Tests/Core/Reactions/Injecting/Patching/Agents/PythonPatcherTests.cs rename to tests/Contrast.K8s.AgentOperator.Tests/Core/Reactions/Injecting/Patching/Agents/PythonAgentPatcherTests.cs index f05f47e7..cb26f679 100644 --- a/tests/Contrast.K8s.AgentOperator.Tests/Core/Reactions/Injecting/Patching/Agents/PythonPatcherTests.cs +++ b/tests/Contrast.K8s.AgentOperator.Tests/Core/Reactions/Injecting/Patching/Agents/PythonAgentPatcherTests.cs @@ -1,18 +1,20 @@ // 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.Collections.Generic; -using System.Linq; using AutoFixture; using Contrast.K8s.AgentOperator.Core.Reactions.Injecting.Patching; using Contrast.K8s.AgentOperator.Core.Reactions.Injecting.Patching.Agents; using Contrast.K8s.AgentOperator.Options; using FluentAssertions; +using FluentAssertions.Execution; +using k8s.Models; +using System.Collections.Generic; +using System.Linq; using Xunit; namespace Contrast.K8s.AgentOperator.Tests.Core.Reactions.Injecting.Patching.Agents { - public class PythonPatcherTests + public class PythonAgentPatcherTests { private static readonly Fixture AutoFixture = new(); @@ -57,5 +59,49 @@ public void GenerateEnvVars_should_return_rewrite_if_rewriting() // Assert result.Should().Equal(expectedEnvVars, (source, expected) => source.Name == expected); } + + [Fact] + public void PatchContainer_should_add_pythonpath_if_already_set() + { + var patcher = new PythonAgentPatcher(new InjectorOptions(false, false)); + var context = AutoFixture.Create(); + var existingPath = AutoFixture.Create(); + var container = AutoFixture.Build() + .With(x => x.Env, new List { new("PYTHONPATH", existingPath) }).Create(); + + // Act + patcher.PatchContainer(container, context); + + // Assert + using (new AssertionScope()) + { + container.Env.Should() + .Contain(x => x.Name == "CONTRAST_EXISTING_PYTHONPATH" && x.Value == existingPath); + container.Env.Should().Contain(x => + x.Name == "PYTHONPATH" && x.Value == + $"{context.AgentMountPath}:{context.AgentMountPath}/contrast/loader:{existingPath}"); + } + } + + [Fact] + public void PatchContainer_should_not_do_anything_if_we_already_injected() + { + var patcher = new PythonAgentPatcher(new InjectorOptions(true, false)); + var context = AutoFixture.Create(); + var existingPath = AutoFixture.Create() + "/contrast/loader"; + var container = AutoFixture.Build() + .With(x => x.Env, new List { new("PYTHONPATH", existingPath) }).Create(); + + // Act + patcher.PatchContainer(container, context); + + // Assert + using (new AssertionScope()) + { + container.Env.Should().NotContain(x => x.Name == "CONTRAST_EXISTING_PYTHONPATH"); + container.Env.Should().Contain(x => x.Name == "PYTHONPATH" && x.Value == existingPath); + } + } + } }