diff --git a/pipeline/backend/kubernetes/backend_options.go b/pipeline/backend/kubernetes/backend_options.go index 3b1109a2c..970158fe7 100644 --- a/pipeline/backend/kubernetes/backend_options.go +++ b/pipeline/backend/kubernetes/backend_options.go @@ -14,6 +14,7 @@ type BackendOptions struct { NodeSelector map[string]string `mapstructure:"nodeSelector"` Tolerations []Toleration `mapstructure:"tolerations"` SecurityContext *SecurityContext `mapstructure:"securityContext"` + SecretNames []string `mapstructure:"secretNames"` } // Resources defines two maps for kubernetes resource definitions diff --git a/pipeline/backend/kubernetes/backend_options_test.go b/pipeline/backend/kubernetes/backend_options_test.go index f9c21a5fe..db3665945 100644 --- a/pipeline/backend/kubernetes/backend_options_test.go +++ b/pipeline/backend/kubernetes/backend_options_test.go @@ -42,6 +42,9 @@ func Test_parseBackendOptions(t *testing.T) { "localhostProfile": "k8s-apparmor-example-deny-write", }, }, + "secretNames": []string{ + "test-secret", + }, }, }, }) @@ -69,5 +72,8 @@ func Test_parseBackendOptions(t *testing.T) { LocalhostProfile: "k8s-apparmor-example-deny-write", }, }, + SecretNames: []string{ + "test-secret", + }, }, got) } diff --git a/pipeline/backend/kubernetes/flags.go b/pipeline/backend/kubernetes/flags.go index 6d5fd9c92..57e1cbf06 100644 --- a/pipeline/backend/kubernetes/flags.go +++ b/pipeline/backend/kubernetes/flags.go @@ -66,4 +66,10 @@ var Flags = []cli.Flag{ Usage: "backend k8s pull secret names for private registries", Value: cli.NewStringSlice("regcred"), }, + &cli.BoolFlag{ + EnvVars: []string{"WOODPECKER_BACKEND_K8S_NATIVE_SECRETS_ALLOW_FROM_STEP"}, + Name: "backend-k8s-native-secrets-allow-from-step", + Usage: "whether to allow existing Kubernetes secrets to be referenced from step", + Value: false, + }, } diff --git a/pipeline/backend/kubernetes/kubernetes.go b/pipeline/backend/kubernetes/kubernetes.go index 3019bba7a..65ea90d6a 100644 --- a/pipeline/backend/kubernetes/kubernetes.go +++ b/pipeline/backend/kubernetes/kubernetes.go @@ -55,14 +55,15 @@ type kube struct { } type config struct { - Namespace string - StorageClass string - VolumeSize string - StorageRwx bool - PodLabels map[string]string - PodAnnotations map[string]string - ImagePullSecretNames []string - SecurityContext SecurityContextConfig + Namespace string + StorageClass string + VolumeSize string + StorageRwx bool + PodLabels map[string]string + PodAnnotations map[string]string + ImagePullSecretNames []string + SecurityContext SecurityContextConfig + NativeSecretsAllowFromStep bool } type SecurityContextConfig struct { RunAsNonRoot bool @@ -92,6 +93,7 @@ func configFromCliContext(ctx context.Context) (*config, error) { SecurityContext: SecurityContextConfig{ RunAsNonRoot: c.Bool("backend-k8s-secctx-nonroot"), }, + NativeSecretsAllowFromStep: c.Bool("backend-k8s-native-secrets-allow-from-step"), } // TODO: remove in next major if len(config.ImagePullSecretNames) == 1 && config.ImagePullSecretNames[0] == "regcred" { diff --git a/pipeline/backend/kubernetes/pod.go b/pipeline/backend/kubernetes/pod.go index 331efae92..e21bf14f6 100644 --- a/pipeline/backend/kubernetes/pod.go +++ b/pipeline/backend/kubernetes/pod.go @@ -48,7 +48,7 @@ func mkPod(step *types.Step, config *config, podName, goos string, options Backe return nil, err } - container, err := podContainer(step, podName, goos, options) + container, err := podContainer(step, config, podName, goos, options) if err != nil { return nil, err } @@ -133,7 +133,7 @@ func podSpec(step *types.Step, config *config, options BackendOptions) (v1.PodSp return spec, nil } -func podContainer(step *types.Step, podName, goos string, options BackendOptions) (v1.Container, error) { +func podContainer(step *types.Step, config *config, podName, goos string, options BackendOptions) (v1.Container, error) { var err error container := v1.Container{ Name: podName, @@ -159,6 +159,14 @@ func podContainer(step *types.Step, podName, goos string, options BackendOptions container.Env = mapToEnvVars(step.Environment) + if len(options.SecretNames) > 0 { + if config.NativeSecretsAllowFromStep { + container.EnvFrom = containerSecrets(options.SecretNames) + } else { + log.Debug().Msg("Secret names were defined in backend options, but its using disallowed by instance configuration ") + } + } + container.Resources, err = resourceRequirements(options.Resources) if err != nil { return container, err @@ -236,6 +244,25 @@ func containerPort(port types.Port) v1.ContainerPort { } } +func containerSecrets(secretNames []string) []v1.EnvFromSource { + if secretNames == nil || len(secretNames) == 0 { + return nil + } + secretRefs := make([]v1.EnvFromSource, len(secretNames)) + for i, secretName := range secretNames { + secretRefs[i] = containerSecret(secretName) + } + return secretRefs +} + +func containerSecret(secretName string) v1.EnvFromSource { + return v1.EnvFromSource{ + SecretRef: &v1.SecretEnvSource{ + LocalObjectReference: secretReference(secretName), + }, + } +} + // Here is the service IPs (placed in /etc/hosts in the Pod) func hostAliases(extraHosts []types.HostAlias) []v1.HostAlias { var hostAliases []v1.HostAlias @@ -258,14 +285,14 @@ func imagePullSecretsReferences(imagePullSecretNames []string) []v1.LocalObjectR secretReferences := make([]v1.LocalObjectReference, len(imagePullSecretNames)) for i, imagePullSecretName := range imagePullSecretNames { - secretReferences[i] = imagePullSecretsReference(imagePullSecretName) + secretReferences[i] = secretReference(imagePullSecretName) } return secretReferences } -func imagePullSecretsReference(imagePullSecretName string) v1.LocalObjectReference { +func secretReference(secretName string) v1.LocalObjectReference { return v1.LocalObjectReference{ - Name: imagePullSecretName, + Name: secretName, } } diff --git a/pipeline/backend/kubernetes/pod_test.go b/pipeline/backend/kubernetes/pod_test.go index bf4f35a41..516e70064 100644 --- a/pipeline/backend/kubernetes/pod_test.go +++ b/pipeline/backend/kubernetes/pod_test.go @@ -200,6 +200,19 @@ func TestFullPod(t *testing.T) { "protocol": "UDP" } ], + "envFrom": [ + "<>", + { + "secretRef": { + "name": "ghcr-push-secret" + } + }, + { + "secretRef": { + "name": "aws-ecr" + } + } + ], "env": [ "<>", { @@ -328,11 +341,12 @@ func TestFullPod(t *testing.T) { ExtraHosts: hostAliases, Ports: ports, }, &config{ - Namespace: "woodpecker", - ImagePullSecretNames: []string{"regcred", "another-pull-secret"}, - PodLabels: map[string]string{"app": "test"}, - PodAnnotations: map[string]string{"apps.kubernetes.io/pod-index": "0"}, - SecurityContext: SecurityContextConfig{RunAsNonRoot: false}, + Namespace: "woodpecker", + ImagePullSecretNames: []string{"regcred", "another-pull-secret"}, + PodLabels: map[string]string{"app": "test"}, + PodAnnotations: map[string]string{"apps.kubernetes.io/pod-index": "0"}, + SecurityContext: SecurityContextConfig{RunAsNonRoot: false}, + NativeSecretsAllowFromStep: true, }, "wp-01he8bebctabr3kgk0qj36d2me-0", "linux/amd64", BackendOptions{ NodeSelector: map[string]string{"storage": "ssd"}, RuntimeClassName: &runtimeClass, @@ -343,6 +357,7 @@ func TestFullPod(t *testing.T) { Limits: map[string]string{"memory": "256Mi", "cpu": "2"}, }, SecurityContext: &secCtx, + SecretNames: []string{"ghcr-push-secret", "aws-ecr"}, }) assert.NoError(t, err) diff --git a/pipeline/frontend/yaml/linter/schema/schema.json b/pipeline/frontend/yaml/linter/schema/schema.json index 13d7af690..31c01404c 100644 --- a/pipeline/frontend/yaml/linter/schema/schema.json +++ b/pipeline/frontend/yaml/linter/schema/schema.json @@ -700,6 +700,12 @@ "runtimeClassName": { "description": "Read more: https://woodpecker-ci.org/docs/administration/backends/kubernetes#runtimeclassname", "type": "string" + }, + "secretNames": { + "type": "array", + "items": { + "type": "string" + } } } },