diff --git a/api/v4/common_types.go b/api/v4/common_types.go index 968ecd8ed..9f78fadd6 100644 --- a/api/v4/common_types.go +++ b/api/v4/common_types.go @@ -238,6 +238,8 @@ type CommonSplunkSpec struct { // Sets imagePullSecrets if image is being pulled from a private registry. // See https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ ImagePullSecrets []corev1.LocalObjectReference `json:"imagePullSecrets,omitempty"` + + VaultIntegration VaultIntegration `json:"vaultIntegration,omitempty"` } // StorageClassSpec defines storage class configuration @@ -569,6 +571,20 @@ type PhaseInfo struct { FailCount uint32 `json:"failCount,omitempty"` } +// Vault represents the Vault configuration for enabling secret injection. +// +kubebuilder:object:generate=true +// +kubebuilder:validation:Optional +type VaultIntegration struct { + // Enable vault support + Enable bool `json:"enable,omitempty"` + + // Vault Role + Role string `json:"role"` + + // Vault secret path + SecretPath string `json:"secretPath"` +} + const ( // AppPkgDownloadPending indicates pending AppPkgDownloadPending AppPhaseStatusType = 101 diff --git a/api/v4/zz_generated.deepcopy.go b/api/v4/zz_generated.deepcopy.go index 4c10f8035..0f21e56b0 100644 --- a/api/v4/zz_generated.deepcopy.go +++ b/api/v4/zz_generated.deepcopy.go @@ -343,6 +343,7 @@ func (in *CommonSplunkSpec) DeepCopyInto(out *CommonSplunkSpec) { *out = make([]v1.LocalObjectReference, len(*in)) copy(*out, *in) } + out.VaultIntegration = in.VaultIntegration } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CommonSplunkSpec. @@ -1097,6 +1098,21 @@ func (in *StorageClassSpec) DeepCopy() *StorageClassSpec { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VaultIntegration) DeepCopyInto(out *VaultIntegration) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VaultIntegration. +func (in *VaultIntegration) DeepCopy() *VaultIntegration { + if in == nil { + return nil + } + out := new(VaultIntegration) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *VolumeAndTypeSpec) DeepCopyInto(out *VolumeAndTypeSpec) { *out = *in diff --git a/pkg/splunk/enterprise/configuration.go b/pkg/splunk/enterprise/configuration.go index 42dcd34bb..9eda3bd3a 100644 --- a/pkg/splunk/enterprise/configuration.go +++ b/pkg/splunk/enterprise/configuration.go @@ -768,14 +768,31 @@ func updateSplunkPodTemplateWithConfig(ctx context.Context, client splcommon.Con } } - // Explicitly set the default value here so we can compare for changes correctly with current statefulset. - secretVolDefaultMode := int32(corev1.SecretVolumeSourceDefaultMode) - addSplunkVolumeToTemplate(podTemplateSpec, "mnt-splunk-secrets", "/mnt/splunk-secrets", corev1.VolumeSource{ - Secret: &corev1.SecretVolumeSource{ - SecretName: secretToMount, - DefaultMode: &secretVolDefaultMode, - }, - }) + // prepare defaults variable + splunkDefaults := "/mnt/splunk-secrets/default.yml" + // Check for apps defaults and add it to only the standalone or deployer/cm/mc instances + if spec.DefaultsURLApps != "" && instanceType != SplunkIndexer && instanceType != SplunkSearchHead { + splunkDefaults = fmt.Sprintf("%s,%s", spec.DefaultsURLApps, splunkDefaults) + } + if spec.DefaultsURL != "" { + splunkDefaults = fmt.Sprintf("%s,%s", spec.DefaultsURL, splunkDefaults) + } + if spec.Defaults != "" { + splunkDefaults = fmt.Sprintf("%s,%s", "/mnt/splunk-defaults/default.yml", splunkDefaults) + } + + if spec.VaultIntegration.Enable { + InjectVaultSecret(ctx, client, podTemplateSpec, &spec.VaultIntegration) + } else { + // Explicitly set the default value here so we can compare for changes correctly with current statefulset. + secretVolDefaultMode := int32(corev1.SecretVolumeSourceDefaultMode) + addSplunkVolumeToTemplate(podTemplateSpec, "mnt-splunk-secrets", "/mnt/splunk-secrets", corev1.VolumeSource{ + Secret: &corev1.SecretVolumeSource{ + SecretName: secretToMount, + DefaultMode: &secretVolDefaultMode, + }, + }) + } // Explicitly set the default value here so we can compare for changes correctly with current statefulset. configMapVolDefaultMode := int32(corev1.ConfigMapVolumeSourceDefaultMode) @@ -845,19 +862,6 @@ func updateSplunkPodTemplateWithConfig(ctx context.Context, client splcommon.Con readinessProbe := getReadinessProbe(ctx, cr, instanceType, spec) startupProbe := getStartupProbe(ctx, cr, instanceType, spec) - // prepare defaults variable - splunkDefaults := "/mnt/splunk-secrets/default.yml" - // Check for apps defaults and add it to only the standalone or deployer/cm/mc instances - if spec.DefaultsURLApps != "" && instanceType != SplunkIndexer && instanceType != SplunkSearchHead { - splunkDefaults = fmt.Sprintf("%s,%s", spec.DefaultsURLApps, splunkDefaults) - } - if spec.DefaultsURL != "" { - splunkDefaults = fmt.Sprintf("%s,%s", spec.DefaultsURL, splunkDefaults) - } - if spec.Defaults != "" { - splunkDefaults = fmt.Sprintf("%s,%s", "/mnt/splunk-defaults/default.yml", splunkDefaults) - } - // prepare container env variables role := instanceType.ToRole() if instanceType == SplunkStandalone && (len(spec.ClusterMasterRef.Name) > 0 || len(spec.ClusterManagerRef.Name) > 0) { @@ -2013,3 +2017,73 @@ func validateStartupProbe(ctx context.Context, cr splcommon.MetaObject, startupP } return err } + +// InjectVaultSecret represents a function that adds Vault injection annotations to the StatefulSet +// Pods deployed by the Splunk Operator. +func InjectVaultSecret(ctx context.Context, client splcommon.ControllerClient, podTemplateSpec *corev1.PodTemplateSpec, vaultSpec *enterpriseApi.VaultIntegration) error { + if !vaultSpec.Enable { + return nil + } + + // Validate if role and secretPath are provided + if vaultSpec.Role == "" { + return fmt.Errorf("vault role is required when vault is enabled") + } + if vaultSpec.SecretPath == "" { + return fmt.Errorf("vault secretPath is required when vault is enabled") + } + + secretPath := vaultSpec.SecretPath + vaultRole := vaultSpec.Role + secretKeyToEnv := map[string]string{ + "hec_token": "HEC_TOKEN", + "idxc_secret": "IDXC_SECRET", + "pass4SymmKey": "PASS4_SYMMKEY", + "password": "SPLUNK_PASSWORD", + "shc_secret": "SHC_SECRET", + } + + // Adding annotations for vault injection + annotations := map[string]string{ + "vault.hashicorp.com/agent-inject": "true", + "vault.hashicorp.com/agent-inject-path": "/mnt/splunk-secrets", + "vault.hashicorp.com/role": vaultRole, + } + + // Adding annotations to indicate specific secrets to be injected as separate files + // Adding annotation for default configuration file + annotations["vault.hashicorp.com/agent-inject-file-defaults"] = "default.yml" + annotations["vault.hashicorp.com/secret-volume-path-defaults"] = "/mnt/splunk-secrets" + annotations["vault.hashicorp.com/agent-inject-template-defaults"] = ` +splunk: + hec_disabled: 0 + hec_enableSSL: 0 + hec_token: "{{- with secret "secret/data/splunk/hec_token" -}}{{ .Data.data.value }}{{- end }}" + password: "{{- with secret "secret/data/splunk/password" -}}{{ .Data.data.value }}{{- end }}" + pass4SymmKey: "{{- with secret "secret/data/splunk/pass4SymmKey" -}}{{ .Data.data.value }}{{- end }}" + idxc: + secret: "{{- with secret "secret/data/splunk/idxc_secret" -}}{{ .Data.data.value }}{{- end }}" + shc: + secret: "{{- with secret "secret/data/splunk/shc_secret" -}}{{ .Data.data.value }}{{- end }}" +` + for key := range secretKeyToEnv { + annotationKey := fmt.Sprintf("vault.hashicorp.com/agent-inject-secret-%s", key) + annotations[annotationKey] = fmt.Sprintf("%s/%s", secretPath, key) + annotationFile := fmt.Sprintf("vault.hashicorp.com/agent-inject-file-%s", key) + annotations[annotationFile] = key + annotationVolumeKey := fmt.Sprintf("vault.hashicorp.com/secret-volume-path-%s", key) + annotations[annotationVolumeKey] = fmt.Sprintf("/mnt/splunk-secrets/%s", key) + } + + // Apply these annotations to the StatefulSet PodTemplateSpec without overwriting existing ones + if podTemplateSpec.ObjectMeta.Annotations == nil { + podTemplateSpec.ObjectMeta.Annotations = make(map[string]string) + } + for key, value := range annotations { + if existingValue, exists := podTemplateSpec.ObjectMeta.Annotations[key]; !exists || existingValue == "" { + podTemplateSpec.ObjectMeta.Annotations[key] = value + } + } + + return nil +}