diff --git a/.envrc.template b/.envrc.template new file mode 100644 index 00000000..68c89536 --- /dev/null +++ b/.envrc.template @@ -0,0 +1,6 @@ +# Variables required for hack/e2e/ca/test.sh +export OCI_BASE=ttl.sh/f702739d-6123-4070-8b2d-b90707d44f8b/cyberark-disco-agent +export ARK_USERNAME= +export ARK_SECRET= +export ARK_PLATFORM_DOMAIN= +export ARK_SUBDOMAIN= diff --git a/deploy/charts/cyberark-disco-agent/.helmignore b/deploy/charts/cyberark-disco-agent/.helmignore new file mode 100644 index 00000000..0e8a0eb3 --- /dev/null +++ b/deploy/charts/cyberark-disco-agent/.helmignore @@ -0,0 +1,23 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/deploy/charts/cyberark-disco-agent/Chart.yaml b/deploy/charts/cyberark-disco-agent/Chart.yaml new file mode 100644 index 00000000..f02b6bf0 --- /dev/null +++ b/deploy/charts/cyberark-disco-agent/Chart.yaml @@ -0,0 +1,18 @@ +apiVersion: v2 +name: cyberark-disco-agent +description: |- + The cyberark-disco-agent connects your Kubernetes or Openshift cluster to CyberArk Discovery and Context. + +maintainers: + - name: CyberArk + email: support@cyberark.com + url: https://cyberark.com + +sources: + - https://github.com/jetstack/jetstack-secure + +# These versions are meant to be overridden by `make helm-chart`. No `v` prefix +# for the `version` because Helm doesn't support auto-determining the latest +# version for OCI Helm charts that use a `v` prefix. +version: 0.0.0 +appVersion: "v0.0.0" diff --git a/deploy/charts/cyberark-disco-agent/templates/NOTES.txt b/deploy/charts/cyberark-disco-agent/templates/NOTES.txt new file mode 100644 index 00000000..8a0136c1 --- /dev/null +++ b/deploy/charts/cyberark-disco-agent/templates/NOTES.txt @@ -0,0 +1,5 @@ +- Check the application is running: +> kubectl get pods -n {{ .Release.Namespace }} -l app.kubernetes.io/instance={{ .Release.Name }} + +- Check the application logs for successful connection to the platform: +> kubectl logs -n {{ .Release.Namespace }} -l app.kubernetes.io/instance={{ .Release.Name }} diff --git a/deploy/charts/cyberark-disco-agent/templates/_helpers.tpl b/deploy/charts/cyberark-disco-agent/templates/_helpers.tpl new file mode 100644 index 00000000..1a43b7cf --- /dev/null +++ b/deploy/charts/cyberark-disco-agent/templates/_helpers.tpl @@ -0,0 +1,62 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "cyberark-disco-agent.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "cyberark-disco-agent.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "cyberark-disco-agent.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "cyberark-disco-agent.labels" -}} +helm.sh/chart: {{ include "cyberark-disco-agent.chart" . }} +{{ include "cyberark-disco-agent.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "cyberark-disco-agent.selectorLabels" -}} +app.kubernetes.io/name: {{ include "cyberark-disco-agent.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "cyberark-disco-agent.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "cyberark-disco-agent.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} diff --git a/deploy/charts/cyberark-disco-agent/templates/configmap.yaml b/deploy/charts/cyberark-disco-agent/templates/configmap.yaml new file mode 100644 index 00000000..75f3e70f --- /dev/null +++ b/deploy/charts/cyberark-disco-agent/templates/configmap.yaml @@ -0,0 +1,75 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "cyberark-disco-agent.fullname" . }}-config + namespace: {{ .Release.Namespace }} + labels: + {{- include "cyberark-disco-agent.labels" . | nindent 4 }} +data: + config.yaml: |- + period: {{ .Values.config.period | quote }} + {{- with .Values.config.excludeAnnotationKeysRegex }} + exclude-annotation-keys-regex: + {{- . | toYaml | nindent 6 }} + {{- end }} + {{- with .Values.config.excludeLabelKeysRegex }} + exclude-label-keys-regex: + {{- . | toYaml | nindent 6 }} + {{- end }} + data-gatherers: + - kind: k8s-discovery + name: ark/discovery + - kind: k8s-dynamic + name: ark/namespaces + config: + resource-type: + version: v1 + resource: namespaces + - kind: k8s-dynamic + name: ark/serviceaccounts + config: + resource-type: + resource: serviceaccounts + version: v1 + - kind: k8s-dynamic + name: ark/secrets + config: + resource-type: + version: v1 + resource: secrets + field-selectors: + - type!=kubernetes.io/service-account-token + - type!=kubernetes.io/dockercfg + - type!=kubernetes.io/dockerconfigjson + - type!=kubernetes.io/basic-auth + - type!=kubernetes.io/ssh-auth + - type!=bootstrap.kubernetes.io/token + - type!=helm.sh/release.v1 + - kind: k8s-dynamic + name: ark/roles + config: + resource-type: + version: v1 + group: rbac.authorization.k8s.io + resource: roles + - kind: k8s-dynamic + name: ark/clusterroles + config: + resource-type: + version: v1 + group: rbac.authorization.k8s.io + resource: clusterroles + - kind: k8s-dynamic + name: ark/rolebindings + config: + resource-type: + version: v1 + group: rbac.authorization.k8s.io + resource: rolebindings + - kind: k8s-dynamic + name: ark/clusterrolebindings + config: + resource-type: + version: v1 + group: rbac.authorization.k8s.io + resource: clusterrolebindings diff --git a/deploy/charts/cyberark-disco-agent/templates/deployment.yaml b/deploy/charts/cyberark-disco-agent/templates/deployment.yaml new file mode 100644 index 00000000..8b82ae1d --- /dev/null +++ b/deploy/charts/cyberark-disco-agent/templates/deployment.yaml @@ -0,0 +1,131 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "cyberark-disco-agent.fullname" . }} + labels: + {{- include "cyberark-disco-agent.labels" . | nindent 4 }} +spec: + replicas: {{ .Values.replicaCount }} + selector: + matchLabels: + {{- include "cyberark-disco-agent.selectorLabels" . | nindent 6 }} + template: + metadata: + {{- with .Values.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "cyberark-disco-agent.labels" . | nindent 8 }} + {{- with .Values.podLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "cyberark-disco-agent.serviceAccountName" . }} + {{- with .Values.podSecurityContext }} + securityContext: + {{- toYaml . | nindent 8 }} + {{- end }} + containers: + - name: {{ .Chart.Name }} + {{- with .Values.securityContext }} + securityContext: + {{- toYaml . | nindent 12 }} + {{- end }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + env: + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: POD_UID + valueFrom: + fieldRef: + fieldPath: metadata.uid + - name: POD_NODE + valueFrom: + fieldRef: + fieldPath: spec.nodeName + - name: ARK_USERNAME + valueFrom: + secretKeyRef: + name: {{ .Values.authentication.secretName }} + key: ARK_USERNAME + - name: ARK_SECRET + valueFrom: + secretKeyRef: + name: {{ .Values.authentication.secretName }} + key: ARK_SECRET + - name: ARK_PLATFORM_DOMAIN + valueFrom: + secretKeyRef: + name: {{ .Values.authentication.secretName }} + key: ARK_PLATFORM_DOMAIN + - name: ARK_SUBDOMAIN + valueFrom: + secretKeyRef: + name: {{ .Values.authentication.secretName }} + key: ARK_SUBDOMAIN + {{- with .Values.http_proxy }} + - name: HTTP_PROXY + value: {{ . }} + {{- end }} + {{- with .Values.https_proxy }} + - name: HTTPS_PROXY + value: {{ . }} + {{- end }} + {{- with .Values.no_proxy }} + - name: NO_PROXY + value: {{ . }} + {{- end }} + args: + - "agent" + - "-c" + - "/etc/cyberark-disco-agent/config.yaml" + - --machine-hub + {{- if .Values.metrics.enabled }} + - --enable-metrics + {{- end }} + {{- range .Values.extraArgs }} + - {{ . | quote }} + {{- end }} + {{- with .Values.resources }} + resources: + {{- toYaml . | nindent 12 }} + {{- end }} + volumeMounts: + - name: config + mountPath: "/etc/cyberark-disco-agent" + readOnly: true + {{- with .Values.volumeMounts }} + {{- toYaml . | nindent 12 }} + {{- end }} + volumes: + - name: config + configMap: + name: {{ include "cyberark-disco-agent.fullname" . }}-config + optional: false + {{- with .Values.volumes }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} diff --git a/deploy/charts/cyberark-disco-agent/templates/poddisruptionbudget.yaml b/deploy/charts/cyberark-disco-agent/templates/poddisruptionbudget.yaml new file mode 100644 index 00000000..fb8c73eb --- /dev/null +++ b/deploy/charts/cyberark-disco-agent/templates/poddisruptionbudget.yaml @@ -0,0 +1,23 @@ +{{- if .Values.podDisruptionBudget.enabled }} +apiVersion: policy/v1 +kind: PodDisruptionBudget +metadata: + name: {{ include "cyberark-disco-agent.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "cyberark-disco-agent.labels" . | nindent 4 }} +spec: + selector: + matchLabels: + {{- include "cyberark-disco-agent.selectorLabels" . | nindent 6 }} + + {{- if not (or (hasKey .Values.podDisruptionBudget "minAvailable") (hasKey .Values.podDisruptionBudget "maxUnavailable")) }} + minAvailable: 1 # Default value because minAvailable and maxUnavailable are not set + {{- end }} + {{- if hasKey .Values.podDisruptionBudget "minAvailable" }} + minAvailable: {{ .Values.podDisruptionBudget.minAvailable }} + {{- end }} + {{- if hasKey .Values.podDisruptionBudget "maxUnavailable" }} + maxUnavailable: {{ .Values.podDisruptionBudget.maxUnavailable }} + {{- end }} +{{- end }} diff --git a/deploy/charts/cyberark-disco-agent/templates/podmonitor.yaml b/deploy/charts/cyberark-disco-agent/templates/podmonitor.yaml new file mode 100644 index 00000000..7d798d75 --- /dev/null +++ b/deploy/charts/cyberark-disco-agent/templates/podmonitor.yaml @@ -0,0 +1,40 @@ +{{- if and .Values.metrics.enabled .Values.metrics.podmonitor.enabled }} +apiVersion: monitoring.coreos.com/v1 +kind: PodMonitor +metadata: + name: {{ include "venafi-kubernetes-agent.fullname" . }} +{{- if .Values.metrics.podmonitor.namespace }} + namespace: {{ .Values.metrics.podmonitor.namespace }} +{{- else }} + namespace: {{ .Release.Namespace | quote }} +{{- end }} + labels: + {{- include "venafi-kubernetes-agent.labels" . | nindent 4 }} + prometheus: {{ .Values.metrics.podmonitor.prometheusInstance }} + {{- with .Values.metrics.podmonitor.labels }} + {{- toYaml . | nindent 4 }} + {{- end }} +{{- with .Values.metrics.podmonitor.annotations }} + annotations: + {{- toYaml . | nindent 4 }} +{{- end }} +spec: + jobLabel: {{ include "venafi-kubernetes-agent.fullname" . }} + selector: + matchLabels: + {{- include "venafi-kubernetes-agent.selectorLabels" . | nindent 6 }} +{{- if .Values.metrics.podmonitor.namespace }} + namespaceSelector: + matchNames: + - {{ .Release.Namespace | quote }} +{{- end }} + podMetricsEndpoints: + - port: http-metrics + path: /metrics + interval: {{ .Values.metrics.podmonitor.interval }} + scrapeTimeout: {{ .Values.metrics.podmonitor.scrapeTimeout }} + honorLabels: {{ .Values.metrics.podmonitor.honorLabels }} + {{- with .Values.metrics.podmonitor.endpointAdditionalProperties }} + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end }} diff --git a/deploy/charts/cyberark-disco-agent/templates/rbac.yaml b/deploy/charts/cyberark-disco-agent/templates/rbac.yaml new file mode 100644 index 00000000..b0a0c7be --- /dev/null +++ b/deploy/charts/cyberark-disco-agent/templates/rbac.yaml @@ -0,0 +1,98 @@ +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: {{ include "cyberark-disco-agent.fullname" . }}-event-emitted + labels: + {{- include "cyberark-disco-agent.labels" . | nindent 4 }} +rules: + - apiGroups: [""] + resources: ["events"] + verbs: ["create"] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: {{ include "cyberark-disco-agent.fullname" . }}-event-emitted + labels: + {{- include "cyberark-disco-agent.labels" . | nindent 4 }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: {{ include "cyberark-disco-agent.fullname" . }}-event-emitted +subjects: + - kind: ServiceAccount + name: {{ include "cyberark-disco-agent.serviceAccountName" . }} + namespace: {{ .Release.Namespace }} +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: {{ include "cyberark-disco-agent.fullname" . }}-cluster-viewer + labels: + {{- include "cyberark-disco-agent.labels" . | nindent 4 }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: view +subjects: + - kind: ServiceAccount + name: {{ include "cyberark-disco-agent.serviceAccountName" . }} + namespace: {{ .Release.Namespace }} +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: {{ include "cyberark-disco-agent.fullname" . }}-secret-reader + labels: + {{- include "cyberark-disco-agent.labels" . | nindent 4 }} +rules: + - apiGroups: [""] + resources: ["secrets"] + verbs: ["get", "list", "watch"] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: {{ include "cyberark-disco-agent.fullname" . }}-secret-reader + labels: + {{- include "cyberark-disco-agent.labels" . | nindent 4 }} +roleRef: + kind: ClusterRole + name: {{ include "cyberark-disco-agent.fullname" . }}-secret-reader + apiGroup: rbac.authorization.k8s.io +subjects: + - kind: ServiceAccount + name: {{ include "cyberark-disco-agent.serviceAccountName" . }} + namespace: {{ .Release.Namespace }} +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: {{ include "cyberark-disco-agent.fullname" . }}-rbac-reader + labels: + {{- include "cyberark-disco-agent.labels" . | nindent 4 }} +rules: + - apiGroups: ["rbac.authorization.k8s.io"] + resources: + - roles + - clusterroles + - rolebindings + - clusterrolebindings + verbs: ["get", "list", "watch"] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: {{ include "cyberark-disco-agent.fullname" . }}-rbac-reader + labels: + {{- include "cyberark-disco-agent.labels" . | nindent 4 }} +roleRef: + kind: ClusterRole + name: {{ include "cyberark-disco-agent.fullname" . }}-rbac-reader + apiGroup: rbac.authorization.k8s.io +subjects: + - kind: ServiceAccount + name: {{ include "cyberark-disco-agent.serviceAccountName" . }} + namespace: {{ .Release.Namespace }} + diff --git a/deploy/charts/cyberark-disco-agent/templates/serviceaccount.yaml b/deploy/charts/cyberark-disco-agent/templates/serviceaccount.yaml new file mode 100644 index 00000000..a69e9643 --- /dev/null +++ b/deploy/charts/cyberark-disco-agent/templates/serviceaccount.yaml @@ -0,0 +1,13 @@ +{{- if .Values.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "cyberark-disco-agent.serviceAccountName" . }} + labels: + {{- include "cyberark-disco-agent.labels" . | nindent 4 }} + {{- with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +automountServiceAccountToken: {{ .Values.serviceAccount.automount }} +{{- end }} diff --git a/deploy/charts/cyberark-disco-agent/values.yaml b/deploy/charts/cyberark-disco-agent/values.yaml new file mode 100644 index 00000000..f9398059 --- /dev/null +++ b/deploy/charts/cyberark-disco-agent/values.yaml @@ -0,0 +1,191 @@ +# Default values for cyberark-disco-agent. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +# This will set the replicaset count more information can be found here: https://kubernetes.io/docs/concepts/workloads/controllers/replicaset/ +replicaCount: 1 + +# This sets the container image more information can be found here: https://kubernetes.io/docs/concepts/containers/images/ +image: + repository: registry.venafi.cloud/venafi-agent/venafi-agent + # This sets the pull policy for images. + pullPolicy: IfNotPresent + # Overrides the image tag whose default is the chart appVersion. + tag: "" + +# This is for the secrets for pulling an image from a private repository more information can be found here: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ +imagePullSecrets: [] +# This is to override the chart name. +nameOverride: "" +fullnameOverride: "" + +# This section builds out the service account more information can be found here: https://kubernetes.io/docs/concepts/security/service-accounts/ +serviceAccount: + # Specifies whether a service account should be created + create: true + # Automatically mount a ServiceAccount's API credentials? + automount: true + # Annotations to add to the service account + annotations: {} + # The name of the service account to use. + # If not set and create is true, a name is generated using the fullname template + name: "" + +# This is for setting Kubernetes Annotations to a Pod. +# For more information checkout: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/ +podAnnotations: {} +# This is for setting Kubernetes Labels to a Pod. +# For more information checkout: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ +podLabels: {} + +podSecurityContext: {} + # fsGroup: 2000 + +# Add Container specific SecurityContext settings to the container. Takes +# precedence over `podSecurityContext` when set. See +# https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-capabilities-for-a-container +# +docs:property +securityContext: + capabilities: + drop: + - ALL + readOnlyRootFilesystem: true + runAsNonRoot: true + allowPrivilegeEscalation: false + seccompProfile: { type: RuntimeDefault } + +resources: {} + # We usually recommend not to specify default resources and to leave this as a conscious + # choice for the user. This also increases chances charts run on environments with little + # resources, such as Minikube. If you do want to specify resources, uncomment the following + # lines, adjust them as necessary, and remove the curly braces after 'resources:'. + # limits: + # cpu: 100m + # memory: 128Mi + # requests: + # cpu: 100m + # memory: 128Mi + +# Additional volumes on the output Deployment definition. +volumes: [] +# - name: foo +# secret: +# secretName: mysecret +# optional: false + +# Additional volumeMounts on the output Deployment definition. +volumeMounts: [] +# - name: foo +# mountPath: "/etc/foo" +# readOnly: true + +nodeSelector: {} + +tolerations: [] + +affinity: {} + +# Configures the HTTP_PROXY environment variable where a HTTP proxy is required. +# +docs:property +# http_proxy: "http://proxy:8080" + +# Configures the HTTPS_PROXY environment variable where a HTTP proxy is required. +# +docs:property +# https_proxy: "https://proxy:8080" + +# Configures the NO_PROXY environment variable where a HTTP proxy is required, +# but certain domains should be excluded. +# +docs:property +# no_proxy: 127.0.0.1,localhost + +# Configure a PodDisruptionBudget for the agent's Deployment. If running with multiple +# replicas, consider setting podDisruptionBudget.enabled to true. +# +docs:property +podDisruptionBudget: + # Enable or disable the PodDisruptionBudget resource, which helps prevent downtime + # during voluntary disruptions such as during a Node upgrade. + enabled: false + + # Configure the minimum available pods for disruptions. Can either be set to + # an integer (e.g. 1) or a percentage value (e.g. 25%). + # Cannot be used if `maxUnavailable` is set. + # +docs:property + # minAvailable: 1 + + # Configure the maximum unavailable pods for disruptions. Can either be set to + # an integer (e.g. 1) or a percentage value (e.g. 25%). + # Cannot be used if `minAvailable` is set. + # +docs:property + # maxUnavailable: 1 + +# Configuration for the agent +config: + # Push data every minute unless changed. + period: "0h1m0s" + + # You can configure the agent to exclude some annotations or + # labels from being pushed . All Kubernetes objects + # are affected. The objects are still pushed, but the specified annotations + # and labels are removed before being pushed. + # + # Dots is the only character that needs to be escaped in the regex. Use either + # double quotes with escaped single quotes or unquoted strings for the regex + # to avoid YAML parsing issues with `\.`. + # + # Example: excludeAnnotationKeysRegex: ['^kapp\.k14s\.io/original.*'] + excludeAnnotationKeysRegex: [] + excludeLabelKeysRegex: [] + +authentication: + secretName: agent-credentials + +# extraArgs: +# - --logging-format=json +# - --log-level=6 # To enable HTTP request logging +extraArgs: [] + +metrics: + # Enable the metrics server. + # If false, the metrics server will be disabled and the other metrics fields below will be ignored. + enabled: true + podmonitor: + # Create a PodMonitor to add the metrics to Prometheus, if you are using Prometheus Operator. + # See https://prometheus-operator.dev/docs/operator/api/#monitoring.coreos.com/v1.PodMonitor + enabled: false + + # The namespace that the pod monitor should live in. + # Defaults to the venafi-kubernetes-agent namespace. + # +docs:property + # namespace: venafi + + # Specifies the `prometheus` label on the created PodMonitor. + # This is used when different Prometheus instances have label selectors + # matching different PodMonitors. + prometheusInstance: default + + # The interval to scrape metrics. + interval: 60s + + # The timeout before a metrics scrape fails. + scrapeTimeout: 30s + + # Additional labels to add to the PodMonitor. + labels: {} + + # Additional annotations to add to the PodMonitor. + annotations: {} + + # Keep labels from scraped data, overriding server-side labels. + honorLabels: false + + # EndpointAdditionalProperties allows setting additional properties on the endpoint such as relabelings, metricRelabelings etc. + # + # For example: + # endpointAdditionalProperties: + # relabelings: + # - action: replace + # sourceLabels: + # - __meta_kubernetes_pod_node_name + # targetLabel: instance + endpointAdditionalProperties: {} + diff --git a/hack/e2e/ca/test.sh b/hack/e2e/ca/test.sh new file mode 100755 index 00000000..c607c5e0 --- /dev/null +++ b/hack/e2e/ca/test.sh @@ -0,0 +1,54 @@ +#!/usr/bin/env bash +# +set -o nounset +set -o errexit +set -o pipefail + +# CyberArk API configuration +: ${ARK_USERNAME?} +: ${ARK_SECRET?} +: ${ARK_PLATFORM_DOMAIN?} +: ${ARK_SUBDOMAIN?} + +# The base URL of the OCI registry used for Docker images and Helm charts +# E.g. ttl.sh/6ee49a01-c8ba-493e-bae9-4d8567574b56 +: ${OCI_BASE?} + +k8s_namespace=cyberark + +script_dir=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd) +root_dir=$(cd "${script_dir}/../../.." && pwd) +export TERM=dumb + +tmp_dir="$(mktemp -d /tmp/jetstack-secure.XXXXX)" + +pushd "${tmp_dir}" +> release.env +make -C "$root_dir" release \ + OCI_SIGN_ON_PUSH=false \ + oci_platforms=linux/amd64 \ + oci_preflight_image_name=$OCI_BASE/images/cyberark-disco-agent \ + helm_chart_image_name=$OCI_BASE/charts/venafi-kubernetes-agent \ + GITHUB_OUTPUT="${tmp_dir}/release.env" +source release.env + +kind create cluster || true +kubectl create ns "$k8s_namespace" || true + +kubectl delete secret agent-credentials --namespace "$k8s_namespace" --ignore-not-found +kubectl create secret generic agent-credentials \ + --namespace "$k8s_namespace" \ + --from-literal=ARK_USERNAME=$ARK_USERNAME \ + --from-literal=ARK_SECRET=$ARK_SECRET \ + --from-literal=ARK_PLATFORM_DOMAIN=$ARK_PLATFORM_DOMAIN \ + --from-literal=ARK_SUBDOMAIN=$ARK_SUBDOMAIN + +helm upgrade agent "${root_dir}/deploy/charts/cyberark-disco-agent" \ + --install \ + --create-namespace \ + --namespace "$k8s_namespace" \ + --set fullnameOverride=disco-agent \ + --set "image.repository=${RELEASE_OCI_PREFLIGHT_IMAGE}" \ + --set "image.tag=${RELEASE_OCI_PREFLIGHT_TAG}" \ + --set-json "podLabels={\"test\": \"${RANDOM}\"}" \ + --values "${script_dir}/values.agent.yaml" diff --git a/hack/e2e/ca/values.agent.yaml b/hack/e2e/ca/values.agent.yaml new file mode 100644 index 00000000..b7db2541 --- /dev/null +++ b/hack/e2e/ca/values.agent.yaml @@ -0,0 +1 @@ +# Empty diff --git a/pkg/agent/config.go b/pkg/agent/config.go index 2686a1bd..70f2750e 100644 --- a/pkg/agent/config.go +++ b/pkg/agent/config.go @@ -1,7 +1,6 @@ package agent import ( - "errors" "fmt" "io" "net/url" @@ -62,9 +61,6 @@ type Config struct { ExcludeAnnotationKeysRegex []string `yaml:"exclude-annotation-keys-regex"` // Skips label keys that match the given set of regular expressions. ExcludeLabelKeysRegex []string `yaml:"exclude-label-keys-regex"` - - // MachineHub holds config specific to MachineHub mode. - MachineHub MachineHubConfig `yaml:"machineHub"` } type Endpoint struct { @@ -92,33 +88,6 @@ type VenafiCloudConfig struct { UploadPath string `yaml:"upload_path,omitempty"` } -// MachineHubConfig holds configuration values specific to the CyberArk Machine Hub integration -type MachineHubConfig struct { - // Subdomain is the subdomain indicating where data should be pushed. Used - // for querying the Service Discovery Service to discover the Identity API - // URL. - Subdomain string `yaml:"subdomain"` - - // CredentialsSecretName is the name of a Kubernetes Secret in the same - // namespace as the agent, which will be watched for a username and password - // to send to CyberArk Identity for authentication. - CredentialsSecretName string `yaml:"credentialsSecretName"` -} - -func (mhc MachineHubConfig) Validate() error { - var errs []error - - if mhc.Subdomain == "" { - errs = append(errs, fmt.Errorf("subdomain must not be empty in MachineHub mode")) - } - - if mhc.CredentialsSecretName == "" { - errs = append(errs, fmt.Errorf("credentialsSecretName must not be empty in MachineHub mode")) - } - - return errors.Join(errs...) -} - type AgentCmdFlags struct { // ConfigFilePath (--config-file, -c) is the path to the agent configuration // YAML file. @@ -355,7 +324,7 @@ func InitAgentCmdFlags(c *cobra.Command, cfg *AgentCmdFlags) { } -// TLSPKMode controls how to authenticate to TLSPK / Jetstack Secure. Only one +// TLSPKMode controls how to authenticate to TLSPK / Jetstack Secure / MachineHub. Only one // TLSPKMode may be provided if using those backends. type TLSPKMode string @@ -364,10 +333,8 @@ const ( JetstackSecureAPIToken TLSPKMode = "Jetstack Secure API Token" VenafiCloudKeypair TLSPKMode = "Venafi Cloud Key Pair Service Account" VenafiCloudVenafiConnection TLSPKMode = "Venafi Cloud VenafiConnection" - - // It is possible to push to both MachineHub and TLSPK. With this mode, the - // agent will only push to MachineHub and not to TLSPK. - Off TLSPKMode = "MachineHub only" + MachineHub TLSPKMode = "Machine Hub" + Off TLSPKMode = "Off" ) // The command-line flags and the config file are combined into this struct by @@ -408,11 +375,6 @@ type CombinedConfig struct { // Only used for testing purposes. OutputPath string InputPath string - - // MachineHub-related settings. - MachineHubMode bool - MachineHubSubdomain string - MachineHubCredentialsSecretName string } // ValidateAndCombineConfig combines and validates the input configuration with @@ -426,20 +388,6 @@ type CombinedConfig struct { // error. func ValidateAndCombineConfig(log logr.Logger, cfg Config, flags AgentCmdFlags) (CombinedConfig, client.Client, error) { res := CombinedConfig{} - - if flags.MachineHubMode { - if err := cfg.MachineHub.Validate(); err != nil { - return CombinedConfig{}, nil, fmt.Errorf("invalid MachineHub config provided: %w", err) - } - - res.MachineHubMode = true - res.MachineHubSubdomain = cfg.MachineHub.Subdomain - res.MachineHubCredentialsSecretName = cfg.MachineHub.CredentialsSecretName - - keysAndValues := []any{"credentialsSecretName", res.MachineHubCredentialsSecretName} - log.V(logs.Info).Info("Will push to CyberArk MachineHub using a username and password loaded from a Kubernetes Secret", keysAndValues...) - } - { var ( mode TLSPKMode @@ -472,23 +420,25 @@ func ValidateAndCombineConfig(log logr.Logger, cfg Config, flags AgentCmdFlags) case !flags.VenafiCloudMode && flags.CredentialsPath != "": mode = JetstackSecureOAuth reason = "--credentials-file was specified without --venafi-cloud" - default: - if !flags.MachineHubMode { - return CombinedConfig{}, nil, fmt.Errorf("no TLSPK mode specified and MachineHub mode is disabled. You must either enable the MachineHub mode (using --machine-hub), or enable one of the TLSPK modes.\n" + - "To enable one of the TLSPK modes, you can:\n" + - " - Use (--venafi-cloud with --credentials-file) or (--client-id with --private-key-path) to use the " + string(VenafiCloudKeypair) + " mode.\n" + - " - Use --venafi-connection for the " + string(VenafiCloudVenafiConnection) + " mode.\n" + - " - Use --credentials-file alone if you want to use the " + string(JetstackSecureOAuth) + " mode.\n" + - " - Use --api-token if you want to use the " + string(JetstackSecureAPIToken) + " mode.\n" + - "Note that it is possible to use one of the TLSPK modes along with the MachineHub mode (--machine-hub).") - } - + case flags.MachineHubMode: + mode = MachineHub + reason = "--machine-hub was specified." + case flags.OutputPath != "": mode = Off + reason = "--output-path was specified. Data will be saved to a file. It will not be uploaded." + default: + return CombinedConfig{}, nil, fmt.Errorf("no upload mode specified. You must either enable one of the upload modes, or use --output-path to save data to a file.\n" + + "To enable one of the upload modes, you can:\n" + + " - Use (--venafi-cloud with --credentials-file) or (--client-id with --private-key-path) to use the " + string(VenafiCloudKeypair) + " mode.\n" + + " - Use --venafi-connection for the " + string(VenafiCloudVenafiConnection) + " mode.\n" + + " - Use --credentials-file alone if you want to use the " + string(JetstackSecureOAuth) + " mode.\n" + + " - Use --api-token if you want to use the " + string(JetstackSecureAPIToken) + " mode.\n" + + " - Use --machine-hub if you want to use the " + string(MachineHub) + " mode.") } keysAndValues = append(keysAndValues, "mode", mode, "reason", reason) if mode != Off { - log.V(logs.Debug).Info("Configured to push to Venafi", keysAndValues...) + log.V(logs.Debug).Info("Configured to push", keysAndValues...) } res.TLSPKMode = mode @@ -606,12 +556,12 @@ func ValidateAndCombineConfig(log logr.Logger, cfg Config, flags AgentCmdFlags) res.ClusterID = clusterID res.ClusterDescription = cfg.ClusterDescription - // Validation of `data-gatherers`. - if dgErr := ValidateDataGatherers(cfg.DataGatherers); dgErr != nil { - errs = multierror.Append(errs, dgErr) - } - res.DataGatherers = cfg.DataGatherers } + // Validation of `data-gatherers`. + if dgErr := ValidateDataGatherers(cfg.DataGatherers); dgErr != nil { + errs = multierror.Append(errs, dgErr) + } + res.DataGatherers = cfg.DataGatherers // Validation of --period, -p, and the `period` field, as well as // --backoff-max-time, --one-shot, and --strict. The flag --period/-p takes @@ -807,6 +757,8 @@ func validateCredsAndCreateClient(log logr.Logger, flagCredentialsPath, flagClie if err != nil { errs = multierror.Append(errs, err) } + case MachineHub: + preflightClient = client.NewMachineHub() case Off: // No client needed in this mode. default: diff --git a/pkg/agent/config_test.go b/pkg/agent/config_test.go index 2743a493..c22a7011 100644 --- a/pkg/agent/config_test.go +++ b/pkg/agent/config_test.go @@ -96,7 +96,7 @@ func Test_ValidateAndCombineConfig(t *testing.T) { withCmdLineFlags("--period", "99m", "--credentials-file", fakeCredsPath)) require.NoError(t, err) assert.Equal(t, testutil.Undent(` - INFO Configured to push to Venafi mode="Jetstack Secure OAuth" reason="--credentials-file was specified without --venafi-cloud" + INFO Configured to push mode="Jetstack Secure OAuth" reason="--credentials-file was specified without --venafi-cloud" INFO Both the 'period' field and --period are set. Using the value provided with --period. `), gotLogs.String()) assert.Equal(t, 99*time.Minute, got.Period) @@ -178,7 +178,7 @@ func Test_ValidateAndCombineConfig(t *testing.T) { // The log line printed by pflag is not captured by the log recorder. assert.Equal(t, testutil.Undent(` - INFO Configured to push to Venafi mode="Jetstack Secure OAuth" reason="--credentials-file was specified without --venafi-cloud" + INFO Configured to push mode="Jetstack Secure OAuth" reason="--credentials-file was specified without --venafi-cloud" INFO Using period from config period="1h0m0s" `), b.String()) }) @@ -194,13 +194,13 @@ func Test_ValidateAndCombineConfig(t *testing.T) { withoutCmdLineFlags(), ) assert.EqualError(t, err, testutil.Undent(` - no TLSPK mode specified and MachineHub mode is disabled. You must either enable the MachineHub mode (using --machine-hub), or enable one of the TLSPK modes. - To enable one of the TLSPK modes, you can: + no upload mode specified. You must either enable one of the upload modes, or use --output-path to save data to a file. + To enable one of the upload modes, you can: - Use (--venafi-cloud with --credentials-file) or (--client-id with --private-key-path) to use the Venafi Cloud Key Pair Service Account mode. - Use --venafi-connection for the Venafi Cloud VenafiConnection mode. - Use --credentials-file alone if you want to use the Jetstack Secure OAuth mode. - Use --api-token if you want to use the Jetstack Secure API Token mode. - Note that it is possible to use one of the TLSPK modes along with the MachineHub mode (--machine-hub).`)) + - Use --machine-hub if you want to use the Machine Hub mode.`)) assert.Nil(t, cl) }) @@ -594,7 +594,7 @@ func Test_ValidateAndCombineConfig(t *testing.T) { ) require.NoError(t, err) assert.Equal(t, testutil.Undent(` - INFO Configured to push to Venafi venConnName="venafi-components" mode="Venafi Cloud VenafiConnection" reason="--venafi-connection was specified" + INFO Configured to push venConnName="venafi-components" mode="Venafi Cloud VenafiConnection" reason="--venafi-connection was specified" INFO ignoring the server field specified in the config file. In Venafi Cloud VenafiConnection mode, this field is not needed. INFO ignoring the venafi-cloud.upload_path field in the config file. In Venafi Cloud VenafiConnection mode, this field is not needed. INFO ignoring the venafi-cloud.uploader_id field in the config file. This field is not needed in Venafi Cloud VenafiConnection mode. @@ -630,28 +630,7 @@ func Test_ValidateAndCombineConfig(t *testing.T) { `)), withCmdLineFlags("--machine-hub")) require.NoError(t, err) - assert.Equal(t, Off, got.TLSPKMode) - assert.Equal(t, true, got.MachineHubMode) - }) - - t.Run("machinehub + venafi-cloud-keypair-auth should work simultaneously", func(t *testing.T) { - t.Setenv("POD_NAMESPACE", "venafi") - t.Setenv("KUBECONFIG", withFile(t, fakeKubeconfig)) - privKeyPath := withFile(t, fakePrivKeyPEM) - got, _, err := ValidateAndCombineConfig(discardLogs(), - withConfig(testutil.Undent(` - machineHub: - subdomain: foo - credentialsSecretName: secret-1 - period: 1h - venafi-cloud: - upload_path: /v1/tlspk/upload/clusterdata - cluster_id: foo - `)), - withCmdLineFlags("--machine-hub", "--venafi-cloud", "--client-id", "5bc7d07c-45da-11ef-a878-523f1e1d7de1", "--private-key-path", privKeyPath)) - require.NoError(t, err) - assert.Equal(t, VenafiCloudKeypair, got.TLSPKMode) - assert.Equal(t, true, got.MachineHubMode) + assert.Equal(t, MachineHub, got.TLSPKMode) }) } diff --git a/pkg/agent/run.go b/pkg/agent/run.go index 7691aed2..47b07bbc 100644 --- a/pkg/agent/run.go +++ b/pkg/agent/run.go @@ -345,18 +345,6 @@ func gatherAndOutputData(ctx context.Context, eventf Eventf, config CombinedConf log.Info("Warning: PushingErr: retrying", "in", t, "reason", err) }) - if config.MachineHubMode { - post := func() (any, error) { - log.Info("machine hub mode not yet implemented") - return struct{}{}, nil - } - - group.Go(func() error { - _, err := backoff.Retry(ctx, post, backoff.WithBackOff(backOff), backoff.WithNotify(notificationFunc), backoff.WithMaxElapsedTime(config.BackoffMaxTime)) - return err - }) - } - if config.TLSPKMode != Off { post := func() (any, error) { return struct{}{}, postData(klog.NewContext(ctx, log), config, preflightClient, readings) @@ -428,39 +416,31 @@ func gatherData(ctx context.Context, config CombinedConfig, dataGatherers map[st func postData(ctx context.Context, config CombinedConfig, preflightClient client.Client, readings []*api.DataReading) error { log := klog.FromContext(ctx).WithName("postData") - baseURL := config.Server - - log.V(logs.Debug).Info("Posting data", "baseURL", baseURL) + var opts client.Options switch config.TLSPKMode { // nolint:exhaustive case VenafiCloudKeypair, VenafiCloudVenafiConnection: // orgID and clusterID are not required for Venafi Cloud auth - err := preflightClient.PostDataReadingsWithOptions(ctx, readings, client.Options{ + opts = client.Options{ ClusterName: config.ClusterID, ClusterDescription: config.ClusterDescription, - }) - if err != nil { - return fmt.Errorf("post to server failed: %+v", err) } - log.Info("Data sent successfully") - - return nil - case JetstackSecureOAuth, JetstackSecureAPIToken: - err := preflightClient.PostDataReadingsWithOptions(ctx, readings, client.Options{ + opts = client.Options{ OrgID: config.OrganizationID, ClusterID: config.ClusterID, - }) - if err != nil { - return fmt.Errorf("post to server failed: %+v", err) } - log.Info("Data sent successfully") - - return err - + case MachineHub: + // Machine hub client gets all its metadata from the data-readings. default: return fmt.Errorf("not implemented for mode %s", config.TLSPKMode) } + + if err := preflightClient.PostDataReadingsWithOptions(ctx, readings, opts); err != nil { + return fmt.Errorf("post to server failed: %+v", err) + } + log.Info("Data sent successfully") + return nil } // listenAndServe starts the supplied HTTP server and stops it gracefully when diff --git a/pkg/client/client_machinehub.go b/pkg/client/client_machinehub.go new file mode 100644 index 00000000..bb37f3a7 --- /dev/null +++ b/pkg/client/client_machinehub.go @@ -0,0 +1,57 @@ +package client + +import ( + "context" + "fmt" + "os" + + "github.com/jetstack/preflight/api" + "github.com/jetstack/preflight/pkg/internal/cyberark/dataupload" + "github.com/jetstack/preflight/pkg/internal/cyberark/identity" + "github.com/jetstack/preflight/pkg/internal/cyberark/servicediscovery" +) + +type MachineHubClient struct{} + +func NewMachineHub() Client { + return &MachineHubClient{} +} + +func (o *MachineHubClient) PostDataReadingsWithOptions(ctx context.Context, readings []*api.DataReading, _ Options) error { + platformDomain := os.Getenv("ARK_PLATFORM_DOMAIN") + subdomain := os.Getenv("ARK_SUBDOMAIN") + username := os.Getenv("ARK_USERNAME") + password := []byte(os.Getenv("ARK_SECRET")) + + const ( + discoveryContextServiceName = "inventory" + separator = "." + ) + + // TODO(wallrj): Maybe get this URL via the service discovery API. + // https://platform-discovery.integration-cyberark.cloud/api/public/tenant-discovery?allEndpoints=true&bySubdomain=tlskp-test + serviceURL := fmt.Sprintf("https://%s%s%s.%s", subdomain, separator, discoveryContextServiceName, platformDomain) + + var ( + identityClient *identity.Client + err error + ) + if platformDomain == "cyberark.cloud" { + identityClient, err = identity.New(ctx, subdomain) + } else { + discoveryClient := servicediscovery.New(servicediscovery.WithIntegrationEndpoint()) + identityClient, err = identity.NewWithDiscoveryClient(ctx, discoveryClient, subdomain) + } + if err != nil { + return fmt.Errorf("while creating the CyberArk identity client: %v", err) + } + if err := identityClient.LoginUsernamePassword(ctx, username, password); err != nil { + return fmt.Errorf("while logging in: %v", err) + } + duClient, err := dataupload.NewCyberArkClient(nil, serviceURL, identityClient.AuthenticateRequest) + if err != nil { + return fmt.Errorf("while creating the CyberArk dataupload client: %v", err) + } + + return duClient.PostDataReadings(ctx, readings) +} diff --git a/pkg/internal/cyberark/dataupload/dataupload_test.go b/pkg/internal/cyberark/dataupload/dataupload_test.go index 73d94c78..87e83638 100644 --- a/pkg/internal/cyberark/dataupload/dataupload_test.go +++ b/pkg/internal/cyberark/dataupload/dataupload_test.go @@ -19,7 +19,7 @@ import ( "github.com/jetstack/preflight/pkg/internal/cyberark/dataupload" "github.com/jetstack/preflight/pkg/internal/cyberark/identity" "github.com/jetstack/preflight/pkg/internal/cyberark/servicediscovery" - "github.com/jetstack/preflight/pkg/testutil" + "github.com/jetstack/preflight/pkg/internal/cyberark/testutil" _ "k8s.io/klog/v2/ktesting/init" ) @@ -31,6 +31,7 @@ func genNamespace(name string) *unstructured.Unstructured { o.SetName(name) return o } + func genArkNamespacesDataReading(clusterID types.UID) *api.DataReading { kubeSystemNamespace := genNamespace("kube-system") kubeSystemNamespace.SetUID(clusterID) @@ -56,6 +57,7 @@ func genArkNamespacesDataReading(clusterID types.UID) *api.DataReading { SchemaVersion: "v1", } } + func TestCyberArkClient_PostDataReadings_MockAPI(t *testing.T) { defaultDataReadings := []*api.DataReading{ genArkNamespacesDataReading("success-cluster-id"), diff --git a/pkg/internal/cyberark/dataupload/snapshot_test.go b/pkg/internal/cyberark/dataupload/snapshot_test.go index f9fae63e..169d2423 100644 --- a/pkg/internal/cyberark/dataupload/snapshot_test.go +++ b/pkg/internal/cyberark/dataupload/snapshot_test.go @@ -8,7 +8,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/jetstack/preflight/pkg/testutil" + "github.com/jetstack/preflight/pkg/internal/cyberark/testutil" ) func TestConvertDataReadingsToCyberarkSnapshot(t *testing.T) { diff --git a/pkg/testutil/datareadings.go b/pkg/internal/cyberark/testutil/datareadings.go similarity index 100% rename from pkg/testutil/datareadings.go rename to pkg/internal/cyberark/testutil/datareadings.go