diff --git a/.secrets.baseline b/.secrets.baseline index c6f667ede7..88d2a3837b 100644 --- a/.secrets.baseline +++ b/.secrets.baseline @@ -340,7 +340,7 @@ "filename": "fleetshard/pkg/central/cloudprovider/dbclient_moq.go", "hashed_secret": "80519927d0f3ce1efe933f46ca9e05e68e491adc", "is_verified": false, - "line_number": 139 + "line_number": 143 } ], "internal/dinosaur/pkg/api/public/api/openapi.yaml": [ @@ -564,5 +564,5 @@ } ] }, - "generated_at": "2023-06-21T12:15:21Z" + "generated_at": "2023-07-19T10:20:12Z" } diff --git a/dev/env/defaults/00-defaults.env b/dev/env/defaults/00-defaults.env index 1cb6c6077d..32e2f1fb84 100644 --- a/dev/env/defaults/00-defaults.env +++ b/dev/env/defaults/00-defaults.env @@ -10,10 +10,10 @@ export CLUSTER_ID_DEFAULT="1234567890abcdef1234567890abcdef" export CLUSTER_DNS_DEFAULT="cluster.local" export IMAGE_REGISTRY_DEFAULT="quay.io/rhacs-eng" -STACKROX_VERSION_TAG="4.0.1" # Note: SCANNER_VERSION_DEFAULT needs to be in sync with this. +STACKROX_VERSION_TAG="4.1.1" # Note: SCANNER_VERSION_DEFAULT needs to be in sync with this. export STACKROX_OPERATOR_VERSION_DEFAULT="${STACKROX_VERSION_TAG}" export CENTRAL_VERSION_DEFAULT=$(echo "$STACKROX_VERSION_TAG" | sed -e 's/0-nightly/x-nightly/;') -export SCANNER_VERSION_DEFAULT="2.29.2" # This one matches the above operator version tag. +export SCANNER_VERSION_DEFAULT="2.30.2" # This one matches the above operator version tag. export STACKROX_OPERATOR_NAMESPACE_DEFAULT="rhacs" export FLEET_MANAGER_IMAGE_DEFAULT="" export IGNORE_REPOSITORY_DIRTINESS_DEFAULT="false" diff --git a/dev/env/manifests/fleetshard-sync/02-fleetshard-sync-deployment.yaml b/dev/env/manifests/fleetshard-sync/02-fleetshard-sync-deployment.yaml index 1527731a5b..5a8e19a6fe 100644 --- a/dev/env/manifests/fleetshard-sync/02-fleetshard-sync-deployment.yaml +++ b/dev/env/manifests/fleetshard-sync/02-fleetshard-sync-deployment.yaml @@ -43,6 +43,8 @@ spec: optional: false - name: RUNTIME_POLL_PERIOD value: 10s + - name: AUDIT_LOG_ENABLED + value: "$AUDIT_LOG_ENABLED" - name: MANAGED_DB_ENABLED value: "$MANAGED_DB_ENABLED" - name: MANAGED_DB_SECURITY_GROUP diff --git a/dev/env/scripts/create-imagepullsecrets b/dev/env/scripts/create-imagepullsecrets index a5a68d78f0..084e5f4a96 100755 --- a/dev/env/scripts/create-imagepullsecrets +++ b/dev/env/scripts/create-imagepullsecrets @@ -70,7 +70,7 @@ function print_auth() { registry_auth="$(print_auth "$(mkauth "${username}" "${password}")")" -if [[ "$INSTALL_OPERATOR" == "true" ]]; then +if [[ "$INSTALL_OPERATOR" == "true" || "$RHACS_TARGETED_OPERATOR_UPGRADES" == "true" ]]; then res=$( cat <Pod) +*/}} +{{- define "aggregator.selectorLabels" -}} +app.kubernetes.io/name: {{ include "aggregator.fullname" . }} +{{- end }} diff --git a/dp-terraform/helm/rhacs-terraform/charts/audit-logs/values.yaml b/dp-terraform/helm/rhacs-terraform/charts/audit-logs/values.yaml new file mode 100644 index 0000000000..b4499a6ac1 --- /dev/null +++ b/dp-terraform/helm/rhacs-terraform/charts/audit-logs/values.yaml @@ -0,0 +1,62 @@ +# Default values for audit-logs charts. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +# Vector image used for audit-logs aggregator. +image: 'registry.redhat.io/openshift-logging/vector-rhel8:v0.21' + +# General annotations for all deployed resources. +annotations: {} + +# Number of pod replicas in stateful set. It should be equal to number of cluster AZs. +replicas: 3 + +# Configuration used to define persistent volumes for Vector buffer. +persistence: + enabled: true + storageClassName: "" + size: 1Gi + +# Customer configuration for Vector. +customConfig: + # We have to set it because default "data_dir" is different from mount path defined by enabled "persistence". + data_dir: /aggregator-data-dir + sources: + http_server: + type: "http" + address: "0.0.0.0:8888" + decoding: + codec: "json" + tls: + enabled: true + ca_file: "/var/run/secrets/kubernetes.io/serviceaccount/service-ca.crt" + crt_file: "/etc/aggregator/tls/tls.crt" + key_file: "/etc/aggregator/tls/tls.key" + sinks: + aws_cloudwatch_logs: + type: "aws_cloudwatch_logs" + region: "us-east-1" + group_name: "" + create_missing_group: false + create_missing_stream: true + inputs: ["http_server"] + stream_name: '{{ "{{" }} .tenant_id {{ "}}" }}' + compression: none + healthcheck: + enabled: true + batch: + timeout_secs: 60 + # 4.5M Bytes + max_size: 4718592 + buffer: + type: disk + # 900M Bytes (disk is 1Gi) + max_size: 943718400 + when_full: block + encoding: + codec: "json" + +# Secrets used to set environment variables for Vector pod. +secrets: + aws_region: "us-east-1" + aws_role_arn: "" diff --git a/dp-terraform/helm/rhacs-terraform/templates/fleetshard-sync.yaml b/dp-terraform/helm/rhacs-terraform/templates/fleetshard-sync.yaml index 7482232962..087becd4d6 100644 --- a/dp-terraform/helm/rhacs-terraform/templates/fleetshard-sync.yaml +++ b/dp-terraform/helm/rhacs-terraform/templates/fleetshard-sync.yaml @@ -62,6 +62,10 @@ spec: value: {{ .Values.fleetshardSync.redHatSSO.realm }} - name: RHSSO_ENDPOINT value: {{ .Values.fleetshardSync.redHatSSO.endpoint }} + - name: AUDIT_LOG_ENABLED + value: {{ .Values.fleetshardSync.auditLogs.enabled | quote }} + - name: AUDIT_LOG_SKIP_TLS_VERIFY + value: {{ .Values.fleetshardSync.auditLogs.skipTLSVerify | quote }} - name: MANAGED_DB_ENABLED value: {{ .Values.fleetshardSync.managedDB.enabled | quote }} {{- if eq .Values.fleetshardSync.managedDB.enabled true }} diff --git a/dp-terraform/helm/rhacs-terraform/terraform_cluster.sh b/dp-terraform/helm/rhacs-terraform/terraform_cluster.sh index 3a0afba728..9033ecb451 100755 --- a/dp-terraform/helm/rhacs-terraform/terraform_cluster.sh +++ b/dp-terraform/helm/rhacs-terraform/terraform_cluster.sh @@ -72,7 +72,7 @@ case $ENVIRONMENT in FLEETSHARD_SYNC_MEMORY_REQUEST="${FLEETSHARD_SYNC_MEMORY_REQUEST:-"1024Mi"}" FLEETSHARD_SYNC_CPU_LIMIT="${FLEETSHARD_SYNC_CPU_LIMIT:-"1000m"}" FLEETSHARD_SYNC_MEMORY_LIMIT="${FLEETSHARD_SYNC_MEMORY_LIMIT:-"1024Mi"}" - SECURED_CLUSTER_ENABLED="false" + SECURED_CLUSTER_ENABLED="true" ;; *) @@ -98,6 +98,10 @@ else "${SCRIPT_DIR}/../../../scripts/check_image_exists.sh" "${FLEETSHARD_SYNC_ORG}" "${FLEETSHARD_SYNC_IMAGE}" "${FLEETSHARD_SYNC_TAG}" fi +echo "Loading external config: audit-logs/${CLUSTER_NAME}" +load_external_config "audit-logs/${CLUSTER_NAME}" AUDIT_LOGS_ + +echo "Loading external config: cluster-${CLUSTER_NAME}" load_external_config "cluster-${CLUSTER_NAME}" CLUSTER_ if [[ "${ENVIRONMENT}" != "dev" ]]; then oc login --token="${CLUSTER_ROBOT_OC_TOKEN}" --server="$CLUSTER_URL" @@ -116,9 +120,6 @@ if [[ "${OPERATOR_USE_UPSTREAM}" == "true" ]]; then OPERATOR_SOURCE="rhacs-operators" fi -# TODO(ROX-14547): Use parameter store value for bucket name. -# load_external_config "audit-logs--${CLUSTER_NAME}" VECTOR_ - # TODO(ROX-16771): Move this to env-specific values.yaml files # TODO(ROX-16645): set acsOperator.enabled to false invoke_helm "${SCRIPT_DIR}" rhacs-terraform \ @@ -169,13 +170,11 @@ invoke_helm "${SCRIPT_DIR}" rhacs-terraform \ --set observability.observatorium.metricsSecret="${OBSERVABILITY_OBSERVATORIUM_METRICS_SECRET}" \ --set observability.pagerduty.key="${OBSERVABILITY_PAGERDUTY_ROUTING_KEY}" \ --set observability.deadMansSwitch.url="${OBSERVABILITY_DEAD_MANS_SWITCH_URL}" \ - --set vector.enabled=false \ - --set vector.service.annotations.rhacs\\.redhat\\.com/cluster-name="${CLUSTER_NAME}" \ - --set vector.service.annotations.rhacs\\.redhat\\.com/environment="${ENVIRONMENT}" \ - --set vector.customConfig.sinks.aws_s3.region="${CLUSTER_REGION}" \ - --set vector.customConfig.sinks.aws_s3.bucket="${VECTOR_BUCKET:-}" \ - --set vector.secrets.generic.aws_access_key_id="${VECTOR_ACCESSKEY:-}" \ - --set vector.secrets.generic.aws_secret_access_key="${VECTOR_SECRETACCESSKEY:-}" \ + --set audit-logs.enabled=true \ + --set audit-logs.annotations.rhacs\\.redhat\\.com/cluster-name="${CLUSTER_NAME}" \ + --set audit-logs.annotations.rhacs\\.redhat\\.com/environment="${ENVIRONMENT}" \ + --set audit-logs.customConfig.sinks.aws_cloudwatch_logs.group_name="${AUDIT_LOGS_LOG_GROUP_NAME}" \ + --set audit-logs.secrets.aws_role_arn="${AUDIT_LOGS_ROLE_ARN:-}" \ --set secured-cluster.enabled="${SECURED_CLUSTER_ENABLED}" \ --set secured-cluster.clusterName="${CLUSTER_NAME}" \ --set secured-cluster.centralEndpoint="${SECURED_CLUSTER_CENTRAL_ENDPOINT}" \ diff --git a/dp-terraform/helm/rhacs-terraform/values.yaml b/dp-terraform/helm/rhacs-terraform/values.yaml index b75974e041..f92d632df1 100644 --- a/dp-terraform/helm/rhacs-terraform/values.yaml +++ b/dp-terraform/helm/rhacs-terraform/values.yaml @@ -27,6 +27,9 @@ fleetshardSync: realm: "redhat-external" egressProxy: image: "registry.redhat.io/openshift4/ose-egress-http-proxy:v4.11.0" + auditLogs: + enabled: true + skipTLSVerify: true managedDB: enabled: true subnetGroup: "" @@ -101,87 +104,54 @@ logging: accessKeyId: "" secretAccessKey: "" -vector: - role: "Aggregator" - service: - annotations: - rhacs.redhat.com/cluster-name: "" - rhacs.redhat.com/environment: "" - service.beta.openshift.io/serving-cert-secret-name: rhacs-vector-tls-secret - podLabels: - app: rhacs-vector - affinity: - podAntiAffinity: - requiredDuringSchedulingIgnoredDuringExecution: - - labelSelector: - matchExpressions: - - key: app - operator: In - values: - - rhacs-vector - topologyKey: "topology.kubernetes.io/zone" +# See available parameters in charts/audit-logs/values.yaml +# - enabled flag is used to completely enable/disable logging sub-chart +audit-logs: + enabled: true + image: 'registry.redhat.io/openshift-logging/vector-rhel8:v0.21' + annotations: {} + replicas: 3 persistence: enabled: true - size: 300Mi - replicas: 3 - extraVolumes: - - name: service-tls-secret - projected: - sources: - - secret: - name: rhacs-vector-tls-secret - extraVolumeMounts: - - name: service-tls-secret - mountPath: /etc/vector/tls - readOnly: true + storageClassName: "" + size: 1Gi customConfig: + data_dir: /aggregator-data-dir sources: http_server: - type: "http_server" + type: "http" address: "0.0.0.0:8888" decoding: codec: "json" tls: enabled: true ca_file: "/var/run/secrets/kubernetes.io/serviceaccount/service-ca.crt" - crt_file: "/etc/vector/tls/tls.crt" - key_file: "/etc/vector/tls/tls.key" + crt_file: "/etc/aggregator/tls/tls.crt" + key_file: "/etc/aggregator/tls/tls.key" sinks: - aws_s3: - type: "aws_s3" - region: "" - bucket: "" - key_prefix: '{{ "{{" }} .tenant_id {{ "}}" }}/%F/' + aws_cloudwatch_logs: + type: "aws_cloudwatch_logs" + group_name: "acs_audit_logs" + create_missing_group: false + create_missing_stream: true inputs: ["http_server"] + stream_name: '{{ "{{" }} .tenant_id {{ "}}" }}' compression: none - filename_extension: "json" healthcheck: - enabled: false + enabled: true batch: timeout_secs: 60 - max_size: 2621440 + # 4.5M Bytes + max_size: 4718592 buffer: type: disk - max_size: 283115520 + # 900M Bytes (disk is 1Gi) + max_size: 943718400 when_full: block encoding: codec: "json" - fullnameOverride: rhacs-vector secrets: - generic: - aws_access_key_id: "" - aws_secret_access_key: "" - env: - - name: AWS_ACCESS_KEY_ID - valueFrom: - secretKeyRef: - name: rhacs-vector - key: aws_access_key_id - - name: AWS_SECRET_ACCESS_KEY - valueFrom: - secretKeyRef: - name: rhacs-vector - key: aws_secret_access_key + aws_role_arn: "" secured-cluster: enabled: true diff --git a/fleetshard/config/config.go b/fleetshard/config/config.go index 09be6494a4..e2987cfc1e 100644 --- a/fleetshard/config/config.go +++ b/fleetshard/config/config.go @@ -2,6 +2,7 @@ package config import ( + "fmt" "time" "github.com/stackrox/rox/pkg/errorhelpers" @@ -29,8 +30,9 @@ type Config struct { EgressProxyImage string `env:"EGRESS_PROXY_IMAGE"` BaseCrdURL string `env:"BASE_CRD_URL" envDefault:"https://raw.githubusercontent.com/stackrox/stackrox/%s/operator/bundle/manifests/"` - ManagedDB ManagedDB - Telemetry Telemetry + ManagedDB ManagedDB + Telemetry Telemetry + AuditLogging AuditLogging } // ManagedDB for configuring managed DB specific parameters @@ -41,6 +43,15 @@ type ManagedDB struct { PerformanceInsights bool `env:"MANAGED_DB_PERFORMANCE_INSIGHTS" envDefault:"false"` } +// AuditLogging defines the parameter of the audit logging target. +type AuditLogging struct { + Enabled bool `env:"AUDIT_LOG_ENABLED" envDefault:"false"` + URLScheme string `env:"AUDIT_LOG_URL_SCHEME" envDefault:"https"` + AuditLogTargetHost string `env:"AUDIT_LOG_HOST" envDefault:"audit-logs-aggregator.rhacs-audit-logs"` + AuditLogTargetPort int `env:"AUDIT_LOG_PORT" envDefault:"8888"` + SkipTLSVerify bool `env:"AUDIT_LOG_SKIP_TLS_VERIFY" envDefault:"false"` +} + // Telemetry defines parameters for pushing telemetry to a remote storage. type Telemetry struct { StorageEndpoint string `env:"TELEMETRY_STORAGE_ENDPOINT"` @@ -81,3 +92,10 @@ func validateManagedDBConfig(c Config, configErrors *errorhelpers.ErrorList) { configErrors.AddError(errors.New("MANAGED_DB_ENABLED == true and MANAGED_DB_SECURITY_GROUP unset in the environment")) } } + +func (a *AuditLogging) Endpoint(withScheme bool) string { + if withScheme { + return fmt.Sprintf("%s://%s:%d", a.URLScheme, a.AuditLogTargetHost, a.AuditLogTargetPort) + } + return fmt.Sprintf("%s:%d", a.AuditLogTargetHost, a.AuditLogTargetPort) +} diff --git a/fleetshard/pkg/central/charts/data/rhacs-operator/templates/rhacs-operator-deployment.yaml b/fleetshard/pkg/central/charts/data/rhacs-operator/templates/rhacs-operator-deployment.yaml index 351e9e0268..c506d2d8c1 100644 --- a/fleetshard/pkg/central/charts/data/rhacs-operator/templates/rhacs-operator-deployment.yaml +++ b/fleetshard/pkg/central/charts/data/rhacs-operator/templates/rhacs-operator-deployment.yaml @@ -82,11 +82,11 @@ spec: containerName: manager resource: limits.memory divisor: '0' - {{- if $.Values.operator.centralLabelSelector -}} + {{- if .labelSelector }} - name: CENTRAL_LABEL_SELECTOR - value: "rhacs.redhat.com/tag={{ substr 0 64 (lower .tag) }},rhacs.redhat.com/repository={{ substr 0 64 (lower .repository )}}" + value: "rhacs.redhat.com/version-selector={{ .labelSelector }}" {{- end }} - image: "{{ .repository }}:{{ .tag }}" + image: "{{ .image }}" imagePullPolicy: IfNotPresent livenessProbe: failureThreshold: 3 diff --git a/fleetshard/pkg/central/charts/data/rhacs-operator/values.yaml b/fleetshard/pkg/central/charts/data/rhacs-operator/values.yaml index 55ea2632f7..5f79e26650 100644 --- a/fleetshard/pkg/central/charts/data/rhacs-operator/values.yaml +++ b/fleetshard/pkg/central/charts/data/rhacs-operator/values.yaml @@ -3,15 +3,15 @@ # Declare variables to be passed into your templates. operator: - # Each item in images should contain `repository` and `tag` key + # Each item in images should contain `deploymentName`, `image` and `labelSelector` keys # example: # images: # - deploymentName: rhacs-operator-manager-4.0.0 - # repository: quay.io/rhacs-eng/stackrox-operator - # tag: 4.0.0 + # image: quay.io/rhacs-eng/stackrox-operator:4.0.0 + # labelSelector: 4.0.0 # - deploymentName: rhacs-operator-manager-4.0.1 - # repository: quay.io/rhacs-eng/stackrox-operator - # tag: 4.0.1 + # image: quay.io/rhacs-eng/stackrox-operator:4.0.1 + # labelSelector: 4.0.1 images: [] # TODO: add values for resource limits and requests for both proxy and manager container # TODO: add value for kube-rbac-proxy image diff --git a/fleetshard/pkg/central/cloudprovider/awsclient/rds.go b/fleetshard/pkg/central/cloudprovider/awsclient/rds.go index 14598fba6f..6c915701ca 100644 --- a/fleetshard/pkg/central/cloudprovider/awsclient/rds.go +++ b/fleetshard/pkg/central/cloudprovider/awsclient/rds.go @@ -40,6 +40,7 @@ const ( dataplaneClusterNameKey = "DataplaneClusterName" instanceTypeTagKey = "ACSInstanceType" + acsInstanceIDKey = "ACSInstanceID" regularInstaceTagValue = "regular" testInstanceTagValue = "test" @@ -60,19 +61,19 @@ type RDS struct { } // EnsureDBProvisioned is a blocking function that makes sure that an RDS database was provisioned for a Central -func (r *RDS) EnsureDBProvisioned(ctx context.Context, databaseID, masterPassword string, isTestInstance bool) error { +func (r *RDS) EnsureDBProvisioned(ctx context.Context, databaseID, acsInstanceID, masterPassword string, isTestInstance bool) error { clusterID := getClusterID(databaseID) - if err := r.ensureDBClusterCreated(clusterID, masterPassword, isTestInstance); err != nil { + if err := r.ensureDBClusterCreated(clusterID, acsInstanceID, masterPassword, isTestInstance); err != nil { return fmt.Errorf("ensuring DB cluster %s exists: %w", clusterID, err) } instanceID := getInstanceID(databaseID) - if err := r.ensureDBInstanceCreated(instanceID, clusterID, isTestInstance); err != nil { + if err := r.ensureDBInstanceCreated(instanceID, clusterID, acsInstanceID, isTestInstance); err != nil { return fmt.Errorf("ensuring DB instance %s exists in cluster %s: %w", instanceID, clusterID, err) } failoverID := getFailoverInstanceID(databaseID) - if err := r.ensureDBInstanceCreated(failoverID, clusterID, isTestInstance); err != nil { + if err := r.ensureDBInstanceCreated(failoverID, clusterID, acsInstanceID, isTestInstance); err != nil { return fmt.Errorf("ensuring failover DB instance %s exists in cluster %s: %w", failoverID, clusterID, err) } @@ -144,7 +145,7 @@ func (r *RDS) GetAccountQuotas(ctx context.Context) (cloudprovider.AccountQuotas return accountQuotas, nil } -func (r *RDS) ensureDBClusterCreated(clusterID, masterPassword string, isTestInstance bool) error { +func (r *RDS) ensureDBClusterCreated(clusterID, acsInstanceID, masterPassword string, isTestInstance bool) error { clusterExists, _, err := r.clusterStatus(clusterID) if err != nil { return fmt.Errorf("checking if DB cluster exists: %w", err) @@ -154,8 +155,16 @@ func (r *RDS) ensureDBClusterCreated(clusterID, masterPassword string, isTestIns } glog.Infof("Initiating provisioning of RDS database cluster %s.", clusterID) - _, err = r.rdsClient.CreateDBCluster(newCreateCentralDBClusterInput(clusterID, masterPassword, r.dbSecurityGroup, - r.dbSubnetGroup, r.dataplaneClusterName, isTestInstance)) + input := &createCentralDBClusterInput{ + clusterID: clusterID, + acsInstanceID: acsInstanceID, + dbPassword: masterPassword, // pragma: allowlist secret + securityGroup: r.dbSecurityGroup, + subnetGroup: r.dbSubnetGroup, + dataplaneClusterName: r.dataplaneClusterName, + isTestInstance: isTestInstance, + } + _, err = r.rdsClient.CreateDBCluster(newCreateCentralDBClusterInput(input)) if err != nil { return fmt.Errorf("creating DB cluster: %w", err) } @@ -163,7 +172,7 @@ func (r *RDS) ensureDBClusterCreated(clusterID, masterPassword string, isTestIns return nil } -func (r *RDS) ensureDBInstanceCreated(instanceID string, clusterID string, isTestInstance bool) error { +func (r *RDS) ensureDBInstanceCreated(instanceID, clusterID, acsInstanceID string, isTestInstance bool) error { instanceExists, _, err := r.instanceStatus(instanceID) if err != nil { return fmt.Errorf("checking if DB instance exists: %w", err) @@ -173,8 +182,15 @@ func (r *RDS) ensureDBInstanceCreated(instanceID string, clusterID string, isTes } glog.Infof("Initiating provisioning of RDS database instance %s.", instanceID) - _, err = r.rdsClient.CreateDBInstance(newCreateCentralDBInstanceInput(clusterID, instanceID, - r.dataplaneClusterName, r.performanceInsights, isTestInstance)) + input := &createCentralDBInstanceInput{ + clusterID: clusterID, + instanceID: instanceID, + acsInstanceID: acsInstanceID, + dataplaneClusterName: r.dataplaneClusterName, + performanceInsights: r.performanceInsights, + isTestInstance: isTestInstance, + } + _, err = r.rdsClient.CreateDBInstance(newCreateCentralDBInstanceInput(input)) if err != nil { return fmt.Errorf("creating DB instance: %w", err) } @@ -350,15 +366,25 @@ func getFailoverInstanceID(databaseID string) string { return dbPrefix + databaseID + dbFailoverSuffix } -func newCreateCentralDBClusterInput(clusterID, dbPassword, securityGroup, subnetGroup, dataplaneClusterName string, isTestInstance bool) *rds.CreateDBClusterInput { - input := &rds.CreateDBClusterInput{ - DBClusterIdentifier: aws.String(clusterID), +type createCentralDBClusterInput struct { + clusterID string + acsInstanceID string + dbPassword string + securityGroup string + subnetGroup string + dataplaneClusterName string + isTestInstance bool +} + +func newCreateCentralDBClusterInput(input *createCentralDBClusterInput) *rds.CreateDBClusterInput { + awsInput := &rds.CreateDBClusterInput{ + DBClusterIdentifier: aws.String(input.clusterID), Engine: aws.String(dbEngine), EngineVersion: aws.String(dbEngineVersion), MasterUsername: aws.String(dbUser), - MasterUserPassword: aws.String(dbPassword), - VpcSecurityGroupIds: aws.StringSlice([]string{securityGroup}), - DBSubnetGroupName: aws.String(subnetGroup), + MasterUserPassword: aws.String(input.dbPassword), + VpcSecurityGroupIds: aws.StringSlice([]string{input.securityGroup}), + DBSubnetGroupName: aws.String(input.subnetGroup), ServerlessV2ScalingConfiguration: &rds.ServerlessV2ScalingConfiguration{ MinCapacity: aws.Float64(dbMinCapacityACU), MaxCapacity: aws.Float64(dbMaxCapacityACU), @@ -368,42 +394,59 @@ func newCreateCentralDBClusterInput(clusterID, dbPassword, securityGroup, subnet Tags: []*rds.Tag{ { Key: aws.String(dataplaneClusterNameKey), - Value: aws.String(dataplaneClusterName), + Value: aws.String(input.dataplaneClusterName), }, { Key: aws.String(instanceTypeTagKey), - Value: aws.String(getInstanceType(isTestInstance)), + Value: aws.String(getInstanceType(input.isTestInstance)), + }, + { + Key: aws.String(acsInstanceIDKey), + Value: aws.String(input.acsInstanceID), }, }, } // do not export DB logs of internal instances (e.g. Probes) - if !isTestInstance { - input.EnableCloudwatchLogsExports = aws.StringSlice([]string{"postgresql"}) + if !input.isTestInstance { + awsInput.EnableCloudwatchLogsExports = aws.StringSlice([]string{"postgresql"}) } - return input + return awsInput } -func newCreateCentralDBInstanceInput(clusterID, instanceID, dataplaneClusterName string, performanceInsights bool, isTestInstance bool) *rds.CreateDBInstanceInput { +type createCentralDBInstanceInput struct { + clusterID string + instanceID string + acsInstanceID string + dataplaneClusterName string + performanceInsights bool + isTestInstance bool +} + +func newCreateCentralDBInstanceInput(input *createCentralDBInstanceInput) *rds.CreateDBInstanceInput { return &rds.CreateDBInstanceInput{ DBInstanceClass: aws.String(dbInstanceClass), - DBClusterIdentifier: aws.String(clusterID), - DBInstanceIdentifier: aws.String(instanceID), + DBClusterIdentifier: aws.String(input.clusterID), + DBInstanceIdentifier: aws.String(input.instanceID), Engine: aws.String(dbEngine), PubliclyAccessible: aws.Bool(false), - EnablePerformanceInsights: aws.Bool(performanceInsights), + EnablePerformanceInsights: aws.Bool(input.performanceInsights), PromotionTier: aws.Int64(dbInstancePromotionTier), CACertificateIdentifier: aws.String(dbCACertificateType), AutoMinorVersionUpgrade: aws.Bool(dbAutoVersionUpgrade), Tags: []*rds.Tag{ { Key: aws.String(dataplaneClusterNameKey), - Value: aws.String(dataplaneClusterName), + Value: aws.String(input.dataplaneClusterName), }, { Key: aws.String(instanceTypeTagKey), - Value: aws.String(getInstanceType(isTestInstance)), + Value: aws.String(getInstanceType(input.isTestInstance)), + }, + { + Key: aws.String(acsInstanceIDKey), + Value: aws.String(input.acsInstanceID), }, }, } diff --git a/fleetshard/pkg/central/cloudprovider/awsclient/rds_test.go b/fleetshard/pkg/central/cloudprovider/awsclient/rds_test.go index e30021d424..60fba2fbd9 100644 --- a/fleetshard/pkg/central/cloudprovider/awsclient/rds_test.go +++ b/fleetshard/pkg/central/cloudprovider/awsclient/rds_test.go @@ -129,7 +129,7 @@ func TestRDSProvisioning(t *testing.T) { require.NoError(t, err) require.False(t, failoverExists) - err = rdsClient.EnsureDBProvisioned(ctx, dbID, dbMasterPassword, false) + err = rdsClient.EnsureDBProvisioned(ctx, dbID, dbID, dbMasterPassword, false) defer func() { // clean-up AWS resources in case the test fails deleteErr := rdsClient.EnsureDBDeprovisioned(dbID, false) diff --git a/fleetshard/pkg/central/cloudprovider/dbclient.go b/fleetshard/pkg/central/cloudprovider/dbclient.go index 957b0bfdf2..584d5c1a7a 100644 --- a/fleetshard/pkg/central/cloudprovider/dbclient.go +++ b/fleetshard/pkg/central/cloudprovider/dbclient.go @@ -13,7 +13,7 @@ import ( type DBClient interface { // EnsureDBProvisioned is a blocking function that makes sure that a database with the given databaseID was provisioned, // using the master password given as parameter - EnsureDBProvisioned(ctx context.Context, databaseID, passwordSecretName string, isTestInstance bool) error + EnsureDBProvisioned(ctx context.Context, databaseID, acsInstanceID, passwordSecretName string, isTestInstance bool) error // EnsureDBDeprovisioned is a non-blocking function that makes sure that a managed DB is deprovisioned (more // specifically, that its deletion was initiated) EnsureDBDeprovisioned(databaseID string, skipFinalSnapshot bool) error diff --git a/fleetshard/pkg/central/cloudprovider/dbclient_moq.go b/fleetshard/pkg/central/cloudprovider/dbclient_moq.go index e8411bb719..f360cdd6f1 100644 --- a/fleetshard/pkg/central/cloudprovider/dbclient_moq.go +++ b/fleetshard/pkg/central/cloudprovider/dbclient_moq.go @@ -22,7 +22,7 @@ var _ DBClient = &DBClientMock{} // EnsureDBDeprovisionedFunc: func(databaseID string, skipFinalSnapshot bool) error { // panic("mock out the EnsureDBDeprovisioned method") // }, -// EnsureDBProvisionedFunc: func(ctx context.Context, databaseID string, passwordSecretName string, isTestInstance bool) error { +// EnsureDBProvisionedFunc: func(ctx context.Context, databaseID string, acsInstanceID string, passwordSecretName string, isTestInstance bool) error { // panic("mock out the EnsureDBProvisioned method") // }, // GetAccountQuotasFunc: func(ctx context.Context) (AccountQuotas, error) { @@ -42,7 +42,7 @@ type DBClientMock struct { EnsureDBDeprovisionedFunc func(databaseID string, skipFinalSnapshot bool) error // EnsureDBProvisionedFunc mocks the EnsureDBProvisioned method. - EnsureDBProvisionedFunc func(ctx context.Context, databaseID string, passwordSecretName string, isTestInstance bool) error + EnsureDBProvisionedFunc func(ctx context.Context, databaseID string, acsInstanceID string, passwordSecretName string, isTestInstance bool) error // GetAccountQuotasFunc mocks the GetAccountQuotas method. GetAccountQuotasFunc func(ctx context.Context) (AccountQuotas, error) @@ -65,6 +65,8 @@ type DBClientMock struct { Ctx context.Context // DatabaseID is the databaseID argument value. DatabaseID string + // AcsInstanceID is the acsInstanceID argument value. + AcsInstanceID string // PasswordSecretName is the passwordSecretName argument value. PasswordSecretName string // IsTestInstance is the isTestInstance argument value. @@ -124,25 +126,27 @@ func (mock *DBClientMock) EnsureDBDeprovisionedCalls() []struct { } // EnsureDBProvisioned calls EnsureDBProvisionedFunc. -func (mock *DBClientMock) EnsureDBProvisioned(ctx context.Context, databaseID string, passwordSecretName string, isTestInstance bool) error { +func (mock *DBClientMock) EnsureDBProvisioned(ctx context.Context, databaseID string, acsInstanceID string, passwordSecretName string, isTestInstance bool) error { if mock.EnsureDBProvisionedFunc == nil { panic("DBClientMock.EnsureDBProvisionedFunc: method is nil but DBClient.EnsureDBProvisioned was just called") } callInfo := struct { Ctx context.Context DatabaseID string + AcsInstanceID string PasswordSecretName string IsTestInstance bool }{ Ctx: ctx, DatabaseID: databaseID, + AcsInstanceID: acsInstanceID, PasswordSecretName: passwordSecretName, IsTestInstance: isTestInstance, } mock.lockEnsureDBProvisioned.Lock() mock.calls.EnsureDBProvisioned = append(mock.calls.EnsureDBProvisioned, callInfo) mock.lockEnsureDBProvisioned.Unlock() - return mock.EnsureDBProvisionedFunc(ctx, databaseID, passwordSecretName, isTestInstance) + return mock.EnsureDBProvisionedFunc(ctx, databaseID, acsInstanceID, passwordSecretName, isTestInstance) } // EnsureDBProvisionedCalls gets all the calls that were made to EnsureDBProvisioned. @@ -152,12 +156,14 @@ func (mock *DBClientMock) EnsureDBProvisioned(ctx context.Context, databaseID st func (mock *DBClientMock) EnsureDBProvisionedCalls() []struct { Ctx context.Context DatabaseID string + AcsInstanceID string PasswordSecretName string IsTestInstance bool } { var calls []struct { Ctx context.Context DatabaseID string + AcsInstanceID string PasswordSecretName string IsTestInstance bool } diff --git a/fleetshard/pkg/central/operator/upgrade.go b/fleetshard/pkg/central/operator/upgrade.go index 38502d8dea..5c56e79ec1 100644 --- a/fleetshard/pkg/central/operator/upgrade.go +++ b/fleetshard/pkg/central/operator/upgrade.go @@ -4,50 +4,56 @@ package operator import ( "context" "fmt" - "strings" - + containerImage "github.com/containers/image/docker/reference" "github.com/stackrox/acs-fleet-manager/fleetshard/pkg/central/charts" "golang.org/x/exp/slices" "helm.sh/helm/v3/pkg/chart" "helm.sh/helm/v3/pkg/chartutil" appsv1 "k8s.io/api/apps/v1" "k8s.io/apimachinery/pkg/api/errors" + apimachineryvalidation "k8s.io/apimachinery/pkg/api/validation" + "k8s.io/apimachinery/pkg/util/validation" ctrlClient "sigs.k8s.io/controller-runtime/pkg/client" ) const ( operatorNamespace = "rhacs" releaseName = "rhacs-operator" - operatorDeploymentPrefix = "rhacs-operator-manager" - - // deployment names should contain at most 63 characters - // RFC 1035 Label Names: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#rfc-1035-label-names - maxOperatorDeploymentNameLength = 63 + operatorDeploymentPrefix = "rhacs-operator" ) -func parseOperatorImages(images []string) ([]chartutil.Values, error) { - var operatorImages []chartutil.Values - uniqueImages := make(map[string]bool) - for _, img := range images { - if !strings.Contains(img, ":") { - return nil, fmt.Errorf("failed to parse image %q", img) +// DeploymentConfig represents operator configuration for deployment +type DeploymentConfig struct { + Image string + GitRef string +} + +func parseOperatorConfigs(operators []DeploymentConfig) ([]chartutil.Values, error) { + var helmValues []chartutil.Values + for _, operator := range operators { + imageReference, err := containerImage.Parse(operator.Image) + if err != nil { + return nil, err } - strs := strings.Split(img, ":") - if len(strs) != 2 { - return nil, fmt.Errorf("failed to split image and tag from %q", img) + image := imageReference.String() + if errs := validation.IsValidLabelValue(operator.GitRef); errs != nil { + return nil, fmt.Errorf("label selector %s is not valid: %v", operator.GitRef, errs) } - repo, tag := strs[0], strs[1] - deploymentName := generateDeploymentName(tag) - if len(deploymentName) > maxOperatorDeploymentNameLength { - return nil, fmt.Errorf("%s contains more than %d characters and cannot be used as a deployment name", deploymentName, maxOperatorDeploymentNameLength) + + deploymentName := generateDeploymentName(operator.GitRef) + // validate deploymentName (RFC-1123) + // More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#dns-subdomain-names + if errs := apimachineryvalidation.NameIsDNSSubdomain(deploymentName, true); errs != nil { + return nil, fmt.Errorf("invalid deploymentName %s: %v", deploymentName, errs) } - if _, used := uniqueImages[repo+tag]; !used { - uniqueImages[repo+tag] = true - img := chartutil.Values{"deploymentName": deploymentName, "repository": repo, "tag": tag} - operatorImages = append(operatorImages, img) + operatorValues := chartutil.Values{ + "deploymentName": deploymentName, + "image": image, + "labelSelector": operator.GitRef, } + helmValues = append(helmValues, operatorValues) } - return operatorImages, nil + return helmValues, nil } // ACSOperatorManager keeps data necessary for managing ACS Operator @@ -57,9 +63,13 @@ type ACSOperatorManager struct { resourcesChart *chart.Chart } -// InstallOrUpgrade provisions or upgrades an existing ACS Operator(s) from helm chart template -func (u *ACSOperatorManager) InstallOrUpgrade(ctx context.Context, images []string, crdTag string) error { - operatorImages, err := parseOperatorImages(images) +// InstallOrUpgrade provisions or upgrades an existing ACS Operator from helm chart template +func (u *ACSOperatorManager) InstallOrUpgrade(ctx context.Context, operators []DeploymentConfig, crdTag string) error { + if len(operators) == 0 { + return nil + } + + operatorImages, err := parseOperatorConfigs(operators) if err != nil { return fmt.Errorf("failed to parse images: %w", err) } @@ -73,6 +83,7 @@ func (u *ACSOperatorManager) InstallOrUpgrade(ctx context.Context, images []stri if crdTag != "" { dynamicTemplatesUrls = u.generateCRDTemplateUrls(crdTag) } + u.resourcesChart = charts.MustGetChart("rhacs-operator", dynamicTemplatesUrls) objs, err := charts.RenderToObjects(releaseName, operatorNamespace, u.resourcesChart, chartVals) if err != nil { @@ -153,8 +164,8 @@ func (u *ACSOperatorManager) RemoveUnusedOperators(ctx context.Context, desiredI return nil } -func generateDeploymentName(tag string) string { - return operatorDeploymentPrefix + "-" + tag +func generateDeploymentName(version string) string { + return operatorDeploymentPrefix + "-" + version } func (u *ACSOperatorManager) generateCRDTemplateUrls(tag string) []string { diff --git a/fleetshard/pkg/central/operator/upgrade_test.go b/fleetshard/pkg/central/operator/upgrade_test.go index 7f97ad00e5..4f5d7ed442 100644 --- a/fleetshard/pkg/central/operator/upgrade_test.go +++ b/fleetshard/pkg/central/operator/upgrade_test.go @@ -31,6 +31,16 @@ const ( deploymentName2 = operatorDeploymentPrefix + "-4.0.2" ) +var operatorConfig1 = DeploymentConfig{ + Image: operatorImage1, + GitRef: "4.0.1", +} + +var operatorConfig2 = DeploymentConfig{ + Image: operatorImage2, + GitRef: "4.0.2", +} + var securedClusterCRD = &unstructured.Unstructured{ Object: map[string]interface{}{ "kind": kindCRDName, @@ -98,7 +108,7 @@ func TestOperatorUpgradeFreshInstall(t *testing.T) { fakeClient := testutils.NewFakeClientBuilder(t).Build() u := NewACSOperatorManager(fakeClient, crdURL) - err := u.InstallOrUpgrade(context.Background(), []string{operatorImage1}, crdTag1) + err := u.InstallOrUpgrade(context.Background(), []DeploymentConfig{operatorConfig1}, crdTag1) require.NoError(t, err) // check Secured Cluster CRD exists and correct @@ -142,9 +152,9 @@ func TestOperatorUpgradeMultipleVersions(t *testing.T) { fakeClient := testutils.NewFakeClientBuilder(t).Build() u := NewACSOperatorManager(fakeClient, crdURL) - operatorImages := []string{operatorImage1, operatorImage2} + operatorConfigs := []DeploymentConfig{operatorConfig1, operatorConfig2} - err := u.InstallOrUpgrade(context.Background(), operatorImages, crdTag1) + err := u.InstallOrUpgrade(context.Background(), operatorConfigs, crdTag1) require.NoError(t, err) err = fakeClient.Get(context.Background(), client.ObjectKey{Namespace: operatorNamespace, Name: operatorDeployment1.Name}, operatorDeployment1) @@ -162,8 +172,12 @@ func TestOperatorUpgradeDoNotInstallLongTagVersion(t *testing.T) { fakeClient := testutils.NewFakeClientBuilder(t).Build() u := NewACSOperatorManager(fakeClient, crdURL) - operatorImageWithLongTag := "quay.io/rhacs-eng/stackrox-operator:4.0.1-with-ridiculously-long-tag-version-name" - err := u.InstallOrUpgrade(context.Background(), []string{operatorImageWithLongTag}, crdTag1) + longVersionName := "4.0.1-with-ridiculously-long-version-name-like-really-long-one-which-has-more-than-63-characters" + operatorConfig := DeploymentConfig{ + Image: operatorImage1, + GitRef: longVersionName, + } + err := u.InstallOrUpgrade(context.Background(), []DeploymentConfig{operatorConfig}, crdTag1) require.Errorf(t, err, "zero tags parsed from images") deployments := &appsv1.DeploymentList{} @@ -172,6 +186,24 @@ func TestOperatorUpgradeDoNotInstallLongTagVersion(t *testing.T) { assert.Len(t, deployments.Items, 0) } +func TestOperatorUpgradeImageWithDigest(t *testing.T) { + fakeClient := testutils.NewFakeClientBuilder(t).Build() + u := NewACSOperatorManager(fakeClient, crdURL) + + digestedImage := "quay.io/rhacs-eng/stackrox-operator:4.0.1@sha256:232a180dbcbcfa7250917507f3827d88a9ae89bb1cdd8fe3ac4db7b764ebb25a" + operatorConfig := DeploymentConfig{ + Image: digestedImage, + GitRef: "4.0.1", + } + err := u.InstallOrUpgrade(context.Background(), []DeploymentConfig{operatorConfig}, crdTag1) + require.NoError(t, err) + + err = fakeClient.Get(context.Background(), client.ObjectKey{Namespace: operatorNamespace, Name: operatorDeployment1.Name}, operatorDeployment1) + require.NoError(t, err) + managerContainer := operatorDeployment1.Spec.Template.Spec.Containers[1] + assert.Equal(t, managerContainer.Image, digestedImage) +} + func TestRemoveUnusedEmpty(t *testing.T) { fakeClient := testutils.NewFakeClientBuilder(t).Build() u := NewACSOperatorManager(fakeClient, crdURL) @@ -240,66 +272,96 @@ func TestRemoveMultipleUnusedOperators(t *testing.T) { require.NoError(t, err) } -func TestParseOperatorImages(t *testing.T) { +func TestParseOperatorConfigs(t *testing.T) { cases := map[string]struct { - images []string - expected []map[string]string - shouldFail bool + operatorConfigs []DeploymentConfig + expected []map[string]string + shouldFail bool }{ "should parse one valid operator image": { - images: []string{operatorImage1}, + operatorConfigs: []DeploymentConfig{operatorConfig1}, expected: []map[string]string{ - {"deploymentName": deploymentName1, "repository": operatorRepository, "tag": "4.0.1"}, + {"deploymentName": deploymentName1, "image": operatorImage1, "labelSelector": "4.0.1"}, }, }, "should parse two valid operator images": { - images: []string{operatorImage1, operatorImage2}, + operatorConfigs: []DeploymentConfig{operatorConfig1, operatorConfig2}, expected: []map[string]string{ - {"deploymentName": deploymentName1, "repository": operatorRepository, "tag": "4.0.1"}, - {"deploymentName": deploymentName2, "repository": operatorRepository, "tag": "4.0.2"}, + {"deploymentName": deploymentName1, "image": operatorImage1, "labelSelector": "4.0.1"}, + {"deploymentName": deploymentName2, "image": operatorImage2, "labelSelector": "4.0.2"}, }, }, - "should ignore duplicate operator images": { - images: []string{operatorImage1, operatorImage1}, + "should parse image with tag and digest": { + operatorConfigs: []DeploymentConfig{{ + Image: "quay.io/image-with-tag-and-digest:1.2.3@sha256:4ff5cb2dcddaaa2a4b702516870c85177e53ccc3566509c36c2d84b01ef8f783", + GitRef: "version1", + }}, expected: []map[string]string{ - {"deploymentName": deploymentName1, "repository": operatorRepository, "tag": "4.0.1"}, + { + "deploymentName": operatorDeploymentPrefix + "-version1", + "image": "quay.io/image-with-tag-and-digest:1.2.3@sha256:4ff5cb2dcddaaa2a4b702516870c85177e53ccc3566509c36c2d84b01ef8f783", + "labelSelector": "version1", + }, + }, + }, + "should parse image without tag": { + operatorConfigs: []DeploymentConfig{{ + Image: "quay.io/image-without-tag", + GitRef: "version1", + }}, + expected: []map[string]string{ + {"deploymentName": operatorDeploymentPrefix + "-version1", "image": "quay.io/image-without-tag", "labelSelector": "version1"}, }, }, "do not fail if images list is empty": { - images: []string{}, - shouldFail: false, + operatorConfigs: []DeploymentConfig{}, + shouldFail: false, }, "should accept images from multiple repositories with the same tag": { - images: []string{"repo1:tag", "repo2:tag"}, + operatorConfigs: []DeploymentConfig{ + { + Image: "repo1:tag", + GitRef: "version1", + }, + { + Image: "repo2:tag", + GitRef: "version2", + }}, expected: []map[string]string{ - {"deploymentName": operatorDeploymentPrefix + "-tag", "repository": "repo1", "tag": "tag"}, - {"deploymentName": operatorDeploymentPrefix + "-tag", "repository": "repo2", "tag": "tag"}, + {"deploymentName": operatorDeploymentPrefix + "-version1", "image": "repo1:tag", "labelSelector": "version1"}, + {"deploymentName": operatorDeploymentPrefix + "-version2", "image": "repo2:tag", "labelSelector": "version2"}, }, }, - "fail if image does contain colon": { - images: []string{"quay.io/without-colon-123-tag"}, - shouldFail: true, - }, "fail if image contains more than one colon": { - images: []string{"quay.io/image-name:1.2.3:"}, + operatorConfigs: []DeploymentConfig{{ + Image: "quay.io/image-name:1.2.3:", + GitRef: "version1", + }}, shouldFail: true, }, - "fail if image tag is too long": { - images: []string{"quay.io/image-name:1.2.3-with-ridiculously-long-tag-version-name"}, + "fail if GitRef is way too long for the DeploymentName": { + operatorConfigs: []DeploymentConfig{{ + Image: "quay.io/image-name:tag", + GitRef: "1.2.3-with-ridiculously-long-version-name-like-really-long-one-which-has-more-than-63-characters", + }}, shouldFail: true, }, } for name, c := range cases { t.Run(name, func(t *testing.T) { - gotImages, err := parseOperatorImages(c.images) + gotImages, err := parseOperatorConfigs(c.operatorConfigs) if c.shouldFail { assert.Error(t, err) } else { assert.NoError(t, err) var expectedRepositoryAndTags []chartutil.Values for _, m := range c.expected { - val := chartutil.Values{"deploymentName": m["deploymentName"], "repository": m["repository"], "tag": m["tag"]} + val := chartutil.Values{ + "deploymentName": m["deploymentName"], + "image": m["image"], + "labelSelector": m["labelSelector"], + } expectedRepositoryAndTags = append(expectedRepositoryAndTags, val) } assert.Equal(t, expectedRepositoryAndTags, gotImages) diff --git a/fleetshard/pkg/central/reconciler/proxy.go b/fleetshard/pkg/central/reconciler/proxy.go index ee153ea29b..e8e4902cfe 100644 --- a/fleetshard/pkg/central/reconciler/proxy.go +++ b/fleetshard/pkg/central/reconciler/proxy.go @@ -2,13 +2,14 @@ package reconciler import ( "fmt" + "net/url" "sort" "strings" corev1 "k8s.io/api/core/v1" ) -func getProxyEnvVars(namespace string) []corev1.EnvVar { +func getProxyEnvVars(namespace string, additionalNoProxyTargets ...url.URL) []corev1.EnvVar { var envVars []corev1.EnvVar proxyURL := fmt.Sprintf("http://egress-proxy.%s.svc:3128", namespace) // Use both upper- and lowercase env var names for maximum compatibility. @@ -34,6 +35,10 @@ func getProxyEnvVars(namespace string) []corev1.EnvVar { ) } } + for _, noProxyURL := range additionalNoProxyTargets { + noProxyEndPoint := fmt.Sprintf("%s:%s", noProxyURL.Hostname(), noProxyURL.Port()) + noProxyTargets = append(noProxyTargets, noProxyEndPoint) + } sort.Strings(noProxyTargets) // ensure deterministic output noProxyStr := strings.Join(noProxyTargets, ",") for _, envVarName := range []string{"no_proxy", "NO_PROXY"} { diff --git a/fleetshard/pkg/central/reconciler/proxy_test.go b/fleetshard/pkg/central/reconciler/proxy_test.go index 08f6ff56ee..85e4d094f5 100644 --- a/fleetshard/pkg/central/reconciler/proxy_test.go +++ b/fleetshard/pkg/central/reconciler/proxy_test.go @@ -11,23 +11,9 @@ import ( const testNS = `acsms-01` -func TestProxyConfiguration(t *testing.T) { - for _, envVar := range getProxyEnvVars(testNS) { - t.Setenv(envVar.Name, envVar.Value) - } - +func testProxyConfiguration(t *testing.T, noProxyURLs []string, proxiedURLs []string) { proxyFunc := httpproxy.FromEnvironment().ProxyFunc() - noProxyURLs := []string{ - "https://central", - "https://central.acsms-01", - "https://central.acsms-01.svc", - "https://central.acsms-01.svc:443", - "https://scanner-db.acsms-01.svc:5432", - "https://scanner:8443", - "https://scanner.acsms-01:8080", - } - for _, u := range noProxyURLs { parsedURL, err := url.Parse(u) require.NoError(t, err) @@ -37,14 +23,6 @@ func TestProxyConfiguration(t *testing.T) { assert.Nilf(t, proxyURL, "expected URL %s to not be proxied, got: %s", u, proxyURL) } - proxiedURLs := []string{ - "https://www.example.com", - "https://www.example.com:8443", - "http://example.com", - "http://example.com:8080", - "https://central.acsms-01.svc:8443", - "https://scanner.acsms-01.svc", - } const expectedProxyURL = "http://egress-proxy.acsms-01.svc:3128" for _, u := range proxiedURLs { @@ -60,6 +38,34 @@ func TestProxyConfiguration(t *testing.T) { } } +func TestProxyConfiguration(t *testing.T) { + for _, envVar := range getProxyEnvVars(testNS) { + t.Setenv(envVar.Name, envVar.Value) + } + + noProxyURLs := []string{ + "https://central", + "https://central.acsms-01", + "https://central.acsms-01.svc", + "https://central.acsms-01.svc:443", + "https://scanner-db.acsms-01.svc:5432", + "https://scanner:8443", + "https://scanner.acsms-01:8080", + } + + proxiedURLs := []string{ + "https://audit-logs-aggregator.rhacs-audit-logs:8888", + "https://www.example.com", + "https://www.example.com:8443", + "http://example.com", + "http://example.com:8080", + "https://central.acsms-01.svc:8443", + "https://scanner.acsms-01.svc", + } + + testProxyConfiguration(t, noProxyURLs, proxiedURLs) +} + func TestProxyConfiguration_IsDeterministic(t *testing.T) { envVars := getProxyEnvVars(testNS) for i := 0; i < 5; i++ { @@ -67,3 +73,47 @@ func TestProxyConfiguration_IsDeterministic(t *testing.T) { assert.Equal(t, envVars, otherEnvVars) } } + +var ( + additionalNoProxyURLs = []url.URL{ + { + Host: "audit-logs-aggregator.rhacs-audit-logs:8888", + }, + } +) + +func TestProxyConfigurationWithAdditionalDirectAccess(t *testing.T) { + for _, envVar := range getProxyEnvVars(testNS, additionalNoProxyURLs...) { + t.Setenv(envVar.Name, envVar.Value) + } + + noProxyURLs := []string{ + "https://central", + "https://central.acsms-01", + "https://central.acsms-01.svc", + "https://central.acsms-01.svc:443", + "https://scanner-db.acsms-01.svc:5432", + "https://scanner:8443", + "https://scanner.acsms-01:8080", + "https://audit-logs-aggregator.rhacs-audit-logs:8888", + } + + proxiedURLs := []string{ + "https://www.example.com", + "https://www.example.com:8443", + "http://example.com", + "http://example.com:8080", + "https://central.acsms-01.svc:8443", + "https://scanner.acsms-01.svc", + } + + testProxyConfiguration(t, noProxyURLs, proxiedURLs) +} + +func TestProxyConfigurationWithAdditionalDirectAccess_IsDeterministic(t *testing.T) { + envVars := getProxyEnvVars(testNS, additionalNoProxyURLs...) + for i := 0; i < 5; i++ { + otherEnvVars := getProxyEnvVars(testNS, additionalNoProxyURLs...) + assert.Equal(t, envVars, otherEnvVars) + } +} diff --git a/fleetshard/pkg/central/reconciler/reconciler.go b/fleetshard/pkg/central/reconciler/reconciler.go index 4043670d15..36bc40caea 100644 --- a/fleetshard/pkg/central/reconciler/reconciler.go +++ b/fleetshard/pkg/central/reconciler/reconciler.go @@ -6,12 +6,15 @@ import ( "context" "encoding/json" "fmt" + "gopkg.in/yaml.v2" + "net/url" "reflect" "sync/atomic" "time" "github.com/stackrox/acs-fleet-manager/pkg/features" + containerImage "github.com/containers/image/docker/reference" "github.com/golang/glog" openshiftRouteV1 "github.com/openshift/api/route/v1" "github.com/pkg/errors" @@ -25,6 +28,7 @@ import ( "github.com/stackrox/acs-fleet-manager/internal/dinosaur/pkg/api/private" "github.com/stackrox/acs-fleet-manager/internal/dinosaur/pkg/converters" "github.com/stackrox/rox/operator/apis/platform/v1alpha1" + "github.com/stackrox/rox/pkg/declarativeconfig" "github.com/stackrox/rox/pkg/random" "helm.sh/helm/v3/pkg/chart" "helm.sh/helm/v3/pkg/chartutil" @@ -33,6 +37,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/util/strategicpatch" + "k8s.io/apimachinery/pkg/util/validation" "k8s.io/apimachinery/pkg/util/wait" "k8s.io/utils/pointer" ctrlClient "sigs.k8s.io/controller-runtime/pkg/client" @@ -43,10 +48,12 @@ const ( FreeStatus int32 = iota BlockedStatus - PauseReconcileAnnotation = "stackrox.io/pause-reconcile" + PauseReconcileAnnotation = "stackrox.io/pause-reconcile" + ReconcileOperatorSelector = "rhacs.redhat.com/version-selector" helmReleaseName = "tenant-resources" + centralPVCAnnotationKey = "platform.stackrox.io/obsolete-central-pvc" managedServicesAnnotation = "platform.stackrox.io/managed-services" envAnnotationKey = "rhacs.redhat.com/environment" clusterNameAnnotationKey = "rhacs.redhat.com/cluster-name" @@ -54,8 +61,10 @@ const ( instanceTypeLabelKey = "rhacs.redhat.com/instance-type" orgIDLabelKey = "rhacs.redhat.com/org-id" tenantIDLabelKey = "rhacs.redhat.com/tenant" - operatorVersionKey = "stackrox.io/operator-version" - defaultOperatorVersion = "rhacs-operator.v3.74.0" + + auditLogNotifierKey = "com.redhat.rhacs.auditLogNotifier" + auditLogNotifierName = "Platform Audit Logs" + auditLogTenantIDKey = "tenant_id" dbUserTypeAnnotation = "platform.stackrox.io/user-type" dbUserTypeMaster = "master" @@ -64,6 +73,8 @@ const ( centralDbSecretName = "central-db-password" // pragma: allowlist secret centralDeletePollInterval = 5 * time.Second + + sensibleDeclarativeConfigSecretName = "cloud-service-sensible-declarative-configs" // pragma: allowlist secret ) // CentralReconcilerOptions are the static options for creating a reconciler. @@ -75,6 +86,7 @@ type CentralReconcilerOptions struct { Telemetry config.Telemetry ClusterName string Environment string + AuditLogging config.AuditLogging } // CentralReconciler is a reconciler tied to a one Central instance. It installs, updates and deletes Central instances @@ -93,6 +105,7 @@ type CentralReconciler struct { telemetry config.Telemetry clusterName string environment string + auditLogging config.AuditLogging managedDBEnabled bool managedDBProvisioningClient cloudprovider.DBClient @@ -160,6 +173,10 @@ func (r *CentralReconciler) Reconcile(ctx context.Context, remoteCentral private } } + if err = r.reconcileDeclarativeConfigurationData(ctx, &remoteCentral); err != nil { + return nil, err + } + if err = r.reconcileCentral(ctx, &remoteCentral, central); err != nil { return nil, err } @@ -233,9 +250,13 @@ func (r *CentralReconciler) getInstanceConfig(remoteCentral *private.ManagedCent } // Set proxy configuration - envVars := getProxyEnvVars(remoteCentralNamespace) + additionalNoProxyURL := url.URL{ + Host: r.auditLogging.Endpoint(false), + } + envVars := getProxyEnvVars(remoteCentralNamespace, additionalNoProxyURL) scannerComponentEnabled := v1alpha1.ScannerComponentEnabled + central := &v1alpha1.Central{ ObjectMeta: metav1.ObjectMeta{ Name: remoteCentralName, @@ -247,9 +268,9 @@ func (r *CentralReconciler) getInstanceConfig(remoteCentral *private.ManagedCent orgIDLabelKey: remoteCentral.Spec.Auth.OwnerOrgId, }, Annotations: map[string]string{ + centralPVCAnnotationKey: "true", managedServicesAnnotation: "true", orgNameAnnotationKey: remoteCentral.Spec.Auth.OwnerOrgName, - // TODO(ROX-17718): Set reconciler selection label for Central }, }, Spec: v1alpha1.CentralSpec{ @@ -272,6 +293,13 @@ func (r *CentralReconciler) getInstanceConfig(remoteCentral *private.ManagedCent Key: &r.telemetry.StorageKey, }, }, + DeclarativeConfiguration: &v1alpha1.DeclarativeConfiguration{ + Secrets: []v1alpha1.LocalSecretReference{ + { + Name: sensibleDeclarativeConfigSecretName, + }, + }, + }, }, Scanner: &v1alpha1.ScannerComponentSpec{ Analyzer: &v1alpha1.ScannerAnalyzerComponent{ @@ -305,9 +333,21 @@ func (r *CentralReconciler) getInstanceConfig(remoteCentral *private.ManagedCent } if features.TargetedOperatorUpgrades.Enabled() { - labels := central.ObjectMeta.Labels - labels[operatorVersionKey] = defaultOperatorVersion - central.ObjectMeta.Labels = labels + // TODO: use GitRef as a LabelSelector + image, err := containerImage.Parse(remoteCentral.Spec.OperatorImage) + if err != nil { + return nil, errors.Wrapf(err, "failed parse labelSelector") + } + var labelSelector string + if tagged, ok := image.(containerImage.Tagged); ok { + labelSelector = tagged.Tag() + } + errs := validation.IsValidLabelValue(labelSelector) + if errs != nil { + return nil, errors.Wrapf(err, "invalid labelSelector %s: %v", labelSelector, errs) + } + central.Labels[ReconcileOperatorSelector] = labelSelector + } return central, nil @@ -383,6 +423,64 @@ func (r *CentralReconciler) reconcileCentralDBConfig(ctx context.Context, remote return nil } +func getAuditLogNotifierConfig( + auditLoggingConfig config.AuditLogging, + namespace string, +) *declarativeconfig.Notifier { + return &declarativeconfig.Notifier{ + Name: auditLogNotifierName, + GenericConfig: &declarativeconfig.GenericConfig{ + Endpoint: auditLoggingConfig.Endpoint(true), + SkipTLSVerify: auditLoggingConfig.SkipTLSVerify, + AuditLoggingEnabled: true, + ExtraFields: []declarativeconfig.KeyValuePair{ + { + Key: auditLogTenantIDKey, + Value: namespace, + }, + }, + }, + } +} + +func (r *CentralReconciler) configureAuditLogNotifier(secret *corev1.Secret, namespace string) error { + if secret.Data == nil { + secret.Data = make(map[string][]byte) + } + auditLogNotifierConfig := getAuditLogNotifierConfig( + r.auditLogging, + namespace, + ) + encodedNotifierConfig, marshalErr := yaml.Marshal(auditLogNotifierConfig) + if marshalErr != nil { + return fmt.Errorf("marshaling audit log notifier configuration: %w", marshalErr) + } + secret.Data[auditLogNotifierKey] = encodedNotifierConfig + return nil +} + +func (r *CentralReconciler) reconcileDeclarativeConfigurationData(ctx context.Context, remoteCentral *private.ManagedCentral) error { + if !r.auditLogging.Enabled { + return nil + } + namespace := remoteCentral.Metadata.Namespace + exists, err := r.checkSecretExists(ctx, namespace, sensibleDeclarativeConfigSecretName) + if err != nil || exists { + return err + } + return r.ensureSecretExists( + ctx, + namespace, + sensibleDeclarativeConfigSecretName, + func(secret *corev1.Secret) error { + if err := r.configureAuditLogNotifier(secret, namespace); err != nil { + return err + } + return nil + }, + ) +} + func (r *CentralReconciler) reconcileCentral(ctx context.Context, remoteCentral *private.ManagedCentral, central *v1alpha1.Central) error { remoteCentralName := remoteCentral.Metadata.Name remoteCentralNamespace := remoteCentral.Metadata.Namespace @@ -566,6 +664,24 @@ func (r *CentralReconciler) collectReconciliationStatus(ctx context.Context, rem return status, nil } +func (r *CentralReconciler) ensureDeclarativeConfigurationSecretCleaned(ctx context.Context, remoteCentralNamespace string) error { + secret := &corev1.Secret{} + secretKey := ctrlClient.ObjectKey{ // pragma: allowlist secret + Namespace: remoteCentralNamespace, + Name: sensibleDeclarativeConfigSecretName, + } + + err := r.client.Get(ctx, secretKey, secret) + if err != nil { + if apiErrors.IsNotFound(err) { + return nil + } + return err + } + + return r.client.Delete(ctx, secret) +} + func isRemoteCentralProvisioning(remoteCentral private.ManagedCentral) bool { return remoteCentral.RequestStatus == centralConstants.CentralRequestStatusProvisioning.String() } @@ -617,6 +733,10 @@ func (r *CentralReconciler) ensureCentralDeleted(ctx context.Context, remoteCent } globalDeleted = globalDeleted && centralDeleted + if err := r.ensureDeclarativeConfigurationSecretCleaned(ctx, central.GetNamespace()); err != nil { + return false, nil + } + if r.managedDBEnabled { // skip Snapshot for remoteCentral created by probe skipSnapshot := remoteCentral.Metadata.Internal @@ -773,7 +893,7 @@ func (r *CentralReconciler) ensureManagedCentralDBInitialized(ctx context.Contex return fmt.Errorf("getting DB password from secret: %w", err) } - err = r.managedDBProvisioningClient.EnsureDBProvisioned(ctx, remoteCentral.Id, dbMasterPassword, remoteCentral.Metadata.Internal) + err = r.managedDBProvisioningClient.EnsureDBProvisioned(ctx, remoteCentral.Id, remoteCentral.Id, dbMasterPassword, remoteCentral.Metadata.Internal) if err != nil { return fmt.Errorf("provisioning RDS DB: %w", err) } @@ -805,7 +925,6 @@ func (r *CentralReconciler) ensureManagedCentralDBInitialized(ctx context.Contex } func (r *CentralReconciler) ensureCentralDBSecretExists(ctx context.Context, remoteCentralNamespace, userType, password string) error { - secret := &corev1.Secret{} setPasswordFunc := func(secret *corev1.Secret, userType, password string) { secret.Data = map[string][]byte{"password": []byte(password)} if secret.Annotations == nil { @@ -813,54 +932,14 @@ func (r *CentralReconciler) ensureCentralDBSecretExists(ctx context.Context, rem } secret.Annotations[dbUserTypeAnnotation] = userType } - - err := r.client.Get(ctx, ctrlClient.ObjectKey{Namespace: remoteCentralNamespace, Name: centralDbSecretName}, secret) - if err == nil { + return r.ensureSecretExists(ctx, remoteCentralNamespace, centralDbSecretName, func(secret *corev1.Secret) error { setPasswordFunc(secret, userType, password) - err = r.client.Update(ctx, secret) - if err != nil { - return fmt.Errorf("updating Central DB secret: %w", err) - } - return nil - } - - if !apiErrors.IsNotFound(err) { - return fmt.Errorf("getting Central DB secret: %w", err) - } - - // create secret if it does not exist - secret = &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: centralDbSecretName, - Namespace: remoteCentralNamespace, - Labels: map[string]string{k8s.ManagedByLabelKey: k8s.ManagedByFleetshardValue}, - Annotations: map[string]string{ - managedServicesAnnotation: "true", - }, - }, - } - - setPasswordFunc(secret, userType, password) - err = r.client.Create(ctx, secret) - if err != nil { - return fmt.Errorf("creating Central DB secret: %w", err) - } - return nil + }) } func (r *CentralReconciler) centralDBSecretExists(ctx context.Context, remoteCentralNamespace string) (bool, error) { - secret := &corev1.Secret{} - err := r.client.Get(ctx, ctrlClient.ObjectKey{Namespace: remoteCentralNamespace, Name: centralDbSecretName}, secret) - if err != nil { - if apiErrors.IsNotFound(err) { - return false, nil - } - - return false, fmt.Errorf("getting central DB secret: %w", err) - } - - return true, nil + return r.checkSecretExists(ctx, remoteCentralNamespace, centralDbSecretName) } func (r *CentralReconciler) centralDBUserExists(ctx context.Context, remoteCentralNamespace string) (bool, error) { @@ -1135,7 +1214,7 @@ func (r *CentralReconciler) ensureRouteDeleted(ctx context.Context, routeSupplie return false, nil } -func (r *CentralReconciler) chartValues(remoteCentral private.ManagedCentral) (chartutil.Values, error) { +func (r *CentralReconciler) chartValues(_ private.ManagedCentral) (chartutil.Values, error) { if r.resourcesChart == nil { return nil, errors.New("resources chart is not set") } @@ -1164,6 +1243,70 @@ func (r *CentralReconciler) needsReconcile(changed bool, forceReconcile string) var resourcesChart = charts.MustGetChart("tenant-resources", nil) +func (r *CentralReconciler) checkSecretExists( + ctx context.Context, + remoteCentralNamespace string, + secretName string, +) (bool, error) { + secret := &corev1.Secret{} + err := r.client.Get(ctx, ctrlClient.ObjectKey{Namespace: remoteCentralNamespace, Name: secretName}, secret) + if err != nil { + if apiErrors.IsNotFound(err) { + return false, nil + } + + return false, fmt.Errorf("getting secret %s/%s: %w", remoteCentralNamespace, secretName, err) + } + + return true, nil +} + +func (r *CentralReconciler) ensureSecretExists( + ctx context.Context, + namespace string, + secretName string, + secretModifyFunc func(secret *corev1.Secret) error, +) error { + secret := &corev1.Secret{} + secretKey := ctrlClient.ObjectKey{Name: secretName, Namespace: namespace} // pragma: allowlist secret + + err := r.client.Get(ctx, secretKey, secret) // pragma: allowlist secret + if err != nil && !apiErrors.IsNotFound(err) { + return fmt.Errorf("getting %s/%s secret: %w", namespace, secretName, err) + } + if err == nil { + modificationErr := secretModifyFunc(secret) + if modificationErr != nil { + return fmt.Errorf("updating %s/%s secret content: %w", namespace, secretName, modificationErr) + } + if updateErr := r.client.Update(ctx, secret); updateErr != nil { // pragma: allowlist secret + return fmt.Errorf("updating %s/%s secret: %w", namespace, secretName, updateErr) + } + + return nil + } + + // Create secret if it does not exist. + secret = &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ // pragma: allowlist secret + Name: secretName, + Namespace: namespace, + Labels: map[string]string{k8s.ManagedByLabelKey: k8s.ManagedByFleetshardValue}, + Annotations: map[string]string{ + managedServicesAnnotation: "true", + }, + }, + } + + if modificationErr := secretModifyFunc(secret); modificationErr != nil { + return fmt.Errorf("initializing %s/%s secret payload: %w", namespace, secretName, modificationErr) + } + if createErr := r.client.Create(ctx, secret); createErr != nil { // pragma: allowlist secret + return fmt.Errorf("creating %s/%s secret: %w", namespace, secretName, createErr) + } + return nil +} + // NewCentralReconciler ... func NewCentralReconciler(k8sClient ctrlClient.Client, central private.ManagedCentral, managedDBProvisioningClient cloudprovider.DBClient, managedDBInitFunc postgres.CentralDBInitFunc, @@ -1180,6 +1323,7 @@ func NewCentralReconciler(k8sClient ctrlClient.Client, central private.ManagedCe telemetry: opts.Telemetry, clusterName: opts.ClusterName, environment: opts.Environment, + auditLogging: opts.AuditLogging, managedDBEnabled: opts.ManagedDBEnabled, managedDBProvisioningClient: managedDBProvisioningClient, diff --git a/fleetshard/pkg/central/reconciler/reconciler_test.go b/fleetshard/pkg/central/reconciler/reconciler_test.go index 8c9264f2ff..2df45af386 100644 --- a/fleetshard/pkg/central/reconciler/reconciler_test.go +++ b/fleetshard/pkg/central/reconciler/reconciler_test.go @@ -1,14 +1,18 @@ package reconciler import ( + "bytes" "context" "embed" "fmt" + "github.com/stackrox/rox/pkg/declarativeconfig" "github.com/stackrox/rox/pkg/utils" + "gopkg.in/yaml.v2" "k8s.io/apimachinery/pkg/api/resource" "k8s.io/apimachinery/pkg/runtime" clientgoscheme "k8s.io/client-go/kubernetes/scheme" "sigs.k8s.io/controller-runtime/pkg/client/fake" + "strings" "testing" "time" @@ -49,6 +53,40 @@ const ( conditionTypeReady = "Ready" clusterName = "test-cluster" environment = "test" + operatorVersion = "4.0.1" + operatorImage = "quay.io/rhacs-eng/stackrox-operator:" + operatorVersion +) + +var ( + defaultCentralConfig = private.ManagedCentral{} + + defaultReconcilerOptions = CentralReconcilerOptions{} + + useRoutesReconcilerOptions = CentralReconcilerOptions{UseRoutes: true} + + defaultAuditLogConfig = config.AuditLogging{ + Enabled: true, + URLScheme: "https", + AuditLogTargetHost: "audit-logs-aggregator.rhacs-audit-logs", + AuditLogTargetPort: 8888, + SkipTLSVerify: true, + } + + vectorAuditLogConfig = config.AuditLogging{ + Enabled: true, + URLScheme: "https", + AuditLogTargetHost: "rhacs-vector.rhacs", + AuditLogTargetPort: 8443, + SkipTLSVerify: true, + } + + disabledAuditLogConfig = config.AuditLogging{ + Enabled: false, + URLScheme: "https", + AuditLogTargetHost: "audit-logs-aggregator.rhacs-audit-logs", + AuditLogTargetPort: 8888, + SkipTLSVerify: false, + } ) var simpleManagedCentral = private.ManagedCentral{ @@ -71,12 +109,31 @@ var simpleManagedCentral = private.ManagedCentral{ Central: private.ManagedCentralAllOfSpecCentral{ InstanceType: "standard", }, + OperatorImage: operatorImage, }, } //go:embed testdata var testdata embed.FS +func getClientTrackerAndReconciler( + t *testing.T, + centralConfig private.ManagedCentral, + managedDBClient cloudprovider.DBClient, + reconcilerOptions CentralReconcilerOptions, + k8sObjects ...client.Object, +) (client.WithWatch, *testutils.ReconcileTracker, *CentralReconciler) { + fakeClient, tracker := testutils.NewFakeClientWithTracker(t, k8sObjects...) + reconciler := NewCentralReconciler( + fakeClient, + centralConfig, + managedDBClient, + centralDBInitFunc, + reconcilerOptions, + ) + return fakeClient, tracker, reconciler +} + func centralDBInitFunc(_ context.Context, _ postgres.DBConnection, _, _ string) error { return nil } @@ -91,12 +148,16 @@ func conditionForType(conditions []private.DataPlaneClusterUpdateStatusRequestCo } func TestReconcileCreate(t *testing.T) { - fakeClient := testutils.NewFakeClientBuilder(t).Build() - r := NewCentralReconciler(fakeClient, - private.ManagedCentral{}, + reconcilerOptions := CentralReconcilerOptions{ + ClusterName: clusterName, + Environment: environment, + UseRoutes: true, + } + fakeClient, _, r := getClientTrackerAndReconciler( + t, + defaultCentralConfig, nil, - centralDBInitFunc, - CentralReconcilerOptions{ClusterName: clusterName, Environment: environment, UseRoutes: true}, + reconcilerOptions, ) status, err := r.Reconcile(context.TODO(), simpleManagedCentral) @@ -118,6 +179,7 @@ func TestReconcileCreate(t *testing.T) { assert.Equal(t, simpleManagedCentral.Spec.Auth.OwnerOrgId, central.Spec.Customize.Labels[orgIDLabelKey]) assert.Equal(t, simpleManagedCentral.Spec.Central.InstanceType, central.Spec.Customize.Labels[instanceTypeLabelKey]) assert.Equal(t, "1", central.GetAnnotations()[util.RevisionAnnotationKey]) + assert.Equal(t, "true", central.GetAnnotations()[centralPVCAnnotationKey]) assert.Equal(t, "true", central.GetAnnotations()[managedServicesAnnotation]) assert.Equal(t, true, *central.Spec.Central.Exposure.Route.Enabled) @@ -130,10 +192,8 @@ func TestReconcileCreate(t *testing.T) { } func TestReconcileCreateWithManagedDB(t *testing.T) { - fakeClient := testutils.NewFakeClientBuilder(t).Build() - managedDBProvisioningClient := &cloudprovider.DBClientMock{} - managedDBProvisioningClient.EnsureDBProvisionedFunc = func(_ context.Context, _ string, _ string, _ bool) error { + managedDBProvisioningClient.EnsureDBProvisionedFunc = func(_ context.Context, _string, _ string, _ string, _ bool) error { return nil } managedDBProvisioningClient.GetDBConnectionFunc = func(_ string) (postgres.DBConnection, error) { @@ -144,11 +204,16 @@ func TestReconcileCreateWithManagedDB(t *testing.T) { return connection, nil } - r := NewCentralReconciler(fakeClient, private.ManagedCentral{}, managedDBProvisioningClient, centralDBInitFunc, - CentralReconcilerOptions{ - UseRoutes: true, - ManagedDBEnabled: true, - }) + reconcilerOptions := CentralReconcilerOptions{ + UseRoutes: true, + ManagedDBEnabled: true, + } + fakeClient, _, r := getClientTrackerAndReconciler( + t, + defaultCentralConfig, + managedDBProvisioningClient, + reconcilerOptions, + ) status, err := r.Reconcile(context.TODO(), simpleManagedCentral) require.NoError(t, err) @@ -175,13 +240,14 @@ func TestReconcileCreateWithManagedDB(t *testing.T) { } func TestReconcileCreateWithLabelOperatorVersion(t *testing.T) { - fakeClient := testutils.NewFakeClientBuilder(t).Build() t.Setenv(features.TargetedOperatorUpgrades.EnvVar(), "true") - r := NewCentralReconciler(fakeClient, private.ManagedCentral{}, nil, centralDBInitFunc, - CentralReconcilerOptions{ - UseRoutes: true, - }) + fakeClient, _, r := getClientTrackerAndReconciler( + t, + defaultCentralConfig, + nil, + useRoutesReconcilerOptions, + ) status, err := r.Reconcile(context.TODO(), simpleManagedCentral) require.NoError(t, err) @@ -192,7 +258,7 @@ func TestReconcileCreateWithLabelOperatorVersion(t *testing.T) { central := &v1alpha1.Central{} err = fakeClient.Get(context.TODO(), client.ObjectKey{Name: centralName, Namespace: centralNamespace}, central) require.NoError(t, err) - assert.Equal(t, defaultOperatorVersion, central.ObjectMeta.Labels[operatorVersionKey]) + assert.Equal(t, operatorVersion, central.ObjectMeta.Labels[ReconcileOperatorSelector]) } func TestReconcileCreateWithManagedDBNoCredentials(t *testing.T) { @@ -202,8 +268,6 @@ func TestReconcileCreateWithManagedDBNoCredentials(t *testing.T) { t.Setenv("AWS_ROLE_ARN", "arn:aws:iam::012456789:role/fake_role") t.Setenv("AWS_WEB_IDENTITY_TOKEN_FILE", "/var/run/secrets/tokens/aws-token") - fakeClient := testutils.NewFakeClientBuilder(t).Build() - managedDBProvisioningClient, err := awsclient.NewRDSClient( &config.Config{ ManagedDB: config.ManagedDB{ @@ -213,11 +277,16 @@ func TestReconcileCreateWithManagedDBNoCredentials(t *testing.T) { }) require.NoError(t, err) - r := NewCentralReconciler(fakeClient, private.ManagedCentral{}, managedDBProvisioningClient, centralDBInitFunc, - CentralReconcilerOptions{ - UseRoutes: true, - ManagedDBEnabled: true, - }) + reconcilerOptions := CentralReconcilerOptions{ + UseRoutes: true, + ManagedDBEnabled: true, + } + _, _, r := getClientTrackerAndReconciler( + t, + defaultCentralConfig, + managedDBProvisioningClient, + reconcilerOptions, + ) _, err = r.Reconcile(context.TODO(), simpleManagedCentral) var awsErr awserr.Error @@ -226,15 +295,20 @@ func TestReconcileCreateWithManagedDBNoCredentials(t *testing.T) { } func TestReconcileUpdateSucceeds(t *testing.T) { - fakeClient := testutils.NewFakeClientBuilder(t, &v1alpha1.Central{ - ObjectMeta: metav1.ObjectMeta{ - Name: centralName, - Namespace: centralNamespace, - Annotations: map[string]string{util.RevisionAnnotationKey: "3"}, + fakeClient, _, r := getClientTrackerAndReconciler( + t, + defaultCentralConfig, + nil, + defaultReconcilerOptions, + &v1alpha1.Central{ + ObjectMeta: metav1.ObjectMeta{ + Name: centralName, + Namespace: centralNamespace, + Annotations: map[string]string{util.RevisionAnnotationKey: "3"}, + }, }, - }, centralDeploymentObject()).Build() - - r := NewCentralReconciler(fakeClient, private.ManagedCentral{}, nil, centralDBInitFunc, CentralReconcilerOptions{}) + centralDeploymentObject(), + ) status, err := r.Reconcile(context.TODO(), simpleManagedCentral) require.NoError(t, err) @@ -271,15 +345,20 @@ func TestReconcileLastHashNotUpdatedOnError(t *testing.T) { } func TestReconcileLastHashSetOnSuccess(t *testing.T) { - fakeClient := testutils.NewFakeClientBuilder(t, &v1alpha1.Central{ - ObjectMeta: metav1.ObjectMeta{ - Name: centralName, - Namespace: centralNamespace, - Annotations: map[string]string{util.RevisionAnnotationKey: "3"}, + fakeClient, _, r := getClientTrackerAndReconciler( + t, + defaultCentralConfig, + nil, + defaultReconcilerOptions, + &v1alpha1.Central{ + ObjectMeta: metav1.ObjectMeta{ + Name: centralName, + Namespace: centralNamespace, + Annotations: map[string]string{util.RevisionAnnotationKey: "3"}, + }, }, - }, centralDeploymentObject()).Build() - - r := NewCentralReconciler(fakeClient, private.ManagedCentral{}, nil, centralDBInitFunc, CentralReconcilerOptions{}) + centralDeploymentObject(), + ) managedCentral := simpleManagedCentral managedCentral.RequestStatus = centralConstants.CentralRequestStatusReady.String() @@ -303,15 +382,20 @@ func TestReconcileLastHashSetOnSuccess(t *testing.T) { } func TestIgnoreCacheForCentralNotReady(t *testing.T) { - fakeClient := testutils.NewFakeClientBuilder(t, &v1alpha1.Central{ - ObjectMeta: metav1.ObjectMeta{ - Name: centralName, - Namespace: centralNamespace, - Annotations: map[string]string{util.RevisionAnnotationKey: "3"}, + _, _, r := getClientTrackerAndReconciler( + t, + defaultCentralConfig, + nil, + defaultReconcilerOptions, + &v1alpha1.Central{ + ObjectMeta: metav1.ObjectMeta{ + Name: centralName, + Namespace: centralNamespace, + Annotations: map[string]string{util.RevisionAnnotationKey: "3"}, + }, }, - }, centralDeploymentObject()).Build() - - r := NewCentralReconciler(fakeClient, private.ManagedCentral{}, nil, centralDBInitFunc, CentralReconcilerOptions{}) + centralDeploymentObject(), + ) managedCentral := simpleManagedCentral managedCentral.RequestStatus = centralConstants.CentralRequestStatusProvisioning.String() @@ -328,15 +412,20 @@ func TestIgnoreCacheForCentralNotReady(t *testing.T) { } func TestIgnoreCacheForCentralForceReconcileAlways(t *testing.T) { - fakeClient := testutils.NewFakeClientBuilder(t, &v1alpha1.Central{ - ObjectMeta: metav1.ObjectMeta{ - Name: centralName, - Namespace: centralNamespace, - Annotations: map[string]string{util.RevisionAnnotationKey: "3"}, + _, _, r := getClientTrackerAndReconciler( + t, + defaultCentralConfig, + nil, + defaultReconcilerOptions, + &v1alpha1.Central{ + ObjectMeta: metav1.ObjectMeta{ + Name: centralName, + Namespace: centralNamespace, + Annotations: map[string]string{util.RevisionAnnotationKey: "3"}, + }, }, - }, centralDeploymentObject()).Build() - - r := NewCentralReconciler(fakeClient, private.ManagedCentral{}, nil, centralDBInitFunc, CentralReconcilerOptions{}) + centralDeploymentObject(), + ) managedCentral := simpleManagedCentral managedCentral.RequestStatus = centralConstants.CentralRequestStatusReady.String() @@ -354,8 +443,12 @@ func TestIgnoreCacheForCentralForceReconcileAlways(t *testing.T) { } func TestReconcileDelete(t *testing.T) { - fakeClient := testutils.NewFakeClientBuilder(t).Build() - r := NewCentralReconciler(fakeClient, private.ManagedCentral{}, nil, centralDBInitFunc, CentralReconcilerOptions{UseRoutes: true}) + fakeClient, _, r := getClientTrackerAndReconciler( + t, + defaultCentralConfig, + nil, + useRoutesReconcilerOptions, + ) _, err := r.Reconcile(context.TODO(), simpleManagedCentral) require.NoError(t, err) @@ -387,8 +480,12 @@ func TestReconcileDelete(t *testing.T) { } func TestDisablePauseAnnotation(t *testing.T) { - fakeClient := testutils.NewFakeClientBuilder(t).Build() - r := NewCentralReconciler(fakeClient, private.ManagedCentral{}, nil, centralDBInitFunc, CentralReconcilerOptions{UseRoutes: true}) + fakeClient, _, r := getClientTrackerAndReconciler( + t, + defaultCentralConfig, + nil, + useRoutesReconcilerOptions, + ) _, err := r.Reconcile(context.TODO(), simpleManagedCentral) require.NoError(t, err) @@ -409,10 +506,8 @@ func TestDisablePauseAnnotation(t *testing.T) { } func TestReconcileDeleteWithManagedDB(t *testing.T) { - fakeClient := testutils.NewFakeClientBuilder(t).Build() - managedDBProvisioningClient := &cloudprovider.DBClientMock{} - managedDBProvisioningClient.EnsureDBProvisionedFunc = func(_ context.Context, _ string, _ string, _ bool) error { + managedDBProvisioningClient.EnsureDBProvisionedFunc = func(_ context.Context, _string, _ string, _ string, _ bool) error { return nil } managedDBProvisioningClient.EnsureDBDeprovisionedFunc = func(_ string, _ bool) error { @@ -426,11 +521,16 @@ func TestReconcileDeleteWithManagedDB(t *testing.T) { return connection, nil } - r := NewCentralReconciler(fakeClient, private.ManagedCentral{}, managedDBProvisioningClient, centralDBInitFunc, - CentralReconcilerOptions{ - UseRoutes: true, - ManagedDBEnabled: true, - }) + reconcilerOptions := CentralReconcilerOptions{ + UseRoutes: true, + ManagedDBEnabled: true, + } + fakeClient, _, r := getClientTrackerAndReconciler( + t, + defaultCentralConfig, + managedDBProvisioningClient, + reconcilerOptions, + ) _, err := r.Reconcile(context.TODO(), simpleManagedCentral) require.NoError(t, err) @@ -440,7 +540,7 @@ func TestReconcileDeleteWithManagedDB(t *testing.T) { deletedCentral.Metadata.DeletionTimestamp = "2006-01-02T15:04:05Z07:00" // trigger deletion - managedDBProvisioningClient.EnsureDBProvisionedFunc = func(_ context.Context, _ string, _ string, _ bool) error { + managedDBProvisioningClient.EnsureDBProvisionedFunc = func(_ context.Context, _ string, _ string, _ string, _ bool) error { return nil } statusTrigger, err := r.Reconcile(context.TODO(), deletedCentral) @@ -507,11 +607,15 @@ func TestCentralChanged(t *testing.T) { }, } - fakeClient := testutils.NewFakeClientBuilder(t, centralDeploymentObject()).Build() - for _, test := range tests { t.Run(test.name, func(t *testing.T) { - reconciler := NewCentralReconciler(fakeClient, test.currentCentral, nil, centralDBInitFunc, CentralReconcilerOptions{}) + _, _, reconciler := getClientTrackerAndReconciler( + t, + test.currentCentral, + nil, + defaultReconcilerOptions, + centralDeploymentObject(), + ) if test.lastCentral != nil { err := reconciler.setLastCentralHash(*test.lastCentral) @@ -526,8 +630,12 @@ func TestCentralChanged(t *testing.T) { } func TestNamespaceLabelsAreSet(t *testing.T) { - fakeClient := testutils.NewFakeClientBuilder(t).Build() - r := NewCentralReconciler(fakeClient, private.ManagedCentral{}, nil, centralDBInitFunc, CentralReconcilerOptions{UseRoutes: true}) + fakeClient, _, r := getClientTrackerAndReconciler( + t, + defaultCentralConfig, + nil, + useRoutesReconcilerOptions, + ) _, err := r.Reconcile(context.TODO(), simpleManagedCentral) require.NoError(t, err) @@ -540,8 +648,12 @@ func TestNamespaceLabelsAreSet(t *testing.T) { } func TestReportRoutesStatuses(t *testing.T) { - fakeClient := testutils.NewFakeClientBuilder(t).Build() - r := NewCentralReconciler(fakeClient, private.ManagedCentral{}, nil, centralDBInitFunc, CentralReconcilerOptions{UseRoutes: true}) + _, _, r := getClientTrackerAndReconciler( + t, + defaultCentralConfig, + nil, + useRoutesReconcilerOptions, + ) status, err := r.Reconcile(context.TODO(), simpleManagedCentral) require.NoError(t, err) @@ -566,8 +678,12 @@ func TestChartResourcesAreAddedAndRemoved(t *testing.T) { chart, err := loader.LoadFiles(chartFiles) require.NoError(t, err) - fakeClient := testutils.NewFakeClientBuilder(t).Build() - r := NewCentralReconciler(fakeClient, private.ManagedCentral{}, nil, centralDBInitFunc, CentralReconcilerOptions{}) + fakeClient, _, r := getClientTrackerAndReconciler( + t, + defaultCentralConfig, + nil, + defaultReconcilerOptions, + ) r.resourcesChart = chart _, err = r.Reconcile(context.TODO(), simpleManagedCentral) @@ -599,8 +715,12 @@ func TestChartResourcesAreAddedAndUpdated(t *testing.T) { chart, err := loader.LoadFiles(chartFiles) require.NoError(t, err) - fakeClient := testutils.NewFakeClientBuilder(t).Build() - r := NewCentralReconciler(fakeClient, private.ManagedCentral{}, nil, centralDBInitFunc, CentralReconcilerOptions{}) + fakeClient, _, r := getClientTrackerAndReconciler( + t, + defaultCentralConfig, + nil, + defaultReconcilerOptions, + ) r.resourcesChart = chart _, err = r.Reconcile(context.TODO(), simpleManagedCentral) @@ -630,8 +750,12 @@ func TestChartResourcesAreAddedAndUpdated(t *testing.T) { } func TestEgressProxyIsDeployed(t *testing.T) { - fakeClient := testutils.NewFakeClientBuilder(t).Build() - r := NewCentralReconciler(fakeClient, private.ManagedCentral{}, nil, centralDBInitFunc, CentralReconcilerOptions{}) + fakeClient, _, r := getClientTrackerAndReconciler( + t, + defaultCentralConfig, + nil, + defaultReconcilerOptions, + ) _, err := r.Reconcile(context.TODO(), simpleManagedCentral) require.NoError(t, err) @@ -680,11 +804,15 @@ func TestEgressProxyIsDeployed(t *testing.T) { } func TestEgressProxyCustomImage(t *testing.T) { - fakeClient := testutils.NewFakeClientBuilder(t).Build() - r := NewCentralReconciler(fakeClient, private.ManagedCentral{}, nil, centralDBInitFunc, - CentralReconcilerOptions{ - EgressProxyImage: "registry.redhat.io/openshift4/ose-egress-http-proxy:version-for-test", - }) + reconcilerOptions := CentralReconcilerOptions{ + EgressProxyImage: "registry.redhat.io/openshift4/ose-egress-http-proxy:version-for-test", + } + fakeClient, _, r := getClientTrackerAndReconciler( + t, + defaultCentralConfig, + nil, + reconcilerOptions, + ) _, err := r.Reconcile(context.TODO(), simpleManagedCentral) require.NoError(t, err) @@ -706,25 +834,37 @@ func TestEgressProxyCustomImage(t *testing.T) { } func TestNoRoutesSentWhenOneNotCreated(t *testing.T) { - fakeClient, tracker := testutils.NewFakeClientWithTracker(t) + _, tracker, r := getClientTrackerAndReconciler( + t, + defaultCentralConfig, + nil, + useRoutesReconcilerOptions, + ) tracker.AddRouteError(centralReencryptRouteName, errors.New("fake error")) - r := NewCentralReconciler(fakeClient, private.ManagedCentral{}, nil, centralDBInitFunc, CentralReconcilerOptions{UseRoutes: true}) _, err := r.Reconcile(context.TODO(), simpleManagedCentral) require.Errorf(t, err, "fake error") } func TestNoRoutesSentWhenOneNotAdmitted(t *testing.T) { - fakeClient, tracker := testutils.NewFakeClientWithTracker(t) + _, tracker, r := getClientTrackerAndReconciler( + t, + defaultCentralConfig, + nil, + useRoutesReconcilerOptions, + ) tracker.SetRouteAdmitted(centralReencryptRouteName, false) - r := NewCentralReconciler(fakeClient, private.ManagedCentral{}, nil, centralDBInitFunc, CentralReconcilerOptions{UseRoutes: true}) _, err := r.Reconcile(context.TODO(), simpleManagedCentral) require.Errorf(t, err, "unable to find admitted ingress") } func TestNoRoutesSentWhenOneNotCreatedYet(t *testing.T) { - fakeClient, tracker := testutils.NewFakeClientWithTracker(t) + _, tracker, r := getClientTrackerAndReconciler( + t, + defaultCentralConfig, + nil, + useRoutesReconcilerOptions, + ) tracker.SetSkipRoute(centralReencryptRouteName, true) - r := NewCentralReconciler(fakeClient, private.ManagedCentral{}, nil, centralDBInitFunc, CentralReconcilerOptions{UseRoutes: true}) _, err := r.Reconcile(context.TODO(), simpleManagedCentral) require.Errorf(t, err, "unable to find admitted ingress") } @@ -757,8 +897,13 @@ func TestTelemetryOptionsAreSetInCR(t *testing.T) { } for _, tc := range tt { t.Run(tc.testName, func(t *testing.T) { - fakeClient := testutils.NewFakeClientBuilder(t).Build() - r := NewCentralReconciler(fakeClient, private.ManagedCentral{}, nil, centralDBInitFunc, CentralReconcilerOptions{Telemetry: tc.telemetry}) + reconcilerOptions := CentralReconcilerOptions{Telemetry: tc.telemetry} + fakeClient, _, r := getClientTrackerAndReconciler( + t, + defaultCentralConfig, + nil, + reconcilerOptions, + ) _, err := r.Reconcile(context.TODO(), simpleManagedCentral) require.NoError(t, err) @@ -817,8 +962,12 @@ func TestReconcileUpdatesRoutes(t *testing.T) { for _, tc := range tt { t.Run(tc.testName, func(t *testing.T) { - fakeClient := testutils.NewFakeClientBuilder(t).Build() - r := NewCentralReconciler(fakeClient, private.ManagedCentral{}, nil, centralDBInitFunc, CentralReconcilerOptions{UseRoutes: true}) + fakeClient, _, r := getClientTrackerAndReconciler( + t, + defaultCentralConfig, + nil, + useRoutesReconcilerOptions, + ) r.routeService = k8s.NewRouteService(fakeClient) central := simpleManagedCentral @@ -1123,3 +1272,471 @@ func Test_stringMapNeedsUpdating(t *testing.T) { }) } } + +func getSecret(name string, namespace string, data map[string][]byte) *v1.Secret { + return &v1.Secret{ + ObjectMeta: metav1.ObjectMeta{ // pragma: allowlist secret + Name: name, + Namespace: namespace, + }, + Data: data, + } +} + +const ( + emptySecretName = "emptySecret" // pragma: allowlist secret + secretWithOtherKeyName = "secretWithOtherKey" // pragma: allowlist secret + secretWithKeyDataToChangeName = "secretWithKeyDataToChange" // pragma: allowlist secret + secretWithExpectedKeyDataOnlyName = "secretWithExpectedKeyDataOnly" // pragma: allowlist secret + + entryKey = "some.key" + otherKey = "other.key" +) + +var ( + entryData = []byte("content") + otherData = []byte("something else") +) + +func compareSecret(t *testing.T, expectedSecret *v1.Secret, secret *v1.Secret, created bool) { + if expectedSecret == nil { // pragma: allowlist secret + assert.Nil(t, secret) + return + } + require.NotNil(t, secret) + assert.Equal(t, expectedSecret.ObjectMeta.Namespace, secret.ObjectMeta.Namespace) // pragma: allowlist secret + assert.Equal(t, expectedSecret.ObjectMeta.Name, secret.ObjectMeta.Name) // pragma: allowlist secret + if created { + require.NotZero(t, len(secret.ObjectMeta.Labels)) + labelVal, labelFound := secret.ObjectMeta.Labels[k8s.ManagedByLabelKey] + require.True(t, labelFound) + assert.Equal(t, labelVal, k8s.ManagedByFleetshardValue) + require.NotZero(t, len(secret.ObjectMeta.Annotations)) + annotationVal, annotationFound := secret.ObjectMeta.Annotations[managedServicesAnnotation] + require.True(t, annotationFound) + assert.Equal(t, annotationVal, "true") + } + assert.Equal(t, expectedSecret.Data, secret.Data) +} + +func TestEnsureSecretExists(t *testing.T) { + fakeClient, _, r := getClientTrackerAndReconciler( + t, + defaultCentralConfig, + nil, + defaultReconcilerOptions, + ) + secretModifyFunc := func(secret *v1.Secret) error { + if secret.Data == nil { + secret.Data = make(map[string][]byte) + } + payload, found := secret.Data[entryKey] + if found { + if bytes.Equal(payload, entryData) { + return nil + } + } + secret.Data[entryKey] = entryData + return nil + } + testCases := []struct { + secretName string + initialData map[string][]byte + expectedData map[string][]byte + }{ + { + secretName: emptySecretName, // pragma: allowlist secret + initialData: nil, + expectedData: map[string][]byte{ + entryKey: entryData, + }, + }, + { + secretName: secretWithOtherKeyName, // pragma: allowlist secret + initialData: map[string][]byte{ + otherKey: otherData, + }, + expectedData: map[string][]byte{ + entryKey: entryData, + otherKey: otherData, + }, + }, + { + secretName: secretWithKeyDataToChangeName, // pragma: allowlist secret + initialData: map[string][]byte{ + entryKey: otherData, + }, + expectedData: map[string][]byte{ + entryKey: entryData, + }, + }, + { + secretName: secretWithExpectedKeyDataOnlyName, // pragma: allowlist secret + initialData: map[string][]byte{ + entryKey: entryData, + }, + expectedData: map[string][]byte{ + entryKey: entryData, + }, + }, + } + for _, tc := range testCases { + t.Run(tc.secretName, func(t *testing.T) { + ctx := context.TODO() + fetchedSecret := &v1.Secret{} + initialSecret := getSecret(tc.secretName, centralNamespace, tc.initialData) + assert.NoError(t, fakeClient.Create(ctx, initialSecret)) + assert.NoError(t, r.ensureSecretExists(ctx, centralNamespace, tc.secretName, secretModifyFunc)) + fakeClient.Get(ctx, client.ObjectKey{Namespace: centralNamespace, Name: tc.secretName}, fetchedSecret) + compareSecret(t, getSecret(tc.secretName, centralNamespace, tc.expectedData), fetchedSecret, false) + }) + } + + t.Run("missing secret", func(t *testing.T) { + secretName := "missingSecret" // pragma: allowlist secret + expectedData := map[string][]byte{ + entryKey: entryData, + } + ctx := context.TODO() + fetchedSecret := &v1.Secret{} + assert.NoError(t, r.ensureSecretExists(ctx, centralNamespace, secretName, secretModifyFunc)) + fakeClient.Get(ctx, client.ObjectKey{Namespace: centralNamespace, Name: secretName}, fetchedSecret) + compareSecret(t, getSecret(secretName, centralNamespace, expectedData), fetchedSecret, true) + }) +} + +func TestGetInstanceConfigSetsNoProxyEnvVarsForAuditLog(t *testing.T) { + testCases := []struct { + auditLoggingConfig config.AuditLogging + }{ + { + auditLoggingConfig: defaultAuditLogConfig, + }, + { + auditLoggingConfig: vectorAuditLogConfig, + }, + { + auditLoggingConfig: disabledAuditLogConfig, + }, + } + for _, testCase := range testCases { + reconcilerOptions := CentralReconcilerOptions{ + AuditLogging: testCase.auditLoggingConfig, + } + _, _, r := getClientTrackerAndReconciler( + t, + simpleManagedCentral, + nil, + reconcilerOptions, + ) + centralConfig, err := r.getInstanceConfig(&simpleManagedCentral) + assert.NoError(t, err) + require.NotNil(t, centralConfig) + require.NotNil(t, centralConfig.Spec.Customize) + noProxyEnvLowerCaseFound := false + noProxyEnvUpperCaseFound := false + for _, envVar := range centralConfig.Spec.Customize.EnvVars { + switch envVar.Name { + case "no_proxy": + noProxyEnvLowerCaseFound = true + assert.Contains(t, strings.Split(envVar.Value, ","), testCase.auditLoggingConfig.Endpoint(false)) + case "NO_PROXY": + noProxyEnvUpperCaseFound = true + assert.Contains(t, strings.Split(envVar.Value, ","), testCase.auditLoggingConfig.Endpoint(false)) + } + } + assert.True(t, noProxyEnvLowerCaseFound) + assert.True(t, noProxyEnvUpperCaseFound) + } +} + +func TestGetInstanceConfigSetsDeclarativeConfigSecretInCentralCR(t *testing.T) { + reconcilerOptions := CentralReconcilerOptions{ + AuditLogging: defaultAuditLogConfig, + } + _, _, r := getClientTrackerAndReconciler( + t, + simpleManagedCentral, + nil, + reconcilerOptions, + ) + centralConfig, err := r.getInstanceConfig(&simpleManagedCentral) + assert.NoError(t, err) + require.NotNil(t, centralConfig) + require.NotNil(t, centralConfig.Spec.Central) + require.NotNil(t, centralConfig.Spec.Central.DeclarativeConfiguration) + centralCRDeclarativeConfig := centralConfig.Spec.Central.DeclarativeConfiguration + assert.NotZero(t, len(centralCRDeclarativeConfig.Secrets)) + expectedSecretReference := v1alpha1.LocalSecretReference{ // pragma: allowlist secret + Name: sensibleDeclarativeConfigSecretName, + } + assert.Contains(t, centralCRDeclarativeConfig.Secrets, expectedSecretReference) +} + +func TestGetAuditLogNotifierConfig(t *testing.T) { + testCases := []struct { + namespace string + auditLogging config.AuditLogging + auditLogTarget string + auditLogPort int + expectedConfig *declarativeconfig.Notifier + }{ + { + namespace: centralNamespace, + auditLogging: defaultAuditLogConfig, + auditLogTarget: defaultAuditLogConfig.AuditLogTargetHost, + auditLogPort: defaultAuditLogConfig.AuditLogTargetPort, + expectedConfig: &declarativeconfig.Notifier{ + Name: auditLogNotifierName, + GenericConfig: &declarativeconfig.GenericConfig{ + Endpoint: fmt.Sprintf( + "https://%s:%d", + defaultAuditLogConfig.AuditLogTargetHost, + defaultAuditLogConfig.AuditLogTargetPort, + ), + SkipTLSVerify: true, + AuditLoggingEnabled: true, + ExtraFields: []declarativeconfig.KeyValuePair{ + { + Key: auditLogTenantIDKey, + Value: centralNamespace, + }, + }, + }, + }, + }, + { + namespace: "rhacs", + auditLogging: vectorAuditLogConfig, + auditLogTarget: vectorAuditLogConfig.AuditLogTargetHost, + auditLogPort: vectorAuditLogConfig.AuditLogTargetPort, + expectedConfig: &declarativeconfig.Notifier{ + Name: auditLogNotifierName, + GenericConfig: &declarativeconfig.GenericConfig{ + Endpoint: fmt.Sprintf( + "https://%s:%d", + vectorAuditLogConfig.AuditLogTargetHost, + vectorAuditLogConfig.AuditLogTargetPort, + ), + SkipTLSVerify: true, + AuditLoggingEnabled: true, + ExtraFields: []declarativeconfig.KeyValuePair{ + { + Key: auditLogTenantIDKey, + Value: "rhacs", + }, + }, + }, + }, + }, + } + for _, testCase := range testCases { + notifierConfig := getAuditLogNotifierConfig(testCase.auditLogging, testCase.namespace) + assert.Equal(t, testCase.expectedConfig, notifierConfig) + } +} + +func populateNotifierSecret( + t *testing.T, + namespace string, + payload map[string][]*declarativeconfig.Notifier, +) *v1.Secret { + if len(payload) == 0 { + return getSecret(sensibleDeclarativeConfigSecretName, namespace, nil) + } + secretData := make(map[string][]byte, len(payload)) + for dataKey, configList := range payload { + switch len(configList) { + case 0: + secretData[dataKey] = nil + case 1: + encodedBytes, encodingErr := yaml.Marshal(configList[0]) + assert.NoError(t, encodingErr) + secretData[dataKey] = encodedBytes + default: + encodedBytes, encodingErr := yaml.Marshal(configList) + assert.NoError(t, encodingErr) + secretData[dataKey] = encodedBytes + } + } + return getSecret(sensibleDeclarativeConfigSecretName, centralNamespace, secretData) +} + +func TestReconcileDeclarativeConfigurationData(t *testing.T) { + defaultNotifierConfig := getAuditLogNotifierConfig( + defaultAuditLogConfig, + centralNamespace, + ) + faultyVectorNotifierConfig := getAuditLogNotifierConfig( + vectorAuditLogConfig, + "rhacs", + ) + correctVectorNotifierConfig := getAuditLogNotifierConfig( + vectorAuditLogConfig, + centralNamespace, + ) + + const otherItemKey = "other.item.key" + + testCases := []struct { + name string + auditLogConfig config.AuditLogging + preExistingSecret bool + initialNotifierConfigs map[string][]*declarativeconfig.Notifier + expectedNotifierConfigs map[string][]*declarativeconfig.Notifier + postReconcileSecret bool + }{ + { + name: "Missing default secret gets created", + auditLogConfig: defaultAuditLogConfig, + preExistingSecret: false, // pragma: allowlist secret + expectedNotifierConfigs: map[string][]*declarativeconfig.Notifier{ + auditLogNotifierKey: {defaultNotifierConfig}, + }, + postReconcileSecret: true, // pragma: allowlist secret + }, + { + name: "Bad default secret is left untouched", + auditLogConfig: defaultAuditLogConfig, + preExistingSecret: true, // pragma: allowlist secret + initialNotifierConfigs: map[string][]*declarativeconfig.Notifier{ + auditLogNotifierKey: {correctVectorNotifierConfig}, + }, + expectedNotifierConfigs: map[string][]*declarativeconfig.Notifier{ + auditLogNotifierKey: {correctVectorNotifierConfig}, + }, + postReconcileSecret: true, // pragma: allowlist secret + }, + { + name: "Bad aggregated default secret is left untouched", + auditLogConfig: defaultAuditLogConfig, + preExistingSecret: true, // pragma: allowlist secret + initialNotifierConfigs: map[string][]*declarativeconfig.Notifier{ + auditLogNotifierKey: {correctVectorNotifierConfig, faultyVectorNotifierConfig}, + }, + expectedNotifierConfigs: map[string][]*declarativeconfig.Notifier{ + auditLogNotifierKey: {correctVectorNotifierConfig, faultyVectorNotifierConfig}, + }, + postReconcileSecret: true, // pragma: allowlist secret + }, + { + name: "Partially correct aggregated default secret is left untouched", + auditLogConfig: defaultAuditLogConfig, + preExistingSecret: true, // pragma: allowlist secret + initialNotifierConfigs: map[string][]*declarativeconfig.Notifier{ + auditLogNotifierKey: { + correctVectorNotifierConfig, + faultyVectorNotifierConfig, + defaultNotifierConfig, + }, + }, + expectedNotifierConfigs: map[string][]*declarativeconfig.Notifier{ + auditLogNotifierKey: { + correctVectorNotifierConfig, + faultyVectorNotifierConfig, + defaultNotifierConfig, + }, + }, + postReconcileSecret: true, // pragma: allowlist secret + }, + { + name: "Correct default secret with other secret key is left untouched", + auditLogConfig: defaultAuditLogConfig, + preExistingSecret: true, // pragma: allowlist secret + initialNotifierConfigs: map[string][]*declarativeconfig.Notifier{ + auditLogNotifierKey: {defaultNotifierConfig}, + otherItemKey: {faultyVectorNotifierConfig}, + }, + expectedNotifierConfigs: map[string][]*declarativeconfig.Notifier{ + auditLogNotifierKey: {defaultNotifierConfig}, + otherItemKey: {faultyVectorNotifierConfig}, + }, + postReconcileSecret: true, // pragma: allowlist secret + }, + { + name: "Missing vector secret gets created", + auditLogConfig: vectorAuditLogConfig, + preExistingSecret: false, // pragma: allowlist secret + expectedNotifierConfigs: map[string][]*declarativeconfig.Notifier{ + auditLogNotifierKey: {correctVectorNotifierConfig}, + }, + postReconcileSecret: true, // pragma: allowlist secret + }, + { + name: "Bad vector secret is left untouched", + auditLogConfig: vectorAuditLogConfig, + preExistingSecret: true, // pragma: allowlist secret + initialNotifierConfigs: map[string][]*declarativeconfig.Notifier{ + auditLogNotifierKey: {faultyVectorNotifierConfig}, + }, + expectedNotifierConfigs: map[string][]*declarativeconfig.Notifier{ + auditLogNotifierKey: {faultyVectorNotifierConfig}, + }, + postReconcileSecret: true, // pragma: allowlist secret + }, + { + name: "Partially correct aggregated vector secret is left untouched", + auditLogConfig: vectorAuditLogConfig, + preExistingSecret: true, // pragma: allowlist secret + initialNotifierConfigs: map[string][]*declarativeconfig.Notifier{ + auditLogNotifierKey: {correctVectorNotifierConfig, faultyVectorNotifierConfig}, + }, + expectedNotifierConfigs: map[string][]*declarativeconfig.Notifier{ + auditLogNotifierKey: {correctVectorNotifierConfig, faultyVectorNotifierConfig}, + }, + postReconcileSecret: true, // pragma: allowlist secret + }, + { + name: "No secret creation when no secret and audit logging disabled", + auditLogConfig: disabledAuditLogConfig, + preExistingSecret: false, // pragma: allowlist secret + postReconcileSecret: false, // pragma: allowlist secret + }, + { + name: "No secret modification when secret and audit logging disabled", + auditLogConfig: disabledAuditLogConfig, + preExistingSecret: true, // pragma: allowlist secret + initialNotifierConfigs: map[string][]*declarativeconfig.Notifier{ + auditLogNotifierKey: {defaultNotifierConfig}, + otherItemKey: {faultyVectorNotifierConfig}, + }, + expectedNotifierConfigs: map[string][]*declarativeconfig.Notifier{ + auditLogNotifierKey: {defaultNotifierConfig}, + otherItemKey: {faultyVectorNotifierConfig}, + }, + postReconcileSecret: true, // pragma: allowlist secret + }, + } + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + ctx := context.TODO() + reconcilerOptions := CentralReconcilerOptions{ + AuditLogging: testCase.auditLogConfig, + } + fakeClient, _, r := getClientTrackerAndReconciler( + t, + simpleManagedCentral, + nil, + reconcilerOptions, + ) + if testCase.preExistingSecret { + secret := populateNotifierSecret(t, centralNamespace, testCase.initialNotifierConfigs) + require.NoError(t, fakeClient.Create(ctx, secret)) + } + r.reconcileDeclarativeConfigurationData(ctx, &simpleManagedCentral) + fetchedSecret := &v1.Secret{} + secretKey := client.ObjectKey{ // pragma: allowlist secret + Name: sensibleDeclarativeConfigSecretName, + Namespace: centralNamespace, + } + postFetchErr := fakeClient.Get(ctx, secretKey, fetchedSecret) + if testCase.postReconcileSecret { + assert.NoError(t, postFetchErr) + expectedSecret := populateNotifierSecret(t, centralNamespace, testCase.expectedNotifierConfigs) + compareSecret(t, expectedSecret, fetchedSecret, !testCase.preExistingSecret) + } else { + assert.True(t, k8sErrors.IsNotFound(postFetchErr)) + } + }) + } +} diff --git a/fleetshard/pkg/cipher/cipher.go b/fleetshard/pkg/cipher/cipher.go new file mode 100644 index 0000000000..db86d8021f --- /dev/null +++ b/fleetshard/pkg/cipher/cipher.go @@ -0,0 +1,76 @@ +// Package cipher defines encryption and decryption methods used by fleetshard-sync +package cipher + +import ( + "crypto/aes" + "crypto/cipher" + "crypto/rand" + "fmt" + "io" +) + +const keySize = 32 + +//go:generate moq -out cipher_moq.go . Cipher + +// Cipher is the interface used to encrypt and decrypt content +type Cipher interface { + Encrypt(plaintext []byte) ([]byte, error) + Decrypt(ciphertext []byte) ([]byte, error) +} + +// LocalAES256Cipher implements encryption and decryption using AES256 GCM +type LocalAES256Cipher struct { + aesgcm cipher.AEAD +} + +// NewLocalAES256Cipher returns a new Cipher using the given key +func NewLocalAES256Cipher(key []byte) (Cipher, error) { + if len(key) != 32 { + return nil, fmt.Errorf("creating AES256Cipher, key does not match required lenght of %d", keySize) + } + + block, err := aes.NewCipher(key) + if err != nil { + return nil, fmt.Errorf("creating cipher block: %s", err) + } + + aesgcm, err := cipher.NewGCM(block) + if err != nil { + return nil, fmt.Errorf("creating AES GCM cipher %s", err) + } + + return LocalAES256Cipher{aesgcm: aesgcm}, nil +} + +var _ Cipher = LocalAES256Cipher{} + +// Encrypt implementes the logic to encrypt plaintext +func (a LocalAES256Cipher) Encrypt(plaintext []byte) ([]byte, error) { + + nonce := make([]byte, a.aesgcm.NonceSize()) + if _, err := io.ReadFull(rand.Reader, nonce); err != nil { + return nil, fmt.Errorf("generating nonce for encryption %v", err) + } + + ciphertext := a.aesgcm.Seal(nil, nonce, plaintext, nil) + + // append nonce to ciphertext so decrypt can use it + ciphertext = append(ciphertext, nonce...) + + return ciphertext, nil +} + +// Decrypt implements the logic to decrypt ciphertext, it assumes +// a nonce has been apended to ciphertext at encryption +func (a LocalAES256Cipher) Decrypt(ciphertext []byte) ([]byte, error) { + nonceIndex := len(ciphertext) - a.aesgcm.NonceSize() + cipher, nonce := ciphertext[:nonceIndex], ciphertext[nonceIndex:] + + plaintext, err := a.aesgcm.Open(nil, nonce, cipher, nil) + if err != nil { + return nil, fmt.Errorf("decrypting ciphertext: %v", err) + } + + return plaintext, nil +} diff --git a/fleetshard/pkg/cipher/cipher_moq.go b/fleetshard/pkg/cipher/cipher_moq.go new file mode 100644 index 0000000000..e70c0486fd --- /dev/null +++ b/fleetshard/pkg/cipher/cipher_moq.go @@ -0,0 +1,118 @@ +// Code generated by moq; DO NOT EDIT. +// github.com/matryer/moq + +package cipher + +import ( + "sync" +) + +// Ensure, that CipherMock does implement Cipher. +// If this is not the case, regenerate this file with moq. +var _ Cipher = &CipherMock{} + +// CipherMock is a mock implementation of Cipher. +// +// func TestSomethingThatUsesCipher(t *testing.T) { +// +// // make and configure a mocked Cipher +// mockedCipher := &CipherMock{ +// DecryptFunc: func(ciphertext []byte) ([]byte, error) { +// panic("mock out the Decrypt method") +// }, +// EncryptFunc: func(plaintext []byte) ([]byte, error) { +// panic("mock out the Encrypt method") +// }, +// } +// +// // use mockedCipher in code that requires Cipher +// // and then make assertions. +// +// } +type CipherMock struct { + // DecryptFunc mocks the Decrypt method. + DecryptFunc func(ciphertext []byte) ([]byte, error) + + // EncryptFunc mocks the Encrypt method. + EncryptFunc func(plaintext []byte) ([]byte, error) + + // calls tracks calls to the methods. + calls struct { + // Decrypt holds details about calls to the Decrypt method. + Decrypt []struct { + // Ciphertext is the ciphertext argument value. + Ciphertext []byte + } + // Encrypt holds details about calls to the Encrypt method. + Encrypt []struct { + // Plaintext is the plaintext argument value. + Plaintext []byte + } + } + lockDecrypt sync.RWMutex + lockEncrypt sync.RWMutex +} + +// Decrypt calls DecryptFunc. +func (mock *CipherMock) Decrypt(ciphertext []byte) ([]byte, error) { + if mock.DecryptFunc == nil { + panic("CipherMock.DecryptFunc: method is nil but Cipher.Decrypt was just called") + } + callInfo := struct { + Ciphertext []byte + }{ + Ciphertext: ciphertext, + } + mock.lockDecrypt.Lock() + mock.calls.Decrypt = append(mock.calls.Decrypt, callInfo) + mock.lockDecrypt.Unlock() + return mock.DecryptFunc(ciphertext) +} + +// DecryptCalls gets all the calls that were made to Decrypt. +// Check the length with: +// +// len(mockedCipher.DecryptCalls()) +func (mock *CipherMock) DecryptCalls() []struct { + Ciphertext []byte +} { + var calls []struct { + Ciphertext []byte + } + mock.lockDecrypt.RLock() + calls = mock.calls.Decrypt + mock.lockDecrypt.RUnlock() + return calls +} + +// Encrypt calls EncryptFunc. +func (mock *CipherMock) Encrypt(plaintext []byte) ([]byte, error) { + if mock.EncryptFunc == nil { + panic("CipherMock.EncryptFunc: method is nil but Cipher.Encrypt was just called") + } + callInfo := struct { + Plaintext []byte + }{ + Plaintext: plaintext, + } + mock.lockEncrypt.Lock() + mock.calls.Encrypt = append(mock.calls.Encrypt, callInfo) + mock.lockEncrypt.Unlock() + return mock.EncryptFunc(plaintext) +} + +// EncryptCalls gets all the calls that were made to Encrypt. +// Check the length with: +// +// len(mockedCipher.EncryptCalls()) +func (mock *CipherMock) EncryptCalls() []struct { + Plaintext []byte +} { + var calls []struct { + Plaintext []byte + } + mock.lockEncrypt.RLock() + calls = mock.calls.Encrypt + mock.lockEncrypt.RUnlock() + return calls +} diff --git a/fleetshard/pkg/cipher/cipher_test.go b/fleetshard/pkg/cipher/cipher_test.go new file mode 100644 index 0000000000..a165e6a329 --- /dev/null +++ b/fleetshard/pkg/cipher/cipher_test.go @@ -0,0 +1,52 @@ +package cipher + +import ( + "crypto/rand" + "io" + "testing" + + "github.com/stretchr/testify/require" +) + +func generateKey() ([]byte, error) { + key := make([]byte, 32) + if _, err := io.ReadFull(rand.Reader, key); err != nil { + return nil, err + } + + return key, nil +} + +// TestDifferentCipherForSamePlaintext tests the correct usage of nonce +// to ensure encrypting the same plaintext twice does not yield the same cipher text +func TestDifferentCipherForSamePlaintext(t *testing.T) { + plaintext := []byte("test plaintext") + key, err := generateKey() + require.NoError(t, err, "generating key") + + aes, err := NewLocalAES256Cipher(key) + require.NoError(t, err, "creating cipher") + cipher1, err := aes.Encrypt(plaintext) + require.NoError(t, err, "encrypting first plaintext") + cipher2, err := aes.Encrypt(plaintext) + require.NoError(t, err, "encrypting second plaintext") + + require.NotEqual(t, cipher1, cipher2, "encrypting same text twice yields same result") +} + +func TestEncryptDecryptMatch(t *testing.T) { + plaintext := []byte("test plaintext") + key, err := generateKey() + require.NoError(t, err, "generating key") + + aes, err := NewLocalAES256Cipher(key) + require.NoError(t, err, "creating cipher") + + cipher, err := aes.Encrypt(plaintext) + require.NoError(t, err, "encyrpting plaintext") + + decrypted, err := aes.Decrypt(cipher) + require.NoError(t, err, "decrypting ciphertext") + + require.Equal(t, string(plaintext), string(decrypted), "decrypted string does not match plaintext") +} diff --git a/fleetshard/pkg/runtime/runtime.go b/fleetshard/pkg/runtime/runtime.go index e7a6774866..991afc64cb 100644 --- a/fleetshard/pkg/runtime/runtime.go +++ b/fleetshard/pkg/runtime/runtime.go @@ -120,6 +120,7 @@ func (r *Runtime) Start() error { Telemetry: r.config.Telemetry, ClusterName: r.config.ClusterName, Environment: r.config.Environment, + AuditLogging: r.config.AuditLogging, } ticker := concurrency.NewRetryTicker(func(ctx context.Context) (timeToNextTick time.Duration, err error) { @@ -131,6 +132,7 @@ func (r *Runtime) Start() error { } if features.TargetedOperatorUpgrades.Enabled() { + glog.Info("Starting operator upgrades") err := r.upgradeOperator(list) if err != nil { err = errors.Wrapf(err, "Upgrading operator") @@ -245,18 +247,27 @@ func (r *Runtime) deleteStaleReconcilers(list *private.ManagedCentralList) { } func (r *Runtime) upgradeOperator(list private.ManagedCentralList) error { + var desiredOperators []operator.DeploymentConfig var desiredOperatorImages []string for _, central := range list.Items { + // TODO: read GitRef ManagedCentral list call + operatorConfiguration := operator.DeploymentConfig{ + Image: central.Spec.OperatorImage, + GitRef: "4.0.1", + } + desiredOperators = append(desiredOperators, operatorConfiguration) desiredOperatorImages = append(desiredOperatorImages, central.Spec.OperatorImage) } + ctx := context.Background() - for _, img := range desiredOperatorImages { - glog.Infof("Installing Operator: %s", img) + for _, operatorDeployment := range desiredOperators { + glog.Infof("Installing Operator version: %s", operatorDeployment.GitRef) } + //TODO(ROX-15080): Download CRD on operator upgrades to always install the latest CRD crdTag := "4.0.1" - err := r.operatorManager.InstallOrUpgrade(ctx, desiredOperatorImages, crdTag) + err := r.operatorManager.InstallOrUpgrade(ctx, desiredOperators, crdTag) if err != nil { return fmt.Errorf("ensuring initial operator installation failed: %w", err) } diff --git a/fleetshard/pkg/testutils/k8s.go b/fleetshard/pkg/testutils/k8s.go index 2caadfa68b..e3acddd4f5 100644 --- a/fleetshard/pkg/testutils/k8s.go +++ b/fleetshard/pkg/testutils/k8s.go @@ -77,12 +77,13 @@ func NewFakeClientBuilder(t *testing.T, objects ...ctrlClient.Object) *fake.Clie } // NewFakeClientWithTracker returns a new fake client and a ReconcileTracker to mock k8s responses -func NewFakeClientWithTracker(t *testing.T) (ctrlClient.WithWatch, *ReconcileTracker) { +func NewFakeClientWithTracker(t *testing.T, objects ...ctrlClient.Object) (ctrlClient.WithWatch, *ReconcileTracker) { scheme := NewScheme(t) tracker := NewReconcileTracker(scheme) client := fake.NewClientBuilder(). WithScheme(scheme). WithObjectTracker(tracker). + WithObjects(objects...). Build() return client, tracker } diff --git a/go.mod b/go.mod index b02ff79ae1..c7097c0646 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,7 @@ require ( github.com/aws/aws-sdk-go v1.44.300 github.com/bxcodec/faker/v3 v3.8.1 github.com/caarlos0/env/v6 v6.10.1 + github.com/containers/image v3.0.2+incompatible github.com/coreos/go-oidc/v3 v3.6.0 github.com/docker/go-healthcheck v0.1.0 github.com/getsentry/sentry-go v0.20.0 @@ -30,8 +31,8 @@ require ( github.com/mendsley/gojwk v0.0.0-20141217222730-4d5ec6e58103 github.com/olekukonko/tablewriter v0.0.5 github.com/onsi/ginkgo/v2 v2.11.0 - github.com/onsi/gomega v1.27.8 - github.com/openshift-online/ocm-sdk-go v0.1.344 + github.com/onsi/gomega v1.27.10 + github.com/openshift-online/ocm-sdk-go v0.1.358 github.com/openshift/api v3.9.1-0.20191201231411-9f834e337466+incompatible github.com/operator-framework/api v0.17.6 github.com/patrickmn/go-cache v2.1.0+incompatible @@ -51,29 +52,29 @@ require ( github.com/stretchr/testify v1.8.4 github.com/xeipuuv/gojsonschema v1.2.0 github.com/zgalor/weberr v0.8.2 - golang.org/x/exp v0.0.0-20230124195608-d38c7dcee874 - golang.org/x/net v0.11.0 + golang.org/x/exp v0.0.0-20230321023759-10a507213a29 + golang.org/x/net v0.12.0 golang.org/x/oauth2 v0.9.0 - golang.org/x/sys v0.9.0 + golang.org/x/sys v0.10.0 gopkg.in/resty.v1 v1.12.0 gopkg.in/yaml.v2 v2.4.0 gorm.io/driver/postgres v1.5.2 gorm.io/gorm v1.25.1 helm.sh/helm/v3 v3.11.3 - k8s.io/api v0.26.4 + k8s.io/api v0.26.5 k8s.io/apimachinery v0.27.3 - k8s.io/client-go v0.26.4 + k8s.io/client-go v0.26.5 k8s.io/utils v0.0.0-20230220204549-a5ecb0141aa5 sigs.k8s.io/controller-runtime v0.14.6 sigs.k8s.io/yaml v1.3.0 ) require ( - github.com/BurntSushi/toml v1.2.1 // indirect + github.com/BurntSushi/toml v1.3.0 // indirect github.com/Masterminds/goutils v1.1.1 // indirect github.com/Masterminds/semver/v3 v3.2.0 // indirect github.com/Masterminds/sprig/v3 v3.2.3 // indirect - github.com/RoaringBitmap/roaring v1.2.3 // indirect + github.com/RoaringBitmap/roaring v1.3.0 // indirect github.com/aymerick/douceur v0.2.0 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bits-and-blooms/bitset v1.2.0 // indirect @@ -82,7 +83,7 @@ require ( github.com/blevesearch/go-porterstemmer v1.0.3 // indirect github.com/blevesearch/mmap-go v1.0.2 // indirect github.com/blevesearch/segment v0.9.0 // indirect - github.com/cenkalti/backoff/v4 v4.1.3 // indirect + github.com/cenkalti/backoff/v4 v4.2.0 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/cloudflare/cfssl v1.6.4 // indirect github.com/couchbase/vellum v1.0.2 // indirect @@ -107,7 +108,7 @@ require ( github.com/gofrs/uuid v4.4.0+incompatible // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/snappy v0.0.4 // indirect - github.com/google/certificate-transparency-go v1.1.5 // indirect + github.com/google/certificate-transparency-go v1.1.6 // indirect github.com/google/gnostic v0.6.9 // indirect github.com/google/gofuzz v1.2.0 // indirect github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 // indirect @@ -148,6 +149,7 @@ require ( github.com/modern-go/reflect2 v1.0.2 // indirect github.com/mschoch/smat v0.2.0 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/opencontainers/go-digest v1.0.0 // indirect github.com/openshift/client-go v0.0.0-20200623090625-83993cebb5ae // indirect github.com/opentracing/opentracing-go v1.2.0 // indirect github.com/pelletier/go-toml v1.9.5 // indirect @@ -172,22 +174,24 @@ require ( go.uber.org/atomic v1.11.0 // indirect go.uber.org/multierr v1.10.0 // indirect go.uber.org/zap v1.24.0 // indirect - golang.org/x/crypto v0.10.0 // indirect - golang.org/x/term v0.9.0 // indirect - golang.org/x/text v0.10.0 // indirect + golang.org/x/crypto v0.11.0 // indirect + golang.org/x/term v0.10.0 // indirect + golang.org/x/text v0.11.0 // indirect golang.org/x/time v0.3.0 // indirect golang.org/x/tools v0.9.3 // indirect gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect - google.golang.org/grpc v1.54.0 // indirect + google.golang.org/genproto v0.0.0-20230530153820-e85fd2cbaebc // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20230530153820-e85fd2cbaebc // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc // indirect + google.golang.org/grpc v1.55.0 // indirect google.golang.org/protobuf v1.30.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/square/go-jose.v2 v2.6.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/apiextensions-apiserver v0.26.1 // indirect - k8s.io/component-base v0.26.4 // indirect - k8s.io/klog/v2 v2.90.1 // indirect + k8s.io/component-base v0.26.5 // indirect + k8s.io/klog/v2 v2.100.1 // indirect k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect @@ -200,7 +204,7 @@ replace ( github.com/gogo/protobuf => github.com/connorgorman/protobuf v1.2.2-0.20210115205927-b892c1b298f7 github.com/heroku/docker-registry-client => github.com/stackrox/docker-registry-client v0.0.0-20230411213734-d75b95d65d28 github.com/operator-framework/helm-operator-plugins => github.com/stackrox/helm-operator v0.0.12-0.20221003092512-fbf71229411f - github.com/stackrox/rox => github.com/stackrox/stackrox v0.0.0-20230516045525-a98c9075fbf9 + github.com/stackrox/rox => github.com/stackrox/stackrox v0.0.0-20230601164127-c2706e2b72bb go.uber.org/zap => github.com/stackrox/zap v1.15.1-0.20200720133746-810fd602fd0f ) diff --git a/go.sum b/go.sum index b2860ad99a..cefe6823e4 100644 --- a/go.sum +++ b/go.sum @@ -62,8 +62,8 @@ github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxB github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc= github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak= -github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/BurntSushi/toml v1.3.0 h1:Ws8e5YmnrGEHzZEzg0YvK/7COGYtTC5PbaH9oSSbgfA= +github.com/BurntSushi/toml v1.3.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60= github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= @@ -83,8 +83,8 @@ github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbt github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= -github.com/RoaringBitmap/roaring v1.2.3 h1:yqreLINqIrX22ErkKI0vY47/ivtJr6n+kMhVOVmhWBY= -github.com/RoaringBitmap/roaring v1.2.3/go.mod h1:plvDsJQpxOC5bw8LRteu/MLWHsHez/3y6cubLI4/1yE= +github.com/RoaringBitmap/roaring v1.3.0 h1:aQmu9zQxDU0uhwR8SXOH/OrqEf+X8A0LQmwW3JX8Lcg= +github.com/RoaringBitmap/roaring v1.3.0/go.mod h1:plvDsJQpxOC5bw8LRteu/MLWHsHez/3y6cubLI4/1yE= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= @@ -120,8 +120,9 @@ github.com/bxcodec/faker/v3 v3.8.1 h1:qO/Xq19V6uHt2xujwpaetgKhraGCapqY2CRWGD/Sqc github.com/bxcodec/faker/v3 v3.8.1/go.mod h1:DdSDccxF5msjFo5aO4vrobRQ8nIApg8kq3QWPEQD6+o= github.com/caarlos0/env/v6 v6.10.1 h1:t1mPSxNpei6M5yAeu1qtRdPAK29Nbcf/n3G7x+b3/II= github.com/caarlos0/env/v6 v6.10.1/go.mod h1:hvp/ryKXKipEkcuYjs9mI4bBCg+UI0Yhgm5Zu0ddvwc= -github.com/cenkalti/backoff/v4 v4.1.3 h1:cFAlzYUlVYDysBEH2T5hyJZMh3+5+WCBvSnK6Q8UtC4= github.com/cenkalti/backoff/v4 v4.1.3/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= +github.com/cenkalti/backoff/v4 v4.2.0 h1:HN5dHm3WBOgndBH6E8V0q2jIYIR3s9yglV8k/+MN3u4= +github.com/cenkalti/backoff/v4 v4.2.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= @@ -147,6 +148,8 @@ github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= github.com/connorgorman/protobuf v1.2.2-0.20210115205927-b892c1b298f7 h1:YsgEuC8PhmdxkjGb3/l4inKcwVKuPXp1YELVPZkIByA= github.com/connorgorman/protobuf v1.2.2-0.20210115205927-b892c1b298f7/go.mod h1:4n/qquk+A505mqkK9+o7Xth9+UxUWFc4/5faDXkYyyU= +github.com/containers/image v3.0.2+incompatible h1:B1lqAE8MUPCrsBLE86J0gnXleeRq8zJnQryhiiGQNyE= +github.com/containers/image v3.0.2+incompatible/go.mod h1:8Vtij257IWSanUQKe1tAeNOm2sRVkSqQTVQ1IlwI3+M= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-oidc/v3 v3.6.0 h1:AKVxfYw1Gmkn/w96z0DbT/B/xFnzTd3MkZvWLjF4n/o= @@ -252,7 +255,7 @@ github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+ github.com/go-resty/resty/v2 v2.7.0 h1:me+K9p3uhSmXtrBZ4k9jcEAfJmuC8IivWHwaLZwPrFY= github.com/go-resty/resty/v2 v2.7.0/go.mod h1:9PWDzw47qPphMRFfhsyk0NnSgvluHcljSMVIq3w7q0I= github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= -github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc= +github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= @@ -314,8 +317,8 @@ github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/certificate-transparency-go v1.1.5 h1:EVfYyOiMSdwwXd6FJxnh0jYgYj/Dh5n9sXtgIr5+Vj0= -github.com/google/certificate-transparency-go v1.1.5/go.mod h1:CnNCSPt9ptZQ8jDSrqyTmh2dT2MQLKymfGYwXqjQ7YY= +github.com/google/certificate-transparency-go v1.1.6 h1:SW5K3sr7ptST/pIvNkSVWMiJqemRmkjJPPT0jzXdOOY= +github.com/google/certificate-transparency-go v1.1.6/go.mod h1:0OJjOsOk+wj6aYQgP7FU0ioQ0AJUmnWPFMqTjQeazPQ= github.com/google/gnostic v0.6.9 h1:ZK/5VhkoX835RikCHpSUJV9a+S3e1zLh59YnyWeBW+0= github.com/google/gnostic v0.6.9/go.mod h1:Nm8234We1lq6iB9OmlgNv3nH91XLLVZHCDayfA3xq+E= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= @@ -596,11 +599,13 @@ github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7J github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= -github.com/onsi/gomega v1.27.8 h1:gegWiwZjBsf2DgiSbf5hpokZ98JVDMcWkUiigk6/KXc= -github.com/onsi/gomega v1.27.8/go.mod h1:2J8vzI/s+2shY9XHRApDkdgPo1TKT7P2u6fXeJKFnNQ= +github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI= +github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= -github.com/openshift-online/ocm-sdk-go v0.1.344 h1:Dv2IhoQzR5ONwCoK2j8TYa3e3AXLvrdUgZa90Gpvovk= -github.com/openshift-online/ocm-sdk-go v0.1.344/go.mod h1:KYOw8kAKAHyPrJcQoVR82CneQ4ofC02Na4cXXaTq4Nw= +github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/openshift-online/ocm-sdk-go v0.1.358 h1:uFVKevnqWaBf5EiDHhpajVAgtMzk3piaQL1R0VvAw4w= +github.com/openshift-online/ocm-sdk-go v0.1.358/go.mod h1:KYOw8kAKAHyPrJcQoVR82CneQ4ofC02Na4cXXaTq4Nw= github.com/openshift/api v0.0.0-20200623075207-eb651a5bb0ad/go.mod h1:l6TGeqJ92DrZBuWMNKcot1iZUHfbYSJyBWHGgg6Dn6s= github.com/openshift/api v3.9.1-0.20191201231411-9f834e337466+incompatible h1:QuymwFhW85sgklix1dTn58zOYqBXWHVlZOFQVIVsMi0= github.com/openshift/api v3.9.1-0.20191201231411-9f834e337466+incompatible/go.mod h1:dh9o4Fs58gpFXGSYfnVxGR9PnV53I8TW84pQaJDdGiY= @@ -711,8 +716,8 @@ github.com/stackrox/bleve v0.0.0-20220907150529-4ecbd2543f9e h1:+gtD6n44cUn+WHL6 github.com/stackrox/bleve v0.0.0-20220907150529-4ecbd2543f9e/go.mod h1:iEpZccUqBH5f0BOF0uH78s8+dUaG/OWu4xuYMXBOdGs= github.com/stackrox/scanner v0.0.0-20230411230651-f2265de65ce4 h1:GfGtz9MCBj9L36d7KGaV6HCEoQY+PAy2fXWvozK0GLs= github.com/stackrox/scanner v0.0.0-20230411230651-f2265de65ce4/go.mod h1:4SRyOkdm9xp3Bca85Hp3636r7FvnA610Laxn3nbQBzc= -github.com/stackrox/stackrox v0.0.0-20230516045525-a98c9075fbf9 h1:FIyoW/el4TbZpemw9TefNl58dOBlcYq1hFnPjhypK7c= -github.com/stackrox/stackrox v0.0.0-20230516045525-a98c9075fbf9/go.mod h1:Ka7dL/IHtJSTDURMBdGW1cRmlDE9ERRSpDUZBItdWn4= +github.com/stackrox/stackrox v0.0.0-20230601164127-c2706e2b72bb h1:nT3rZEKrjeb/99ctdPDYAJlZ7nbwGyq8DAhVNQKz1hE= +github.com/stackrox/stackrox v0.0.0-20230601164127-c2706e2b72bb/go.mod h1:E3ukZlVTWDpPy7PynORKW9Y8j1zJe6xSreUqp4vcmiA= github.com/stackrox/zap v1.15.1-0.20200720133746-810fd602fd0f h1:Ofa3PAa609eSHcHP2kCJDUWUnuEWfiqXhsuppk/QtOE= github.com/stackrox/zap v1.15.1-0.20200720133746-810fd602fd0f/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ= github.com/steveyen/gtreap v0.1.0 h1:CjhzTa274PyJLJuMZwIzCO1PfC00oRa8d1Kc78bFXJM= @@ -814,8 +819,8 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= -golang.org/x/crypto v0.10.0 h1:LKqV2xt9+kDzSTfOhx4FrkEBcMrAgHSYgzywV9zcGmM= -golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I= +golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA= +golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -826,8 +831,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/exp v0.0.0-20230124195608-d38c7dcee874 h1:kWC3b7j6Fu09SnEBr7P4PuQyM0R6sqyH9R+EjIvT1nQ= -golang.org/x/exp v0.0.0-20230124195608-d38c7dcee874/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= +golang.org/x/exp v0.0.0-20230321023759-10a507213a29 h1:ooxPy7fPvB4kwsA2h+iBNHkAbp/4JxTSwCmvdjEYmug= +golang.org/x/exp v0.0.0-20230321023759-10a507213a29/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -921,8 +926,8 @@ golang.org/x/net v0.0.0-20220926192436-02166a98028e/go.mod h1:YDH+HFinaLZZlnHAfS golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.11.0 h1:Gi2tvZIJyBtO9SDr1q9h5hEQCp/4L2RQ+ar0qjx2oNU= -golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ= +golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50= +golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1052,16 +1057,16 @@ golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s= -golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= +golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.9.0 h1:GRRCnKYhdQrD8kfRAdQ6Zcw1P0OcELxGLKJvtjVMZ28= -golang.org/x/term v0.9.0/go.mod h1:M6DEAAIenWoTxdKrOltXcmDY3rSplQUkrvaDU5FcQyo= +golang.org/x/term v0.10.0 h1:3R7pNqamzBraeqj/Tj8qt1aQ2HpmlC+Cx/qL/7hn4/c= +golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o= golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1074,8 +1079,8 @@ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.10.0 h1:UpjohKhiEgNc0CSauXmwYftY1+LlaC75SJwh0SgCX58= -golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4= +golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -1284,8 +1289,12 @@ google.golang.org/genproto v0.0.0-20220518221133-4f43b3371335/go.mod h1:RAyBrSAP google.golang.org/genproto v0.0.0-20220523171625-347a074981d8/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= google.golang.org/genproto v0.0.0-20220608133413-ed9918b62aac/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= google.golang.org/genproto v0.0.0-20220616135557-88e70c0c3a90/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= -google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 h1:KpwkzHKEF7B9Zxg18WzOa7djJ+Ha5DzthMyZYQfEn2A= -google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU= +google.golang.org/genproto v0.0.0-20230530153820-e85fd2cbaebc h1:8DyZCyvI8mE1IdLy/60bS+52xfymkE72wv1asokgtao= +google.golang.org/genproto v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:xZnkP7mREFX5MORlOPEzLMr+90PPZQ2QWzrVTWfAq64= +google.golang.org/genproto/googleapis/api v0.0.0-20230530153820-e85fd2cbaebc h1:kVKPf/IiYSBWEWtkIn6wZXwWGCnLKcC8oWfZvXjsGnM= +google.golang.org/genproto/googleapis/api v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc h1:XSJ8Vk1SWuNr8S18z1NZSziL0CPIXLCCMDOEFtHBOFc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -1317,8 +1326,8 @@ google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11 google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= google.golang.org/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= -google.golang.org/grpc v1.54.0 h1:EhTqbhiYeixwWQtAEZAxmV9MGqcjEU2mFx52xCzNyag= -google.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g= +google.golang.org/grpc v1.55.0 h1:3Oj82/tFSCeUrRTg/5E/7d/W5A1tj6Ky1ABAuZuv5ag= +google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGONTY8= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/grpc/examples v0.0.0-20210902184326-c93e472777b9 h1:nuV5/Eu1pLmXFqSuM5yYgg1z+m3f7+HC1HO1xsmCz9I= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= @@ -1388,8 +1397,8 @@ honnef.co/go/tools v0.0.1-2020.1.4 h1:UoveltGrhghAA7ePc+e+QYDHXrBps2PqFZiHkGR/xK honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= k8s.io/api v0.18.3/go.mod h1:UOaMwERbqJMfeeeHc8XJKawj4P9TgDRnViIqqBeH2QA= k8s.io/api v0.18.4/go.mod h1:lOIQAKYgai1+vz9J7YcDZwC26Z0zQewYOGWdyIPUUQ4= -k8s.io/api v0.26.4 h1:qSG2PmtcD23BkYiWfoYAcak870eF/hE7NNYBYavTT94= -k8s.io/api v0.26.4/go.mod h1:WwKEXU3R1rgCZ77AYa7DFksd9/BAIKyOmRlbVxgvjCk= +k8s.io/api v0.26.5 h1:Npao/+sMSng6nkEcNydgH3BNo4s5YoBg7iw35HM7Hcw= +k8s.io/api v0.26.5/go.mod h1:O7ICW7lj6+ZQQQ3cxekgCoW+fnGo5kWT0nTHkLZ5grc= k8s.io/apiextensions-apiserver v0.26.1 h1:cB8h1SRk6e/+i3NOrQgSFij1B2S0Y0wDoNl66bn8RMI= k8s.io/apiextensions-apiserver v0.26.1/go.mod h1:AptjOSXDGuE0JICx/Em15PaoO7buLwTs0dGleIHixSM= k8s.io/apimachinery v0.18.3/go.mod h1:OaXp26zu/5J7p0f92ASynJa1pZo06YlV9fG7BoWbCko= @@ -1397,18 +1406,18 @@ k8s.io/apimachinery v0.18.4/go.mod h1:OaXp26zu/5J7p0f92ASynJa1pZo06YlV9fG7BoWbCk k8s.io/apimachinery v0.27.3 h1:Ubye8oBufD04l9QnNtW05idcOe9Z3GQN8+7PqmuVcUM= k8s.io/apimachinery v0.27.3/go.mod h1:XNfZ6xklnMCOGGFNqXG7bUrQCoR04dh/E7FprV6pb+E= k8s.io/client-go v0.18.3/go.mod h1:4a/dpQEvzAhT1BbuWW09qvIaGw6Gbu1gZYiQZIi1DMw= -k8s.io/client-go v0.26.4 h1:/7P/IbGBuT73A+G97trf44NTPSNqvuBREpOfdLbHvD4= -k8s.io/client-go v0.26.4/go.mod h1:6qOItWm3EwxJdl/8p5t7FWtWUOwyMdA8N9ekbW4idpI= +k8s.io/client-go v0.26.5 h1:e8Z44pafL/c6ayF/6qYEypbJoDSakaFxhJ9lqULEJEo= +k8s.io/client-go v0.26.5/go.mod h1:/CYyNt+ZLMvWqMF8h1SvkUXz2ujFWQLwdDrdiQlZ5X0= k8s.io/code-generator v0.18.3/go.mod h1:TgNEVx9hCyPGpdtCWA34olQYLkh3ok9ar7XfSsr8b6c= -k8s.io/component-base v0.26.4 h1:Bg2xzyXNKL3eAuiTEu3XE198d6z22ENgFgGQv2GGOUk= -k8s.io/component-base v0.26.4/go.mod h1:lTuWL1Xz/a4e80gmIC3YZG2JCO4xNwtKWHJWeJmsq20= +k8s.io/component-base v0.26.5 h1:nHAzDvXQ4whYpOqrQGWrDIYI/GIeXkuxzqC/iVICfZo= +k8s.io/component-base v0.26.5/go.mod h1:wvfNAS05EtKdPeUxFceo8WNh8bGPcFY8QfPhv5MYjA4= k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/gengo v0.0.0-20200114144118-36b2048a9120/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= -k8s.io/klog/v2 v2.90.1 h1:m4bYOKall2MmOiRaR1J+We67Do7vm9KiQVlT96lnHUw= -k8s.io/klog/v2 v2.90.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= +k8s.io/klog/v2 v2.100.1 h1:7WCHKK6K8fNhTqfBhISHQ97KrnJNFZMcQvKp7gP/tmg= +k8s.io/klog/v2 v2.100.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= k8s.io/kube-openapi v0.0.0-20200410145947-61e04a5be9a6/go.mod h1:GRQhZsXIAJ1xR0C9bd8UpWHZ5plfAS9fzPjJuQ6JL3E= k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f h1:2kWPakN3i/k81b0gvD5C5FJ2kxm1WrQFanWchyKuqGg= k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f/go.mod h1:byini6yhqGC14c3ebc/QwanvYwhuMWF6yz2F8uwW8eg= diff --git a/internal/dinosaur/pkg/handlers/admin_dinosaur.go b/internal/dinosaur/pkg/handlers/admin_dinosaur.go index c329b7277a..d10b1efb2f 100644 --- a/internal/dinosaur/pkg/handlers/admin_dinosaur.go +++ b/internal/dinosaur/pkg/handlers/admin_dinosaur.go @@ -24,6 +24,26 @@ import ( "k8s.io/apimachinery/pkg/util/strategicpatch" ) +// AdminCentralHandler is the interface for the admin central handler +type AdminCentralHandler interface { + // Create a central + Create(w http.ResponseWriter, r *http.Request) + // Get a central + Get(w http.ResponseWriter, r *http.Request) + // List all centrals + List(w http.ResponseWriter, r *http.Request) + // Update a central + Update(w http.ResponseWriter, r *http.Request) + // Delete a central + Delete(w http.ResponseWriter, r *http.Request) + // DbDelete deletes a central from the database + DbDelete(w http.ResponseWriter, r *http.Request) + // SetCentralDefaultVersion sets the default version for a central + SetCentralDefaultVersion(w http.ResponseWriter, r *http.Request) + // GetCentralDefaultVersion gets the default version for a central + GetCentralDefaultVersion(w http.ResponseWriter, r *http.Request) +} + type adminCentralHandler struct { service services.DinosaurService accountService account.AccountService @@ -32,13 +52,15 @@ type adminCentralHandler struct { centralDefaultVersionService services.CentralDefaultVersionService } +var _ AdminCentralHandler = (*adminCentralHandler)(nil) + // NewAdminCentralHandler ... func NewAdminCentralHandler( service services.DinosaurService, accountService account.AccountService, providerConfig *config.ProviderConfig, telemetry *services.Telemetry, - centralDefaultVersionService services.CentralDefaultVersionService) *adminCentralHandler { + centralDefaultVersionService services.CentralDefaultVersionService) AdminCentralHandler { return &adminCentralHandler{ service: service, accountService: accountService, @@ -392,3 +414,39 @@ func (h adminCentralHandler) GetCentralDefaultVersion(w http.ResponseWriter, r * handlers.Handle(w, r, cfg, http.StatusOK) } + +type gitOpsAdminHandler struct{} + +var _ AdminCentralHandler = (*gitOpsAdminHandler)(nil) + +func (g gitOpsAdminHandler) Create(w http.ResponseWriter, r *http.Request) { + http.Error(w, "not implemented", http.StatusNotImplemented) +} + +func (g gitOpsAdminHandler) Get(w http.ResponseWriter, r *http.Request) { + http.Error(w, "not implemented", http.StatusNotImplemented) +} + +func (g gitOpsAdminHandler) List(w http.ResponseWriter, r *http.Request) { + http.Error(w, "not implemented", http.StatusNotImplemented) +} + +func (g gitOpsAdminHandler) Update(w http.ResponseWriter, r *http.Request) { + http.Error(w, "not implemented", http.StatusNotImplemented) +} + +func (g gitOpsAdminHandler) Delete(w http.ResponseWriter, r *http.Request) { + http.Error(w, "not implemented", http.StatusNotImplemented) +} + +func (g gitOpsAdminHandler) DbDelete(w http.ResponseWriter, r *http.Request) { + http.Error(w, "not implemented", http.StatusNotImplemented) +} + +func (g gitOpsAdminHandler) SetCentralDefaultVersion(w http.ResponseWriter, r *http.Request) { + http.Error(w, "not implemented", http.StatusNotImplemented) +} + +func (g gitOpsAdminHandler) GetCentralDefaultVersion(w http.ResponseWriter, r *http.Request) { + http.Error(w, "not implemented", http.StatusNotImplemented) +} diff --git a/scripts/install_operator_canary.sh b/scripts/install_operator_canary.sh index 0579db0b7c..a4da994207 100755 --- a/scripts/install_operator_canary.sh +++ b/scripts/install_operator_canary.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash set -eo pipefail -export RHACS_TARGETED_OPERATOR_UPGRADES=true +export RHACS_TARGETED_OPERATOR_UPGRADES="true" export INSTALL_OLM="false" export INSTALL_OPERATOR="false" make deploy/bootstrap diff --git a/templates/service-template.yml b/templates/service-template.yml index 0cbbcf3ad8..05e3bfff8a 100644 --- a/templates/service-template.yml +++ b/templates/service-template.yml @@ -920,6 +920,23 @@ objects: # cluster_name: limiter # transport_api_version: V3 + # TODO(ROX-18694): upgrade envoy version and change implementation for headers response to LuaPerRoute + # This allows Lua scripts to be run during response flow + - name: lua_security_headers + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua + inline_code: | + function envoy_on_response(response_handle) + csp = "default-src 'none'; script-src 'self'; img-src 'self'; style-src 'self'; connect-src 'self'"; + response_handle:headers():add("Content-Security-Policy", csp); + response_handle:headers():add("X-Frame-Options", "deny"); + response_handle:headers():add("X-XSS-Protection", "1; mode=block"); + response_handle:headers():add("X-Content-Type-Options", "nosniff"); + response_handle:headers():add("Referrer-Policy", "no-referrer"); + response_handle:headers():add("X-Download-Options", "noopen"); + response_handle:headers():add("X-DNS-Prefetch-Control", "off"); + response_handle:headers():add("Strict-Transport-Security", "max-age=31536000; includeSubDomains"); + end # This is mandatory in order to have the HTTP routes above. - name: envoy.filters.http.router