├── manifests ├── helm │ ├── README.md │ ├── crds │ │ └── .git-directory │ ├── .helmignore │ ├── build │ │ └── crds │ │ │ └── kustomization.yaml │ ├── templates │ │ ├── operator │ │ │ ├── namespace.yaml.tpl │ │ │ ├── rbac │ │ │ │ ├── service-account.yaml.tpl │ │ │ │ └── cluster-role-binding.yaml.tpl │ │ │ ├── disruption-budget.yaml.tpl │ │ │ ├── service.yaml.tpl │ │ │ └── webhook.yaml.tpl │ │ └── image-pull-secrets.yaml.tpl │ ├── .schema.yaml │ ├── Chart.yaml │ ├── LICENSE │ ├── values.testing.yaml │ └── build.ps1 ├── examples │ ├── dev │ │ ├── namespace.yaml │ │ ├── kustomization.yaml │ │ └── sample-apps │ │ │ ├── java.yaml │ │ │ ├── python.yaml │ │ │ ├── nodejs.yaml │ │ │ ├── dotnet-core.yaml │ │ │ ├── nodejs-legacy.yaml │ │ │ ├── cluster-dotnet-core.yaml │ │ │ ├── php.yaml │ │ │ ├── flex.yaml │ │ │ └── dotnet-core-chaining.yaml │ ├── testing │ │ ├── scenarios │ │ │ ├── namespaced │ │ │ │ ├── namespace.yaml │ │ │ │ ├── shared │ │ │ │ │ ├── testing-agent-configuration.yaml │ │ │ │ │ ├── testing-agent-connection-secret.yaml │ │ │ │ │ └── testing-agent-connection.yaml │ │ │ │ ├── unmatched.yaml │ │ │ │ ├── missing-deps.yaml │ │ │ │ ├── enabled-flag.yaml │ │ │ │ ├── type-daemonset.yaml │ │ │ │ ├── config-optional.yaml │ │ │ │ ├── injection-dummy.yaml │ │ │ │ ├── injection-php.yaml │ │ │ │ ├── injection-flex.yaml │ │ │ │ ├── injection-java.yaml │ │ │ │ ├── type-deployment.yaml │ │ │ │ ├── injection-python.yaml │ │ │ │ ├── injection-dotnet.yaml │ │ │ │ ├── custom-version.yaml │ │ │ │ ├── injection-nodejs-import.yaml │ │ │ │ ├── injection-nodejs-require.yaml │ │ │ │ ├── chaining-flex.yaml │ │ │ │ ├── chaining-java.yaml │ │ │ │ ├── chaining-python.yaml │ │ │ │ ├── chaining-dotnet.yaml │ │ │ │ ├── kustomization.yaml │ │ │ │ ├── glob-selecting.yaml │ │ │ │ ├── multiple-images.yaml │ │ │ │ ├── custom-images.yaml │ │ │ │ ├── type-statefulset.yaml │ │ │ │ ├── init-container-overrides.yaml │ │ │ │ └── connection-volume-mount.yaml │ │ │ ├── token │ │ │ │ ├── namespace.yaml │ │ │ │ ├── kustomization.yaml │ │ │ │ ├── operator-config.yaml │ │ │ │ ├── token-dummy.yaml │ │ │ │ └── oldauth-dummy.yaml │ │ │ ├── config-variables │ │ │ │ ├── namespace.yaml │ │ │ │ ├── shared │ │ │ │ │ ├── testing-agent-connection-secret.yaml │ │ │ │ │ ├── testing-agent-connection.yaml │ │ │ │ │ └── testing-agent-configuration.yaml │ │ │ │ ├── kustomization.yaml │ │ │ │ ├── injector.yaml │ │ │ │ └── yaml-variables.yaml │ │ │ ├── restricted-policy │ │ │ │ ├── namespace.yaml │ │ │ │ ├── shared │ │ │ │ │ ├── testing-agent-connection-secret.yaml │ │ │ │ │ ├── testing-agent-configuration.yaml │ │ │ │ │ └── testing-agent-connection.yaml │ │ │ │ ├── kustomization.yaml │ │ │ │ ├── injector.yaml │ │ │ │ └── injection-restricted.yaml │ │ │ └── cluster │ │ │ │ ├── kustomization.yaml │ │ │ │ ├── cluster-agent-connection-secret.yaml │ │ │ │ ├── cluster-image-pullsecret.yaml │ │ │ │ ├── injection-cluster.yaml │ │ │ │ ├── cluster-agent-configuration.yaml │ │ │ │ ├── cluster-agent-injector.yaml │ │ │ │ ├── injection-namespacelabel.yaml │ │ │ │ └── cluster-agent-connection.yaml │ │ └── kustomization.yaml │ ├── openshift │ │ ├── kustomization.yaml │ │ └── base │ │ │ ├── dotnet-core.yaml │ │ │ └── minimal-setup.yaml │ ├── argo-rollouts │ │ ├── kustomization.yaml │ │ └── base │ │ │ ├── dotnet-core.yaml │ │ │ └── minimal-setup.yaml │ └── README.md ├── .gitignore ├── .vscode │ ├── settings.json │ └── tasks.json ├── install │ ├── prod │ │ └── kustomization.yaml │ ├── all │ │ ├── kustomization.yaml │ │ ├── operator │ │ │ ├── base │ │ │ │ ├── namespace.yaml │ │ │ │ ├── rbac │ │ │ │ │ ├── service-account.yaml │ │ │ │ │ └── cluster-role-binding.yaml │ │ │ │ ├── disruption-budget.yaml │ │ │ │ ├── service.yaml │ │ │ │ └── webhook.yaml │ │ │ └── kustomization.yaml │ │ └── crds │ │ │ └── kustomization.yaml │ ├── dev │ │ ├── kustomization.yaml │ │ └── overlays │ │ │ └── deployment.yaml │ ├── testing │ │ ├── kustomization.yaml │ │ └── overlays │ │ │ └── deployment.yaml │ ├── testing-gh │ │ ├── kustomization.yaml │ │ └── overlays │ │ │ └── deployment.yaml │ ├── examples │ │ ├── classicfullstack-chaining │ │ │ ├── README.md │ │ │ └── kustomization.yaml │ │ └── custom-registry │ │ │ └── kustomization.yaml │ ├── README.md │ └── prod-quay │ │ └── kustomization.yaml ├── README.md ├── license-header.yaml └── generate-manifests.ps1 ├── .github ├── CODEOWNERS └── dependabot.yml ├── .gitattributes ├── src ├── Contrast.K8s.AgentOperator │ ├── appsettings.json │ ├── Options │ │ ├── ImageRepositoryOptions.cs │ │ ├── InjectorOptions.cs │ │ ├── MutatingWebHookOptions.cs │ │ ├── TelemetryOptions.cs │ │ ├── InitContainerOptions.cs │ │ ├── TlsCertificateOptions.cs │ │ ├── TlsStorageOptions.cs │ │ └── OperatorOptions.cs │ ├── Core │ │ ├── Events │ │ │ ├── StateSettled.cs │ │ │ ├── LeaderStateChanged.cs │ │ │ ├── TickChanged.cs │ │ │ ├── EntityDeleted.cs │ │ │ ├── EntityReconciled.cs │ │ │ ├── InjectorMatched.cs │ │ │ ├── EntityCreating.cs │ │ │ └── StateModified.cs │ │ ├── State │ │ │ ├── Resources │ │ │ │ ├── Interfaces │ │ │ │ │ ├── IMutableResource.cs │ │ │ │ │ ├── INamespacedResource.cs │ │ │ │ │ ├── IClusterResourceTemplate.cs │ │ │ │ │ ├── IClusterResource.cs │ │ │ │ │ └── IResourceWithPodTemplate.cs │ │ │ │ ├── Primitives │ │ │ │ │ ├── LabelPattern.cs │ │ │ │ │ ├── MetadataLabel.cs │ │ │ │ │ ├── MetadataAnnotations.cs │ │ │ │ │ ├── PodContainer.cs │ │ │ │ │ ├── InitContainerOverrides.cs │ │ │ │ │ ├── AgentConfigurationReference.cs │ │ │ │ │ ├── PodInjectionConvergenceCondition.cs │ │ │ │ │ ├── PodSelector.cs │ │ │ │ │ ├── SecretReference.cs │ │ │ │ │ ├── AgentInjectorConnectionReference.cs │ │ │ │ │ ├── LabelMatchOperation.cs │ │ │ │ │ ├── PodMatchExpression.cs │ │ │ │ │ ├── ContainerImageReference.cs │ │ │ │ │ ├── AgentInjectionType.cs │ │ │ │ │ ├── ResourceWithPodSpecSelector.cs │ │ │ │ │ ├── AgentInjectorTemplate.cs │ │ │ │ │ ├── PodTemplate.cs │ │ │ │ │ └── SecretKeyValue.cs │ │ │ │ ├── NamespaceResource.cs │ │ │ │ ├── SecretResource.cs │ │ │ │ ├── ClusterAgentInjectorResource.cs │ │ │ │ ├── AgentConnectionResource.cs │ │ │ │ ├── ClusterAgentConnectionResource.cs │ │ │ │ ├── DaemonSetResource.cs │ │ │ │ ├── DeploymentResource.cs │ │ │ │ ├── PodResource.cs │ │ │ │ ├── RolloutResource.cs │ │ │ │ ├── StatefulSetResource.cs │ │ │ │ ├── ClusterAgentConfigurationResource.cs │ │ │ │ ├── DeploymentConfigResource.cs │ │ │ │ ├── AgentConfigurationResource.cs │ │ │ │ └── AgentInjectorResource.cs │ │ │ ├── Storage │ │ │ │ └── ResourceHolder.cs │ │ │ ├── ResourceMetadata.cs │ │ │ ├── ResourceAnnotations.cs │ │ │ ├── NamespacedResourceIdentity.cs │ │ │ ├── StateSettledHandler.cs │ │ │ ├── TickChangedWorker.cs │ │ │ ├── Appliers │ │ │ │ ├── DaemonSetApplier.cs │ │ │ │ ├── DeploymentApplier.cs │ │ │ │ ├── StatefulSetApplier.cs │ │ │ │ ├── SecretApplier.cs │ │ │ │ ├── RolloutApplier.cs │ │ │ │ └── NamespaceApplier.cs │ │ │ └── StateSettledWorker.cs │ │ ├── Telemetry │ │ │ ├── Models │ │ │ │ ├── TelemetrySubmissionResult.cs │ │ │ │ ├── ExceptionReportWithOccurrences.cs │ │ │ │ ├── TelemetryMeasurement.cs │ │ │ │ └── ExceptionReport.cs │ │ │ ├── Cluster │ │ │ │ ├── ClusterId.cs │ │ │ │ └── ClusterIdState.cs │ │ │ ├── TelemetryState.cs │ │ │ ├── Getters │ │ │ │ ├── IsPublicTelemetryBuildGetter.cs │ │ │ │ └── MachineIdGetter.cs │ │ │ ├── Client │ │ │ │ ├── TelemetryClientFactory.cs │ │ │ │ ├── SubmitMeasurementPayload.cs │ │ │ │ └── ITelemetryClient.cs │ │ │ ├── Helpers │ │ │ │ └── Sha256Hasher.cs │ │ │ ├── Counters │ │ │ │ ├── PerformanceCountersWorker.cs │ │ │ │ └── PerformanceCounterContainer.cs │ │ │ ├── Services │ │ │ │ └── Exceptions │ │ │ │ │ └── TelemetryExceptionsBuffer.cs │ │ │ └── TelemetryOptOut.cs │ │ ├── OperatorVersion.cs │ │ ├── Reactions │ │ │ ├── Injecting │ │ │ │ ├── Patching │ │ │ │ │ ├── VariableReplacement.cs │ │ │ │ │ ├── Utility │ │ │ │ │ │ └── PatchingExtensions.cs │ │ │ │ │ ├── Agents │ │ │ │ │ │ ├── IAgentPatcher.cs │ │ │ │ │ │ ├── PhpAgentPatcher.cs │ │ │ │ │ │ ├── NodeJsAgentPatcher.cs │ │ │ │ │ │ ├── NodeJsLegacyAgentPatcher.cs │ │ │ │ │ │ └── NodeJsEsmAgentPatcher.cs │ │ │ │ │ └── PatchingContext.cs │ │ │ │ └── InjectionConstants.cs │ │ │ ├── Monitoring │ │ │ │ └── PodConditionConstants.cs │ │ │ ├── Defaults │ │ │ │ └── ClusterDefaultsConstants.cs │ │ │ ├── Matching │ │ │ │ ├── ObjectReferenceComparer.cs │ │ │ │ └── GlobMatcher.cs │ │ │ ├── Secrets │ │ │ │ └── VolumeSecrets.cs │ │ │ └── ReactionHelper.cs │ │ ├── Tls │ │ │ ├── TlsCertificateChain.cs │ │ │ └── TlsHelper.cs │ │ ├── Kube │ │ │ ├── VerbConstants.cs │ │ │ └── KubernetesJsonSerializer.cs │ │ ├── Comparing │ │ │ ├── ResourceComparer.cs │ │ │ └── FastComparerHelper.cs │ │ ├── HexConverter.cs │ │ └── ReadinessCheck.cs │ ├── Entities │ │ ├── Common │ │ │ └── ClusterNamespaceLabelSelectorSpec.cs │ │ ├── RegexConstants.cs │ │ ├── Argo │ │ │ └── V1Alpha1Rollout.cs │ │ ├── BuiltinEntityRbac.cs │ │ └── OpenShift │ │ │ └── V1DeploymentConfig.cs │ ├── Modules │ │ ├── KubeOpsModule.cs │ │ └── MediatorModule.cs │ └── Controllers │ │ └── PodMutationWebhook.cs └── get-info.sh ├── .vscode └── settings.json ├── docs ├── assets │ ├── run-task.png │ ├── bridge-task.png │ ├── data-flow.png │ ├── debug-service.png │ └── select-namespace.png ├── design.md └── public │ ├── 05-advanced-topics.md │ └── 01-introduction.md ├── .config └── dotnet-tools.json ├── .editorconfig ├── .dockerignore ├── LICENSE ├── CONTRIBUTING.md ├── tests ├── performance-tests │ └── Contrast.K8s.AgentOperator.Performance.ClusterFaker │ │ ├── Contrast.K8s.AgentOperator.Performance.ClusterFaker.csproj │ │ ├── Program.cs │ │ └── Options.cs ├── Contrast.K8s.AgentOperator.FunctionalTests │ └── Scenarios │ │ └── Injection │ │ ├── EnabledFlagTests.cs │ │ ├── MissingDependenciesTests.cs │ │ ├── ConfigOptionalTests.cs │ │ ├── CustomVersionTest.cs │ │ ├── Agents │ │ └── DummyInjectionTests.cs │ │ └── ClusterNamespaceLabelTests.cs └── Contrast.K8s.AgentOperator.Tests │ ├── Core │ └── Comparing │ │ └── FastComparerHelperTests.cs │ └── Entities │ └── Dynatrace │ └── V1Beta1DynaKubeTests.cs ├── security.md └── Contrast.K8s.AgentOperator.sln.DotSettings /manifests/helm/README.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /manifests/helm/crds/.git-directory: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /manifests/helm/.helmignore: -------------------------------------------------------------------------------- 1 | build.ps1 2 | dist/ 3 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @Contrast-Security-OSS/dotnet 2 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | *.rc -text 3 | *.sh text eol=lf 4 | -------------------------------------------------------------------------------- /src/Contrast.K8s.AgentOperator/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "AllowedHosts": "*" 3 | } 4 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cSpell.words": [ 3 | "Kubernetes" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /manifests/examples/dev/namespace.yaml: -------------------------------------------------------------------------------- 1 | kind: Namespace 2 | apiVersion: v1 3 | metadata: 4 | name: dev 5 | -------------------------------------------------------------------------------- /manifests/.gitignore: -------------------------------------------------------------------------------- 1 | /helm/crds/generated.yaml 2 | /helm/templates/generated.yaml 3 | /helm/dist/ 4 | /generated/ -------------------------------------------------------------------------------- /manifests/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cSpell.words": [ 3 | "contrastsecurity" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /manifests/install/prod/kustomization.yaml: -------------------------------------------------------------------------------- 1 | namespace: contrast-agent-operator 2 | resources: 3 | - ../all 4 | -------------------------------------------------------------------------------- /docs/assets/run-task.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Contrast-Security-OSS/agent-operator/master/docs/assets/run-task.png -------------------------------------------------------------------------------- /docs/assets/bridge-task.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Contrast-Security-OSS/agent-operator/master/docs/assets/bridge-task.png -------------------------------------------------------------------------------- /docs/assets/data-flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Contrast-Security-OSS/agent-operator/master/docs/assets/data-flow.png -------------------------------------------------------------------------------- /docs/design.md: -------------------------------------------------------------------------------- 1 | # Design 2 | 3 | Data flow is unidirectional when possible. 4 | 5 | ![Data Flow](./assets/data-flow.png) 6 | -------------------------------------------------------------------------------- /manifests/install/all/kustomization.yaml: -------------------------------------------------------------------------------- 1 | namespace: contrast-agent-operator 2 | resources: 3 | - crds 4 | - operator 5 | -------------------------------------------------------------------------------- /docs/assets/debug-service.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Contrast-Security-OSS/agent-operator/master/docs/assets/debug-service.png -------------------------------------------------------------------------------- /docs/assets/select-namespace.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Contrast-Security-OSS/agent-operator/master/docs/assets/select-namespace.png -------------------------------------------------------------------------------- /manifests/examples/testing/scenarios/namespaced/namespace.yaml: -------------------------------------------------------------------------------- 1 | kind: Namespace 2 | apiVersion: v1 3 | metadata: 4 | name: testing 5 | -------------------------------------------------------------------------------- /manifests/examples/testing/scenarios/token/namespace.yaml: -------------------------------------------------------------------------------- 1 | kind: Namespace 2 | apiVersion: v1 3 | metadata: 4 | name: testing-token 5 | -------------------------------------------------------------------------------- /manifests/examples/openshift/kustomization.yaml: -------------------------------------------------------------------------------- 1 | namespace: default 2 | resources: 3 | - base/dotnet-core.yaml 4 | - base/minimal-setup.yaml 5 | -------------------------------------------------------------------------------- /manifests/helm/build/crds/kustomization.yaml: -------------------------------------------------------------------------------- 1 | namespace: >- 2 | {{ .Values.namespace }} 3 | 4 | resources: 5 | - ../../../install/all/crds 6 | -------------------------------------------------------------------------------- /manifests/examples/argo-rollouts/kustomization.yaml: -------------------------------------------------------------------------------- 1 | namespace: default 2 | resources: 3 | - base/dotnet-core.yaml 4 | - base/minimal-setup.yaml 5 | -------------------------------------------------------------------------------- /manifests/examples/testing/scenarios/config-variables/namespace.yaml: -------------------------------------------------------------------------------- 1 | kind: Namespace 2 | apiVersion: v1 3 | metadata: 4 | name: testing-variables 5 | -------------------------------------------------------------------------------- /manifests/install/all/operator/base/namespace.yaml: -------------------------------------------------------------------------------- 1 | kind: Namespace 2 | apiVersion: v1 3 | metadata: 4 | name: contrast-agent-operator 5 | labels: 6 | app.kubernetes.io/part-of: contrast-agent-operator 7 | -------------------------------------------------------------------------------- /manifests/examples/testing/scenarios/token/kustomization.yaml: -------------------------------------------------------------------------------- 1 | namespace: testing-token 2 | resources: 3 | - ./operator-config.yaml 4 | - ./oldauth-dummy.yaml 5 | - ./token-dummy.yaml 6 | - ./namespace.yaml 7 | -------------------------------------------------------------------------------- /manifests/examples/testing/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - ./scenarios/cluster 3 | - ./scenarios/namespaced 4 | - ./scenarios/restricted-policy 5 | - ./scenarios/config-variables 6 | - ./scenarios/token 7 | -------------------------------------------------------------------------------- /manifests/install/dev/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - ../all 3 | 4 | images: 5 | - name: contrast/agent-operator 6 | newName: local/agent-operator 7 | newTag: latest 8 | 9 | patches: 10 | - path: overlays/deployment.yaml 11 | -------------------------------------------------------------------------------- /.config/dotnet-tools.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "isRoot": true, 4 | "tools": { 5 | "kubeops.cli": { 6 | "version": "9.5.0", 7 | "commands": [ 8 | "kubeops" 9 | ], 10 | "rollForward": false 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /manifests/examples/testing/scenarios/restricted-policy/namespace.yaml: -------------------------------------------------------------------------------- 1 | kind: Namespace 2 | apiVersion: v1 3 | metadata: 4 | name: testing-restricted 5 | labels: 6 | # Note that this is ignored in v1.21 and v1.22. 7 | pod-security.kubernetes.io/enforce: restricted 8 | -------------------------------------------------------------------------------- /manifests/examples/testing/scenarios/token/operator-config.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: agents.contrastsecurity.com/v1beta1 2 | kind: AgentConfiguration 3 | metadata: 4 | name: testing-agent-configuration 5 | spec: 6 | yaml: | 7 | enabled: false 8 | foo: 9 | bar: "foobar" 10 | -------------------------------------------------------------------------------- /manifests/install/testing/kustomization.yaml: -------------------------------------------------------------------------------- 1 | namespace: testing-agent-operator 2 | 3 | resources: 4 | - ../all 5 | 6 | images: 7 | - name: contrast/agent-operator 8 | newName: local/agent-operator 9 | newTag: latest 10 | 11 | patches: 12 | - path: overlays/deployment.yaml 13 | -------------------------------------------------------------------------------- /manifests/examples/testing/scenarios/config-variables/shared/testing-agent-connection-secret.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Secret 3 | metadata: 4 | name: testing-agent-connection-secret 5 | type: Opaque 6 | stringData: 7 | apiKey: apiKey 8 | serviceKey: serviceKey 9 | userName: userName 10 | -------------------------------------------------------------------------------- /manifests/examples/testing/scenarios/restricted-policy/shared/testing-agent-connection-secret.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Secret 3 | metadata: 4 | name: testing-agent-connection-secret 5 | type: Opaque 6 | stringData: 7 | apiKey: apiKey 8 | serviceKey: serviceKey 9 | userName: userName 10 | -------------------------------------------------------------------------------- /manifests/examples/testing/scenarios/namespaced/shared/testing-agent-configuration.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: agents.contrastsecurity.com/v1beta1 2 | kind: AgentConfiguration 3 | metadata: 4 | name: testing-agent-configuration 5 | spec: 6 | yaml: | 7 | enabled: false 8 | foo: 9 | bar: "foobar" 10 | -------------------------------------------------------------------------------- /manifests/examples/testing/scenarios/namespaced/shared/testing-agent-connection-secret.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Secret 3 | metadata: 4 | name: testing-agent-connection-secret 5 | type: Opaque 6 | stringData: 7 | token: token 8 | apiKey: apiKey 9 | serviceKey: serviceKey 10 | userName: userName 11 | -------------------------------------------------------------------------------- /manifests/examples/testing/scenarios/restricted-policy/shared/testing-agent-configuration.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: agents.contrastsecurity.com/v1beta1 2 | kind: AgentConfiguration 3 | metadata: 4 | name: testing-agent-configuration 5 | spec: 6 | yaml: | 7 | enabled: false 8 | foo: 9 | bar: "foobar" 10 | -------------------------------------------------------------------------------- /manifests/helm/templates/operator/namespace.yaml.tpl: -------------------------------------------------------------------------------- 1 | {{ if and (ne .Values.operator.enabled false) .Values.namespace }} 2 | kind: Namespace 3 | apiVersion: v1 4 | metadata: 5 | name: '{{ default .Release.Namespace .Values.namespace }}' 6 | labels: 7 | app.kubernetes.io/part-of: contrast-agent-operator 8 | {{ end }} 9 | -------------------------------------------------------------------------------- /manifests/install/all/operator/base/rbac/service-account.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | name: contrast-agent-operator-service-account 5 | namespace: contrast-agent-operator 6 | labels: 7 | app.kubernetes.io/name: operator 8 | app.kubernetes.io/part-of: contrast-agent-operator 9 | -------------------------------------------------------------------------------- /manifests/install/all/operator/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - base/deployment.yaml 3 | - base/disruption-budget.yaml 4 | - base/namespace.yaml 5 | - base/service.yaml 6 | - base/webhook.yaml 7 | - base/rbac/cluster-role-binding.yaml 8 | - base/rbac/cluster-role.yaml 9 | - base/rbac/service-account.yaml 10 | -------------------------------------------------------------------------------- /manifests/README.md: -------------------------------------------------------------------------------- 1 | # Manifests 2 | 3 | This directory contains manifests to install the operator into a cluster and examples on how to configure the operator. 4 | 5 | ## Development 6 | 7 | Install manifests for local development: 8 | 9 | ``` 10 | kubectl apply -k .\install\dev 11 | kubectl apply -k .\examples\dev 12 | ``` 13 | -------------------------------------------------------------------------------- /manifests/examples/testing/scenarios/cluster/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - cluster-agent-configuration.yaml 3 | - cluster-agent-connection-secret.yaml 4 | - cluster-agent-connection.yaml 5 | - cluster-agent-injector.yaml 6 | - cluster-image-pullsecret.yaml 7 | - injection-cluster.yaml 8 | - injection-namespacelabel.yaml 9 | -------------------------------------------------------------------------------- /src/Contrast.K8s.AgentOperator/Options/ImageRepositoryOptions.cs: -------------------------------------------------------------------------------- 1 | // Contrast Security, Inc licenses this file to you under the Apache 2.0 License. 2 | // See the LICENSE file in the project root for more information. 3 | 4 | namespace Contrast.K8s.AgentOperator.Options; 5 | 6 | public record ImageRepositoryOptions(string DefaultRegistry); 7 | -------------------------------------------------------------------------------- /manifests/examples/testing/scenarios/config-variables/kustomization.yaml: -------------------------------------------------------------------------------- 1 | namespace: testing-variables 2 | resources: 3 | - ./shared/testing-agent-configuration.yaml 4 | - ./shared/testing-agent-connection-secret.yaml 5 | - ./shared/testing-agent-connection.yaml 6 | - ./yaml-variables.yaml 7 | - ./injector.yaml 8 | - ./namespace.yaml 9 | -------------------------------------------------------------------------------- /src/Contrast.K8s.AgentOperator/Core/Events/StateSettled.cs: -------------------------------------------------------------------------------- 1 | // Contrast Security, Inc licenses this file to you under the Apache 2.0 License. 2 | // See the LICENSE file in the project root for more information. 3 | 4 | using MediatR; 5 | 6 | namespace Contrast.K8s.AgentOperator.Core.Events; 7 | 8 | public record StateSettled : INotification; 9 | -------------------------------------------------------------------------------- /manifests/examples/testing/scenarios/cluster/cluster-agent-connection-secret.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Secret 3 | metadata: 4 | name: cluster-agent-connection-secret 5 | namespace: testing-agent-operator 6 | type: Opaque 7 | stringData: 8 | token: token 9 | apiKey: apiKey 10 | serviceKey: serviceKey 11 | userName: userName 12 | -------------------------------------------------------------------------------- /manifests/examples/testing/scenarios/restricted-policy/kustomization.yaml: -------------------------------------------------------------------------------- 1 | namespace: testing-restricted 2 | resources: 3 | - ./shared/testing-agent-configuration.yaml 4 | - ./shared/testing-agent-connection-secret.yaml 5 | - ./shared/testing-agent-connection.yaml 6 | - ./injection-restricted.yaml 7 | - ./injector.yaml 8 | - ./namespace.yaml 9 | -------------------------------------------------------------------------------- /manifests/helm/.schema.yaml: -------------------------------------------------------------------------------- 1 | # .schema.yaml 2 | # yaml-language-server: $schema=https://github.com/losisin/helm-values-schema-json/raw/refs/heads/main/config.schema.json 3 | 4 | values: 5 | - values.schema.yaml 6 | 7 | output: values.schema.json 8 | useHelmDocs: true 9 | 10 | schemaRoot: 11 | title: Contrast Agent Operator Helm Schema 12 | -------------------------------------------------------------------------------- /src/Contrast.K8s.AgentOperator/Options/InjectorOptions.cs: -------------------------------------------------------------------------------- 1 | // Contrast Security, Inc licenses this file to you under the Apache 2.0 License. 2 | // See the LICENSE file in the project root for more information. 3 | 4 | namespace Contrast.K8s.AgentOperator.Options; 5 | 6 | public record InjectorOptions(bool EnableEarlyChaining, bool EnablePythonRewriter); 7 | -------------------------------------------------------------------------------- /manifests/examples/testing/scenarios/cluster/cluster-image-pullsecret.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Secret 3 | metadata: 4 | name: cluster-image-pullsecret 5 | namespace: testing-agent-operator 6 | type: kubernetes.io/dockerconfigjson 7 | data: 8 | .dockerconfigjson: eyJhdXRocyI6eyIiOnsidXNlcm5hbWUiOiIiLCJwYXNzd29yZCI6IiIsImVtYWlsIjoiIiwiYXV0aCI6IiJ9fX0= 9 | -------------------------------------------------------------------------------- /src/Contrast.K8s.AgentOperator/Core/State/Resources/Interfaces/IMutableResource.cs: -------------------------------------------------------------------------------- 1 | // Contrast Security, Inc licenses this file to you under the Apache 2.0 License. 2 | // See the LICENSE file in the project root for more information. 3 | 4 | namespace Contrast.K8s.AgentOperator.Core.State.Resources.Interfaces; 5 | 6 | public interface IMutableResource 7 | { 8 | } 9 | -------------------------------------------------------------------------------- /src/Contrast.K8s.AgentOperator/Core/Events/LeaderStateChanged.cs: -------------------------------------------------------------------------------- 1 | // Contrast Security, Inc licenses this file to you under the Apache 2.0 License. 2 | // See the LICENSE file in the project root for more information. 3 | 4 | using MediatR; 5 | 6 | namespace Contrast.K8s.AgentOperator.Core.Events; 7 | 8 | public record LeaderStateChanged(bool IsLeader) : INotification; 9 | -------------------------------------------------------------------------------- /src/Contrast.K8s.AgentOperator/Core/State/Resources/Interfaces/INamespacedResource.cs: -------------------------------------------------------------------------------- 1 | // Contrast Security, Inc licenses this file to you under the Apache 2.0 License. 2 | // See the LICENSE file in the project root for more information. 3 | 4 | namespace Contrast.K8s.AgentOperator.Core.State.Resources.Interfaces; 5 | 6 | public interface INamespacedResource 7 | { 8 | } 9 | -------------------------------------------------------------------------------- /src/Contrast.K8s.AgentOperator/Core/State/Resources/Primitives/LabelPattern.cs: -------------------------------------------------------------------------------- 1 | // Contrast Security, Inc licenses this file to you under the Apache 2.0 License. 2 | // See the LICENSE file in the project root for more information. 3 | 4 | namespace Contrast.K8s.AgentOperator.Core.State.Resources.Primitives; 5 | 6 | public record LabelPattern(string Key, string Value); 7 | -------------------------------------------------------------------------------- /src/Contrast.K8s.AgentOperator/Core/State/Resources/Primitives/MetadataLabel.cs: -------------------------------------------------------------------------------- 1 | // Contrast Security, Inc licenses this file to you under the Apache 2.0 License. 2 | // See the LICENSE file in the project root for more information. 3 | 4 | namespace Contrast.K8s.AgentOperator.Core.State.Resources.Primitives; 5 | 6 | public record MetadataLabel(string Name, string Value); 7 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "docker" 4 | directory: "/" 5 | schedule: 6 | interval: "daily" 7 | - package-ecosystem: "github-actions" 8 | directory: "/" 9 | schedule: 10 | interval: "daily" 11 | - package-ecosystem: "nuget" 12 | directory: "/" 13 | schedule: 14 | interval: "daily" 15 | -------------------------------------------------------------------------------- /manifests/install/all/operator/base/disruption-budget.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: policy/v1 2 | kind: PodDisruptionBudget 3 | metadata: 4 | name: contrast-agent-operator 5 | namespace: contrast-agent-operator 6 | spec: 7 | maxUnavailable: 1 8 | selector: 9 | matchLabels: 10 | app.kubernetes.io/name: operator 11 | app.kubernetes.io/part-of: contrast-agent-operator 12 | -------------------------------------------------------------------------------- /src/Contrast.K8s.AgentOperator/Core/State/Resources/Primitives/MetadataAnnotations.cs: -------------------------------------------------------------------------------- 1 | // Contrast Security, Inc licenses this file to you under the Apache 2.0 License. 2 | // See the LICENSE file in the project root for more information. 3 | 4 | namespace Contrast.K8s.AgentOperator.Core.State.Resources.Primitives; 5 | 6 | public record MetadataAnnotations(string Name, string Value); 7 | -------------------------------------------------------------------------------- /manifests/install/testing-gh/kustomization.yaml: -------------------------------------------------------------------------------- 1 | namespace: contrast-agent-operator 2 | 3 | resources: 4 | - ../all 5 | 6 | images: 7 | - name: contrast/agent-operator 8 | newName: ghcr.io/contrast-security-oss/agent-operator/operator 9 | digest: sha256:d251c8bfa6105df4bdc32a4a0f4b551f8827acfdfc0c53f8fcf0d1c2501eae59 10 | 11 | patches: 12 | - path: overlays/deployment.yaml 13 | -------------------------------------------------------------------------------- /src/Contrast.K8s.AgentOperator/Options/MutatingWebHookOptions.cs: -------------------------------------------------------------------------------- 1 | // Contrast Security, Inc licenses this file to you under the Apache 2.0 License. 2 | // See the LICENSE file in the project root for more information. 3 | 4 | namespace Contrast.K8s.AgentOperator.Options; 5 | 6 | public record MutatingWebHookOptions(string ConfigurationName, string WebHookName = "pods.agents.contrastsecurity.com"); 7 | -------------------------------------------------------------------------------- /src/Contrast.K8s.AgentOperator/Core/State/Resources/Primitives/PodContainer.cs: -------------------------------------------------------------------------------- 1 | // Contrast Security, Inc licenses this file to you under the Apache 2.0 License. 2 | // See the LICENSE file in the project root for more information. 3 | 4 | namespace Contrast.K8s.AgentOperator.Core.State.Resources.Primitives; 5 | 6 | public record PodContainer( 7 | string Name, 8 | string Image 9 | ); 10 | -------------------------------------------------------------------------------- /manifests/examples/testing/scenarios/namespaced/unmatched.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: agents.contrastsecurity.com/v1beta1 2 | kind: AgentInjector 3 | metadata: 4 | name: unmatched 5 | spec: 6 | enabled: true 7 | type: dummy 8 | image: 9 | pullPolicy: Never 10 | selector: 11 | labels: 12 | - name: app 13 | value: unmatched 14 | connection: 15 | name: testing-agent-connection 16 | -------------------------------------------------------------------------------- /manifests/helm/templates/operator/rbac/service-account.yaml.tpl: -------------------------------------------------------------------------------- 1 | {{ if ne .Values.operator.enabled false }} 2 | apiVersion: v1 3 | kind: ServiceAccount 4 | metadata: 5 | name: contrast-agent-operator-service-account 6 | namespace: '{{ default .Release.Namespace .Values.namespace }}' 7 | labels: 8 | app.kubernetes.io/name: operator 9 | app.kubernetes.io/part-of: contrast-agent-operator 10 | {{ end }} 11 | -------------------------------------------------------------------------------- /src/Contrast.K8s.AgentOperator/Core/Events/TickChanged.cs: -------------------------------------------------------------------------------- 1 | // Contrast Security, Inc licenses this file to you under the Apache 2.0 License. 2 | // See the LICENSE file in the project root for more information. 3 | 4 | using MediatR; 5 | 6 | namespace Contrast.K8s.AgentOperator.Core.Events; 7 | 8 | public record TickChanged : INotification 9 | { 10 | public static TickChanged Instance { get; } = new(); 11 | } 12 | -------------------------------------------------------------------------------- /src/Contrast.K8s.AgentOperator/Core/State/Resources/Primitives/InitContainerOverrides.cs: -------------------------------------------------------------------------------- 1 | // Contrast Security, Inc licenses this file to you under the Apache 2.0 License. 2 | // See the LICENSE file in the project root for more information. 3 | 4 | using k8s.Models; 5 | 6 | namespace Contrast.K8s.AgentOperator.Core.State.Resources.Primitives; 7 | 8 | public record InitContainerOverrides(V1SecurityContext? SecurityContext); 9 | -------------------------------------------------------------------------------- /src/Contrast.K8s.AgentOperator/Core/State/Resources/Primitives/AgentConfigurationReference.cs: -------------------------------------------------------------------------------- 1 | // Contrast Security, Inc licenses this file to you under the Apache 2.0 License. 2 | // See the LICENSE file in the project root for more information. 3 | 4 | namespace Contrast.K8s.AgentOperator.Core.State.Resources.Primitives; 5 | 6 | public record AgentConfigurationReference(string Namespace, string Name, bool IsNamespaceDefault); 7 | -------------------------------------------------------------------------------- /src/Contrast.K8s.AgentOperator/Core/State/Resources/Primitives/PodInjectionConvergenceCondition.cs: -------------------------------------------------------------------------------- 1 | // Contrast Security, Inc licenses this file to you under the Apache 2.0 License. 2 | // See the LICENSE file in the project root for more information. 3 | 4 | namespace Contrast.K8s.AgentOperator.Core.State.Resources.Primitives; 5 | 6 | public record PodInjectionConvergenceCondition(string Status, string Reason, string Message); 7 | -------------------------------------------------------------------------------- /src/Contrast.K8s.AgentOperator/Core/State/Resources/Primitives/PodSelector.cs: -------------------------------------------------------------------------------- 1 | // Contrast Security, Inc licenses this file to you under the Apache 2.0 License. 2 | // See the LICENSE file in the project root for more information. 3 | 4 | using System.Collections.Generic; 5 | 6 | namespace Contrast.K8s.AgentOperator.Core.State.Resources.Primitives; 7 | 8 | public record PodSelector(IReadOnlyList Expressions); 9 | -------------------------------------------------------------------------------- /src/Contrast.K8s.AgentOperator/Core/State/Resources/Primitives/SecretReference.cs: -------------------------------------------------------------------------------- 1 | // Contrast Security, Inc licenses this file to you under the Apache 2.0 License. 2 | // See the LICENSE file in the project root for more information. 3 | 4 | namespace Contrast.K8s.AgentOperator.Core.State.Resources.Primitives; 5 | 6 | public record SecretReference( 7 | string Namespace, 8 | string Name, 9 | string Key 10 | ); 11 | -------------------------------------------------------------------------------- /src/Contrast.K8s.AgentOperator/Core/Telemetry/Models/TelemetrySubmissionResult.cs: -------------------------------------------------------------------------------- 1 | // Contrast Security, Inc licenses this file to you under the Apache 2.0 License. 2 | // See the LICENSE file in the project root for more information. 3 | 4 | namespace Contrast.K8s.AgentOperator.Core.Telemetry.Models; 5 | 6 | public enum TelemetrySubmissionResult 7 | { 8 | Success, 9 | TransientError, 10 | PermanentError 11 | } 12 | -------------------------------------------------------------------------------- /manifests/install/examples/classicfullstack-chaining/README.md: -------------------------------------------------------------------------------- 1 | # Examples: Dynatrace Classic Full Stack Chaining 2 | 3 | The Agent Operator supports chaining the .NET Core Contrast Agent with Dynatrace's operator but requires extra configuration if using Dynatrace's operator in `classicFullStack` mode 4 | 5 | This is an example of how to set `CONTRAST_ENABLE_EARLY_CHAINING=true` on the operator to support chaining `classicFullStack` mode. 6 | -------------------------------------------------------------------------------- /src/Contrast.K8s.AgentOperator/Core/State/Resources/Primitives/AgentInjectorConnectionReference.cs: -------------------------------------------------------------------------------- 1 | // Contrast Security, Inc licenses this file to you under the Apache 2.0 License. 2 | // See the LICENSE file in the project root for more information. 3 | 4 | namespace Contrast.K8s.AgentOperator.Core.State.Resources.Primitives; 5 | 6 | public record AgentInjectorConnectionReference(string Namespace, string Name, bool IsNamespaceDefault); 7 | -------------------------------------------------------------------------------- /src/Contrast.K8s.AgentOperator/Core/OperatorVersion.cs: -------------------------------------------------------------------------------- 1 | // Contrast Security, Inc licenses this file to you under the Apache 2.0 License. 2 | // See the LICENSE file in the project root for more information. 3 | 4 | namespace Contrast.K8s.AgentOperator.Core; 5 | 6 | public static class OperatorVersion 7 | { 8 | public static string Version { get; } = typeof(OperatorVersion).Assembly.GetName().Version?.ToString() ?? "0.0.2"; 9 | } 10 | -------------------------------------------------------------------------------- /src/Contrast.K8s.AgentOperator/Core/Events/EntityDeleted.cs: -------------------------------------------------------------------------------- 1 | // Contrast Security, Inc licenses this file to you under the Apache 2.0 License. 2 | // See the LICENSE file in the project root for more information. 3 | 4 | using k8s; 5 | using k8s.Models; 6 | using MediatR; 7 | 8 | namespace Contrast.K8s.AgentOperator.Core.Events; 9 | 10 | public record EntityDeleted(T Entity) : INotification where T : IKubernetesObject; 11 | -------------------------------------------------------------------------------- /manifests/install/all/crds/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - base/agentconfigurations_agents_contrastsecurity_com.yaml 3 | - base/agentconnections_agents_contrastsecurity_com.yaml 4 | - base/agentinjectors_agents_contrastsecurity_com.yaml 5 | - base/clusteragentconfigurations_agents_contrastsecurity_com.yaml 6 | - base/clusteragentconnections_agents_contrastsecurity_com.yaml 7 | - base/clusteragentinjectors_agents_contrastsecurity_com.yaml 8 | -------------------------------------------------------------------------------- /src/Contrast.K8s.AgentOperator/Core/Events/EntityReconciled.cs: -------------------------------------------------------------------------------- 1 | // Contrast Security, Inc licenses this file to you under the Apache 2.0 License. 2 | // See the LICENSE file in the project root for more information. 3 | 4 | using k8s; 5 | using k8s.Models; 6 | using MediatR; 7 | 8 | namespace Contrast.K8s.AgentOperator.Core.Events; 9 | 10 | public record EntityReconciled(T Entity) : INotification where T : IKubernetesObject; 11 | -------------------------------------------------------------------------------- /src/Contrast.K8s.AgentOperator/Core/State/Resources/Interfaces/IClusterResourceTemplate.cs: -------------------------------------------------------------------------------- 1 | // Contrast Security, Inc licenses this file to you under the Apache 2.0 License. 2 | // See the LICENSE file in the project root for more information. 3 | 4 | namespace Contrast.K8s.AgentOperator.Core.State.Resources.Interfaces; 5 | 6 | public interface IClusterResourceTemplate : IClusterResource 7 | { 8 | public T Template { get; } 9 | } 10 | -------------------------------------------------------------------------------- /src/Contrast.K8s.AgentOperator/Options/TelemetryOptions.cs: -------------------------------------------------------------------------------- 1 | // Contrast Security, Inc licenses this file to you under the Apache 2.0 License. 2 | // See the LICENSE file in the project root for more information. 3 | 4 | namespace Contrast.K8s.AgentOperator.Options; 5 | 6 | public record TelemetryOptions(string ClusterIdSecretName, 7 | string ClusterIdSecretNamespace, 8 | string InstallSource); 9 | -------------------------------------------------------------------------------- /manifests/examples/dev/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - sample-apps/cluster-dotnet-core.yaml 3 | - sample-apps/dotnet-core-chaining.yaml 4 | - sample-apps/dotnet-core.yaml 5 | - sample-apps/java.yaml 6 | - sample-apps/nodejs-legacy.yaml 7 | - sample-apps/nodejs.yaml 8 | - sample-apps/php.yaml 9 | - sample-apps/python.yaml 10 | - sample-apps/flex.yaml 11 | - cluster-defaults.yaml 12 | - minimal-setup.yaml 13 | - namespace.yaml 14 | -------------------------------------------------------------------------------- /src/Contrast.K8s.AgentOperator/Core/State/Resources/Primitives/LabelMatchOperation.cs: -------------------------------------------------------------------------------- 1 | // Contrast Security, Inc licenses this file to you under the Apache 2.0 License. 2 | // See the LICENSE file in the project root for more information. 3 | 4 | namespace Contrast.K8s.AgentOperator.Core.State.Resources.Primitives; 5 | 6 | public enum LabelMatchOperation 7 | { 8 | Unknown = 0, 9 | In, 10 | NotIn, 11 | Exists, 12 | DoesNotExist 13 | } 14 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | trim_trailing_whitespace = true 6 | insert_final_newline = true 7 | file_header_template = Contrast Security, Inc licenses this file to you under the Apache 2.0 License.\nSee the LICENSE file in the project root for more information. 8 | 9 | [*.yaml] 10 | indent_style = space 11 | indent_size = 2 12 | 13 | [*.cs] 14 | # IDE0073: File header 15 | dotnet_diagnostic.IDE0073.severity = warning 16 | -------------------------------------------------------------------------------- /src/Contrast.K8s.AgentOperator/Core/State/Resources/Primitives/PodMatchExpression.cs: -------------------------------------------------------------------------------- 1 | // Contrast Security, Inc licenses this file to you under the Apache 2.0 License. 2 | // See the LICENSE file in the project root for more information. 3 | 4 | using System.Collections.Generic; 5 | 6 | namespace Contrast.K8s.AgentOperator.Core.State.Resources.Primitives; 7 | 8 | public record PodMatchExpression(string Key, LabelMatchOperation Operator, IReadOnlyList Values); 9 | -------------------------------------------------------------------------------- /manifests/helm/templates/operator/disruption-budget.yaml.tpl: -------------------------------------------------------------------------------- 1 | {{ if ne .Values.operator.enabled false }} 2 | apiVersion: policy/v1 3 | kind: PodDisruptionBudget 4 | metadata: 5 | name: contrast-agent-operator 6 | namespace: '{{ default .Release.Namespace .Values.namespace }}' 7 | spec: 8 | maxUnavailable: 1 9 | selector: 10 | matchLabels: 11 | app.kubernetes.io/name: operator 12 | app.kubernetes.io/part-of: contrast-agent-operator 13 | {{ end }} 14 | -------------------------------------------------------------------------------- /src/Contrast.K8s.AgentOperator/Core/State/Storage/ResourceHolder.cs: -------------------------------------------------------------------------------- 1 | // Contrast Security, Inc licenses this file to you under the Apache 2.0 License. 2 | // See the LICENSE file in the project root for more information. 3 | 4 | using Contrast.K8s.AgentOperator.Core.State.Resources.Interfaces; 5 | 6 | namespace Contrast.K8s.AgentOperator.Core.State.Storage; 7 | 8 | public record ResourceHolder(INamespacedResource? Resource, ResourceMetadata? Metadata, bool IsDirty = false); 9 | -------------------------------------------------------------------------------- /src/Contrast.K8s.AgentOperator/Core/Reactions/Injecting/Patching/VariableReplacement.cs: -------------------------------------------------------------------------------- 1 | // Contrast Security, Inc licenses this file to you under the Apache 2.0 License. 2 | // See the LICENSE file in the project root for more information. 3 | 4 | using k8s.Models; 5 | using System.Collections.Generic; 6 | 7 | namespace Contrast.K8s.AgentOperator.Core.Reactions.Injecting.Patching; 8 | 9 | public record VariableReplacement(string Value, IList AdditionalEnvVars); 10 | -------------------------------------------------------------------------------- /src/Contrast.K8s.AgentOperator/Core/Reactions/Monitoring/PodConditionConstants.cs: -------------------------------------------------------------------------------- 1 | // Contrast Security, Inc licenses this file to you under the Apache 2.0 License. 2 | // See the LICENSE file in the project root for more information. 3 | 4 | namespace Contrast.K8s.AgentOperator.Core.Reactions.Monitoring; 5 | 6 | public static class PodConditionConstants 7 | { 8 | public const string InjectionConvergenceConditionType = "agents.contrastsecurity.com/injection-converged"; 9 | } 10 | -------------------------------------------------------------------------------- /manifests/examples/testing/scenarios/config-variables/injector.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: agents.contrastsecurity.com/v1beta1 2 | kind: AgentInjector 3 | metadata: 4 | name: yaml-variables 5 | spec: 6 | enabled: true 7 | type: dotnet-core 8 | image: 9 | pullPolicy: Never 10 | selector: 11 | labels: 12 | - name: app 13 | value: yaml-variables 14 | connection: 15 | name: testing-agent-connection 16 | configuration: 17 | name: testing-agent-configuration 18 | -------------------------------------------------------------------------------- /src/Contrast.K8s.AgentOperator/Core/State/ResourceMetadata.cs: -------------------------------------------------------------------------------- 1 | // Contrast Security, Inc licenses this file to you under the Apache 2.0 License. 2 | // See the LICENSE file in the project root for more information. 3 | 4 | using Contrast.K8s.AgentOperator.Core.State.Resources.Primitives; 5 | using System.Collections.Generic; 6 | 7 | namespace Contrast.K8s.AgentOperator.Core.State; 8 | 9 | public record ResourceMetadata(string Uid, IReadOnlyCollection OperatorAnnotations); 10 | -------------------------------------------------------------------------------- /manifests/examples/testing/scenarios/restricted-policy/injector.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: agents.contrastsecurity.com/v1beta1 2 | kind: AgentInjector 3 | metadata: 4 | name: injection-restricted 5 | spec: 6 | enabled: true 7 | type: dotnet-core 8 | image: 9 | pullPolicy: Never 10 | selector: 11 | labels: 12 | - name: app 13 | value: injection-restricted 14 | connection: 15 | name: testing-agent-connection 16 | configuration: 17 | name: testing-agent-configuration 18 | -------------------------------------------------------------------------------- /manifests/install/all/operator/base/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: contrast-agent-operator 5 | namespace: contrast-agent-operator 6 | labels: 7 | app.kubernetes.io/name: operator 8 | app.kubernetes.io/part-of: contrast-agent-operator 9 | spec: 10 | ports: 11 | - name: https 12 | port: 443 13 | targetPort: https 14 | selector: 15 | app.kubernetes.io/name: operator 16 | app.kubernetes.io/part-of: contrast-agent-operator 17 | -------------------------------------------------------------------------------- /src/Contrast.K8s.AgentOperator/Options/InitContainerOptions.cs: -------------------------------------------------------------------------------- 1 | // Contrast Security, Inc licenses this file to you under the Apache 2.0 License. 2 | // See the LICENSE file in the project root for more information. 3 | 4 | namespace Contrast.K8s.AgentOperator.Options; 5 | 6 | public record InitContainerOptions( 7 | string CpuRequest, 8 | string CpuLimit, 9 | string MemoryRequest, 10 | string MemoryLimit, 11 | string EphemeralStorageRequest, 12 | string EphemeralStorageLimit); 13 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | **/Dockerfile 2 | **/Dockerfile-* 3 | **/docker-compose.yaml 4 | **/docker-compose.yml 5 | 6 | **/.git/ 7 | **/.vs/ 8 | **/.vscode/ 9 | **/.azure-pipelines/ 10 | **/bin/ 11 | **/obj/ 12 | **/vcpkg/ 13 | **/Debug/ 14 | **/Release/ 15 | **/packages/ 16 | **/node_modules/ 17 | **/dist/ 18 | **/x64/ 19 | **/Win32/ 20 | **/TestResults/ 21 | 22 | **/*.ncrunchsolution 23 | **/*.ncrunchproject 24 | **/*.DotSettings 25 | **/*.user 26 | 27 | **/.github/ 28 | **/.vscode 29 | **/manifests 30 | **/docs 31 | **/README.md 32 | -------------------------------------------------------------------------------- /src/Contrast.K8s.AgentOperator/Core/Telemetry/Cluster/ClusterId.cs: -------------------------------------------------------------------------------- 1 | // Contrast Security, Inc licenses this file to you under the Apache 2.0 License. 2 | // See the LICENSE file in the project root for more information. 3 | 4 | using System; 5 | 6 | namespace Contrast.K8s.AgentOperator.Core.Telemetry.Cluster; 7 | 8 | public record ClusterId(Guid Guid, DateTimeOffset CreatedOn) 9 | { 10 | public static ClusterId NewId() 11 | { 12 | return new ClusterId(Guid.NewGuid(), DateTimeOffset.Now); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /manifests/install/dev/overlays/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: contrast-agent-operator 5 | namespace: contrast-agent-operator 6 | spec: 7 | replicas: 1 8 | template: 9 | spec: 10 | containers: 11 | - name: contrast-agent-operator 12 | imagePullPolicy: Never 13 | env: 14 | - name: CONTRAST_AGENT_TELEMETRY_OPTOUT 15 | value: "true" 16 | - name: "CONTRAST_LOG_LEVEL" 17 | value: "TRACE" 18 | -------------------------------------------------------------------------------- /src/Contrast.K8s.AgentOperator/Core/State/Resources/Primitives/ContainerImageReference.cs: -------------------------------------------------------------------------------- 1 | // Contrast Security, Inc licenses this file to you under the Apache 2.0 License. 2 | // See the LICENSE file in the project root for more information. 3 | 4 | namespace Contrast.K8s.AgentOperator.Core.State.Resources.Primitives; 5 | 6 | public record ContainerImageReference(string Registry, string Name, string Tag) 7 | { 8 | public string GetFullyQualifiedContainerImageName() => $"{Registry}/{Name}:{Tag}".ToLowerInvariant(); 9 | } 10 | -------------------------------------------------------------------------------- /src/Contrast.K8s.AgentOperator/Core/State/Resources/Primitives/AgentInjectionType.cs: -------------------------------------------------------------------------------- 1 | // Contrast Security, Inc licenses this file to you under the Apache 2.0 License. 2 | // See the LICENSE file in the project root for more information. 3 | 4 | namespace Contrast.K8s.AgentOperator.Core.State.Resources.Primitives; 5 | 6 | public enum AgentInjectionType 7 | { 8 | DotNetCore, 9 | Java, 10 | NodeJs, 11 | NodeJsEsm, //Deprecated 12 | NodeJsLegacy, 13 | Php, 14 | Python, 15 | Flex, 16 | Dummy 17 | } 18 | -------------------------------------------------------------------------------- /manifests/helm/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v2 2 | name: contrast-agent-operator 3 | version: 0.0.1 4 | appVersion: 0.0.1 5 | kubeVersion: '>= 1.21.0-0' # This must include pre-releases due to a AWS bug. https://github.com/aws/containers-roadmap/issues/1404 6 | description: A K8s operator to inject agents into existing K8s workloads. 7 | icon: https://contrastsecurity.dev/helm-charts/contrast-icon.svg 8 | home: https://docs.contrastsecurity.com/en/agent-operator.html 9 | sources: 10 | - https://github.com/Contrast-Security-OSS/agent-operator 11 | -------------------------------------------------------------------------------- /src/Contrast.K8s.AgentOperator/Options/TlsCertificateOptions.cs: -------------------------------------------------------------------------------- 1 | // Contrast Security, Inc licenses this file to you under the Apache 2.0 License. 2 | // See the LICENSE file in the project root for more information. 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | 7 | namespace Contrast.K8s.AgentOperator.Options; 8 | 9 | public record TlsCertificateOptions(string NamePrefix, 10 | IReadOnlyCollection SanDnsNames, 11 | TimeSpan ExpiresAfter); 12 | -------------------------------------------------------------------------------- /manifests/examples/testing/scenarios/config-variables/shared/testing-agent-connection.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: agents.contrastsecurity.com/v1beta1 2 | kind: AgentConnection 3 | metadata: 4 | name: testing-agent-connection 5 | spec: 6 | url: http://localhost 7 | apiKey: 8 | secretName: testing-agent-connection-secret 9 | secretKey: apiKey 10 | serviceKey: 11 | secretName: testing-agent-connection-secret 12 | secretKey: serviceKey 13 | userName: 14 | secretName: testing-agent-connection-secret 15 | secretKey: userName 16 | -------------------------------------------------------------------------------- /manifests/examples/testing/scenarios/restricted-policy/shared/testing-agent-connection.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: agents.contrastsecurity.com/v1beta1 2 | kind: AgentConnection 3 | metadata: 4 | name: testing-agent-connection 5 | spec: 6 | url: http://localhost 7 | apiKey: 8 | secretName: testing-agent-connection-secret 9 | secretKey: apiKey 10 | serviceKey: 11 | secretName: testing-agent-connection-secret 12 | secretKey: serviceKey 13 | userName: 14 | secretName: testing-agent-connection-secret 15 | secretKey: userName 16 | -------------------------------------------------------------------------------- /manifests/examples/testing/scenarios/cluster/injection-cluster.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: injection-cluster 5 | namespace: testing 6 | labels: 7 | app: injection-cluster 8 | spec: 9 | replicas: 1 10 | selector: 11 | matchLabels: 12 | app: injection-cluster 13 | strategy: 14 | type: Recreate 15 | template: 16 | metadata: 17 | labels: 18 | app: injection-cluster 19 | spec: 20 | containers: 21 | - image: k8s.gcr.io/pause:3.3 22 | name: pause 23 | -------------------------------------------------------------------------------- /src/Contrast.K8s.AgentOperator/Core/State/Resources/Primitives/ResourceWithPodSpecSelector.cs: -------------------------------------------------------------------------------- 1 | // Contrast Security, Inc licenses this file to you under the Apache 2.0 License. 2 | // See the LICENSE file in the project root for more information. 3 | 4 | using System.Collections.Generic; 5 | 6 | namespace Contrast.K8s.AgentOperator.Core.State.Resources.Primitives; 7 | 8 | public record ResourceWithPodSpecSelector( 9 | IReadOnlyCollection ImagesPatterns, 10 | IReadOnlyCollection LabelPatterns, 11 | IReadOnlyCollection Namespaces); 12 | -------------------------------------------------------------------------------- /manifests/examples/testing/scenarios/cluster/cluster-agent-configuration.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: agents.contrastsecurity.com/v1beta1 2 | kind: ClusterAgentConfiguration 3 | metadata: 4 | name: cluster-agent-configuration 5 | namespace: testing-agent-operator 6 | spec: 7 | template: 8 | spec: 9 | yaml: | 10 | enabled: false 11 | foo: 12 | bar: "foobar" 13 | special: "%specialyaml" 14 | initContainer: 15 | securityContext: 16 | runAsUser: 499 17 | runAsNonRoot: false 18 | namespaces: 19 | - test* 20 | -------------------------------------------------------------------------------- /manifests/.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "bridge-to-kubernetes.resource", 6 | "type": "bridge-to-kubernetes.resource", 7 | "resource": "contrast-agent-operator", 8 | "resourceType": "service", 9 | "ports": [ 10 | 5001 11 | ], 12 | "targetCluster": "docker-desktop", 13 | "targetNamespace": "contrast-agent-operator", 14 | "useKubernetesServiceEnvironmentVariables": false 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /manifests/install/examples/classicfullstack-chaining/kustomization.yaml: -------------------------------------------------------------------------------- 1 | namespace: contrast-agent-operator 2 | resources: 3 | - ../../all 4 | 5 | patches: 6 | - patch: |- 7 | apiVersion: apps/v1 8 | kind: Deployment 9 | metadata: 10 | name: contrast-agent-operator 11 | namespace: contrast-agent-operator 12 | spec: 13 | template: 14 | spec: 15 | containers: 16 | - name: contrast-agent-operator 17 | env: 18 | - name: CONTRAST_ENABLE_EARLY_CHAINING 19 | value: "true" 20 | 21 | -------------------------------------------------------------------------------- /src/Contrast.K8s.AgentOperator/Core/State/Resources/Primitives/AgentInjectorTemplate.cs: -------------------------------------------------------------------------------- 1 | // Contrast Security, Inc licenses this file to you under the Apache 2.0 License. 2 | // See the LICENSE file in the project root for more information. 3 | 4 | namespace Contrast.K8s.AgentOperator.Core.State.Resources.Primitives; 5 | 6 | public record AgentInjectorTemplate( 7 | bool Enabled, 8 | AgentInjectionType Type, 9 | ContainerImageReference Image, 10 | ResourceWithPodSpecSelector Selector, 11 | SecretReference? ImagePullSecret, 12 | string ImagePullPolicy 13 | ); 14 | -------------------------------------------------------------------------------- /manifests/install/testing-gh/overlays/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: contrast-agent-operator 5 | namespace: contrast-agent-operator 6 | spec: 7 | template: 8 | spec: 9 | containers: 10 | - name: contrast-agent-operator 11 | env: 12 | - name: CONTRAST_AGENT_TELEMETRY_OPTOUT 13 | value: "true" 14 | - name: CONTRAST_RUN_INIT_CONTAINER_AS_NON_ROOT 15 | value: "true" 16 | - name: CONTRAST_SUPPRESS_SECCOMP_PROFILE 17 | value: "true" 18 | -------------------------------------------------------------------------------- /src/Contrast.K8s.AgentOperator/Core/State/Resources/Primitives/PodTemplate.cs: -------------------------------------------------------------------------------- 1 | // Contrast Security, Inc licenses this file to you under the Apache 2.0 License. 2 | // See the LICENSE file in the project root for more information. 3 | 4 | using System.Collections.Generic; 5 | 6 | namespace Contrast.K8s.AgentOperator.Core.State.Resources.Primitives; 7 | 8 | public record PodTemplate(IReadOnlyCollection Labels, 9 | IReadOnlyCollection Annotations, 10 | IReadOnlyCollection Containers); 11 | -------------------------------------------------------------------------------- /manifests/helm/templates/operator/service.yaml.tpl: -------------------------------------------------------------------------------- 1 | {{ if ne .Values.operator.enabled false }} 2 | apiVersion: v1 3 | kind: Service 4 | metadata: 5 | name: contrast-agent-operator 6 | namespace: '{{ default .Release.Namespace .Values.namespace }}' 7 | labels: 8 | app.kubernetes.io/name: operator 9 | app.kubernetes.io/part-of: contrast-agent-operator 10 | spec: 11 | ports: 12 | - name: https 13 | port: 443 14 | targetPort: https 15 | selector: 16 | app.kubernetes.io/name: operator 17 | app.kubernetes.io/part-of: contrast-agent-operator 18 | {{ end }} 19 | -------------------------------------------------------------------------------- /manifests/install/all/operator/base/rbac/cluster-role-binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | name: contrast-agent-operator-service-role-binding 5 | labels: 6 | app.kubernetes.io/name: operator 7 | app.kubernetes.io/part-of: contrast-agent-operator 8 | roleRef: 9 | apiGroup: rbac.authorization.k8s.io 10 | kind: ClusterRole 11 | name: contrast-agent-operator-service-role 12 | subjects: 13 | - kind: ServiceAccount 14 | name: contrast-agent-operator-service-account 15 | namespace: contrast-agent-operator 16 | -------------------------------------------------------------------------------- /src/Contrast.K8s.AgentOperator/Core/State/Resources/NamespaceResource.cs: -------------------------------------------------------------------------------- 1 | // Contrast Security, Inc licenses this file to you under the Apache 2.0 License. 2 | // See the LICENSE file in the project root for more information. 3 | 4 | using System.Collections.Generic; 5 | using Contrast.K8s.AgentOperator.Core.State.Resources.Interfaces; 6 | using Contrast.K8s.AgentOperator.Core.State.Resources.Primitives; 7 | 8 | namespace Contrast.K8s.AgentOperator.Core.State.Resources; 9 | 10 | public record NamespaceResource(string Uid, IReadOnlyCollection Labels) : INamespacedResource; 11 | -------------------------------------------------------------------------------- /src/Contrast.K8s.AgentOperator/Core/State/Resources/SecretResource.cs: -------------------------------------------------------------------------------- 1 | // Contrast Security, Inc licenses this file to you under the Apache 2.0 License. 2 | // See the LICENSE file in the project root for more information. 3 | 4 | using System.Collections.Generic; 5 | using Contrast.K8s.AgentOperator.Core.State.Resources.Interfaces; 6 | using Contrast.K8s.AgentOperator.Core.State.Resources.Primitives; 7 | 8 | namespace Contrast.K8s.AgentOperator.Core.State.Resources; 9 | 10 | public record SecretResource(IReadOnlyCollection KeyPairs) : INamespacedResource, IMutableResource; 11 | -------------------------------------------------------------------------------- /src/Contrast.K8s.AgentOperator/Core/Telemetry/TelemetryState.cs: -------------------------------------------------------------------------------- 1 | // Contrast Security, Inc licenses this file to you under the Apache 2.0 License. 2 | // See the LICENSE file in the project root for more information. 3 | 4 | using System; 5 | 6 | namespace Contrast.K8s.AgentOperator.Core.Telemetry; 7 | 8 | public class TelemetryState 9 | { 10 | public string OperatorVersion { get; } 11 | 12 | public DateTimeOffset StartupTime { get; } = DateTimeOffset.Now; 13 | 14 | public TelemetryState(string operatorVersion) 15 | { 16 | OperatorVersion = operatorVersion; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /manifests/examples/testing/scenarios/config-variables/shared/testing-agent-configuration.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: agents.contrastsecurity.com/v1beta1 2 | kind: AgentConfiguration 3 | metadata: 4 | name: testing-agent-configuration 5 | spec: 6 | yaml: | 7 | enabled: false 8 | foo: 9 | bar: "foobar" 10 | test: 11 | namespace: "%namespace%" 12 | label: "%labels.test-label%" 13 | annotation: "%annotations.test-annotation%" 14 | container_image: "%container.dotnet-test.image%" 15 | multiple: "%namespace%_%namespace%" 16 | enableYamlVariableReplacement: true 17 | 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2024 Contrast Security, Inc. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /manifests/install/README.md: -------------------------------------------------------------------------------- 1 | # install 2 | 3 | This directory contains the installation manifests for the agent operator workload. 4 | 5 | - [`./all`](./all) - Contains the generic variant that the other variants use as their base. 6 | - [`./dev`](./dev) - Contains a variant used for local development. 7 | - [`./testing`](./testing) - Contains a variant used for testing in CI (functional testing). 8 | - [`./examples`](./testing) - Contains example variants. 9 | - [`./prod`](./prod) - Contains the variant used for production releases. 10 | - [`./prod-quay`](./prod-quay) - Contains the prod variant, but using quay.io. 11 | -------------------------------------------------------------------------------- /manifests/examples/testing/scenarios/namespaced/shared/testing-agent-connection.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: agents.contrastsecurity.com/v1beta1 2 | kind: AgentConnection 3 | metadata: 4 | name: testing-agent-connection 5 | spec: 6 | token: 7 | secretName: testing-agent-connection-secret 8 | secretKey: token 9 | url: http://localhost 10 | apiKey: 11 | secretName: testing-agent-connection-secret 12 | secretKey: apiKey 13 | serviceKey: 14 | secretName: testing-agent-connection-secret 15 | secretKey: serviceKey 16 | userName: 17 | secretName: testing-agent-connection-secret 18 | secretKey: userName 19 | -------------------------------------------------------------------------------- /manifests/helm/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2024 Contrast Security, Inc. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /src/Contrast.K8s.AgentOperator/Core/Reactions/Defaults/ClusterDefaultsConstants.cs: -------------------------------------------------------------------------------- 1 | // Contrast Security, Inc licenses this file to you under the Apache 2.0 License. 2 | // See the LICENSE file in the project root for more information. 3 | 4 | namespace Contrast.K8s.AgentOperator.Core.Reactions.Defaults; 5 | 6 | public static class ClusterDefaultsConstants 7 | { 8 | public const string DefaultTokenSecretKey = "token"; 9 | public const string DefaultUsernameSecretKey = "username"; 10 | public const string DefaultApiKeySecretKey = "api-key"; 11 | public const string DefaultServiceKeySecretKey = "service-key"; 12 | } 13 | -------------------------------------------------------------------------------- /src/Contrast.K8s.AgentOperator/Core/Telemetry/Models/ExceptionReportWithOccurrences.cs: -------------------------------------------------------------------------------- 1 | // Contrast Security, Inc licenses this file to you under the Apache 2.0 License. 2 | // See the LICENSE file in the project root for more information. 3 | 4 | using System.Threading; 5 | 6 | namespace Contrast.K8s.AgentOperator.Core.Telemetry.Models; 7 | 8 | public record ExceptionReportWithOccurrences(ExceptionReport Report) 9 | { 10 | private int _occurrences; 11 | 12 | public int Occurrences => _occurrences; 13 | 14 | public void IncrementOccurrences() 15 | { 16 | Interlocked.Increment(ref _occurrences); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/Contrast.K8s.AgentOperator/Core/State/Resources/Interfaces/IClusterResource.cs: -------------------------------------------------------------------------------- 1 | // Contrast Security, Inc licenses this file to you under the Apache 2.0 License. 2 | // See the LICENSE file in the project root for more information. 3 | 4 | using Contrast.K8s.AgentOperator.Core.State.Resources.Primitives; 5 | using System.Collections.Generic; 6 | 7 | namespace Contrast.K8s.AgentOperator.Core.State.Resources.Interfaces; 8 | 9 | public interface IClusterResource : INamespacedResource 10 | { 11 | public IReadOnlyCollection NamespacePatterns { get; } 12 | public IReadOnlyCollection NamespaceLabelPatterns { get; } 13 | } 14 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Hi there! We welcome any contribution to the Contrast Agent Operator, be it raising issues, adding improvements, or just asking questions. 4 | 5 | Have feedback or having problems, open a [GitHub issue](https://github.com/Contrast-Security-OSS/agent-operator/issues). If your issue contains sensitive information and cannot be public, contact us through your standard [support channel](https://support.contrastsecurity.com/hc/en-us). 6 | 7 | A developer and wanting to code? See the [./docs/development.md](./docs/development.md) and [./docs/design.md](./docs/design.md) on getting started. 8 | 9 | Have fun out there! 10 | -------------------------------------------------------------------------------- /manifests/license-header.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2024 Contrast Security, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | -------------------------------------------------------------------------------- /src/Contrast.K8s.AgentOperator/Core/Events/InjectorMatched.cs: -------------------------------------------------------------------------------- 1 | // Contrast Security, Inc licenses this file to you under the Apache 2.0 License. 2 | // See the LICENSE file in the project root for more information. 3 | 4 | using Contrast.K8s.AgentOperator.Core.State; 5 | using Contrast.K8s.AgentOperator.Core.State.Resources; 6 | using Contrast.K8s.AgentOperator.Core.State.Resources.Interfaces; 7 | using MediatR; 8 | 9 | namespace Contrast.K8s.AgentOperator.Core.Events; 10 | 11 | public record InjectorMatched(ResourceIdentityPair Target, 12 | ResourceIdentityPair? Injector) : INotification; 13 | -------------------------------------------------------------------------------- /manifests/examples/openshift/base/dotnet-core.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps.openshift.io/v1 2 | kind: DeploymentConfig 3 | metadata: 4 | name: dotnet-core-app 5 | namespace: default 6 | labels: 7 | app: dotnet-core-app 8 | spec: 9 | replicas: 2 10 | revisionHistoryLimit: 1 11 | selector: 12 | app: dotnet-core-app 13 | template: 14 | metadata: 15 | labels: 16 | app: dotnet-core-app 17 | annotations: 18 | test: test 19 | spec: 20 | containers: 21 | - image: contrast/sample-app-aspnetcore:latest 22 | name: dotnet-core-app 23 | ports: 24 | - containerPort: 80 25 | name: http 26 | -------------------------------------------------------------------------------- /manifests/examples/testing/scenarios/cluster/cluster-agent-injector.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: agents.contrastsecurity.com/v1beta1 2 | kind: ClusterAgentInjector 3 | metadata: 4 | name: cluster-agent-injector-dummy 5 | namespace: testing-agent-operator 6 | spec: 7 | namespaces: 8 | - testing 9 | namespaceLabelSelector: 10 | - name: testing-label 11 | value: dummy-value 12 | template: 13 | spec: 14 | enabled: true 15 | type: dummy 16 | image: 17 | pullSecretName: cluster-image-pullsecret 18 | selector: 19 | images: 20 | - "*" 21 | labels: 22 | - name: app 23 | value: injection-cluster 24 | -------------------------------------------------------------------------------- /manifests/helm/templates/operator/rbac/cluster-role-binding.yaml.tpl: -------------------------------------------------------------------------------- 1 | {{ if ne .Values.operator.enabled false }} 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRoleBinding 4 | metadata: 5 | name: contrast-agent-operator-service-role-binding 6 | labels: 7 | app.kubernetes.io/name: operator 8 | app.kubernetes.io/part-of: contrast-agent-operator 9 | roleRef: 10 | apiGroup: rbac.authorization.k8s.io 11 | kind: ClusterRole 12 | name: contrast-agent-operator-service-role 13 | subjects: 14 | - kind: ServiceAccount 15 | name: contrast-agent-operator-service-account 16 | namespace: '{{ default .Release.Namespace .Values.namespace }}' 17 | {{ end }} 18 | -------------------------------------------------------------------------------- /manifests/install/prod-quay/kustomization.yaml: -------------------------------------------------------------------------------- 1 | namespace: contrast-agent-operator 2 | resources: 3 | - ../all 4 | 5 | images: 6 | - name: contrast/agent-operator 7 | newName: quay.io/contrast/agent-operator 8 | newTag: latest 9 | 10 | patches: 11 | - patch: |- 12 | apiVersion: apps/v1 13 | kind: Deployment 14 | metadata: 15 | name: contrast-agent-operator 16 | namespace: contrast-agent-operator 17 | spec: 18 | template: 19 | spec: 20 | containers: 21 | - name: contrast-agent-operator 22 | env: 23 | - name: CONTRAST_DEFAULT_REGISTRY 24 | value: "quay.io/contrast" 25 | -------------------------------------------------------------------------------- /manifests/install/testing/overlays/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: contrast-agent-operator 5 | namespace: contrast-agent-operator 6 | spec: 7 | template: 8 | spec: 9 | containers: 10 | - name: contrast-agent-operator 11 | imagePullPolicy: Never 12 | env: 13 | - name: CONTRAST_AGENT_TELEMETRY_OPTOUT 14 | value: "true" 15 | - name: CONTRAST_RUN_INIT_CONTAINER_AS_NON_ROOT 16 | value: "true" 17 | - name: "CONTRAST_LOG_LEVEL" 18 | value: "TRACE" 19 | - name: "CONTRAST_ENABLE_AGENT_STDOUT" 20 | value: "true" 21 | -------------------------------------------------------------------------------- /src/Contrast.K8s.AgentOperator/Core/State/Resources/Interfaces/IResourceWithPodTemplate.cs: -------------------------------------------------------------------------------- 1 | // Contrast Security, Inc licenses this file to you under the Apache 2.0 License. 2 | // See the LICENSE file in the project root for more information. 3 | 4 | using System.Collections.Generic; 5 | using Contrast.K8s.AgentOperator.Core.State.Resources.Primitives; 6 | 7 | namespace Contrast.K8s.AgentOperator.Core.State.Resources.Interfaces; 8 | 9 | public interface IResourceWithPodTemplate : INamespacedResource, IMutableResource 10 | { 11 | string Uid { get; } 12 | IReadOnlyCollection Labels { get; } 13 | PodTemplate PodTemplate { get; } 14 | PodSelector Selector { get; } 15 | } 16 | -------------------------------------------------------------------------------- /manifests/examples/README.md: -------------------------------------------------------------------------------- 1 | Create new pods: 2 | 3 | ``` 4 | kubectl -n default rollout restart deployment asp-net-core 5 | ``` 6 | 7 | Exec into cluster: 8 | ``` 9 | kubectl -n default run -it --rm --image ubuntu -- bash 10 | ``` 11 | 12 | Operator service: 13 | ``` 14 | contrast-agent-operator.contrast-agent-operator.svc.cluster.local 15 | ``` 16 | 17 | Dump web hooks: 18 | ``` 19 | kubectl get MutatingWebhookConfiguration -o yaml 20 | ``` 21 | 22 | Exec into the cluster's node. 23 | ``` 24 | docker run -it --rm --privileged --pid=host ubuntu nsenter -t 1 -m -u -n -i bash 25 | 26 | # Logs 27 | # e.g. kube-apiserver-docker-desktop_kube-system_kube-apiserver-.... 28 | cd /var/log/containers/ 29 | ``` 30 | -------------------------------------------------------------------------------- /manifests/helm/templates/image-pull-secrets.yaml.tpl: -------------------------------------------------------------------------------- 1 | {{- define "imagePullSecret" }} 2 | {{- with .Values.imageCredentials }} 3 | {{- printf "{\"auths\":{\"%s\":{\"username\":\"%s\",\"password\":\"%s\",\"email\":\"%s\",\"auth\":\"%s\"}}}" .registry .username .password .email (printf "%s:%s" .username .password | b64enc) | b64enc }} 4 | {{- end }} 5 | {{- end }} 6 | {{ if .Values.imageCredentials.enabled }} 7 | apiVersion: v1 8 | kind: Secret 9 | metadata: 10 | name: {{ .Values.imageCredentials.pullSecretName }} 11 | namespace: '{{ default .Release.Namespace .Values.namespace }}' 12 | type: kubernetes.io/dockerconfigjson 13 | data: 14 | .dockerconfigjson: {{ template "imagePullSecret" . }} 15 | {{ end }} 16 | -------------------------------------------------------------------------------- /tests/performance-tests/Contrast.K8s.AgentOperator.Performance.ClusterFaker/Contrast.K8s.AgentOperator.Performance.ClusterFaker.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net8.0 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/Contrast.K8s.AgentOperator/Core/State/Resources/ClusterAgentInjectorResource.cs: -------------------------------------------------------------------------------- 1 | // Contrast Security, Inc licenses this file to you under the Apache 2.0 License. 2 | // See the LICENSE file in the project root for more information. 3 | 4 | using System.Collections.Generic; 5 | using Contrast.K8s.AgentOperator.Core.State.Resources.Interfaces; 6 | using Contrast.K8s.AgentOperator.Core.State.Resources.Primitives; 7 | 8 | namespace Contrast.K8s.AgentOperator.Core.State.Resources; 9 | 10 | public record ClusterAgentInjectorResource( 11 | AgentInjectorTemplate Template, 12 | IReadOnlyCollection NamespacePatterns, 13 | IReadOnlyCollection NamespaceLabelPatterns 14 | ) : IClusterResource; 15 | -------------------------------------------------------------------------------- /src/Contrast.K8s.AgentOperator/Core/Telemetry/Getters/IsPublicTelemetryBuildGetter.cs: -------------------------------------------------------------------------------- 1 | // Contrast Security, Inc licenses this file to you under the Apache 2.0 License. 2 | // See the LICENSE file in the project root for more information. 3 | 4 | #nullable enable 5 | 6 | namespace Contrast.K8s.AgentOperator.Core.Telemetry.Getters; 7 | 8 | public class IsPublicTelemetryBuildGetter 9 | { 10 | #if CONTRAST_IS_PUBLIC_TELEMETRY_BUILD 11 | private const bool PublicBuildFlag = true; 12 | #else 13 | private const bool PublicBuildFlag = false; 14 | #endif 15 | 16 | // ReSharper disable once MemberCanBeMadeStatic.Global 17 | public bool IsPublicBuild() 18 | { 19 | return PublicBuildFlag; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Contrast.K8s.AgentOperator/Entities/Common/ClusterNamespaceLabelSelectorSpec.cs: -------------------------------------------------------------------------------- 1 | // Contrast Security, Inc licenses this file to you under the Apache 2.0 License. 2 | // See the LICENSE file in the project root for more information. 3 | 4 | using KubeOps.Abstractions.Entities.Attributes; 5 | 6 | namespace Contrast.K8s.AgentOperator.Entities.Common; 7 | 8 | public class ClusterNamespaceLabelSelectorSpec 9 | { 10 | [Required] 11 | [Description("The name of the label to match. Required.")] 12 | public string Name { get; set; } = null!; 13 | 14 | [Required] 15 | [Description("The value of the label to match. Glob patterns are supported. Required.")] 16 | public string Value { get; set; } = null!; 17 | } 18 | -------------------------------------------------------------------------------- /manifests/examples/testing/scenarios/cluster/injection-namespacelabel.yaml: -------------------------------------------------------------------------------- 1 | kind: Namespace 2 | apiVersion: v1 3 | metadata: 4 | name: testing-namespacelabel 5 | labels: 6 | testing-label: dummy-value 7 | --- 8 | apiVersion: apps/v1 9 | kind: Deployment 10 | metadata: 11 | name: injection-namespacelabel 12 | namespace: testing-namespacelabel 13 | labels: 14 | app: injection-cluster 15 | spec: 16 | replicas: 1 17 | selector: 18 | matchLabels: 19 | app: injection-cluster 20 | strategy: 21 | type: Recreate 22 | template: 23 | metadata: 24 | labels: 25 | app: injection-cluster 26 | spec: 27 | containers: 28 | - image: k8s.gcr.io/pause:3.3 29 | name: pause 30 | -------------------------------------------------------------------------------- /src/Contrast.K8s.AgentOperator/Core/Reactions/Injecting/Patching/Utility/PatchingExtensions.cs: -------------------------------------------------------------------------------- 1 | // Contrast Security, Inc licenses this file to you under the Apache 2.0 License. 2 | // See the LICENSE file in the project root for more information. 3 | 4 | using k8s.Models; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | 9 | namespace Contrast.K8s.AgentOperator.Core.Reactions.Injecting.Patching.Utility; 10 | 11 | public static class PatchingExtensions 12 | { 13 | public static V1EnvVar? FirstOrDefault(this IEnumerable collection, string name) 14 | { 15 | return collection.FirstOrDefault(x => string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase)); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Contrast.K8s.AgentOperator/Core/State/Resources/AgentConnectionResource.cs: -------------------------------------------------------------------------------- 1 | // Contrast Security, Inc licenses this file to you under the Apache 2.0 License. 2 | // See the LICENSE file in the project root for more information. 3 | 4 | using Contrast.K8s.AgentOperator.Core.State.Resources.Interfaces; 5 | using Contrast.K8s.AgentOperator.Core.State.Resources.Primitives; 6 | 7 | namespace Contrast.K8s.AgentOperator.Core.State.Resources; 8 | 9 | public record AgentConnectionResource( 10 | bool? MountAsVolume, 11 | SecretReference? Token, 12 | string? TeamServerUri, 13 | SecretReference? ApiKey, 14 | SecretReference? ServiceKey, 15 | SecretReference? UserName 16 | ) : INamespacedResource, IMutableResource; 17 | -------------------------------------------------------------------------------- /docs/public/05-advanced-topics.md: -------------------------------------------------------------------------------- 1 | # Advanced topics 2 | 3 | ## .NET Core chaining support 4 | 5 | The .NET Core Agent, paired with the Contrast Agent Operator, supports profiler chaining with Dynatrace using the [Dynatrace Operator](https://github.com/Dynatrace/dynatrace-operator). Support is enabled automatically when the Dynatrace Operator is used to inject the Dynatrace agent into workloads. 6 | 7 | | Vendor | Version | Support Validated On | 8 | |-----------|-----------------|----------------------| 9 | | Dynatrace | Operator v0.6.0 | 2022/06/09 | 10 | 11 | Future Dynatrace versions may break chaining. Chaining can introduce incompatibilities and can be disabled using the `agent.dotnet.enable_chaining: false` option. 12 | -------------------------------------------------------------------------------- /src/Contrast.K8s.AgentOperator/Core/State/Resources/ClusterAgentConnectionResource.cs: -------------------------------------------------------------------------------- 1 | // Contrast Security, Inc licenses this file to you under the Apache 2.0 License. 2 | // See the LICENSE file in the project root for more information. 3 | 4 | using Contrast.K8s.AgentOperator.Core.State.Resources.Interfaces; 5 | using Contrast.K8s.AgentOperator.Core.State.Resources.Primitives; 6 | using System.Collections.Generic; 7 | 8 | namespace Contrast.K8s.AgentOperator.Core.State.Resources; 9 | 10 | public record ClusterAgentConnectionResource( 11 | AgentConnectionResource Template, 12 | IReadOnlyCollection NamespacePatterns, 13 | IReadOnlyCollection NamespaceLabelPatterns 14 | ) : IClusterResourceTemplate; 15 | -------------------------------------------------------------------------------- /src/Contrast.K8s.AgentOperator/Core/State/Resources/DaemonSetResource.cs: -------------------------------------------------------------------------------- 1 | // Contrast Security, Inc licenses this file to you under the Apache 2.0 License. 2 | // See the LICENSE file in the project root for more information. 3 | 4 | using System.Collections.Generic; 5 | using Contrast.K8s.AgentOperator.Core.State.Resources.Interfaces; 6 | using Contrast.K8s.AgentOperator.Core.State.Resources.Primitives; 7 | 8 | namespace Contrast.K8s.AgentOperator.Core.State.Resources; 9 | 10 | public record DaemonSetResource(string Uid, 11 | IReadOnlyCollection Labels, 12 | PodTemplate PodTemplate, 13 | PodSelector Selector) 14 | : IResourceWithPodTemplate; 15 | -------------------------------------------------------------------------------- /src/Contrast.K8s.AgentOperator/Core/State/Resources/DeploymentResource.cs: -------------------------------------------------------------------------------- 1 | // Contrast Security, Inc licenses this file to you under the Apache 2.0 License. 2 | // See the LICENSE file in the project root for more information. 3 | 4 | using System.Collections.Generic; 5 | using Contrast.K8s.AgentOperator.Core.State.Resources.Interfaces; 6 | using Contrast.K8s.AgentOperator.Core.State.Resources.Primitives; 7 | 8 | namespace Contrast.K8s.AgentOperator.Core.State.Resources; 9 | 10 | public record DeploymentResource(string Uid, 11 | IReadOnlyCollection Labels, 12 | PodTemplate PodTemplate, 13 | PodSelector Selector) 14 | : IResourceWithPodTemplate; 15 | -------------------------------------------------------------------------------- /src/Contrast.K8s.AgentOperator/Core/State/Resources/PodResource.cs: -------------------------------------------------------------------------------- 1 | // Contrast Security, Inc licenses this file to you under the Apache 2.0 License. 2 | // See the LICENSE file in the project root for more information. 3 | 4 | using System.Collections.Generic; 5 | using Contrast.K8s.AgentOperator.Core.State.Resources.Interfaces; 6 | using Contrast.K8s.AgentOperator.Core.State.Resources.Primitives; 7 | 8 | namespace Contrast.K8s.AgentOperator.Core.State.Resources; 9 | 10 | public record PodResource(IReadOnlyList Labels, 11 | bool IsInjected, 12 | PodInjectionConvergenceCondition? InjectionStatus, 13 | AgentInjectionType? InjectionType) 14 | : INamespacedResource; 15 | -------------------------------------------------------------------------------- /src/Contrast.K8s.AgentOperator/Core/State/Resources/RolloutResource.cs: -------------------------------------------------------------------------------- 1 | // Contrast Security, Inc licenses this file to you under the Apache 2.0 License. 2 | // See the LICENSE file in the project root for more information. 3 | 4 | using System.Collections.Generic; 5 | using Contrast.K8s.AgentOperator.Core.State.Resources.Interfaces; 6 | using Contrast.K8s.AgentOperator.Core.State.Resources.Primitives; 7 | 8 | namespace Contrast.K8s.AgentOperator.Core.State.Resources; 9 | 10 | public record RolloutResource(string Uid, 11 | IReadOnlyCollection Labels, 12 | PodTemplate PodTemplate, 13 | PodSelector Selector) 14 | : IResourceWithPodTemplate; 15 | -------------------------------------------------------------------------------- /src/Contrast.K8s.AgentOperator/Core/State/Resources/StatefulSetResource.cs: -------------------------------------------------------------------------------- 1 | // Contrast Security, Inc licenses this file to you under the Apache 2.0 License. 2 | // See the LICENSE file in the project root for more information. 3 | 4 | using System.Collections.Generic; 5 | using Contrast.K8s.AgentOperator.Core.State.Resources.Interfaces; 6 | using Contrast.K8s.AgentOperator.Core.State.Resources.Primitives; 7 | 8 | namespace Contrast.K8s.AgentOperator.Core.State.Resources; 9 | 10 | public record StatefulSetResource(string Uid, 11 | IReadOnlyCollection Labels, 12 | PodTemplate PodTemplate, 13 | PodSelector Selector) 14 | : IResourceWithPodTemplate; 15 | -------------------------------------------------------------------------------- /src/Contrast.K8s.AgentOperator/Core/Tls/TlsCertificateChain.cs: -------------------------------------------------------------------------------- 1 | // Contrast Security, Inc licenses this file to you under the Apache 2.0 License. 2 | // See the LICENSE file in the project root for more information. 3 | 4 | using System; 5 | using System.Security.Cryptography.X509Certificates; 6 | 7 | namespace Contrast.K8s.AgentOperator.Core.Tls; 8 | 9 | public record TlsCertificateChain(X509Certificate2 CaCertificate, 10 | X509Certificate2 ServerCertificate, 11 | byte[] SanDnsNamesHash, 12 | byte[] Version) : IDisposable 13 | { 14 | public void Dispose() 15 | { 16 | CaCertificate.Dispose(); 17 | ServerCertificate.Dispose(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Contrast.K8s.AgentOperator/Core/State/Resources/ClusterAgentConfigurationResource.cs: -------------------------------------------------------------------------------- 1 | // Contrast Security, Inc licenses this file to you under the Apache 2.0 License. 2 | // See the LICENSE file in the project root for more information. 3 | 4 | using System.Collections.Generic; 5 | using Contrast.K8s.AgentOperator.Core.State.Resources.Interfaces; 6 | using Contrast.K8s.AgentOperator.Core.State.Resources.Primitives; 7 | 8 | namespace Contrast.K8s.AgentOperator.Core.State.Resources; 9 | 10 | public record ClusterAgentConfigurationResource( 11 | AgentConfigurationResource Template, 12 | IReadOnlyCollection NamespacePatterns, 13 | IReadOnlyCollection NamespaceLabelPatterns 14 | ) : IClusterResourceTemplate; 15 | -------------------------------------------------------------------------------- /src/Contrast.K8s.AgentOperator/Entities/RegexConstants.cs: -------------------------------------------------------------------------------- 1 | // Contrast Security, Inc licenses this file to you under the Apache 2.0 License. 2 | // See the LICENSE file in the project root for more information. 3 | 4 | using JetBrains.Annotations; 5 | 6 | namespace Contrast.K8s.AgentOperator.Entities; 7 | 8 | public static class RegexConstants 9 | { 10 | [RegexPattern] 11 | public const string AgentTypeRegex = @"^(dotnet-core|dotnet|java|node|nodejs|node-esm|nodejs-esm|nodejs-legacy|php|personal-home-page|python|flex|dummy)$"; 12 | 13 | [RegexPattern] 14 | public const string InjectorVersionRegex = @"^(latest|(\d+(\.\d+){0,3}(-.+)?))$"; 15 | 16 | [RegexPattern] 17 | public const string PullPolicyRegex = @"^(Always|IfNotPresent|Never)$"; 18 | } 19 | -------------------------------------------------------------------------------- /src/get-info.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | readyEndpoint="https://localhost:5001/ready" 4 | healthEndpoint="https://localhost:5001/health" 5 | metricsEndpoint="https://localhost:5001/api/v1/metrics" 6 | tagsEndpoint="https://localhost:5001/api/v1/metrics/tags" 7 | 8 | echo "Accessing ready endpoint at $readyEndpoint..." 9 | curl --insecure --silent $readyEndpoint 10 | echo "" 11 | 12 | echo "Accessing health endpoint at $healthEndpoint..." 13 | curl --insecure --silent $healthEndpoint 14 | echo "" 15 | 16 | echo "Accessing metrics endpoint at $tagsEndpoint..." 17 | curl --insecure --silent $tagsEndpoint | jq 18 | 19 | echo "Accessing metrics endpoint at $metricsEndpoint (this can take a minute to gather performance metrics)..." 20 | curl --insecure --silent $metricsEndpoint | jq 21 | -------------------------------------------------------------------------------- /manifests/examples/testing/scenarios/cluster/cluster-agent-connection.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: agents.contrastsecurity.com/v1beta1 2 | kind: ClusterAgentConnection 3 | metadata: 4 | name: cluster-agent-connection 5 | namespace: testing-agent-operator 6 | spec: 7 | template: 8 | spec: 9 | token: 10 | secretName: cluster-agent-connection-secret 11 | secretKey: token 12 | url: http://not-localhost 13 | apiKey: 14 | secretName: cluster-agent-connection-secret 15 | secretKey: apiKey 16 | serviceKey: 17 | secretName: cluster-agent-connection-secret 18 | secretKey: serviceKey 19 | userName: 20 | secretName: cluster-agent-connection-secret 21 | secretKey: userName 22 | # namespaces: defaults to all 23 | -------------------------------------------------------------------------------- /src/Contrast.K8s.AgentOperator/Core/Kube/VerbConstants.cs: -------------------------------------------------------------------------------- 1 | // Contrast Security, Inc licenses this file to you under the Apache 2.0 License. 2 | // See the LICENSE file in the project root for more information. 3 | 4 | using KubeOps.Abstractions.Rbac; 5 | 6 | namespace Contrast.K8s.AgentOperator.Core.Kube; 7 | 8 | public static class VerbConstants 9 | { 10 | public const RbacVerb ReadAndPatch = RbacVerb.Get | RbacVerb.List | RbacVerb.Patch | RbacVerb.Watch; 11 | 12 | public const RbacVerb AllButDelete = 13 | RbacVerb.Get | RbacVerb.List | RbacVerb.Watch | RbacVerb.Create | RbacVerb.Update | RbacVerb.Patch; 14 | 15 | public const RbacVerb ReadOnly = RbacVerb.Get | RbacVerb.List | RbacVerb.Watch; 16 | 17 | public const RbacVerb All = RbacVerb.AllExplicit; 18 | } 19 | -------------------------------------------------------------------------------- /src/Contrast.K8s.AgentOperator/Core/Reactions/Matching/ObjectReferenceComparer.cs: -------------------------------------------------------------------------------- 1 | // Contrast Security, Inc licenses this file to you under the Apache 2.0 License. 2 | // See the LICENSE file in the project root for more information. 3 | 4 | using System.Collections.Generic; 5 | using System.Runtime.CompilerServices; 6 | 7 | namespace Contrast.K8s.AgentOperator.Core.Reactions.Matching; 8 | 9 | public class ObjectReferenceComparer : IEqualityComparer where T : class 10 | { 11 | public static ObjectReferenceComparer Default { get; } = new(); 12 | 13 | public bool Equals(T? x, T? y) 14 | { 15 | return ReferenceEquals(x, y); 16 | } 17 | 18 | public int GetHashCode(T obj) 19 | { 20 | return RuntimeHelpers.GetHashCode(obj); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Contrast.K8s.AgentOperator/Options/TlsStorageOptions.cs: -------------------------------------------------------------------------------- 1 | // Contrast Security, Inc licenses this file to you under the Apache 2.0 License. 2 | // See the LICENSE file in the project root for more information. 3 | 4 | namespace Contrast.K8s.AgentOperator.Options; 5 | 6 | public record TlsStorageOptions(string SecretName, 7 | string SecretNamespace, 8 | string ServerCertificateName = "server_certificate", 9 | string CaCertificateName = "ca_certificate", 10 | string CaPublicName = "ca_pem", 11 | string VersionName = "compatibility_version", 12 | string SanDnsNamesHashName = "sans_dns_names_hash"); 13 | -------------------------------------------------------------------------------- /src/Contrast.K8s.AgentOperator/Core/State/Resources/DeploymentConfigResource.cs: -------------------------------------------------------------------------------- 1 | // Contrast Security, Inc licenses this file to you under the Apache 2.0 License. 2 | // See the LICENSE file in the project root for more information. 3 | 4 | using System.Collections.Generic; 5 | using Contrast.K8s.AgentOperator.Core.State.Resources.Interfaces; 6 | using Contrast.K8s.AgentOperator.Core.State.Resources.Primitives; 7 | 8 | namespace Contrast.K8s.AgentOperator.Core.State.Resources; 9 | 10 | public record DeploymentConfigResource(string Uid, 11 | IReadOnlyCollection Labels, 12 | PodTemplate PodTemplate, 13 | PodSelector Selector) 14 | : IResourceWithPodTemplate; 15 | -------------------------------------------------------------------------------- /src/Contrast.K8s.AgentOperator/Core/State/Resources/Primitives/SecretKeyValue.cs: -------------------------------------------------------------------------------- 1 | // Contrast Security, Inc licenses this file to you under the Apache 2.0 License. 2 | // See the LICENSE file in the project root for more information. 3 | 4 | using System.Security.Cryptography; 5 | 6 | namespace Contrast.K8s.AgentOperator.Core.State.Resources.Primitives; 7 | 8 | public record SecretKeyValue(string Key, string DataHash) 9 | { 10 | public static SecretKeyValue Create(string key, byte[] value) 11 | { 12 | return new SecretKeyValue(key, Sha256(value)); 13 | } 14 | private static string Sha256(byte[] data) 15 | { 16 | using var sha256 = SHA256.Create(); 17 | var bytes = sha256.ComputeHash(data); 18 | return HexConverter.ToLowerHex(bytes); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Contrast.K8s.AgentOperator/Core/State/Resources/AgentConfigurationResource.cs: -------------------------------------------------------------------------------- 1 | // Contrast Security, Inc licenses this file to you under the Apache 2.0 License. 2 | // See the LICENSE file in the project root for more information. 3 | 4 | using System.Collections.Generic; 5 | using Contrast.K8s.AgentOperator.Core.State.Resources.Interfaces; 6 | using Contrast.K8s.AgentOperator.Core.State.Resources.Primitives; 7 | 8 | namespace Contrast.K8s.AgentOperator.Core.State.Resources; 9 | 10 | public record AgentConfigurationResource( 11 | IReadOnlyDictionary YamlKeys, 12 | bool? SuppressDefaultServerName, 13 | bool? SuppressDefaultApplicationName, 14 | bool? EnableYamlVariableReplacement, 15 | InitContainerOverrides? InitContainerOverrides 16 | ) : INamespacedResource, IMutableResource; 17 | -------------------------------------------------------------------------------- /manifests/generate-manifests.ps1: -------------------------------------------------------------------------------- 1 | #!/bin/pwsh 2 | # Run under powershell core 3 | #Requires -Version 6.0 4 | 5 | $project = [System.IO.Path]::GetFullPath("$PSScriptroot\..\src\Contrast.K8s.AgentOperator\Contrast.K8s.AgentOperator.csproj") 6 | $output = [System.IO.Path]::GetFullPath("$PSScriptroot\generated\") 7 | 8 | Write-Host "Project: $project" 9 | Write-Host "Output: $output" 10 | 11 | dotnet kubeops generate operator contrast-agent-operator $project --out $output 12 | 13 | @( 14 | "$($output)*.pem" 15 | "$($output)Dockerfile" 16 | "$($output)kustomization.yaml" 17 | ) | ForEach-Object { 18 | Write-Host "Cleaning up object $_" 19 | Remove-Item $_ 20 | } 21 | 22 | Write-Host "Done. Compare with manifests in `manifests/install/all` and `manifests/helm/templates/operator` folders and merge changes (CRDs and RBAC)." 23 | -------------------------------------------------------------------------------- /manifests/examples/argo-rollouts/base/dotnet-core.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: argoproj.io/v1alpha1 2 | kind: Rollout 3 | metadata: 4 | name: dotnet-core-app 5 | namespace: default 6 | labels: 7 | app: dotnet-core-app 8 | spec: 9 | replicas: 2 10 | strategy: 11 | canary: 12 | steps: 13 | - setWeight: 20 14 | - pause: {} 15 | - setWeight: 40 16 | - pause: {duration: 10} 17 | revisionHistoryLimit: 1 18 | selector: 19 | matchLabels: 20 | app: dotnet-core-app 21 | template: 22 | metadata: 23 | labels: 24 | app: dotnet-core-app 25 | annotations: 26 | test: test 27 | spec: 28 | containers: 29 | - image: contrast/sample-app-aspnetcore:latest 30 | name: dotnet-core-app 31 | ports: 32 | - containerPort: 80 33 | name: http 34 | -------------------------------------------------------------------------------- /src/Contrast.K8s.AgentOperator/Core/State/ResourceAnnotations.cs: -------------------------------------------------------------------------------- 1 | // Contrast Security, Inc licenses this file to you under the Apache 2.0 License. 2 | // See the LICENSE file in the project root for more information. 3 | 4 | using System.Collections.Generic; 5 | 6 | namespace Contrast.K8s.AgentOperator.Core.State 7 | { 8 | public class ResourceAnnotations 9 | { 10 | public const string ResourceManagedByAttributeName = "agents.contrastsecurity.com/managed-by"; 11 | 12 | public static Dictionary GetAnnotationsForManagedResources(string resourceName, string resourceNamespace) 13 | { 14 | return new Dictionary 15 | { 16 | { ResourceManagedByAttributeName, $"{resourceNamespace}/{resourceName}" }, 17 | }; 18 | } 19 | 20 | 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /manifests/examples/testing/scenarios/namespaced/missing-deps.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: agents.contrastsecurity.com/v1beta1 2 | kind: AgentInjector 3 | metadata: 4 | name: missing-deps 5 | spec: 6 | enabled: true 7 | type: dummy 8 | image: 9 | pullPolicy: Never 10 | connection: 11 | name: missing-testing-agent-connection 12 | configuration: 13 | name: missing-testing-agent-configuration 14 | --- 15 | apiVersion: apps/v1 16 | kind: Deployment 17 | metadata: 18 | name: missing-deps 19 | labels: 20 | app: missing-deps 21 | spec: 22 | replicas: 1 23 | selector: 24 | matchLabels: 25 | app: missing-deps 26 | strategy: 27 | type: Recreate 28 | template: 29 | metadata: 30 | labels: 31 | app: missing-deps 32 | spec: 33 | containers: 34 | - image: k8s.gcr.io/pause:3.3 35 | name: pause 36 | -------------------------------------------------------------------------------- /manifests/helm/values.testing.yaml: -------------------------------------------------------------------------------- 1 | # yaml-language-server: $schema=values.schema.json 2 | clusterDefaults: 3 | enabled: true 4 | url: https://app-agents.contrastsecurity.com/Contrast 5 | apiKeyValue: testing 6 | serviceKeyValue: testing 7 | userNameValue: testing 8 | yaml: |- 9 | enable: true 10 | second-line: something 11 | 12 | image: 13 | registry: contrast 14 | repository: agent-operator 15 | tag: 16 | 17 | agentInjectors: 18 | enabled: true 19 | useClusterAgentInjectors: true 20 | namespaces: 21 | - test1 22 | - test2 23 | namespaceLabelSelector: 24 | - name: agents.contrastsecurity.com/agent-injectors 25 | value: 'true' 26 | injectors: 27 | - language: java 28 | name: helm-java-injector 29 | selector: 30 | labels: 31 | - name: contrast-agent 32 | value: java 33 | -------------------------------------------------------------------------------- /src/Contrast.K8s.AgentOperator/Core/State/Resources/AgentInjectorResource.cs: -------------------------------------------------------------------------------- 1 | // Contrast Security, Inc licenses this file to you under the Apache 2.0 License. 2 | // See the LICENSE file in the project root for more information. 3 | 4 | using Contrast.K8s.AgentOperator.Core.State.Resources.Interfaces; 5 | using Contrast.K8s.AgentOperator.Core.State.Resources.Primitives; 6 | 7 | namespace Contrast.K8s.AgentOperator.Core.State.Resources; 8 | 9 | public record AgentInjectorResource( 10 | bool Enabled, 11 | AgentInjectionType Type, 12 | ContainerImageReference Image, 13 | ResourceWithPodSpecSelector Selector, 14 | AgentInjectorConnectionReference ConnectionReference, 15 | AgentConfigurationReference ConfigurationReference, 16 | SecretReference? ImagePullSecret, 17 | string ImagePullPolicy 18 | ) : INamespacedResource, IMutableResource; 19 | -------------------------------------------------------------------------------- /src/Contrast.K8s.AgentOperator/Core/Telemetry/Models/TelemetryMeasurement.cs: -------------------------------------------------------------------------------- 1 | // Contrast Security, Inc licenses this file to you under the Apache 2.0 License. 2 | // See the LICENSE file in the project root for more information. 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | 7 | namespace Contrast.K8s.AgentOperator.Core.Telemetry.Models; 8 | 9 | public class TelemetryMeasurement 10 | { 11 | public DateTimeOffset Timestamp { get; set; } = DateTimeOffset.Now; 12 | 13 | public string Path { get; set; } 14 | 15 | public IReadOnlyDictionary Values { get; set; } = new Dictionary(); 16 | 17 | public IReadOnlyDictionary ExtraTags { get; set; } = new Dictionary(); 18 | 19 | public TelemetryMeasurement(string path) 20 | { 21 | Path = path; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /manifests/examples/testing/scenarios/namespaced/enabled-flag.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: agents.contrastsecurity.com/v1beta1 2 | kind: AgentInjector 3 | metadata: 4 | name: enabled-flag 5 | spec: 6 | enabled: false 7 | type: dummy 8 | image: 9 | pullPolicy: Never 10 | selector: 11 | labels: 12 | - name: app 13 | value: enabled-flag 14 | connection: 15 | name: testing-agent-connection 16 | --- 17 | apiVersion: apps/v1 18 | kind: Deployment 19 | metadata: 20 | name: enabled-flag 21 | labels: 22 | app: enabled-flag 23 | spec: 24 | replicas: 1 25 | selector: 26 | matchLabels: 27 | app: enabled-flag 28 | strategy: 29 | type: Recreate 30 | template: 31 | metadata: 32 | labels: 33 | app: enabled-flag 34 | spec: 35 | containers: 36 | - image: k8s.gcr.io/pause:3.3 37 | name: pause 38 | -------------------------------------------------------------------------------- /src/Contrast.K8s.AgentOperator/Core/Comparing/ResourceComparer.cs: -------------------------------------------------------------------------------- 1 | // Contrast Security, Inc licenses this file to you under the Apache 2.0 License. 2 | // See the LICENSE file in the project root for more information. 3 | 4 | using Contrast.K8s.AgentOperator.Core.State.Resources.Interfaces; 5 | 6 | namespace Contrast.K8s.AgentOperator.Core.Comparing; 7 | 8 | public interface IResourceComparer 9 | { 10 | bool AreEqual(T left, T right) where T : INamespacedResource?; 11 | } 12 | 13 | public class ResourceComparer : IResourceComparer 14 | { 15 | private readonly FastComparer _comparer; 16 | 17 | public ResourceComparer(FastComparer comparer) 18 | { 19 | _comparer = comparer; 20 | } 21 | 22 | public bool AreEqual(T left, T right) where T : INamespacedResource? 23 | { 24 | return _comparer.AreEqual(left, right); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Contrast.K8s.AgentOperator/Core/Reactions/Secrets/VolumeSecrets.cs: -------------------------------------------------------------------------------- 1 | // Contrast Security, Inc licenses this file to you under the Apache 2.0 License. 2 | // See the LICENSE file in the project root for more information. 3 | 4 | using System.Security.Cryptography; 5 | using System.Text; 6 | 7 | namespace Contrast.K8s.AgentOperator.Core.Reactions.Secrets; 8 | 9 | public class VolumeSecrets 10 | { 11 | public static string GetConnectionVolumeSecretName(string agentConnection) 12 | { 13 | return "agent-connection-volume-secret-" + GetShortHash(agentConnection); 14 | } 15 | 16 | private static string GetShortHash(string text) 17 | { 18 | using var sha256 = SHA256.Create(); 19 | var bytes = Encoding.UTF8.GetBytes(text); 20 | var hash = sha256.ComputeHash(bytes); 21 | return HexConverter.ToLowerHex(hash, 8); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /manifests/examples/testing/scenarios/namespaced/type-daemonset.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: agents.contrastsecurity.com/v1beta1 2 | kind: AgentInjector 3 | metadata: 4 | name: type-daemonset 5 | spec: 6 | enabled: true 7 | type: dummy 8 | selector: 9 | labels: 10 | - name: app 11 | value: type-daemonset 12 | image: 13 | pullPolicy: Never 14 | connection: 15 | name: testing-agent-connection 16 | configuration: 17 | name: testing-agent-configuration 18 | --- 19 | apiVersion: apps/v1 20 | kind: DaemonSet 21 | metadata: 22 | name: type-daemonset 23 | labels: 24 | app: type-daemonset 25 | spec: 26 | selector: 27 | matchLabels: 28 | app: type-daemonset 29 | template: 30 | metadata: 31 | labels: 32 | app: type-daemonset 33 | spec: 34 | containers: 35 | - image: k8s.gcr.io/pause:3.3 36 | name: pause 37 | -------------------------------------------------------------------------------- /src/Contrast.K8s.AgentOperator/Core/Reactions/Injecting/Patching/Agents/IAgentPatcher.cs: -------------------------------------------------------------------------------- 1 | // Contrast Security, Inc licenses this file to you under the Apache 2.0 License. 2 | // See the LICENSE file in the project root for more information. 3 | 4 | using System.Collections.Generic; 5 | using Contrast.K8s.AgentOperator.Core.State.Resources.Primitives; 6 | using k8s.Models; 7 | 8 | namespace Contrast.K8s.AgentOperator.Core.Reactions.Injecting.Patching.Agents; 9 | 10 | public interface IAgentPatcher 11 | { 12 | bool Deprecated => false; 13 | string? DeprecatedMessage => null; 14 | 15 | AgentInjectionType Type { get; } 16 | 17 | IEnumerable GenerateEnvVars(PatchingContext context); 18 | 19 | public void PatchContainer(V1Container container, PatchingContext context) 20 | { 21 | } 22 | 23 | public string? GetOverrideAgentMountPath() => null; 24 | } 25 | -------------------------------------------------------------------------------- /manifests/examples/testing/scenarios/namespaced/config-optional.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: agents.contrastsecurity.com/v1beta1 2 | kind: AgentInjector 3 | metadata: 4 | name: config-optional 5 | spec: 6 | enabled: true 7 | type: dummy 8 | image: 9 | pullPolicy: Never 10 | selector: 11 | labels: 12 | - name: app 13 | value: config-optional 14 | connection: 15 | name: testing-agent-connection 16 | --- 17 | apiVersion: apps/v1 18 | kind: Deployment 19 | metadata: 20 | name: config-optional 21 | labels: 22 | app: config-optional 23 | spec: 24 | replicas: 1 25 | selector: 26 | matchLabels: 27 | app: config-optional 28 | strategy: 29 | type: Recreate 30 | template: 31 | metadata: 32 | labels: 33 | app: config-optional 34 | spec: 35 | containers: 36 | - image: k8s.gcr.io/pause:3.3 37 | name: pause 38 | -------------------------------------------------------------------------------- /src/Contrast.K8s.AgentOperator/Core/Reactions/Injecting/Patching/PatchingContext.cs: -------------------------------------------------------------------------------- 1 | // Contrast Security, Inc licenses this file to you under the Apache 2.0 License. 2 | // See the LICENSE file in the project root for more information. 3 | 4 | using Contrast.K8s.AgentOperator.Core.State.Resources; 5 | 6 | namespace Contrast.K8s.AgentOperator.Core.Reactions.Injecting.Patching; 7 | 8 | public record PatchingContext(string WorkloadName, 9 | string WorkloadNamespace, 10 | AgentInjectorResource Injector, 11 | AgentConnectionResource Connection, 12 | AgentConfigurationResource? Configuration, 13 | string AgentMountPath, 14 | string WritableMountPath, 15 | string ConnectionSecretMountPath); 16 | -------------------------------------------------------------------------------- /src/Contrast.K8s.AgentOperator/Core/Telemetry/Models/ExceptionReport.cs: -------------------------------------------------------------------------------- 1 | // Contrast Security, Inc licenses this file to you under the Apache 2.0 License. 2 | // See the LICENSE file in the project root for more information. 3 | 4 | using System; 5 | 6 | namespace Contrast.K8s.AgentOperator.Core.Telemetry.Models; 7 | 8 | public class ExceptionReport 9 | { 10 | public DateTimeOffset Timestamp { get; init; } = DateTimeOffset.Now; 11 | 12 | public string LoggerName { get; init; } 13 | 14 | public string LogMessage { get; init; } 15 | 16 | public Exception Exception { get; init; } 17 | 18 | public ExceptionReport(string loggerName, 19 | string logMessage, 20 | Exception exception) 21 | { 22 | LoggerName = loggerName; 23 | LogMessage = logMessage; 24 | Exception = exception; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Contrast.K8s.AgentOperator/Core/State/NamespacedResourceIdentity.cs: -------------------------------------------------------------------------------- 1 | // Contrast Security, Inc licenses this file to you under the Apache 2.0 License. 2 | // See the LICENSE file in the project root for more information. 3 | 4 | using System; 5 | using Contrast.K8s.AgentOperator.Core.State.Resources.Interfaces; 6 | using JetBrains.Annotations; 7 | 8 | namespace Contrast.K8s.AgentOperator.Core.State; 9 | 10 | public record NamespacedResourceIdentity([UsedImplicitly] string Name, [UsedImplicitly] string Namespace, [UsedImplicitly] Type Type) 11 | { 12 | public static NamespacedResourceIdentity Create(string name, string @namespace) where T : INamespacedResource 13 | { 14 | return new NamespacedResourceIdentity(name, @namespace, typeof(T)); 15 | } 16 | 17 | public override string ToString() 18 | { 19 | return $"{Type.Name}/{Namespace}/{Name}"; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /manifests/install/examples/custom-registry/kustomization.yaml: -------------------------------------------------------------------------------- 1 | namespace: contrast-agent-operator 2 | resources: 3 | - ../../all 4 | 5 | # 1. Update `contrast/agent-operator` (DockerHub by default) to your custom registry. 6 | images: 7 | - name: contrast/agent-operator 8 | newName: contrastdotnet.azurecr.io/agent-operator/agent-operator 9 | newTag: latest 10 | 11 | # 2. Update the default registry to your custom registry. 12 | patches: 13 | - patch: |- 14 | apiVersion: apps/v1 15 | kind: Deployment 16 | metadata: 17 | name: contrast-agent-operator 18 | namespace: contrast-agent-operator 19 | spec: 20 | template: 21 | spec: 22 | containers: 23 | - name: contrast-agent-operator 24 | env: 25 | - name: CONTRAST_DEFAULT_REGISTRY 26 | value: "contrastdotnet.azurecr.io/agent-operator" 27 | 28 | -------------------------------------------------------------------------------- /manifests/examples/testing/scenarios/namespaced/injection-dummy.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: agents.contrastsecurity.com/v1beta1 2 | kind: AgentInjector 3 | metadata: 4 | name: injection-dummy 5 | spec: 6 | enabled: true 7 | type: dummy 8 | selector: 9 | labels: 10 | - name: app 11 | value: injection-dummy 12 | connection: 13 | name: testing-agent-connection 14 | configuration: 15 | name: testing-agent-configuration 16 | --- 17 | apiVersion: apps/v1 18 | kind: Deployment 19 | metadata: 20 | name: injection-dummy 21 | labels: 22 | app: injection-dummy 23 | spec: 24 | replicas: 1 25 | selector: 26 | matchLabels: 27 | app: injection-dummy 28 | strategy: 29 | type: Recreate 30 | template: 31 | metadata: 32 | labels: 33 | app: injection-dummy 34 | spec: 35 | containers: 36 | - image: k8s.gcr.io/pause:3.3 37 | name: pause 38 | -------------------------------------------------------------------------------- /src/Contrast.K8s.AgentOperator/Core/Kube/KubernetesJsonSerializer.cs: -------------------------------------------------------------------------------- 1 | // Contrast Security, Inc licenses this file to you under the Apache 2.0 License. 2 | // See the LICENSE file in the project root for more information. 3 | 4 | using System.Text.Json.Nodes; 5 | using k8s; 6 | 7 | namespace Contrast.K8s.AgentOperator.Core.Kube; 8 | 9 | public class KubernetesJsonSerializer 10 | { 11 | public string SerializeObject(T entity) 12 | { 13 | return KubernetesJson.Serialize(entity); 14 | } 15 | 16 | public T DeserializeObject(string json) 17 | { 18 | return KubernetesJson.Deserialize(json); 19 | } 20 | 21 | public JsonNode? ToJsonNode(T entity) 22 | { 23 | return JsonNode.Parse(SerializeObject(entity)); 24 | } 25 | 26 | public T DeepClone(T entity) 27 | { 28 | return DeserializeObject(SerializeObject(entity)); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /security.md: -------------------------------------------------------------------------------- 1 | # Reporting Security Issues 2 | 3 | Contrast takes security vulnerabilities seriously. We appreciate your efforts to responsibly disclose your findings, and will make every effort to acknowledge your contributions. 4 | 5 | To report a security issue, please see our official [Vulnerability Disclosure Policy 6 | ](https://www.contrastsecurity.com/disclosure-policy) 7 | 8 | Contrast will send a response indicating the next steps in handling your report. After the initial reply to your report, the security team will keep you informed of the progress towards a fix and full announcement, and may ask for additional information or guidance. 9 | 10 | Report security bugs in third-party modules to the person or team maintaining the module. 11 | 12 | ## Learning More About Security 13 | 14 | To learn more about securing your applications with Contrast, please see the [our docs](https://docs.contrastsecurity.com/?lang=en). 15 | -------------------------------------------------------------------------------- /manifests/examples/testing/scenarios/namespaced/injection-php.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: agents.contrastsecurity.com/v1beta1 2 | kind: AgentInjector 3 | metadata: 4 | name: injection-php 5 | spec: 6 | enabled: true 7 | type: php 8 | selector: 9 | labels: 10 | - name: app 11 | value: injection-php 12 | image: 13 | pullPolicy: Never 14 | connection: 15 | name: testing-agent-connection 16 | configuration: 17 | name: testing-agent-configuration 18 | --- 19 | apiVersion: apps/v1 20 | kind: Deployment 21 | metadata: 22 | name: injection-php 23 | labels: 24 | app: injection-php 25 | spec: 26 | replicas: 1 27 | selector: 28 | matchLabels: 29 | app: injection-php 30 | strategy: 31 | type: Recreate 32 | template: 33 | metadata: 34 | labels: 35 | app: injection-php 36 | spec: 37 | containers: 38 | - image: k8s.gcr.io/pause:3.3 39 | name: pause 40 | -------------------------------------------------------------------------------- /manifests/examples/testing/scenarios/namespaced/injection-flex.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: agents.contrastsecurity.com/v1beta1 2 | kind: AgentInjector 3 | metadata: 4 | name: injection-flex 5 | spec: 6 | enabled: true 7 | type: flex 8 | selector: 9 | labels: 10 | - name: app 11 | value: injection-flex 12 | image: 13 | pullPolicy: Never 14 | connection: 15 | name: testing-agent-connection 16 | configuration: 17 | name: testing-agent-configuration 18 | --- 19 | apiVersion: apps/v1 20 | kind: Deployment 21 | metadata: 22 | name: injection-flex 23 | labels: 24 | app: injection-flex 25 | spec: 26 | replicas: 1 27 | selector: 28 | matchLabels: 29 | app: injection-flex 30 | strategy: 31 | type: Recreate 32 | template: 33 | metadata: 34 | labels: 35 | app: injection-flex 36 | spec: 37 | containers: 38 | - image: k8s.gcr.io/pause:3.3 39 | name: pause 40 | -------------------------------------------------------------------------------- /manifests/examples/testing/scenarios/namespaced/injection-java.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: agents.contrastsecurity.com/v1beta1 2 | kind: AgentInjector 3 | metadata: 4 | name: injection-java 5 | spec: 6 | enabled: true 7 | type: java 8 | selector: 9 | labels: 10 | - name: app 11 | value: injection-java 12 | image: 13 | pullPolicy: Never 14 | connection: 15 | name: testing-agent-connection 16 | configuration: 17 | name: testing-agent-configuration 18 | --- 19 | apiVersion: apps/v1 20 | kind: Deployment 21 | metadata: 22 | name: injection-java 23 | labels: 24 | app: injection-java 25 | spec: 26 | replicas: 1 27 | selector: 28 | matchLabels: 29 | app: injection-java 30 | strategy: 31 | type: Recreate 32 | template: 33 | metadata: 34 | labels: 35 | app: injection-java 36 | spec: 37 | containers: 38 | - image: k8s.gcr.io/pause:3.3 39 | name: pause 40 | -------------------------------------------------------------------------------- /src/Contrast.K8s.AgentOperator/Options/OperatorOptions.cs: -------------------------------------------------------------------------------- 1 | // Contrast Security, Inc licenses this file to you under the Apache 2.0 License. 2 | // See the LICENSE file in the project root for more information. 3 | 4 | using System.Threading.Channels; 5 | 6 | namespace Contrast.K8s.AgentOperator.Options; 7 | 8 | public record OperatorOptions(string Namespace, 9 | int SettlingDurationSeconds, 10 | int EventQueueSize, 11 | BoundedChannelFullMode EventQueueFullMode, 12 | int EventQueueMergeWindowSeconds, 13 | bool RunInitContainersAsNonRoot, 14 | bool SuppressSeccompProfile, 15 | bool EnableAgentStdout, 16 | decimal ChaosRatio, 17 | string FieldManagerName = "agents.contrastsecurity.com"); 18 | -------------------------------------------------------------------------------- /manifests/examples/testing/scenarios/namespaced/type-deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: agents.contrastsecurity.com/v1beta1 2 | kind: AgentInjector 3 | metadata: 4 | name: type-deployment 5 | spec: 6 | enabled: true 7 | type: dummy 8 | selector: 9 | labels: 10 | - name: app 11 | value: type-deployment 12 | image: 13 | pullPolicy: Never 14 | connection: 15 | name: testing-agent-connection 16 | configuration: 17 | name: testing-agent-configuration 18 | --- 19 | apiVersion: apps/v1 20 | kind: Deployment 21 | metadata: 22 | name: type-deployment 23 | labels: 24 | app: type-deployment 25 | spec: 26 | replicas: 1 27 | selector: 28 | matchLabels: 29 | app: type-deployment 30 | strategy: 31 | type: Recreate 32 | template: 33 | metadata: 34 | labels: 35 | app: type-deployment 36 | spec: 37 | containers: 38 | - image: k8s.gcr.io/pause:3.3 39 | name: pause 40 | -------------------------------------------------------------------------------- /manifests/examples/testing/scenarios/namespaced/injection-python.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: agents.contrastsecurity.com/v1beta1 2 | kind: AgentInjector 3 | metadata: 4 | name: injection-python 5 | spec: 6 | enabled: true 7 | type: python 8 | selector: 9 | labels: 10 | - name: app 11 | value: injection-python 12 | image: 13 | pullPolicy: Never 14 | connection: 15 | name: testing-agent-connection 16 | configuration: 17 | name: testing-agent-configuration 18 | --- 19 | apiVersion: apps/v1 20 | kind: Deployment 21 | metadata: 22 | name: injection-python 23 | labels: 24 | app: injection-python 25 | spec: 26 | replicas: 1 27 | selector: 28 | matchLabels: 29 | app: injection-python 30 | strategy: 31 | type: Recreate 32 | template: 33 | metadata: 34 | labels: 35 | app: injection-python 36 | spec: 37 | containers: 38 | - image: k8s.gcr.io/pause:3.3 39 | name: pause 40 | -------------------------------------------------------------------------------- /src/Contrast.K8s.AgentOperator/Modules/KubeOpsModule.cs: -------------------------------------------------------------------------------- 1 | // Contrast Security, Inc licenses this file to you under the Apache 2.0 License. 2 | // See the LICENSE file in the project root for more information. 3 | 4 | using Autofac; 5 | using JetBrains.Annotations; 6 | using k8s; 7 | using KubeOps.KubernetesClient; 8 | 9 | namespace Contrast.K8s.AgentOperator.Modules; 10 | 11 | [UsedImplicitly] 12 | public class KubeOpsModule : Module 13 | { 14 | protected override void Load(ContainerBuilder builder) 15 | { 16 | // These must be cached, as they parse PEM's on ctor. 17 | builder.Register(_ => KubernetesClientConfiguration.BuildDefaultConfig()).AsSelf().SingleInstance(); 18 | builder.Register(x => new KubernetesClient(x.Resolve())).As().SingleInstance(); 19 | builder.Register(x => x.Resolve().ApiClient).As().SingleInstance(); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /manifests/examples/testing/scenarios/namespaced/injection-dotnet.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: agents.contrastsecurity.com/v1beta1 2 | kind: AgentInjector 3 | metadata: 4 | name: injection-dotnet 5 | spec: 6 | enabled: true 7 | type: dotnet-core 8 | selector: 9 | labels: 10 | - name: app 11 | value: injection-dotnet 12 | image: 13 | pullPolicy: Never 14 | connection: 15 | name: testing-agent-connection 16 | configuration: 17 | name: testing-agent-configuration 18 | --- 19 | apiVersion: apps/v1 20 | kind: Deployment 21 | metadata: 22 | name: injection-dotnet 23 | labels: 24 | app: injection-dotnet 25 | spec: 26 | replicas: 1 27 | selector: 28 | matchLabels: 29 | app: injection-dotnet 30 | strategy: 31 | type: Recreate 32 | template: 33 | metadata: 34 | labels: 35 | app: injection-dotnet 36 | spec: 37 | containers: 38 | - image: k8s.gcr.io/pause:3.3 39 | name: pause 40 | -------------------------------------------------------------------------------- /manifests/examples/testing/scenarios/namespaced/custom-version.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: agents.contrastsecurity.com/v1beta1 2 | kind: AgentInjector 3 | metadata: 4 | name: custom-version 5 | spec: 6 | enabled: true 7 | type: dummy 8 | version: "1.0" 9 | selector: 10 | labels: 11 | - name: app 12 | value: custom-version 13 | image: 14 | pullPolicy: Never 15 | connection: 16 | name: testing-agent-connection 17 | configuration: 18 | name: testing-agent-configuration 19 | --- 20 | apiVersion: apps/v1 21 | kind: Deployment 22 | metadata: 23 | name: custom-version 24 | labels: 25 | app: custom-version 26 | spec: 27 | replicas: 1 28 | selector: 29 | matchLabels: 30 | app: custom-version 31 | strategy: 32 | type: Recreate 33 | template: 34 | metadata: 35 | labels: 36 | app: custom-version 37 | spec: 38 | containers: 39 | - image: k8s.gcr.io/pause:3.3 40 | name: pause 41 | -------------------------------------------------------------------------------- /docs/public/01-introduction.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | 3 | > Important 4 | > 5 | > This feature is in beta. Beta status means the feature might change or act unexpectedly. By using this feature, you agree to the [Contrast Beta Terms and Conditions](https://docs.contrastsecurity.com/en/beta-terms-and-conditions.html "Contrast Beta Terms and Conditions"). 6 | 7 | The Contrast Agent Operator is a standard [Kubernetes operator](https://kubernetes.io/docs/concepts/extend-kubernetes/operator/) that executes within Kubernetes and OpenShift clusters to automate injecting Contrast agents into existing workloads, configuring injected agents, and facilitating agent upgrades. 8 | 9 | See the [setup](./setup/01-installation.md) section to get started, or take a look at a [full example](./04-full-example.md). 10 | 11 | The operator is configured using declarative Kubernetes native resource types. Resources types are documented in the [configuration reference](./03-configuration-reference.md) section. 12 | -------------------------------------------------------------------------------- /manifests/examples/dev/sample-apps/java.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: java-app 5 | namespace: dev 6 | labels: 7 | app: java-app 8 | spec: 9 | replicas: 1 10 | revisionHistoryLimit: 1 11 | selector: 12 | matchLabels: 13 | app: java-app 14 | template: 15 | metadata: 16 | labels: 17 | app: java-app 18 | annotations: 19 | test: test 20 | spec: 21 | containers: 22 | - image: webgoat/webgoat-8.0:v8.1.0 23 | name: java-app 24 | ports: 25 | - containerPort: 8080 26 | name: http 27 | # livenessProbe: 28 | # httpGet: 29 | # path: / 30 | # port: 8080 31 | # readinessProbe: 32 | # httpGet: 33 | # path: / 34 | # port: 8080 35 | # resources: 36 | # limits: 37 | # cpu: '2' 38 | # memory: 1024M 39 | -------------------------------------------------------------------------------- /src/Contrast.K8s.AgentOperator/Core/Events/EntityCreating.cs: -------------------------------------------------------------------------------- 1 | // Contrast Security, Inc licenses this file to you under the Apache 2.0 License. 2 | // See the LICENSE file in the project root for more information. 3 | 4 | using k8s; 5 | using k8s.Models; 6 | using MediatR; 7 | 8 | namespace Contrast.K8s.AgentOperator.Core.Events; 9 | 10 | public record EntityCreating(T Entity) : IRequest> where T : IKubernetesObject; 11 | 12 | public abstract record EntityCreatingMutationResult 13 | where T : IKubernetesObject 14 | { 15 | public static NoChangeEntityCreatingMutationResult NoChange { get; } = new(); 16 | } 17 | 18 | public record NoChangeEntityCreatingMutationResult : EntityCreatingMutationResult 19 | where T : IKubernetesObject; 20 | 21 | public record NeedsChangeEntityCreatingMutationResult(T Entity) : EntityCreatingMutationResult 22 | where T : IKubernetesObject; 23 | -------------------------------------------------------------------------------- /src/Contrast.K8s.AgentOperator/Entities/Argo/V1Alpha1Rollout.cs: -------------------------------------------------------------------------------- 1 | // Contrast Security, Inc licenses this file to you under the Apache 2.0 License. 2 | // See the LICENSE file in the project root for more information. 3 | 4 | using Contrast.K8s.AgentOperator.Core.Kube; 5 | using k8s.Models; 6 | using KubeOps.Abstractions.Entities; 7 | using KubeOps.Abstractions.Entities.Attributes; 8 | using KubeOps.Abstractions.Rbac; 9 | 10 | namespace Contrast.K8s.AgentOperator.Entities.Argo; 11 | 12 | [Ignore] //Don't generate a CRD for this 13 | [KubernetesEntity(Group = "argoproj.io", ApiVersion = "v1alpha1", Kind = "Rollout", PluralName = "rollouts")] 14 | [EntityRbac(typeof(V1Alpha1Rollout), Verbs = VerbConstants.ReadAndPatch)] 15 | public partial class V1Alpha1Rollout : CustomKubernetesEntity 16 | { 17 | //Drop-in replacement for Deployment (with additional fields for the rollout info) 18 | public class RolloutSpec : V1DeploymentSpec 19 | { 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /manifests/examples/dev/sample-apps/python.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: python-app 5 | namespace: dev 6 | labels: 7 | app: python-app 8 | spec: 9 | replicas: 1 10 | revisionHistoryLimit: 1 11 | selector: 12 | matchLabels: 13 | app: python-app 14 | template: 15 | metadata: 16 | labels: 17 | app: python-app 18 | annotations: 19 | test: test 20 | spec: 21 | containers: 22 | - image: contrast/sample-app-flask 23 | name: python-app 24 | ports: 25 | - containerPort: 8000 26 | name: http 27 | # livenessProbe: 28 | # httpGet: 29 | # path: / 30 | # port: 3000 31 | # readinessProbe: 32 | # httpGet: 33 | # path: / 34 | # port: 3000 35 | # resources: 36 | # limits: 37 | # cpu: '2' 38 | # memory: 1024M 39 | -------------------------------------------------------------------------------- /src/Contrast.K8s.AgentOperator/Core/Telemetry/Client/TelemetryClientFactory.cs: -------------------------------------------------------------------------------- 1 | // Contrast Security, Inc licenses this file to you under the Apache 2.0 License. 2 | // See the LICENSE file in the project root for more information. 3 | 4 | using RestEase; 5 | 6 | namespace Contrast.K8s.AgentOperator.Core.Telemetry.Client; 7 | 8 | public interface ITelemetryClientFactory 9 | { 10 | ITelemetryClient Create(); 11 | } 12 | 13 | public class TelemetryClientFactory : ITelemetryClientFactory 14 | { 15 | private const string TelemetryUri = "https://telemetry.dotnet.contrastsecurity.com"; 16 | private readonly TelemetryState _state; 17 | 18 | public TelemetryClientFactory(TelemetryState state) 19 | { 20 | _state = state; 21 | } 22 | 23 | public ITelemetryClient Create() 24 | { 25 | var client = RestClient.For(TelemetryUri); 26 | client.UserAgent = $"AgentOperator/{_state.OperatorVersion}"; 27 | return client; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /manifests/examples/dev/sample-apps/nodejs.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: nodejs-app 5 | namespace: dev 6 | labels: 7 | app: nodejs-app 8 | spec: 9 | replicas: 1 10 | revisionHistoryLimit: 1 11 | selector: 12 | matchLabels: 13 | app: nodejs-app 14 | template: 15 | metadata: 16 | labels: 17 | app: nodejs-app 18 | annotations: 19 | test: test 20 | spec: 21 | containers: 22 | - image: contrast/sample-app-fastify-esm:latest 23 | name: nodejs-app 24 | ports: 25 | - containerPort: 3000 26 | name: http 27 | # livenessProbe: 28 | # httpGet: 29 | # path: / 30 | # port: 3000 31 | # readinessProbe: 32 | # httpGet: 33 | # path: / 34 | # port: 3000 35 | # resources: 36 | # limits: 37 | # cpu: '2' 38 | # memory: 1024M 39 | -------------------------------------------------------------------------------- /manifests/examples/testing/scenarios/namespaced/injection-nodejs-import.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: agents.contrastsecurity.com/v1beta1 2 | kind: AgentInjector 3 | metadata: 4 | name: injection-nodejs-import 5 | spec: 6 | enabled: true 7 | type: nodejs 8 | selector: 9 | labels: 10 | - name: app 11 | value: injection-nodejs-import 12 | image: 13 | pullPolicy: Never 14 | connection: 15 | name: testing-agent-connection 16 | configuration: 17 | name: testing-agent-configuration 18 | --- 19 | apiVersion: apps/v1 20 | kind: Deployment 21 | metadata: 22 | name: injection-nodejs-import 23 | labels: 24 | app: injection-nodejs-import 25 | spec: 26 | replicas: 1 27 | selector: 28 | matchLabels: 29 | app: injection-nodejs-import 30 | strategy: 31 | type: Recreate 32 | template: 33 | metadata: 34 | labels: 35 | app: injection-nodejs-import 36 | spec: 37 | containers: 38 | - image: k8s.gcr.io/pause:3.3 39 | name: pause 40 | -------------------------------------------------------------------------------- /manifests/examples/testing/scenarios/restricted-policy/injection-restricted.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: injection-restricted 5 | labels: 6 | app: injection-restricted 7 | spec: 8 | replicas: 1 9 | selector: 10 | matchLabels: 11 | app: injection-restricted 12 | strategy: 13 | type: Recreate 14 | template: 15 | metadata: 16 | labels: 17 | app: injection-restricted 18 | spec: 19 | containers: 20 | - image: contrast/sample-app-aspnetcore:latest 21 | name: dotnet 22 | resources: 23 | limits: 24 | cpu: 100m 25 | memory: 100Mi 26 | securityContext: 27 | allowPrivilegeEscalation: false 28 | capabilities: 29 | drop: 30 | - ALL 31 | runAsUser: 1001 32 | runAsGroup: 1001 33 | runAsNonRoot: true 34 | seccompProfile: 35 | type: RuntimeDefault 36 | -------------------------------------------------------------------------------- /src/Contrast.K8s.AgentOperator/Core/State/StateSettledHandler.cs: -------------------------------------------------------------------------------- 1 | // Contrast Security, Inc licenses this file to you under the Apache 2.0 License. 2 | // See the LICENSE file in the project root for more information. 3 | 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using Contrast.K8s.AgentOperator.Core.Events; 7 | using MediatR; 8 | 9 | namespace Contrast.K8s.AgentOperator.Core.State; 10 | 11 | public class StateSettledHandler : INotificationHandler 12 | { 13 | private readonly IMediator _mediator; 14 | private readonly IStateContainer _state; 15 | 16 | public StateSettledHandler(IMediator mediator, IStateContainer state) 17 | { 18 | _mediator = mediator; 19 | _state = state; 20 | } 21 | 22 | public async Task Handle(StateSettled notification, CancellationToken cancellationToken) 23 | { 24 | await _state.Settled(cancellationToken); 25 | await _mediator.Publish(new StateModified(), cancellationToken); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /manifests/examples/dev/sample-apps/dotnet-core.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: dotnet-core-app 5 | namespace: dev 6 | labels: 7 | app: dotnet-core-app 8 | spec: 9 | replicas: 1 10 | revisionHistoryLimit: 1 11 | selector: 12 | matchLabels: 13 | app: dotnet-core-app 14 | template: 15 | metadata: 16 | labels: 17 | app: dotnet-core-app 18 | annotations: 19 | test: test 20 | spec: 21 | containers: 22 | - image: contrast/sample-app-aspnetcore:latest 23 | name: dotnet-core-app 24 | ports: 25 | - containerPort: 80 26 | name: http 27 | # livenessProbe: 28 | # httpGet: 29 | # path: / 30 | # port: 80 31 | # readinessProbe: 32 | # httpGet: 33 | # path: / 34 | # port: 80 35 | resources: 36 | limits: 37 | cpu: '1' 38 | memory: 1024M 39 | -------------------------------------------------------------------------------- /manifests/examples/testing/scenarios/namespaced/injection-nodejs-require.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: agents.contrastsecurity.com/v1beta1 2 | kind: AgentInjector 3 | metadata: 4 | name: injection-nodejs-require 5 | spec: 6 | enabled: true 7 | type: nodejs-legacy 8 | selector: 9 | labels: 10 | - name: app 11 | value: injection-nodejs-require 12 | image: 13 | pullPolicy: Never 14 | connection: 15 | name: testing-agent-connection 16 | configuration: 17 | name: testing-agent-configuration 18 | --- 19 | apiVersion: apps/v1 20 | kind: Deployment 21 | metadata: 22 | name: injection-nodejs-require 23 | labels: 24 | app: injection-nodejs-require 25 | spec: 26 | replicas: 1 27 | selector: 28 | matchLabels: 29 | app: injection-nodejs-require 30 | strategy: 31 | type: Recreate 32 | template: 33 | metadata: 34 | labels: 35 | app: injection-nodejs-require 36 | spec: 37 | containers: 38 | - image: k8s.gcr.io/pause:3.3 39 | name: pause 40 | -------------------------------------------------------------------------------- /manifests/examples/testing/scenarios/namespaced/chaining-flex.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: agents.contrastsecurity.com/v1beta1 2 | kind: AgentInjector 3 | metadata: 4 | name: chaining-flex 5 | spec: 6 | enabled: true 7 | type: flex 8 | selector: 9 | labels: 10 | - name: app 11 | value: chaining-flex 12 | image: 13 | pullPolicy: Never 14 | connection: 15 | name: testing-agent-connection 16 | configuration: 17 | name: testing-agent-configuration 18 | --- 19 | apiVersion: apps/v1 20 | kind: Deployment 21 | metadata: 22 | name: chaining-flex 23 | labels: 24 | app: chaining-flex 25 | spec: 26 | replicas: 1 27 | selector: 28 | matchLabels: 29 | app: chaining-flex 30 | strategy: 31 | type: Recreate 32 | template: 33 | metadata: 34 | labels: 35 | app: chaining-flex 36 | spec: 37 | containers: 38 | - image: k8s.gcr.io/pause:3.3 39 | name: pause 40 | env: 41 | - name: LD_PRELOAD 42 | value: something 43 | -------------------------------------------------------------------------------- /manifests/examples/testing/scenarios/namespaced/chaining-java.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: agents.contrastsecurity.com/v1beta1 2 | kind: AgentInjector 3 | metadata: 4 | name: chaining-java 5 | spec: 6 | enabled: true 7 | type: java 8 | selector: 9 | labels: 10 | - name: app 11 | value: chaining-java 12 | image: 13 | pullPolicy: Never 14 | connection: 15 | name: testing-agent-connection 16 | configuration: 17 | name: testing-agent-configuration 18 | --- 19 | apiVersion: apps/v1 20 | kind: Deployment 21 | metadata: 22 | name: chaining-java 23 | labels: 24 | app: chaining-java 25 | spec: 26 | replicas: 1 27 | selector: 28 | matchLabels: 29 | app: chaining-java 30 | strategy: 31 | type: Recreate 32 | template: 33 | metadata: 34 | labels: 35 | app: chaining-java 36 | spec: 37 | containers: 38 | - image: k8s.gcr.io/pause:3.3 39 | name: pause 40 | env: 41 | - name: JAVA_TOOL_OPTIONS 42 | value: something 43 | -------------------------------------------------------------------------------- /manifests/examples/dev/sample-apps/nodejs-legacy.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: nodejs-legacy-app 5 | namespace: dev 6 | labels: 7 | app: nodejs-legacy-app 8 | spec: 9 | replicas: 1 10 | revisionHistoryLimit: 1 11 | selector: 12 | matchLabels: 13 | app: nodejs-legacy-app 14 | template: 15 | metadata: 16 | labels: 17 | app: nodejs-legacy-app 18 | annotations: 19 | test: test 20 | spec: 21 | containers: 22 | - image: bkimminich/juice-shop:v14.0.0 23 | name: nodejs-legacy-app 24 | ports: 25 | - containerPort: 3000 26 | name: http 27 | # livenessProbe: 28 | # httpGet: 29 | # path: / 30 | # port: 3000 31 | # readinessProbe: 32 | # httpGet: 33 | # path: / 34 | # port: 3000 35 | # resources: 36 | # limits: 37 | # cpu: '2' 38 | # memory: 1024M 39 | -------------------------------------------------------------------------------- /manifests/examples/testing/scenarios/namespaced/chaining-python.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: agents.contrastsecurity.com/v1beta1 2 | kind: AgentInjector 3 | metadata: 4 | name: chaining-python 5 | spec: 6 | enabled: true 7 | type: python 8 | selector: 9 | labels: 10 | - name: app 11 | value: chaining-python 12 | image: 13 | pullPolicy: Never 14 | connection: 15 | name: testing-agent-connection 16 | configuration: 17 | name: testing-agent-configuration 18 | --- 19 | apiVersion: apps/v1 20 | kind: Deployment 21 | metadata: 22 | name: chaining-python 23 | labels: 24 | app: chaining-python 25 | spec: 26 | replicas: 1 27 | selector: 28 | matchLabels: 29 | app: chaining-python 30 | strategy: 31 | type: Recreate 32 | template: 33 | metadata: 34 | labels: 35 | app: chaining-python 36 | spec: 37 | containers: 38 | - image: k8s.gcr.io/pause:3.3 39 | name: pause 40 | env: 41 | - name: PYTHONPATH 42 | value: something 43 | -------------------------------------------------------------------------------- /src/Contrast.K8s.AgentOperator/Core/Events/StateModified.cs: -------------------------------------------------------------------------------- 1 | // Contrast Security, Inc licenses this file to you under the Apache 2.0 License. 2 | // See the LICENSE file in the project root for more information. 3 | 4 | using Contrast.K8s.AgentOperator.Core.State.Resources.Interfaces; 5 | using MediatR; 6 | 7 | namespace Contrast.K8s.AgentOperator.Core.Events; 8 | 9 | public record StateModified(TResource? Previous, TResource? Current) : StateModified where TResource : INamespacedResource; 10 | 11 | public record StateModified : INotification 12 | { 13 | public static StateModified Create(TResource? previous, 14 | TResource? current) 15 | where TResource : INamespacedResource 16 | { 17 | return new StateModified(previous, current); 18 | } 19 | } 20 | 21 | public record DeferredStateModified(int MergedChanges) : INotification 22 | { 23 | public static DeferredStateModified FirstMerged { get; } = new(1); 24 | } 25 | -------------------------------------------------------------------------------- /manifests/examples/dev/sample-apps/cluster-dotnet-core.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: cluster-dotnet-core 5 | namespace: dev 6 | labels: 7 | app: cluster-dotnet-core 8 | spec: 9 | replicas: 1 10 | revisionHistoryLimit: 1 11 | selector: 12 | matchLabels: 13 | app: cluster-dotnet-core 14 | template: 15 | metadata: 16 | labels: 17 | app: cluster-dotnet-core 18 | annotations: 19 | test: test 20 | spec: 21 | containers: 22 | - image: contrast/sample-app-aspnetcore:latest 23 | name: cluster-dotnet-core 24 | ports: 25 | - containerPort: 80 26 | name: http 27 | # livenessProbe: 28 | # httpGet: 29 | # path: / 30 | # port: 80 31 | # readinessProbe: 32 | # httpGet: 33 | # path: / 34 | # port: 80 35 | resources: 36 | limits: 37 | cpu: '1' 38 | memory: 1024M 39 | -------------------------------------------------------------------------------- /manifests/examples/testing/scenarios/namespaced/chaining-dotnet.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: agents.contrastsecurity.com/v1beta1 2 | kind: AgentInjector 3 | metadata: 4 | name: chaining-dotnet 5 | spec: 6 | enabled: true 7 | type: dotnet-core 8 | selector: 9 | labels: 10 | - name: app 11 | value: chaining-dotnet 12 | image: 13 | pullPolicy: Never 14 | connection: 15 | name: testing-agent-connection 16 | configuration: 17 | name: testing-agent-configuration 18 | --- 19 | apiVersion: apps/v1 20 | kind: Deployment 21 | metadata: 22 | name: chaining-dotnet 23 | labels: 24 | app: chaining-dotnet 25 | spec: 26 | replicas: 1 27 | selector: 28 | matchLabels: 29 | app: chaining-dotnet 30 | strategy: 31 | type: Recreate 32 | template: 33 | metadata: 34 | labels: 35 | app: chaining-dotnet 36 | spec: 37 | containers: 38 | - image: k8s.gcr.io/pause:3.3 39 | name: pause 40 | env: 41 | - name: LD_PRELOAD 42 | value: something 43 | -------------------------------------------------------------------------------- /tests/performance-tests/Contrast.K8s.AgentOperator.Performance.ClusterFaker/Program.cs: -------------------------------------------------------------------------------- 1 | // Contrast Security, Inc licenses this file to you under the Apache 2.0 License. 2 | // See the LICENSE file in the project root for more information. 3 | 4 | using System.Threading.Tasks; 5 | using CommandLine; 6 | using KubeOps.KubernetesClient; 7 | using static Contrast.K8s.AgentOperator.Performance.ClusterFaker.Options; 8 | 9 | namespace Contrast.K8s.AgentOperator.Performance.ClusterFaker 10 | { 11 | internal class Program 12 | { 13 | private static async Task Main(string[] args) 14 | { 15 | var faker = new Faker(new KubernetesClient()); 16 | return await Parser.Default.ParseArguments(args) 17 | .MapResult( 18 | (UpOptions opts) => faker.Up(opts), 19 | (DownOptions opts) => faker.Down(opts), 20 | errs => Task.FromResult(1)); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Contrast.K8s.AgentOperator/Core/HexConverter.cs: -------------------------------------------------------------------------------- 1 | // Contrast Security, Inc licenses this file to you under the Apache 2.0 License. 2 | // See the LICENSE file in the project root for more information. 3 | 4 | using System.Text; 5 | using HexMate; 6 | 7 | namespace Contrast.K8s.AgentOperator.Core; 8 | 9 | public static class HexConverter 10 | { 11 | public static string ToLowerHex(byte[] bytes) 12 | { 13 | return Convert.ToHexString(bytes, HexFormattingOptions.Lowercase); 14 | } 15 | 16 | public static string ToLowerHex(byte[] bytes, int length) 17 | { 18 | // This returns null bytes for some reason... 19 | //return Convert.ToHexString(bytes, 0, length, HexFormattingOptions.Lowercase); 20 | 21 | var hashString = new StringBuilder(); 22 | for (var index = 0; index < bytes.Length && hashString.Length <= length; index++) 23 | { 24 | var x = bytes[index]; 25 | hashString.Append($"{x:x2}"); 26 | } 27 | 28 | return hashString.ToString(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /manifests/examples/testing/scenarios/namespaced/kustomization.yaml: -------------------------------------------------------------------------------- 1 | namespace: testing 2 | resources: 3 | - ./shared/testing-agent-configuration.yaml 4 | - ./shared/testing-agent-connection-secret.yaml 5 | - ./shared/testing-agent-connection.yaml 6 | - ./config-optional.yaml 7 | - ./connection-volume-mount.yaml 8 | - ./custom-images.yaml 9 | - ./custom-version.yaml 10 | - ./enabled-flag.yaml 11 | - ./glob-selecting.yaml 12 | - ./init-container-overrides.yaml 13 | - ./injection-dotnet.yaml 14 | - ./injection-dummy.yaml 15 | - ./injection-java.yaml 16 | - ./injection-nodejs-require.yaml 17 | - ./injection-nodejs-import.yaml 18 | - ./injection-php.yaml 19 | - ./injection-python.yaml 20 | - ./injection-flex.yaml 21 | - ./missing-deps.yaml 22 | - ./multiple-images.yaml 23 | - ./namespace.yaml 24 | - ./type-daemonset.yaml 25 | - ./type-deployment.yaml 26 | - ./type-statefulset.yaml 27 | - ./unmatched.yaml 28 | - ./chaining-dotnet.yaml 29 | - ./chaining-flex.yaml 30 | - ./chaining-java.yaml 31 | - ./chaining-python.yaml 32 | -------------------------------------------------------------------------------- /src/Contrast.K8s.AgentOperator/Core/Reactions/Matching/GlobMatcher.cs: -------------------------------------------------------------------------------- 1 | // Contrast Security, Inc licenses this file to you under the Apache 2.0 License. 2 | // See the LICENSE file in the project root for more information. 3 | 4 | using System.Collections.Concurrent; 5 | using GlobExpressions; 6 | using NLog; 7 | 8 | namespace Contrast.K8s.AgentOperator.Core.Reactions.Matching; 9 | 10 | public interface IGlobMatcher 11 | { 12 | bool Matches(string pattern, string value); 13 | } 14 | 15 | public class GlobMatcher : IGlobMatcher 16 | { 17 | private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); 18 | 19 | private readonly ConcurrentDictionary _cache = new(); 20 | 21 | public bool Matches(string pattern, string value) 22 | { 23 | var glob = _cache.GetOrAdd(pattern, s => 24 | { 25 | Logger.Trace($"Compiling glob pattern '{s}'."); 26 | return new Glob(s, GlobOptions.CaseInsensitive | GlobOptions.Compiled); 27 | }); 28 | return glob.IsMatch(value); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Contrast.K8s.AgentOperator/Core/Telemetry/Helpers/Sha256Hasher.cs: -------------------------------------------------------------------------------- 1 | // Contrast Security, Inc licenses this file to you under the Apache 2.0 License. 2 | // See the LICENSE file in the project root for more information. 3 | 4 | #nullable enable 5 | 6 | using System.Security.Cryptography; 7 | using System.Text; 8 | 9 | namespace Contrast.K8s.AgentOperator.Core.Telemetry.Helpers; 10 | 11 | public class Sha256Hasher 12 | { 13 | // Super lazy, copied from the CoreFX repo. 14 | 15 | public string Hash(string text) 16 | { 17 | using var sha256 = SHA256.Create(); 18 | return HashInFormat(sha256, text); 19 | } 20 | 21 | private static string HashInFormat(HashAlgorithm algorithm, string text) 22 | { 23 | var bytes = Encoding.UTF8.GetBytes(text); 24 | var hash = algorithm.ComputeHash(bytes); 25 | var hashString = new StringBuilder(); 26 | foreach (var x in hash) 27 | { 28 | hashString.AppendFormat("{0:x2}", x); 29 | } 30 | 31 | return hashString.ToString(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /manifests/examples/testing/scenarios/config-variables/yaml-variables.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: yaml-variables 5 | labels: 6 | app: yaml-variables 7 | spec: 8 | replicas: 1 9 | selector: 10 | matchLabels: 11 | app: yaml-variables 12 | strategy: 13 | type: Recreate 14 | template: 15 | metadata: 16 | labels: 17 | app: yaml-variables 18 | test-label: "hello" 19 | annotations: 20 | test-annotation: "world" 21 | spec: 22 | containers: 23 | - image: contrast/sample-app-aspnetcore:latest 24 | name: dotnet-test 25 | resources: 26 | limits: 27 | cpu: 100m 28 | memory: 100Mi 29 | securityContext: 30 | allowPrivilegeEscalation: false 31 | capabilities: 32 | drop: 33 | - ALL 34 | runAsUser: 1001 35 | runAsGroup: 1001 36 | runAsNonRoot: true 37 | seccompProfile: 38 | type: RuntimeDefault 39 | -------------------------------------------------------------------------------- /manifests/examples/dev/sample-apps/php.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: php-app 5 | namespace: dev 6 | labels: 7 | app: php-app 8 | spec: 9 | replicas: 1 10 | revisionHistoryLimit: 1 11 | selector: 12 | matchLabels: 13 | app: php-app 14 | template: 15 | metadata: 16 | labels: 17 | app: php-app 18 | annotations: 19 | test: test 20 | spec: 21 | containers: 22 | - image: contrast/sample-app-php 23 | name: php-app 24 | ports: 25 | - containerPort: 80 26 | name: http 27 | env: 28 | - name: CONTRAST__AGENT__LOGGER__PATH 29 | value: /tmp/contrast.log 30 | # livenessProbe: 31 | # httpGet: 32 | # path: / 33 | # port: 80 34 | # readinessProbe: 35 | # httpGet: 36 | # path: / 37 | # port: 80 38 | # resources: 39 | # limits: 40 | # cpu: '2' 41 | # memory: 1024M 42 | -------------------------------------------------------------------------------- /src/Contrast.K8s.AgentOperator/Core/Telemetry/Client/SubmitMeasurementPayload.cs: -------------------------------------------------------------------------------- 1 | // Contrast Security, Inc licenses this file to you under the Apache 2.0 License. 2 | // See the LICENSE file in the project root for more information. 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using Newtonsoft.Json; 7 | 8 | namespace Contrast.K8s.AgentOperator.Core.Telemetry.Client; 9 | 10 | public class SubmitMeasurementPayLoad 11 | { 12 | [JsonProperty("timestamp")] 13 | public DateTimeOffset Timestamp { get; set; } 14 | 15 | [JsonProperty("instance")] 16 | public string Instance { get; set; } 17 | 18 | [JsonProperty("tags")] 19 | public IReadOnlyDictionary Tags { get; set; } = new Dictionary(); 20 | 21 | [JsonProperty("fields")] 22 | public IReadOnlyDictionary Fields { get; set; } = new Dictionary(); 23 | 24 | public SubmitMeasurementPayLoad(DateTimeOffset timestamp, string instance) 25 | { 26 | Timestamp = timestamp; 27 | Instance = instance; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /manifests/examples/dev/sample-apps/flex.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: flex-app 5 | namespace: dev 6 | labels: 7 | app: flex-app 8 | spec: 9 | replicas: 2 10 | revisionHistoryLimit: 1 11 | selector: 12 | matchLabels: 13 | app: flex-app 14 | template: 15 | metadata: 16 | labels: 17 | app: flex-app 18 | annotations: 19 | test: test 20 | spec: 21 | containers: 22 | - image: contrast/sample-app-aspnetcore:latest 23 | name: flex-app 24 | env: 25 | - name: "CONTRAST_FLEX_INJECTOR_LOG_LEVEL" 26 | value: "TRACE" 27 | ports: 28 | - containerPort: 80 29 | name: http 30 | # livenessProbe: 31 | # httpGet: 32 | # path: / 33 | # port: 80 34 | # readinessProbe: 35 | # httpGet: 36 | # path: / 37 | # port: 80 38 | resources: 39 | limits: 40 | cpu: '1' 41 | memory: 1024M 42 | -------------------------------------------------------------------------------- /src/Contrast.K8s.AgentOperator/Core/Reactions/Injecting/Patching/Agents/PhpAgentPatcher.cs: -------------------------------------------------------------------------------- 1 | // Contrast Security, Inc licenses this file to you under the Apache 2.0 License. 2 | // See the LICENSE file in the project root for more information. 3 | 4 | using System.Collections.Generic; 5 | using Contrast.K8s.AgentOperator.Core.State.Resources.Primitives; 6 | using k8s.Models; 7 | 8 | namespace Contrast.K8s.AgentOperator.Core.Reactions.Injecting.Patching.Agents; 9 | 10 | public class PhpAgentPatcher : IAgentPatcher 11 | { 12 | public AgentInjectionType Type => AgentInjectionType.Php; 13 | 14 | public IEnumerable GenerateEnvVars(PatchingContext context) 15 | { 16 | yield return new V1EnvVar("PHP_INI_SCAN_DIR", $":{context.AgentMountPath}/ini/"); 17 | yield return new V1EnvVar("CONTRAST__AGENT__LOGGER__PATH", $"{context.WritableMountPath}/logs/contrast_agent.log"); 18 | yield return new V1EnvVar("CONTRAST_INSTALLATION_TOOL", "KUBERNETES_OPERATOR"); 19 | } 20 | 21 | public string GetOverrideAgentMountPath() => "/usr/local/lib/contrast/php"; 22 | } 23 | -------------------------------------------------------------------------------- /src/Contrast.K8s.AgentOperator/Core/State/TickChangedWorker.cs: -------------------------------------------------------------------------------- 1 | // Contrast Security, Inc licenses this file to you under the Apache 2.0 License. 2 | // See the LICENSE file in the project root for more information. 3 | 4 | using System; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | using Contrast.K8s.AgentOperator.Core.Events; 8 | using Microsoft.Extensions.Hosting; 9 | 10 | namespace Contrast.K8s.AgentOperator.Core.State; 11 | 12 | public class TickChangedWorker : BackgroundService 13 | { 14 | private readonly IEventStream _eventStream; 15 | 16 | public TickChangedWorker(IEventStream eventStream) 17 | { 18 | _eventStream = eventStream; 19 | } 20 | 21 | protected override async Task ExecuteAsync(CancellationToken stoppingToken) 22 | { 23 | var tickDelay = TimeSpan.FromSeconds(1); 24 | while (!stoppingToken.IsCancellationRequested) 25 | { 26 | await Task.Delay(tickDelay, stoppingToken); 27 | await _eventStream.DispatchDeferred(TickChanged.Instance, stoppingToken); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /manifests/install/all/operator/base/webhook.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: admissionregistration.k8s.io/v1 2 | kind: MutatingWebhookConfiguration 3 | metadata: 4 | name: contrast-web-hook-configuration 5 | labels: 6 | app.kubernetes.io/name: operator 7 | app.kubernetes.io/part-of: contrast-agent-operator 8 | webhooks: 9 | - name: pods.agents.contrastsecurity.com 10 | reinvocationPolicy: IfNeeded 11 | failurePolicy: Ignore 12 | timeoutSeconds: 2 13 | namespaceSelector: 14 | matchExpressions: 15 | - key: kubernetes.io/metadata.name 16 | operator: NotIn 17 | values: 18 | - kube-system 19 | - kube-node-lease 20 | matchPolicy: Equivalent 21 | rules: 22 | - apiGroups: [ "" ] 23 | apiVersions: [ "v1" ] 24 | operations: [ "CREATE" ] 25 | resources: [ "pods" ] 26 | scope: Namespaced 27 | clientConfig: 28 | service: 29 | name: contrast-agent-operator 30 | namespace: contrast-agent-operator 31 | path: /mutate/v1pod 32 | admissionReviewVersions: [ "v1" ] 33 | sideEffects: None 34 | -------------------------------------------------------------------------------- /src/Contrast.K8s.AgentOperator/Core/Telemetry/Getters/MachineIdGetter.cs: -------------------------------------------------------------------------------- 1 | // Contrast Security, Inc licenses this file to you under the Apache 2.0 License. 2 | // See the LICENSE file in the project root for more information. 3 | 4 | #nullable enable 5 | 6 | using System; 7 | using Contrast.K8s.AgentOperator.Core.Telemetry.Cluster; 8 | using Contrast.K8s.AgentOperator.Core.Telemetry.Helpers; 9 | 10 | namespace Contrast.K8s.AgentOperator.Core.Telemetry.Getters; 11 | 12 | public class MachineIdGetter 13 | { 14 | private readonly IClusterIdState _clusterIdState; 15 | private readonly Sha256Hasher _hasher; 16 | 17 | public MachineIdGetter(IClusterIdState clusterIdState, Sha256Hasher hasher) 18 | { 19 | _clusterIdState = clusterIdState; 20 | _hasher = hasher; 21 | } 22 | 23 | public string GetMachineId() 24 | { 25 | var uniqueId = _clusterIdState.GetClusterId()?.ToString(); 26 | if (uniqueId != null) 27 | { 28 | return _hasher.Hash(uniqueId); 29 | } 30 | 31 | return "_" + Guid.NewGuid().ToString("N"); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Contrast.K8s.AgentOperator/Entities/BuiltinEntityRbac.cs: -------------------------------------------------------------------------------- 1 | // Contrast Security, Inc licenses this file to you under the Apache 2.0 License. 2 | // See the LICENSE file in the project root for more information. 3 | 4 | using Contrast.K8s.AgentOperator.Core.Kube; 5 | using JetBrains.Annotations; 6 | using k8s.Models; 7 | using KubeOps.Abstractions.Rbac; 8 | 9 | namespace Contrast.K8s.AgentOperator.Entities 10 | { 11 | //Dummy entity so rbac for builtin models generates correctly 12 | [EntityRbac(typeof(V1DaemonSet), Verbs = VerbConstants.ReadAndPatch)] 13 | [EntityRbac(typeof(V1Deployment), Verbs = VerbConstants.ReadAndPatch)] 14 | [EntityRbac(typeof(V1Pod), Verbs = VerbConstants.ReadAndPatch)] 15 | [EntityRbac(typeof(V1Secret), Verbs = VerbConstants.All)] 16 | [EntityRbac(typeof(V1MutatingWebhookConfiguration), Verbs = VerbConstants.ReadAndPatch)] 17 | [EntityRbac(typeof(V1StatefulSet), Verbs = VerbConstants.ReadAndPatch)] 18 | [EntityRbac(typeof(V1Namespace), Verbs = VerbConstants.ReadOnly)] 19 | [UsedImplicitly] 20 | public class BuiltinEntityRbac 21 | { 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Contrast.K8s.AgentOperator/Core/Tls/TlsHelper.cs: -------------------------------------------------------------------------------- 1 | // Contrast Security, Inc licenses this file to you under the Apache 2.0 License. 2 | // See the LICENSE file in the project root for more information. 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Security.Cryptography; 8 | using System.Text; 9 | 10 | namespace Contrast.K8s.AgentOperator.Core.Tls; 11 | 12 | public static class TlsHelper 13 | { 14 | public static byte[] GenerateSansHash(IEnumerable sans) 15 | { 16 | // This string needs to be stable. 17 | var normalizedList = sans.Select(x=>x.ToLowerInvariant()) 18 | .DistinctBy(x => x, StringComparer.Ordinal) 19 | .OrderBy(x => x); 20 | 21 | var sansStr = string.Join(";", normalizedList); 22 | return Sha256(sansStr); 23 | } 24 | 25 | private static byte[] Sha256(string text) 26 | { 27 | using var sha256 = SHA256.Create(); 28 | var bytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(text)); 29 | return bytes; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /manifests/examples/testing/scenarios/namespaced/glob-selecting.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: agents.contrastsecurity.com/v1beta1 2 | kind: AgentInjector 3 | metadata: 4 | name: glob-selecting 5 | spec: 6 | enabled: true 7 | type: dummy 8 | selector: 9 | images: 10 | - b* 11 | labels: 12 | - name: app 13 | value: glob-select* 14 | image: 15 | pullPolicy: Never 16 | connection: 17 | name: testing-agent-connection 18 | configuration: 19 | name: testing-agent-configuration 20 | --- 21 | apiVersion: apps/v1 22 | kind: Deployment 23 | metadata: 24 | name: glob-selecting 25 | labels: 26 | app: glob-selecting 27 | spec: 28 | replicas: 1 29 | selector: 30 | matchLabels: 31 | app: glob-selecting 32 | strategy: 33 | type: Recreate 34 | template: 35 | metadata: 36 | labels: 37 | app: glob-selecting 38 | spec: 39 | containers: 40 | - image: busybox:stable 41 | name: busybox 42 | command: [ "/bin/sh", "-c", "--" ] 43 | args: [ "sleep infinity" ] 44 | - image: k8s.gcr.io/pause:3.3 45 | name: ignore 46 | -------------------------------------------------------------------------------- /src/Contrast.K8s.AgentOperator/Core/Telemetry/Counters/PerformanceCountersWorker.cs: -------------------------------------------------------------------------------- 1 | // Contrast Security, Inc licenses this file to you under the Apache 2.0 License. 2 | // See the LICENSE file in the project root for more information. 3 | 4 | using System; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | using JetBrains.Annotations; 8 | using Microsoft.Extensions.Hosting; 9 | 10 | namespace Contrast.K8s.AgentOperator.Core.Telemetry.Counters; 11 | 12 | [UsedImplicitly] 13 | public class PerformanceCountersWorker : BackgroundService 14 | { 15 | private readonly Func _performanceCountersListenerFactory; 16 | 17 | public PerformanceCountersWorker(Func performanceCountersListenerFactory) 18 | { 19 | _performanceCountersListenerFactory = performanceCountersListenerFactory; 20 | } 21 | 22 | protected override async Task ExecuteAsync(CancellationToken stoppingToken) 23 | { 24 | using var listener = _performanceCountersListenerFactory.Invoke(); 25 | await Task.Delay(Timeout.Infinite, stoppingToken); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /manifests/examples/testing/scenarios/namespaced/multiple-images.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: agents.contrastsecurity.com/v1beta1 2 | kind: AgentInjector 3 | metadata: 4 | name: multiple-images 5 | spec: 6 | enabled: true 7 | type: dummy 8 | selector: 9 | images: 10 | - busybox:stable 11 | labels: 12 | - name: app 13 | value: multiple-images 14 | image: 15 | pullPolicy: Never 16 | connection: 17 | name: testing-agent-connection 18 | configuration: 19 | name: testing-agent-configuration 20 | --- 21 | apiVersion: apps/v1 22 | kind: Deployment 23 | metadata: 24 | name: multiple-images 25 | labels: 26 | app: multiple-images 27 | spec: 28 | replicas: 1 29 | selector: 30 | matchLabels: 31 | app: multiple-images 32 | strategy: 33 | type: Recreate 34 | template: 35 | metadata: 36 | labels: 37 | app: multiple-images 38 | spec: 39 | containers: 40 | - image: busybox:stable 41 | name: busybox 42 | command: [ "/bin/sh", "-c", "--" ] 43 | args: [ "sleep infinity" ] 44 | - image: k8s.gcr.io/pause:3.3 45 | name: pause 46 | -------------------------------------------------------------------------------- /src/Contrast.K8s.AgentOperator/Core/Comparing/FastComparerHelper.cs: -------------------------------------------------------------------------------- 1 | // Contrast Security, Inc licenses this file to you under the Apache 2.0 License. 2 | // See the LICENSE file in the project root for more information. 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | 7 | namespace Contrast.K8s.AgentOperator.Core.Comparing; 8 | 9 | public static class FastComparerHelper 10 | { 11 | private static readonly HashSet PrimitiveTypes = new() 12 | { 13 | typeof(decimal), 14 | typeof(string), 15 | typeof(DateTime), 16 | typeof(DateTimeOffset), 17 | typeof(Guid), 18 | }; 19 | 20 | public static bool IsPrimitive(Type type) 21 | { 22 | return type.IsPrimitive 23 | || type.IsEnum 24 | || PrimitiveTypes.Contains(type) 25 | || IsNullablePrimitive(type); 26 | } 27 | 28 | private static bool IsNullablePrimitive(Type type) 29 | { 30 | return type.IsGenericType 31 | && type.GetGenericTypeDefinition() == typeof(Nullable<>) 32 | && IsPrimitive(type.GetGenericArguments()[0]); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /manifests/examples/dev/sample-apps/dotnet-core-chaining.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: dotnet-core-chaining-app 5 | namespace: dev 6 | labels: 7 | app: dotnet-core-chaining-app 8 | spec: 9 | replicas: 1 10 | revisionHistoryLimit: 1 11 | selector: 12 | matchLabels: 13 | app: dotnet-core-chaining-app 14 | template: 15 | metadata: 16 | labels: 17 | app: dotnet-core-chaining-app 18 | annotations: 19 | test: test 20 | spec: 21 | containers: 22 | - image: contrast/sample-app-aspnetcore:latest 23 | name: dotnet-core-chaining-app 24 | env: 25 | - name: LD_PRELOAD 26 | value: "/foobar.so" 27 | ports: 28 | - containerPort: 80 29 | name: http 30 | # livenessProbe: 31 | # httpGet: 32 | # path: / 33 | # port: 80 34 | # readinessProbe: 35 | # httpGet: 36 | # path: / 37 | # port: 80 38 | # resources: 39 | # limits: 40 | # cpu: '2' 41 | # memory: 1024M 42 | -------------------------------------------------------------------------------- /src/Contrast.K8s.AgentOperator/Core/Reactions/ReactionHelper.cs: -------------------------------------------------------------------------------- 1 | // Contrast Security, Inc licenses this file to you under the Apache 2.0 License. 2 | // See the LICENSE file in the project root for more information. 3 | 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using Contrast.K8s.AgentOperator.Core.Leading; 7 | using Contrast.K8s.AgentOperator.Core.State; 8 | 9 | namespace Contrast.K8s.AgentOperator.Core.Reactions; 10 | 11 | public interface IReactionHelper 12 | { 13 | ValueTask CanReact(CancellationToken cancellationToken = default); 14 | } 15 | 16 | public class ReactionHelper : IReactionHelper 17 | { 18 | private readonly ILeaderElectionState _electionState; 19 | private readonly IStateContainer _state; 20 | 21 | public ReactionHelper(ILeaderElectionState electionState, IStateContainer state) 22 | { 23 | _electionState = electionState; 24 | _state = state; 25 | } 26 | 27 | public async ValueTask CanReact(CancellationToken cancellationToken = default) 28 | { 29 | return _electionState.IsLeader() 30 | && await _state.GetHasSettled(cancellationToken); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /manifests/helm/templates/operator/webhook.yaml.tpl: -------------------------------------------------------------------------------- 1 | {{ if ne .Values.operator.enabled false }} 2 | apiVersion: admissionregistration.k8s.io/v1 3 | kind: MutatingWebhookConfiguration 4 | metadata: 5 | name: contrast-web-hook-configuration 6 | labels: 7 | app.kubernetes.io/name: operator 8 | app.kubernetes.io/part-of: contrast-agent-operator 9 | webhooks: 10 | - name: pods.agents.contrastsecurity.com 11 | reinvocationPolicy: IfNeeded 12 | failurePolicy: Ignore 13 | timeoutSeconds: 2 14 | namespaceSelector: 15 | matchExpressions: 16 | - key: kubernetes.io/metadata.name 17 | operator: NotIn 18 | values: 19 | - kube-system 20 | - kube-node-lease 21 | matchPolicy: Equivalent 22 | rules: 23 | - apiGroups: [ "" ] 24 | apiVersions: [ "v1" ] 25 | operations: [ "CREATE" ] 26 | resources: [ "pods" ] 27 | scope: Namespaced 28 | clientConfig: 29 | service: 30 | name: contrast-agent-operator 31 | namespace: '{{ default .Release.Namespace .Values.namespace }}' 32 | path: /mutate/v1pod 33 | admissionReviewVersions: [ "v1" ] 34 | sideEffects: None 35 | {{ end }} 36 | -------------------------------------------------------------------------------- /manifests/examples/testing/scenarios/namespaced/custom-images.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: agents.contrastsecurity.com/v1beta1 2 | kind: AgentInjector 3 | metadata: 4 | name: custom-images 5 | spec: 6 | enabled: true 7 | type: dotnet-core 8 | image: 9 | pullSecretName: custom-images 10 | name: custom-name 11 | registry: custom-registry/sub-path 12 | pullPolicy: Never 13 | selector: 14 | labels: 15 | - name: app 16 | value: custom-images 17 | connection: 18 | name: testing-agent-connection 19 | configuration: 20 | name: testing-agent-configuration 21 | --- 22 | apiVersion: apps/v1 23 | kind: Deployment 24 | metadata: 25 | name: custom-images 26 | labels: 27 | app: custom-images 28 | spec: 29 | replicas: 1 30 | selector: 31 | matchLabels: 32 | app: custom-images 33 | strategy: 34 | type: Recreate 35 | template: 36 | metadata: 37 | labels: 38 | app: custom-images 39 | spec: 40 | containers: 41 | - image: k8s.gcr.io/pause:3.3 42 | name: pause 43 | --- 44 | apiVersion: v1 45 | kind: Secret 46 | metadata: 47 | name: custom-images 48 | type: Opaque 49 | stringData: 50 | .dockerconfigjson: "{}" 51 | -------------------------------------------------------------------------------- /manifests/examples/testing/scenarios/namespaced/type-statefulset.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: agents.contrastsecurity.com/v1beta1 2 | kind: AgentInjector 3 | metadata: 4 | name: type-statefulset 5 | spec: 6 | enabled: true 7 | type: dummy 8 | selector: 9 | labels: 10 | - name: app 11 | value: type-statefulset 12 | image: 13 | pullPolicy: Never 14 | connection: 15 | name: testing-agent-connection 16 | configuration: 17 | name: testing-agent-configuration 18 | --- 19 | apiVersion: v1 20 | kind: Service 21 | metadata: 22 | name: type-statefulset 23 | labels: 24 | app: type-statefulset 25 | spec: 26 | ports: 27 | - port: 80 28 | name: web 29 | clusterIP: None 30 | selector: 31 | app: type-statefulset 32 | --- 33 | apiVersion: apps/v1 34 | kind: StatefulSet 35 | metadata: 36 | name: type-statefulset 37 | labels: 38 | app: type-statefulset 39 | spec: 40 | serviceName: type-statefulset 41 | replicas: 1 42 | selector: 43 | matchLabels: 44 | app: type-statefulset 45 | template: 46 | metadata: 47 | labels: 48 | app: type-statefulset 49 | spec: 50 | containers: 51 | - image: k8s.gcr.io/pause:3.3 52 | name: pause 53 | -------------------------------------------------------------------------------- /src/Contrast.K8s.AgentOperator/Core/State/Appliers/DaemonSetApplier.cs: -------------------------------------------------------------------------------- 1 | // Contrast Security, Inc licenses this file to you under the Apache 2.0 License. 2 | // See the LICENSE file in the project root for more information. 3 | 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using Contrast.K8s.AgentOperator.Core.Kube; 7 | using Contrast.K8s.AgentOperator.Core.State.Resources; 8 | using JetBrains.Annotations; 9 | using k8s.Models; 10 | using MediatR; 11 | 12 | namespace Contrast.K8s.AgentOperator.Core.State.Appliers; 13 | 14 | [UsedImplicitly] 15 | public class DaemonSetApplier : BaseApplier 16 | { 17 | public DaemonSetApplier(IStateContainer stateContainer, IMediator mediator) : base(stateContainer, mediator) 18 | { 19 | } 20 | 21 | public override ValueTask CreateFrom(V1DaemonSet entity, CancellationToken cancellationToken = default) 22 | { 23 | var resource = new DaemonSetResource( 24 | entity.Uid(), 25 | entity.Metadata.GetLabels(), 26 | entity.Spec.Template.GetPod(), 27 | entity.Spec.Selector.ToPodSelector() 28 | ); 29 | return ValueTask.FromResult(resource); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /manifests/examples/testing/scenarios/namespaced/init-container-overrides.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: agents.contrastsecurity.com/v1beta1 2 | kind: AgentInjector 3 | metadata: 4 | name: init-container-overrides 5 | spec: 6 | enabled: true 7 | type: dummy 8 | selector: 9 | labels: 10 | - name: app 11 | value: init-container-overrides 12 | connection: 13 | name: testing-agent-connection 14 | configuration: 15 | name: init-container-overrides-configuration 16 | --- 17 | apiVersion: agents.contrastsecurity.com/v1beta1 18 | kind: AgentConfiguration 19 | metadata: 20 | name: init-container-overrides-configuration 21 | spec: 22 | initContainer: 23 | securityContext: 24 | runAsUser: 499 25 | runAsNonRoot: false 26 | --- 27 | apiVersion: apps/v1 28 | kind: Deployment 29 | metadata: 30 | name: init-container-overrides 31 | labels: 32 | app: init-container-overrides 33 | spec: 34 | replicas: 1 35 | selector: 36 | matchLabels: 37 | app: init-container-overrides 38 | strategy: 39 | type: Recreate 40 | template: 41 | metadata: 42 | labels: 43 | app: init-container-overrides 44 | spec: 45 | containers: 46 | - image: k8s.gcr.io/pause:3.3 47 | name: pause 48 | -------------------------------------------------------------------------------- /src/Contrast.K8s.AgentOperator/Core/State/Appliers/DeploymentApplier.cs: -------------------------------------------------------------------------------- 1 | // Contrast Security, Inc licenses this file to you under the Apache 2.0 License. 2 | // See the LICENSE file in the project root for more information. 3 | 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using Contrast.K8s.AgentOperator.Core.Kube; 7 | using Contrast.K8s.AgentOperator.Core.State.Resources; 8 | using JetBrains.Annotations; 9 | using k8s.Models; 10 | using MediatR; 11 | 12 | namespace Contrast.K8s.AgentOperator.Core.State.Appliers; 13 | 14 | [UsedImplicitly] 15 | public class DeploymentApplier : BaseApplier 16 | { 17 | public DeploymentApplier(IStateContainer stateContainer, IMediator mediator) : base(stateContainer, mediator) 18 | { 19 | } 20 | 21 | public override ValueTask CreateFrom(V1Deployment entity, CancellationToken cancellationToken = default) 22 | { 23 | var resource = new DeploymentResource( 24 | entity.Uid(), 25 | entity.Metadata.GetLabels(), 26 | entity.Spec.Template.GetPod(), 27 | entity.Spec.Selector.ToPodSelector() 28 | ); 29 | 30 | return ValueTask.FromResult(resource); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Contrast.K8s.AgentOperator/Core/State/Appliers/StatefulSetApplier.cs: -------------------------------------------------------------------------------- 1 | // Contrast Security, Inc licenses this file to you under the Apache 2.0 License. 2 | // See the LICENSE file in the project root for more information. 3 | 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using Contrast.K8s.AgentOperator.Core.Kube; 7 | using Contrast.K8s.AgentOperator.Core.State.Resources; 8 | using JetBrains.Annotations; 9 | using k8s.Models; 10 | using MediatR; 11 | 12 | namespace Contrast.K8s.AgentOperator.Core.State.Appliers; 13 | 14 | [UsedImplicitly] 15 | public class StatefulSetApplier : BaseApplier 16 | { 17 | public StatefulSetApplier(IStateContainer stateContainer, IMediator mediator) : base(stateContainer, mediator) 18 | { 19 | } 20 | 21 | public override ValueTask CreateFrom(V1StatefulSet entity, CancellationToken cancellationToken = default) 22 | { 23 | var resource = new StatefulSetResource( 24 | entity.Uid(), 25 | entity.Metadata.GetLabels(), 26 | entity.Spec.Template.GetPod(), 27 | entity.Spec.Selector.ToPodSelector() 28 | ); 29 | return ValueTask.FromResult(resource); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /tests/Contrast.K8s.AgentOperator.FunctionalTests/Scenarios/Injection/EnabledFlagTests.cs: -------------------------------------------------------------------------------- 1 | // Contrast Security, Inc licenses this file to you under the Apache 2.0 License. 2 | // See the LICENSE file in the project root for more information. 3 | 4 | using System.Threading.Tasks; 5 | using Contrast.K8s.AgentOperator.FunctionalTests.Fixtures; 6 | using FluentAssertions; 7 | using k8s.Models; 8 | using Xunit; 9 | using Xunit.Abstractions; 10 | 11 | namespace Contrast.K8s.AgentOperator.FunctionalTests.Scenarios.Injection; 12 | 13 | public class EnabledFlagTests : IClassFixture 14 | { 15 | private const string ScenarioName = "enabled-flag"; 16 | 17 | private readonly TestingContext _context; 18 | 19 | public EnabledFlagTests(TestingContext context, ITestOutputHelper outputHelper) 20 | { 21 | _context = context; 22 | _context.RegisterOutput(outputHelper); 23 | } 24 | 25 | [Fact] 26 | public async Task When_disabled_then_injector_should_not_inject() 27 | { 28 | var client = await _context.GetClient(); 29 | 30 | // Act 31 | var result = await client.GetByPrefix(ScenarioName); 32 | 33 | // Assert 34 | result.Annotations().Should().BeNull(); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /manifests/examples/testing/scenarios/token/token-dummy.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: agents.contrastsecurity.com/v1beta1 2 | kind: AgentConnection 3 | metadata: 4 | name: token-agent-connection 5 | spec: 6 | token: 7 | secretName: token-agent-connection-secret 8 | secretKey: token 9 | --- 10 | apiVersion: v1 11 | kind: Secret 12 | metadata: 13 | name: token-agent-connection-secret 14 | type: Opaque 15 | stringData: 16 | token: token 17 | --- 18 | apiVersion: agents.contrastsecurity.com/v1beta1 19 | kind: AgentInjector 20 | metadata: 21 | name: token-dummy 22 | spec: 23 | enabled: true 24 | type: dummy 25 | selector: 26 | labels: 27 | - name: app 28 | value: token-dummy 29 | connection: 30 | name: token-agent-connection 31 | configuration: 32 | name: testing-agent-configuration 33 | --- 34 | apiVersion: apps/v1 35 | kind: Deployment 36 | metadata: 37 | name: token-dummy 38 | labels: 39 | app: token-dummy 40 | spec: 41 | replicas: 1 42 | selector: 43 | matchLabels: 44 | app: token-dummy 45 | strategy: 46 | type: Recreate 47 | template: 48 | metadata: 49 | labels: 50 | app: token-dummy 51 | spec: 52 | containers: 53 | - image: k8s.gcr.io/pause:3.3 54 | name: pause 55 | -------------------------------------------------------------------------------- /src/Contrast.K8s.AgentOperator/Entities/OpenShift/V1DeploymentConfig.cs: -------------------------------------------------------------------------------- 1 | // Contrast Security, Inc licenses this file to you under the Apache 2.0 License. 2 | // See the LICENSE file in the project root for more information. 3 | 4 | using System.Collections.Generic; 5 | using System.Text.Json.Serialization; 6 | using Contrast.K8s.AgentOperator.Core.Kube; 7 | using k8s.Models; 8 | using KubeOps.Abstractions.Entities; 9 | using KubeOps.Abstractions.Entities.Attributes; 10 | using KubeOps.Abstractions.Rbac; 11 | 12 | namespace Contrast.K8s.AgentOperator.Entities.OpenShift; 13 | 14 | [Ignore] //Don't generate a CRD for this 15 | [KubernetesEntity(Group = "apps.openshift.io", ApiVersion = "v1", Kind = "DeploymentConfig", PluralName = "deploymentconfigs")] 16 | [EntityRbac(typeof(V1DeploymentConfig), Verbs = VerbConstants.ReadAndPatch)] 17 | public partial class V1DeploymentConfig : CustomKubernetesEntity 18 | { 19 | public class DeploymentConfigSpec 20 | { 21 | [JsonPropertyName("selector")] 22 | public IDictionary Selector { get; set; } = new Dictionary(); 23 | 24 | [JsonPropertyName("template")] 25 | public V1PodTemplateSpec? Template { get; set; } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Contrast.K8s.AgentOperator/Core/Telemetry/Services/Exceptions/TelemetryExceptionsBuffer.cs: -------------------------------------------------------------------------------- 1 | // Contrast Security, Inc licenses this file to you under the Apache 2.0 License. 2 | // See the LICENSE file in the project root for more information. 3 | 4 | using System.Threading; 5 | using System.Threading.Channels; 6 | using System.Threading.Tasks; 7 | using Contrast.K8s.AgentOperator.Core.Telemetry.Models; 8 | 9 | namespace Contrast.K8s.AgentOperator.Core.Telemetry.Services.Exceptions; 10 | 11 | public class TelemetryExceptionsBuffer 12 | { 13 | private readonly Channel _channel = Channel.CreateBounded(new BoundedChannelOptions(128) 14 | { 15 | FullMode = BoundedChannelFullMode.DropWrite 16 | }); 17 | 18 | public static TelemetryExceptionsBuffer Instance { get; } = new(); 19 | 20 | private TelemetryExceptionsBuffer() 21 | { 22 | } 23 | 24 | public ValueTask Add(ExceptionReport report, CancellationToken cancellationToken = default) 25 | { 26 | return _channel.Writer.WriteAsync(report, cancellationToken); 27 | } 28 | 29 | public ValueTask Take(CancellationToken cancellationToken = default) 30 | { 31 | return _channel.Reader.ReadAsync(cancellationToken); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Contrast.K8s.AgentOperator/Core/ReadinessCheck.cs: -------------------------------------------------------------------------------- 1 | // Contrast Security, Inc licenses this file to you under the Apache 2.0 License. 2 | // See the LICENSE file in the project root for more information. 3 | 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using Contrast.K8s.AgentOperator.Core.Tls; 7 | using JetBrains.Annotations; 8 | using Microsoft.Extensions.Diagnostics.HealthChecks; 9 | 10 | namespace Contrast.K8s.AgentOperator.Core; 11 | 12 | [UsedImplicitly] 13 | public class ReadinessCheck : IHealthCheck 14 | { 15 | private readonly IKestrelCertificateSelector _certificateSelector; 16 | 17 | public ReadinessCheck(IKestrelCertificateSelector certificateSelector) 18 | { 19 | _certificateSelector = certificateSelector; 20 | } 21 | 22 | public Task CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default) 23 | { 24 | var result = HealthCheckResult.Healthy(); 25 | if (!_certificateSelector.HasValidCertificate()) 26 | { 27 | result = HealthCheckResult.Unhealthy("No valid certificate has been loaded. If this warning continues to occur, a permission problem may be present."); 28 | } 29 | 30 | return Task.FromResult(result); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Contrast.K8s.AgentOperator/Core/State/Appliers/SecretApplier.cs: -------------------------------------------------------------------------------- 1 | // Contrast Security, Inc licenses this file to you under the Apache 2.0 License. 2 | // See the LICENSE file in the project root for more information. 3 | 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | using Contrast.K8s.AgentOperator.Core.State.Resources; 9 | using Contrast.K8s.AgentOperator.Core.State.Resources.Primitives; 10 | using JetBrains.Annotations; 11 | using k8s.Models; 12 | using MediatR; 13 | 14 | namespace Contrast.K8s.AgentOperator.Core.State.Appliers; 15 | 16 | [UsedImplicitly] 17 | public class SecretApplier : BaseApplier 18 | { 19 | public SecretApplier(IStateContainer stateContainer, IMediator mediator) : base(stateContainer, mediator) 20 | { 21 | } 22 | 23 | public override ValueTask CreateFrom(V1Secret entity, CancellationToken cancellationToken = default) 24 | { 25 | var data = entity.Data ?? new Dictionary(); 26 | var keyPairs = data.Select(x => SecretKeyValue.Create(x.Key, x.Value)).NormalizeSecrets(); 27 | 28 | var resource = new SecretResource(keyPairs); 29 | return ValueTask.FromResult(resource); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Contrast.K8s.AgentOperator.sln.DotSettings: -------------------------------------------------------------------------------- 1 | 2 | True 3 | True 4 | True 5 | True 6 | True 7 | True 8 | True 9 | True 10 | True 11 | True -------------------------------------------------------------------------------- /src/Contrast.K8s.AgentOperator/Core/Telemetry/Counters/PerformanceCounterContainer.cs: -------------------------------------------------------------------------------- 1 | // Contrast Security, Inc licenses this file to you under the Apache 2.0 License. 2 | // See the LICENSE file in the project root for more information. 3 | 4 | using System.Collections.Concurrent; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Threading.Tasks; 8 | 9 | namespace Contrast.K8s.AgentOperator.Core.Telemetry.Counters; 10 | 11 | public class PerformanceCounterContainer 12 | { 13 | private readonly ConcurrentDictionary _counters = new(); 14 | private readonly TaskCompletionSource _ready = new(); 15 | 16 | public void UpdateCounters(IReadOnlyDictionary counters) 17 | { 18 | foreach (var (key, value) in counters) 19 | { 20 | _counters.AddOrUpdate(key, value, (_, _) => value); 21 | } 22 | 23 | if (!_ready.Task.IsCompleted) 24 | { 25 | _ready.TrySetResult(true); 26 | } 27 | } 28 | 29 | public async Task> GetCounters() 30 | { 31 | if (!_ready.Task.IsCompleted) 32 | { 33 | await _ready.Task; 34 | } 35 | 36 | return _counters.ToDictionary(x => x.Key, x => x.Value); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Contrast.K8s.AgentOperator/Core/State/Appliers/RolloutApplier.cs: -------------------------------------------------------------------------------- 1 | // Contrast Security, Inc licenses this file to you under the Apache 2.0 License. 2 | // See the LICENSE file in the project root for more information. 3 | 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using Contrast.K8s.AgentOperator.Core.Kube; 7 | using Contrast.K8s.AgentOperator.Core.State.Resources; 8 | using Contrast.K8s.AgentOperator.Entities.Argo; 9 | using JetBrains.Annotations; 10 | using k8s.Models; 11 | using MediatR; 12 | 13 | namespace Contrast.K8s.AgentOperator.Core.State.Appliers; 14 | 15 | [UsedImplicitly] 16 | public class RolloutApplier : BaseApplier 17 | { 18 | public RolloutApplier(IStateContainer stateContainer, IMediator mediator) : base(stateContainer, mediator) 19 | { 20 | } 21 | 22 | public override ValueTask CreateFrom(V1Alpha1Rollout entity, CancellationToken cancellationToken = default) 23 | { 24 | var resource = new RolloutResource( 25 | entity.Uid(), 26 | entity.Metadata.GetLabels(), 27 | entity.Spec.Template.GetPod(), 28 | entity.Spec.Selector.ToPodSelector() 29 | ); 30 | 31 | return ValueTask.FromResult(resource); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /tests/Contrast.K8s.AgentOperator.FunctionalTests/Scenarios/Injection/MissingDependenciesTests.cs: -------------------------------------------------------------------------------- 1 | // Contrast Security, Inc licenses this file to you under the Apache 2.0 License. 2 | // See the LICENSE file in the project root for more information. 3 | 4 | using System.Threading.Tasks; 5 | using Contrast.K8s.AgentOperator.FunctionalTests.Fixtures; 6 | using FluentAssertions; 7 | using k8s.Models; 8 | using Xunit; 9 | using Xunit.Abstractions; 10 | 11 | namespace Contrast.K8s.AgentOperator.FunctionalTests.Scenarios.Injection; 12 | 13 | public class MissingDependenciesTests : IClassFixture 14 | { 15 | private const string ScenarioName = "missing-deps"; 16 | 17 | private readonly TestingContext _context; 18 | 19 | public MissingDependenciesTests(TestingContext context, ITestOutputHelper outputHelper) 20 | { 21 | _context = context; 22 | _context.RegisterOutput(outputHelper); 23 | } 24 | 25 | [Fact] 26 | public async Task When_injection_configuration_is_missing_required_dependencies_then_pod_should_not_be_modified() 27 | { 28 | var client = await _context.GetClient(); 29 | 30 | // Act 31 | var result = await client.GetByPrefix(ScenarioName); 32 | 33 | // Assert 34 | result.Annotations().Should().BeNull(); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Contrast.K8s.AgentOperator/Core/Telemetry/Client/ITelemetryClient.cs: -------------------------------------------------------------------------------- 1 | // Contrast Security, Inc licenses this file to you under the Apache 2.0 License. 2 | // See the LICENSE file in the project root for more information. 3 | 4 | using System.Collections.Generic; 5 | using System.Net.Http; 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | using RestEase; 9 | 10 | namespace Contrast.K8s.AgentOperator.Core.Telemetry.Client; 11 | 12 | public interface ITelemetryClient 13 | { 14 | [Header("User-Agent")] 15 | string UserAgent { get; set; } 16 | 17 | [Post("api/v1/telemetry/metrics/{path}"), AllowAnyStatusCode] 18 | Task SubmitMeasurement([Path(UrlEncode = false)] string path, 19 | [Body] IReadOnlyCollection measurements, 20 | CancellationToken cancellationToken = default); 21 | 22 | [Post("api/v1/telemetry/exceptions/{path}"), AllowAnyStatusCode] 23 | Task SubmitExceptionReports([Path(UrlEncode = false)] string path, 24 | [Body] IReadOnlyCollection reports, 25 | CancellationToken cancellationToken = default); 26 | } 27 | -------------------------------------------------------------------------------- /tests/Contrast.K8s.AgentOperator.FunctionalTests/Scenarios/Injection/ConfigOptionalTests.cs: -------------------------------------------------------------------------------- 1 | // Contrast Security, Inc licenses this file to you under the Apache 2.0 License. 2 | // See the LICENSE file in the project root for more information. 3 | 4 | using System.Threading.Tasks; 5 | using Contrast.K8s.AgentOperator.FunctionalTests.Fixtures; 6 | using FluentAssertions; 7 | using k8s.Models; 8 | using Xunit; 9 | using Xunit.Abstractions; 10 | 11 | namespace Contrast.K8s.AgentOperator.FunctionalTests.Scenarios.Injection; 12 | 13 | public class ConfigOptionalTests : IClassFixture 14 | { 15 | private const string ScenarioName = "config-optional"; 16 | 17 | private readonly TestingContext _context; 18 | 19 | public ConfigOptionalTests(TestingContext context, ITestOutputHelper outputHelper) 20 | { 21 | _context = context; 22 | _context.RegisterOutput(outputHelper); 23 | } 24 | 25 | [Fact] 26 | public async Task When_injected_then_pod_should_have_injection_annotations() 27 | { 28 | var client = await _context.GetClient(); 29 | 30 | // Act 31 | var result = await client.GetInjectedPodByPrefix(ScenarioName); 32 | 33 | // Assert 34 | result.Annotations().Should().ContainKey("agents.contrastsecurity.com/is-injected").WhoseValue.Should().Be("True"); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /manifests/examples/openshift/base/minimal-setup.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: agents.contrastsecurity.com/v1beta1 2 | kind: AgentInjector 3 | metadata: 4 | name: example-injector-dotnet-core 5 | spec: 6 | enabled: true 7 | version: latest 8 | type: dotnet-core 9 | selector: 10 | images: 11 | - "*" 12 | labels: 13 | - name: app 14 | value: dotnet-core-app 15 | connection: 16 | name: example-agent-connection 17 | configuration: 18 | name: example-agent-configuration 19 | --- 20 | apiVersion: agents.contrastsecurity.com/v1beta1 21 | kind: AgentConnection 22 | metadata: 23 | name: example-agent-connection 24 | spec: 25 | url: http://localhost 26 | apiKey: 27 | secretName: example-agent-connection-secret 28 | secretKey: apiKey 29 | serviceKey: 30 | secretName: example-agent-connection-secret 31 | secretKey: serviceKey 32 | userName: 33 | secretName: example-agent-connection-secret 34 | secretKey: userName 35 | --- 36 | apiVersion: v1 37 | kind: Secret 38 | metadata: 39 | name: example-agent-connection-secret 40 | type: Opaque 41 | stringData: 42 | apiKey: apiKey 43 | serviceKey: serviceKey 44 | userName: userName 45 | --- 46 | apiVersion: agents.contrastsecurity.com/v1beta1 47 | kind: AgentConfiguration 48 | metadata: 49 | name: example-agent-configuration 50 | spec: 51 | yaml: | 52 | enabled: true 53 | -------------------------------------------------------------------------------- /manifests/examples/argo-rollouts/base/minimal-setup.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: agents.contrastsecurity.com/v1beta1 2 | kind: AgentInjector 3 | metadata: 4 | name: example-injector-dotnet-core 5 | spec: 6 | enabled: true 7 | version: latest 8 | type: dotnet-core 9 | selector: 10 | images: 11 | - "*" 12 | labels: 13 | - name: app 14 | value: dotnet-core-app 15 | connection: 16 | name: example-agent-connection 17 | configuration: 18 | name: example-agent-configuration 19 | --- 20 | apiVersion: agents.contrastsecurity.com/v1beta1 21 | kind: AgentConnection 22 | metadata: 23 | name: example-agent-connection 24 | spec: 25 | url: http://localhost 26 | apiKey: 27 | secretName: example-agent-connection-secret 28 | secretKey: apiKey 29 | serviceKey: 30 | secretName: example-agent-connection-secret 31 | secretKey: serviceKey 32 | userName: 33 | secretName: example-agent-connection-secret 34 | secretKey: userName 35 | --- 36 | apiVersion: v1 37 | kind: Secret 38 | metadata: 39 | name: example-agent-connection-secret 40 | type: Opaque 41 | stringData: 42 | apiKey: apiKey 43 | serviceKey: serviceKey 44 | userName: userName 45 | --- 46 | apiVersion: agents.contrastsecurity.com/v1beta1 47 | kind: AgentConfiguration 48 | metadata: 49 | name: example-agent-configuration 50 | spec: 51 | yaml: | 52 | enabled: true 53 | -------------------------------------------------------------------------------- /src/Contrast.K8s.AgentOperator/Core/State/Appliers/NamespaceApplier.cs: -------------------------------------------------------------------------------- 1 | // Contrast Security, Inc licenses this file to you under the Apache 2.0 License. 2 | // See the LICENSE file in the project root for more information. 3 | 4 | using Contrast.K8s.AgentOperator.Core.Kube; 5 | using Contrast.K8s.AgentOperator.Core.State.Resources; 6 | using JetBrains.Annotations; 7 | using k8s.Models; 8 | using MediatR; 9 | using System.Threading; 10 | using System.Threading.Tasks; 11 | 12 | namespace Contrast.K8s.AgentOperator.Core.State.Appliers; 13 | 14 | [UsedImplicitly] 15 | public class NamespaceApplier : BaseApplier 16 | { 17 | public NamespaceApplier(IStateContainer stateContainer, IMediator mediator) : base(stateContainer, mediator) 18 | { 19 | } 20 | 21 | public override ValueTask CreateFrom(V1Namespace entity, CancellationToken cancellationToken = default) 22 | { 23 | var resource = new NamespaceResource( 24 | entity.Uid(), 25 | entity.Metadata.GetLabels() 26 | ); 27 | return ValueTask.FromResult(resource); 28 | } 29 | 30 | protected override string GetNamespace(V1Namespace entity) 31 | { 32 | return entity.Name(); //Namespace() is null for namespaces so we use Name() TODO: make SateContainer hold namespaces separately 33 | } 34 | 35 | } 36 | 37 | -------------------------------------------------------------------------------- /tests/Contrast.K8s.AgentOperator.FunctionalTests/Scenarios/Injection/CustomVersionTest.cs: -------------------------------------------------------------------------------- 1 | // Contrast Security, Inc licenses this file to you under the Apache 2.0 License. 2 | // See the LICENSE file in the project root for more information. 3 | 4 | using System.Threading.Tasks; 5 | using Contrast.K8s.AgentOperator.FunctionalTests.Fixtures; 6 | using FluentAssertions; 7 | using Xunit; 8 | using Xunit.Abstractions; 9 | 10 | namespace Contrast.K8s.AgentOperator.FunctionalTests.Scenarios.Injection; 11 | 12 | public class CustomVersionTest : IClassFixture 13 | { 14 | private const string ScenarioName = "custom-version"; 15 | 16 | private readonly TestingContext _context; 17 | 18 | public CustomVersionTest(TestingContext context, ITestOutputHelper outputHelper) 19 | { 20 | _context = context; 21 | _context.RegisterOutput(outputHelper); 22 | } 23 | 24 | [Fact] 25 | public async Task When_version_is_configured_then_init_container_should_use_version() 26 | { 27 | var client = await _context.GetClient(); 28 | 29 | // Act 30 | var result = await client.GetInjectedPodByPrefix(ScenarioName); 31 | 32 | // Assert 33 | result.Spec.InitContainers.Should().ContainSingle(x => x.Name == "contrast-init") 34 | .Which.Image.Should().Be("contrast/agent-dummy:1.0"); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Contrast.K8s.AgentOperator/Core/Reactions/Injecting/Patching/Agents/NodeJsAgentPatcher.cs: -------------------------------------------------------------------------------- 1 | // Contrast Security, Inc licenses this file to you under the Apache 2.0 License. 2 | // See the LICENSE file in the project root for more information. 3 | 4 | using System.Collections.Generic; 5 | using Contrast.K8s.AgentOperator.Core.State.Resources.Primitives; 6 | using k8s.Models; 7 | 8 | namespace Contrast.K8s.AgentOperator.Core.Reactions.Injecting.Patching.Agents; 9 | 10 | public class NodeJsAgentPatcher : IAgentPatcher 11 | { 12 | public AgentInjectionType Type => AgentInjectionType.NodeJs; 13 | 14 | public IEnumerable GenerateEnvVars(PatchingContext context) 15 | { 16 | // https://nodejs.org/api/cli.html#node_optionsoptions 17 | yield return new V1EnvVar("NODE_OPTIONS", $"--import {context.AgentMountPath}/node_modules/@contrast/agent/lib/esm-loader.mjs"); 18 | yield return new V1EnvVar("CONTRAST__AGENT__LOGGER__PATH", $"{context.WritableMountPath}/logs/contrast_agent.log"); 19 | yield return new V1EnvVar("CONTRAST__AGENT__SECURITY_LOGGER__PATH", $"{context.WritableMountPath}/logs/contrast_agent_cef.log"); 20 | yield return new V1EnvVar("CONTRAST__AGENT__NODE__REWRITE__CACHE__PATH", $"{context.WritableMountPath}/cache"); 21 | yield return new V1EnvVar("CONTRAST_INSTALLATION_TOOL", "KUBERNETES_OPERATOR"); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Contrast.K8s.AgentOperator/Core/Reactions/Injecting/Patching/Agents/NodeJsLegacyAgentPatcher.cs: -------------------------------------------------------------------------------- 1 | // Contrast Security, Inc licenses this file to you under the Apache 2.0 License. 2 | // See the LICENSE file in the project root for more information. 3 | 4 | using System.Collections.Generic; 5 | using Contrast.K8s.AgentOperator.Core.State.Resources.Primitives; 6 | using k8s.Models; 7 | 8 | namespace Contrast.K8s.AgentOperator.Core.Reactions.Injecting.Patching.Agents; 9 | 10 | public class NodeJsLegacyAgentPatcher : IAgentPatcher 11 | { 12 | public AgentInjectionType Type => AgentInjectionType.NodeJsLegacy; 13 | 14 | public IEnumerable GenerateEnvVars(PatchingContext context) 15 | { 16 | // https://nodejs.org/api/cli.html#node_optionsoptions 17 | yield return new V1EnvVar("NODE_OPTIONS", $"--require {context.AgentMountPath}/node_modules/@contrast/agent"); 18 | yield return new V1EnvVar("CONTRAST__AGENT__LOGGER__PATH", $"{context.WritableMountPath}/logs/contrast_agent.log"); 19 | yield return new V1EnvVar("CONTRAST__AGENT__SECURITY_LOGGER__PATH", $"{context.WritableMountPath}/logs/contrast_agent_cef.log"); 20 | yield return new V1EnvVar("CONTRAST__AGENT__NODE__REWRITE__CACHE__PATH", $"{context.WritableMountPath}/cache"); 21 | yield return new V1EnvVar("CONTRAST_INSTALLATION_TOOL", "KUBERNETES_OPERATOR"); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Contrast.K8s.AgentOperator/Core/Telemetry/Cluster/ClusterIdState.cs: -------------------------------------------------------------------------------- 1 | // Contrast Security, Inc licenses this file to you under the Apache 2.0 License. 2 | // See the LICENSE file in the project root for more information. 3 | 4 | using System; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | 8 | namespace Contrast.K8s.AgentOperator.Core.Telemetry.Cluster; 9 | 10 | public interface IClusterIdState 11 | { 12 | ClusterId? GetClusterId(); 13 | bool SetClusterId(ClusterId clusterId); 14 | Task GetClusterIdAsync(CancellationToken cancellationToken = default); 15 | } 16 | 17 | public class ClusterIdState : IClusterIdState 18 | { 19 | private ClusterId? _cache; 20 | 21 | public ClusterId? GetClusterId() 22 | { 23 | return _cache; 24 | } 25 | 26 | public bool SetClusterId(ClusterId clusterId) 27 | { 28 | var lastId = _cache; 29 | _cache = clusterId; 30 | 31 | var updated = lastId != _cache; 32 | return updated; 33 | } 34 | 35 | public async Task GetClusterIdAsync(CancellationToken cancellationToken = default) 36 | { 37 | ClusterId? clusterId; 38 | while ((clusterId = GetClusterId()) == null) 39 | { 40 | await Task.Delay(TimeSpan.FromSeconds(5), cancellationToken); 41 | } 42 | 43 | return clusterId; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Contrast.K8s.AgentOperator/Core/Telemetry/TelemetryOptOut.cs: -------------------------------------------------------------------------------- 1 | // Contrast Security, Inc licenses this file to you under the Apache 2.0 License. 2 | // See the LICENSE file in the project root for more information. 3 | 4 | #nullable enable 5 | 6 | using System; 7 | 8 | namespace Contrast.K8s.AgentOperator.Core.Telemetry; 9 | 10 | public interface ITelemetryOptOut 11 | { 12 | bool IsOptOutActive(); 13 | } 14 | 15 | public class TelemetryOptOut : ITelemetryOptOut 16 | { 17 | // ReSharper disable once StringLiteralTypo 18 | private const string OptOutEnvironmentVariableNameOld = "CONTRAST_DOTNET_TELEMETRY_OPTOUT"; 19 | private const string OptOutEnvironmentVariableNameNew = "CONTRAST_AGENT_TELEMETRY_OPTOUT"; 20 | 21 | private bool? _cache; 22 | 23 | public bool IsOptOutActive() 24 | { 25 | return _cache ??= IsOptOutActiveFor(OptOutEnvironmentVariableNameOld) 26 | || IsOptOutActiveFor(OptOutEnvironmentVariableNameNew); 27 | } 28 | 29 | private static bool IsOptOutActiveFor(string variable) 30 | { 31 | var optOutFlag = Environment.GetEnvironmentVariable(variable)?.Trim(); 32 | return !string.IsNullOrEmpty(optOutFlag) 33 | && (string.Equals(optOutFlag, true.ToString(), StringComparison.OrdinalIgnoreCase) 34 | || string.Equals(optOutFlag, 1.ToString())); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /tests/Contrast.K8s.AgentOperator.FunctionalTests/Scenarios/Injection/Agents/DummyInjectionTests.cs: -------------------------------------------------------------------------------- 1 | // Contrast Security, Inc licenses this file to you under the Apache 2.0 License. 2 | // See the LICENSE file in the project root for more information. 3 | 4 | using System.Threading.Tasks; 5 | using Contrast.K8s.AgentOperator.FunctionalTests.Fixtures; 6 | using FluentAssertions; 7 | using Xunit; 8 | using Xunit.Abstractions; 9 | 10 | namespace Contrast.K8s.AgentOperator.FunctionalTests.Scenarios.Injection.Agents; 11 | 12 | public class DummyInjectionTests : IClassFixture 13 | { 14 | private const string ScenarioName = "injection-dummy"; 15 | 16 | private readonly TestingContext _context; 17 | 18 | public DummyInjectionTests(TestingContext context, ITestOutputHelper outputHelper) 19 | { 20 | _context = context; 21 | _context.RegisterOutput(outputHelper); 22 | } 23 | 24 | [Fact] 25 | public async Task When_injected_then_pod_should_have_agent_injection_init_image() 26 | { 27 | var client = await _context.GetClient(); 28 | 29 | // Act 30 | var result = await client.GetInjectedPodByPrefix(ScenarioName); 31 | 32 | // Assert 33 | result.Spec.InitContainers.Should().ContainSingle(x => x.Name == "contrast-init") 34 | .Which.Image.Should().Be("contrast/agent-dummy:latest"); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /tests/Contrast.K8s.AgentOperator.FunctionalTests/Scenarios/Injection/ClusterNamespaceLabelTests.cs: -------------------------------------------------------------------------------- 1 | // Contrast Security, Inc licenses this file to you under the Apache 2.0 License. 2 | // See the LICENSE file in the project root for more information. 3 | 4 | using System.Threading.Tasks; 5 | using Contrast.K8s.AgentOperator.FunctionalTests.Fixtures; 6 | using FluentAssertions; 7 | using k8s.Models; 8 | using Xunit; 9 | using Xunit.Abstractions; 10 | 11 | namespace Contrast.K8s.AgentOperator.FunctionalTests.Scenarios.Injection; 12 | 13 | public class ClusterNamespaceLabelTests : IClassFixture 14 | { 15 | private const string ScenarioName = "injection-namespacelabel"; 16 | 17 | private readonly TestingContext _context; 18 | 19 | public ClusterNamespaceLabelTests(TestingContext context, ITestOutputHelper outputHelper) 20 | { 21 | _context = context; 22 | _context.RegisterOutput(outputHelper); 23 | } 24 | 25 | [Fact] 26 | public async Task When_injected_then_pod_should_have_injection_annotations() 27 | { 28 | var client = await _context.GetClient(defaultNamespace: "testing-namespacelabel"); 29 | 30 | // Act 31 | var result = await client.GetInjectedPodByPrefix(ScenarioName); 32 | 33 | // Assert 34 | result.Annotations().Should().ContainKey("agents.contrastsecurity.com/is-injected").WhoseValue.Should().Be("True"); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Contrast.K8s.AgentOperator/Controllers/PodMutationWebhook.cs: -------------------------------------------------------------------------------- 1 | // Contrast Security, Inc licenses this file to you under the Apache 2.0 License. 2 | // See the LICENSE file in the project root for more information. 3 | 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using Contrast.K8s.AgentOperator.Core.Events; 7 | using Contrast.K8s.AgentOperator.Core.Kube; 8 | using JetBrains.Annotations; 9 | using k8s.Models; 10 | using KubeOps.Abstractions.Rbac; 11 | using KubeOps.Operator.Web.Webhooks.Admission.Mutation; 12 | using MediatR; 13 | 14 | namespace Contrast.K8s.AgentOperator.Controllers; 15 | 16 | [EntityRbac(typeof(V1Pod), Verbs = VerbConstants.ReadAndPatch), UsedImplicitly] 17 | [MutationWebhook(typeof(V1Pod))] 18 | public class PodMutationWebhook : MutationWebhook 19 | { 20 | private readonly IMediator _mediator; 21 | 22 | 23 | public PodMutationWebhook(IMediator mediator) 24 | { 25 | _mediator = mediator; 26 | } 27 | public override async Task> CreateAsync( 28 | V1Pod newEntity, 29 | bool dryRun, 30 | CancellationToken cancellationToken) 31 | { 32 | var result = await _mediator.Send(new EntityCreating(newEntity), cancellationToken); 33 | 34 | return result is NeedsChangeEntityCreatingMutationResult mutationResult 35 | ? Modified(mutationResult.Entity) 36 | : NoChanges(); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Contrast.K8s.AgentOperator/Core/Reactions/Injecting/InjectionConstants.cs: -------------------------------------------------------------------------------- 1 | // Contrast Security, Inc licenses this file to you under the Apache 2.0 License. 2 | // See the LICENSE file in the project root for more information. 3 | 4 | namespace Contrast.K8s.AgentOperator.Core.Reactions.Injecting; 5 | 6 | public static class InjectionConstants 7 | { 8 | public const string OperatorAttributePrefix = "agents.contrastsecurity.com/"; 9 | 10 | // Pod annotations via the mutation webhook. 11 | public const string IsInjectedAttributeName = "agents.contrastsecurity.com/is-injected"; 12 | public const string InjectedOnAttributeName = "agents.contrastsecurity.com/injected-on"; 13 | public const string InjectedByAttributeName = "agents.contrastsecurity.com/injected-by"; 14 | public const string InjectorTypeAttributeName = "agents.contrastsecurity.com/injector-type"; 15 | 16 | // Pod annotations via the workload (our direct patching). 17 | public const string InjectorHashAttributeName = "agents.contrastsecurity.com/injector-hash"; 18 | public const string InjectorNameAttributeName = "agents.contrastsecurity.com/injector-name"; 19 | public const string InjectorNamespaceAttributeName = "agents.contrastsecurity.com/injector-namespace"; 20 | public const string WorkloadNameAttributeName = "agents.contrastsecurity.com/workload-name"; 21 | public const string WorkloadNamespaceAttributeName = "agents.contrastsecurity.com/workload-namespace"; 22 | } 23 | -------------------------------------------------------------------------------- /tests/performance-tests/Contrast.K8s.AgentOperator.Performance.ClusterFaker/Options.cs: -------------------------------------------------------------------------------- 1 | // Contrast Security, Inc licenses this file to you under the Apache 2.0 License. 2 | // See the LICENSE file in the project root for more information. 3 | 4 | using CommandLine; 5 | 6 | namespace Contrast.K8s.AgentOperator.Performance.ClusterFaker 7 | { 8 | public static class Options 9 | { 10 | [Verb("up")] 11 | public class UpOptions 12 | { 13 | [Option('n', "namespaces")] 14 | public int NamespaceCount { get; set; } = 1; 15 | 16 | [Option('d', "deployments")] 17 | public int DeploymentsPerNamespaceCount { get; set; } = 1; 18 | 19 | [Option('p', "pods")] 20 | public int PodsPerDeploymentCount { get; set; } = 1; 21 | 22 | [Option('s', "secrets")] 23 | public int SecretsPerNamespaceCount { get; set; } = 10; 24 | 25 | public override string ToString() 26 | { 27 | return 28 | $"{nameof(NamespaceCount)}: {NamespaceCount}, {nameof(DeploymentsPerNamespaceCount)}: {DeploymentsPerNamespaceCount}, {nameof(PodsPerDeploymentCount)}: {PodsPerDeploymentCount}"; 29 | } 30 | } 31 | 32 | [Verb("down")] 33 | public class DownOptions 34 | { 35 | public override string ToString() 36 | { 37 | return ""; 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /manifests/helm/build.ps1: -------------------------------------------------------------------------------- 1 | #!/usr/bin/pwsh 2 | 3 | param( 4 | [string]$ChartVersion = "0.0.2", 5 | [string]$AppVersion = "0.0.2", 6 | [switch]$WriteSchemaUrl 7 | ) 8 | 9 | function Invoke-Kustomize([string] $BasePath, [string] $OutputFilePath) 10 | { 11 | kubectl kustomize ([System.IO.Path]::GetFullPath($BasePath)) | Out-File -Append -FilePath $OutputFilePath 12 | } 13 | 14 | $root = $PSScriptRoot 15 | 16 | # Generate CRDs 17 | Write-Host "Generating CRDs..." 18 | Invoke-Kustomize -BasePath "$root/build/crds" -OutputFilePath "$root/crds/generated.yaml" 19 | 20 | # Schema 21 | if($WriteSchemaUrl) 22 | { 23 | Write-Host "Updating values.yaml schema url" 24 | $content = Get-Content -Path "$root/values.yaml" 25 | $schemaText = [string]::Format('# yaml-language-server: $schema=https://github.com/Contrast-Security-OSS/agent-operator/releases/download/v{0}/values.schema.json',$AppVersion) 26 | $content = $content.Replace('# yaml-language-server: $schema=values.schema.json', $schemaText) 27 | Set-Content -Path "$root/values.yaml" -Value $content 28 | } 29 | 30 | # Package 31 | Write-Host "Linting chart." 32 | helm lint $root --values "$root/values.testing.yaml" 33 | if (-Not $?) 34 | { 35 | throw "Linting failed." 36 | } 37 | 38 | Write-Host "Packaging chart." 39 | helm package ` 40 | $root ` 41 | --app-version $AppVersion ` 42 | --version $ChartVersion ` 43 | --destination "$root/dist" 44 | if (-Not $?) 45 | { 46 | throw "Packing failed." 47 | } 48 | 49 | 50 | -------------------------------------------------------------------------------- /tests/Contrast.K8s.AgentOperator.Tests/Core/Comparing/FastComparerHelperTests.cs: -------------------------------------------------------------------------------- 1 | // Contrast Security, Inc licenses this file to you under the Apache 2.0 License. 2 | // See the LICENSE file in the project root for more information. 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Net; 7 | using Contrast.K8s.AgentOperator.Core.Comparing; 8 | using FluentAssertions; 9 | using Xunit; 10 | 11 | namespace Contrast.K8s.AgentOperator.Tests.Core.Comparing 12 | { 13 | public class FastComparerHelperTests 14 | { 15 | [Theory] 16 | [InlineData(typeof(string))] 17 | [InlineData(typeof(int))] 18 | [InlineData(typeof(HttpStatusCode))] 19 | [InlineData(typeof(decimal))] 20 | [InlineData(typeof(DateTimeOffset))] 21 | [InlineData(typeof(int?))] 22 | public void When_type_is_primitive_then_IsPrimitive_should_return_true(Type type) 23 | { 24 | // Act 25 | var result = FastComparerHelper.IsPrimitive(type); 26 | 27 | // Assert 28 | result.Should().BeTrue(); 29 | } 30 | 31 | [Theory] 32 | [InlineData(typeof(object))] 33 | [InlineData(typeof(Dictionary))] 34 | public void When_type_is_not_primitive_then_IsPrimitive_should_return_false(Type type) 35 | { 36 | // Act 37 | var result = FastComparerHelper.IsPrimitive(type); 38 | 39 | // Assert 40 | result.Should().BeFalse(); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Contrast.K8s.AgentOperator/Modules/MediatorModule.cs: -------------------------------------------------------------------------------- 1 | // Contrast Security, Inc licenses this file to you under the Apache 2.0 License. 2 | // See the LICENSE file in the project root for more information. 3 | 4 | using Autofac; 5 | using Autofac.Features.Variance; 6 | using Contrast.K8s.AgentOperator.Extensions; 7 | using JetBrains.Annotations; 8 | using MediatR; 9 | 10 | namespace Contrast.K8s.AgentOperator.Modules; 11 | 12 | [UsedImplicitly] 13 | public class MediatorModule : Module 14 | { 15 | protected override void Load(ContainerBuilder builder) 16 | { 17 | builder.RegisterType() 18 | .As() 19 | .InstancePerLifetimeScope(); 20 | builder.RegisterSource(new ContravariantRegistrationSource()); 21 | builder.RegisterAssemblyTypes(ThisAssembly) 22 | .PublicOnly() 23 | .AssignableToOpenType(typeof(IRequestHandler<>)) 24 | .AsImplementedInterfaces() 25 | .InstancePerLifetimeScope(); 26 | builder.RegisterAssemblyTypes(ThisAssembly) 27 | .PublicOnly() 28 | .AssignableToOpenType(typeof(IRequestHandler<,>)) 29 | .AsImplementedInterfaces() 30 | .InstancePerLifetimeScope(); 31 | builder.RegisterAssemblyTypes(ThisAssembly) 32 | .PublicOnly() 33 | .AssignableToOpenType(typeof(INotificationHandler<>)) 34 | .AsImplementedInterfaces() 35 | .InstancePerLifetimeScope(); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /tests/Contrast.K8s.AgentOperator.Tests/Entities/Dynatrace/V1Beta1DynaKubeTests.cs: -------------------------------------------------------------------------------- 1 | // Contrast Security, Inc licenses this file to you under the Apache 2.0 License. 2 | // See the LICENSE file in the project root for more information. 3 | 4 | using System.Text.Json; 5 | using Contrast.K8s.AgentOperator.Entities.Dynatrace; 6 | using FluentAssertions; 7 | using Xunit; 8 | 9 | namespace Contrast.K8s.AgentOperator.Tests.Entities.Dynatrace 10 | { 11 | public class V1Beta1DynaKubeTests 12 | { 13 | [Fact] 14 | public void When_V1Beta1DynaKube_is_deserialized_then_returned_properties_should_be_an_empty_type() 15 | { 16 | // JsonElement appears to break the compare function. 17 | 18 | const string json = @"{ 19 | ""oneAgent"": { 20 | ""hostMonitoring"": { 21 | ""tolerations"": [ 22 | { 23 | ""effect"": ""NoSchedule"", 24 | ""key"": ""node-role.kubernetes.io/master"", 25 | ""operator"": ""Exists"" 26 | } 27 | ] 28 | } 29 | } 30 | }"; 31 | 32 | // Act 33 | var result = JsonSerializer.Deserialize(json); 34 | 35 | // Assert 36 | result!.OneAgent!.HostMonitoring.Should().BeOfType(); 37 | result.OneAgent!.HostMonitoring.Should().NotBeOfType(); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /manifests/examples/testing/scenarios/namespaced/connection-volume-mount.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: agents.contrastsecurity.com/v1beta1 2 | kind: AgentConnection 3 | metadata: 4 | name: connection-volume-mount-connection 5 | spec: 6 | mountAsVolume: true 7 | token: 8 | secretName: testing-agent-connection-secret 9 | secretKey: token 10 | url: http://localhost 11 | apiKey: 12 | secretName: testing-agent-connection-secret 13 | secretKey: apiKey 14 | serviceKey: 15 | secretName: testing-agent-connection-secret 16 | secretKey: serviceKey 17 | userName: 18 | secretName: testing-agent-connection-secret 19 | secretKey: userName 20 | --- 21 | apiVersion: agents.contrastsecurity.com/v1beta1 22 | kind: AgentInjector 23 | metadata: 24 | name: connection-volume-mount 25 | spec: 26 | enabled: true 27 | type: dummy 28 | selector: 29 | labels: 30 | - name: app 31 | value: connection-volume-mount 32 | connection: 33 | name: connection-volume-mount-connection 34 | configuration: 35 | name: testing-agent-configuration 36 | --- 37 | apiVersion: apps/v1 38 | kind: Deployment 39 | metadata: 40 | name: connection-volume-mount 41 | labels: 42 | app: connection-volume-mount 43 | spec: 44 | replicas: 1 45 | selector: 46 | matchLabels: 47 | app: connection-volume-mount 48 | strategy: 49 | type: Recreate 50 | template: 51 | metadata: 52 | labels: 53 | app: connection-volume-mount 54 | spec: 55 | containers: 56 | - image: k8s.gcr.io/pause:3.3 57 | name: pause 58 | -------------------------------------------------------------------------------- /manifests/examples/testing/scenarios/token/oldauth-dummy.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: agents.contrastsecurity.com/v1beta1 2 | kind: AgentConnection 3 | metadata: 4 | name: oldauth-agent-connection 5 | spec: 6 | url: http://localhost 7 | apiKey: 8 | secretName: oldauth-agent-connection-secret 9 | secretKey: apiKey 10 | serviceKey: 11 | secretName: oldauth-agent-connection-secret 12 | secretKey: serviceKey 13 | userName: 14 | secretName: oldauth-agent-connection-secret 15 | secretKey: userName 16 | --- 17 | apiVersion: v1 18 | kind: Secret 19 | metadata: 20 | name: oldauth-agent-connection-secret 21 | type: Opaque 22 | stringData: 23 | apiKey: apiKey 24 | serviceKey: serviceKey 25 | userName: userName 26 | --- 27 | apiVersion: agents.contrastsecurity.com/v1beta1 28 | kind: AgentInjector 29 | metadata: 30 | name: oldauth-dummy 31 | spec: 32 | enabled: true 33 | type: dummy 34 | selector: 35 | labels: 36 | - name: app 37 | value: oldauth-dummy 38 | connection: 39 | name: oldauth-agent-connection 40 | configuration: 41 | name: testing-agent-configuration 42 | --- 43 | apiVersion: apps/v1 44 | kind: Deployment 45 | metadata: 46 | name: oldauth-dummy 47 | labels: 48 | app: oldauth-dummy 49 | spec: 50 | replicas: 1 51 | selector: 52 | matchLabels: 53 | app: oldauth-dummy 54 | strategy: 55 | type: Recreate 56 | template: 57 | metadata: 58 | labels: 59 | app: oldauth-dummy 60 | spec: 61 | containers: 62 | - image: k8s.gcr.io/pause:3.3 63 | name: pause 64 | -------------------------------------------------------------------------------- /src/Contrast.K8s.AgentOperator/Core/State/StateSettledWorker.cs: -------------------------------------------------------------------------------- 1 | // Contrast Security, Inc licenses this file to you under the Apache 2.0 License. 2 | // See the LICENSE file in the project root for more information. 3 | 4 | using System; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | using Contrast.K8s.AgentOperator.Core.Events; 8 | using Contrast.K8s.AgentOperator.Options; 9 | using JetBrains.Annotations; 10 | using Microsoft.Extensions.Hosting; 11 | using NLog; 12 | 13 | namespace Contrast.K8s.AgentOperator.Core.State; 14 | 15 | [UsedImplicitly] 16 | public class StateSettledWorker : BackgroundService 17 | { 18 | private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); 19 | private readonly OperatorOptions _operatorOptions; 20 | private readonly IEventStream _eventStream; 21 | 22 | public StateSettledWorker(OperatorOptions operatorOptions, IEventStream eventStream) 23 | { 24 | _operatorOptions = operatorOptions; 25 | _eventStream = eventStream; 26 | } 27 | 28 | protected override async Task ExecuteAsync(CancellationToken stoppingToken) 29 | { 30 | var delay = TimeSpan.FromSeconds(_operatorOptions.SettlingDurationSeconds); 31 | 32 | Logger.Info($"Waiting {delay.TotalSeconds} seconds for operator to rebuild cluster state before applying changes."); 33 | await Task.Delay(delay, stoppingToken); 34 | 35 | await _eventStream.DispatchDeferred(new StateSettled(), stoppingToken); 36 | 37 | await Task.Delay(Timeout.Infinite, stoppingToken); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Contrast.K8s.AgentOperator/Core/Reactions/Injecting/Patching/Agents/NodeJsEsmAgentPatcher.cs: -------------------------------------------------------------------------------- 1 | // Contrast Security, Inc licenses this file to you under the Apache 2.0 License. 2 | // See the LICENSE file in the project root for more information. 3 | 4 | using System.Collections.Generic; 5 | using Contrast.K8s.AgentOperator.Core.State.Resources.Primitives; 6 | using k8s.Models; 7 | 8 | namespace Contrast.K8s.AgentOperator.Core.Reactions.Injecting.Patching.Agents; 9 | 10 | public class NodeJsEsmAgentPatcher : IAgentPatcher 11 | { 12 | public bool Deprecated => true; 13 | public string? DeprecatedMessage => "Please migrate to use 'nodejs' for NodeJS LTS >= 18.19.0 and 'nodejs-legacy' for NodeJS LTS < 18.19.0"; 14 | 15 | public AgentInjectionType Type => AgentInjectionType.NodeJsEsm; 16 | 17 | public IEnumerable GenerateEnvVars(PatchingContext context) 18 | { 19 | // https://nodejs.org/api/cli.html#node_optionsoptions 20 | yield return new V1EnvVar("NODE_OPTIONS", $"--import {context.AgentMountPath}/node_modules/@contrast/agent/lib/esm-loader.mjs"); 21 | yield return new V1EnvVar("CONTRAST__AGENT__LOGGER__PATH", $"{context.WritableMountPath}/logs/contrast_agent.log"); 22 | yield return new V1EnvVar("CONTRAST__AGENT__SECURITY_LOGGER__PATH", $"{context.WritableMountPath}/logs/contrast_agent_cef.log"); 23 | yield return new V1EnvVar("CONTRAST__AGENT__NODE__REWRITE__CACHE__PATH", $"{context.WritableMountPath}/cache"); 24 | yield return new V1EnvVar("CONTRAST_INSTALLATION_TOOL", "KUBERNETES_OPERATOR"); 25 | } 26 | } 27 | --------------------------------------------------------------------------------