Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

K8s secrets reference from step #3655

Merged
merged 27 commits into from
Jun 23, 2024
Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
f33488c
K8s secrets reference from step
zc-devs Apr 27, 2024
a688ae4
PR notes
zc-devs Apr 28, 2024
ad477b2
Merge remote-tracking branch 'upstream/main' into 3582-k8s-secrets-p1
zc-devs Apr 28, 2024
2ffe59f
Merge branch 'main' into 3582-k8s-secrets-p1
qwerty287 May 1, 2024
d4af4a5
Merge remote-tracking branch 'upstream/main' into 3582-k8s-secrets-p1
zc-devs Jun 3, 2024
d4d2c72
PR notes
zc-devs Jun 3, 2024
b875968
new schema
zc-devs Jun 3, 2024
073ec7f
Merge branch 'main' into 3582-k8s-secrets-p1
zc-devs Jun 6, 2024
a038d6a
Merge branch 'woodpecker-ci:main' into 3582-k8s-secrets-p1
zc-devs Jun 13, 2024
d8aad5b
new schema & be options
zc-devs Jun 14, 2024
f6b9bd2
env secrets
zc-devs Jun 14, 2024
0da65d9
file secrets
zc-devs Jun 14, 2024
20fa310
native secrets processor
zc-devs Jun 14, 2024
fac22f9
Merge branch 'main' into 3582-k8s-secrets-p1
zc-devs Jun 14, 2024
255feda
clean up
zc-devs Jun 14, 2024
2c22567
clean up 2
zc-devs Jun 15, 2024
52b6252
Merge branch 'main' into 3582-k8s-secrets-p1
zc-devs Jun 15, 2024
06eb896
pr notes
zc-devs Jun 15, 2024
6df704a
removed empty lines
zc-devs Jun 20, 2024
8e6a778
Merge branch 'main' into 3582-k8s-secrets-p1
zc-devs Jun 20, 2024
fdae76e
Merge branch 'main' into 3582-k8s-secrets-p1
qwerty287 Jun 20, 2024
713f9cb
switch-case + gofumpt
zc-devs Jun 21, 2024
0866d18
Merge remote-tracking branch 'upstream/main' into 3582-k8s-secrets-p1
zc-devs Jun 21, 2024
51471bf
gofumpt
zc-devs Jun 21, 2024
9404453
Merge branch 'main' into 3582-k8s-secrets-p1
zc-devs Jun 21, 2024
20e86b3
Apply suggestions from code review
qwerty287 Jun 23, 2024
c4da333
Merge branch 'main' into 3582-k8s-secrets-p1
qwerty287 Jun 23, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions pipeline/backend/kubernetes/backend_options.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ type BackendOptions struct {
NodeSelector map[string]string `mapstructure:"nodeSelector"`
Tolerations []Toleration `mapstructure:"tolerations"`
SecurityContext *SecurityContext `mapstructure:"securityContext"`
Secrets []SecretRef `mapstructure:"secrets"`
}

// Resources defines two maps for kubernetes resource definitions.
Expand Down Expand Up @@ -65,6 +66,19 @@ type SecProfile struct {

type SecProfileType string

// SecretRef defines Kubernetes secret reference.
type SecretRef struct {
Name string `mapstructure:"name"`
Key string `mapstructure:"key"`
Target SecretTarget `mapstructure:"target"`
}

// SecretTarget defines secret mount target.
type SecretTarget struct {
Env string `mapstructure:"env"`
File string `mapstructure:"file"`
}

const (
SecProfileTypeRuntimeDefault SecProfileType = "RuntimeDefault"
SecProfileTypeLocalhost SecProfileType = "Localhost"
Expand Down
28 changes: 28 additions & 0 deletions pipeline/backend/kubernetes/backend_options_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,22 @@ func Test_parseBackendOptions(t *testing.T) {
"localhostProfile": "k8s-apparmor-example-deny-write",
},
},
"secrets": []map[string]any{
{
"name": "aws",
"key": "access-key",
"target": map[string]any{
"env": "AWS_SECRET_ACCESS_KEY",
},
},
{
"name": "reg-cred",
"key": ".dockerconfigjson",
"target": map[string]any{
"file": "~/.docker/config.json",
},
},
},
},
},
})
Expand Down Expand Up @@ -73,5 +89,17 @@ func Test_parseBackendOptions(t *testing.T) {
LocalhostProfile: "k8s-apparmor-example-deny-write",
},
},
Secrets: []SecretRef{
{
Name: "aws",
Key: "access-key",
Target: SecretTarget{Env: "AWS_SECRET_ACCESS_KEY"},
},
{
Name: "reg-cred",
Key: ".dockerconfigjson",
Target: SecretTarget{File: "~/.docker/config.json"},
},
},
}, got)
}
10 changes: 7 additions & 3 deletions pipeline/backend/kubernetes/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,7 @@

package kubernetes

import (
"github.com/urfave/cli/v2"
)
import "github.com/urfave/cli/v2"

var Flags = []cli.Flag{
&cli.StringFlag{
Expand Down Expand Up @@ -84,4 +82,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_ALLOW_NATIVE_SECRETS"},
Name: "backend-k8s-allow-native-secrets",
Usage: "whether to allow existing Kubernetes secrets to be referenced from steps",
Value: false,
},
}
2 changes: 2 additions & 0 deletions pipeline/backend/kubernetes/kubernetes.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ type config struct {
PodNodeSelector map[string]string
ImagePullSecretNames []string
SecurityContext SecurityContextConfig
NativeSecretsAllowFromStep bool
}
type SecurityContextConfig struct {
RunAsNonRoot bool
Expand Down Expand Up @@ -97,6 +98,7 @@ func configFromCliContext(ctx context.Context) (*config, error) {
SecurityContext: SecurityContextConfig{
RunAsNonRoot: c.Bool("backend-k8s-secctx-nonroot"), // cspell:words secctx nonroot
},
NativeSecretsAllowFromStep: c.Bool("backend-k8s-allow-native-secrets"),
}
// TODO: remove in next major
if len(config.ImagePullSecretNames) == 1 && config.ImagePullSecretNames[0] == "regcred" {
Expand Down
48 changes: 23 additions & 25 deletions pipeline/backend/kubernetes/pod.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,17 +38,23 @@ const (
func mkPod(step *types.Step, config *config, podName, goos string, options BackendOptions) (*v1.Pod, error) {
var err error

nsp := newNativeSecretsProcessor(config, options.Secrets)
err = nsp.process()
if err != nil {
return nil, err
}

meta, err := podMeta(step, config, options, podName)
if err != nil {
return nil, err
}

spec, err := podSpec(step, config, options)
spec, err := podSpec(step, config, options, nsp)
if err != nil {
return nil, err
}

container, err := podContainer(step, podName, goos, options)
container, err := podContainer(step, podName, goos, options, nsp)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -146,27 +152,31 @@ func podAnnotations(config *config, options BackendOptions, podName string) map[
return annotations
}

func podSpec(step *types.Step, config *config, options BackendOptions) (v1.PodSpec, error) {
func podSpec(step *types.Step, config *config, options BackendOptions, nsp nativeSecretsProcessor) (v1.PodSpec, error) {
var err error
spec := v1.PodSpec{
RestartPolicy: v1.RestartPolicyNever,
RuntimeClassName: options.RuntimeClassName,
ServiceAccountName: options.ServiceAccountName,
ImagePullSecrets: imagePullSecretsReferences(config.ImagePullSecretNames),
HostAliases: hostAliases(step.ExtraHosts),
NodeSelector: nodeSelector(options.NodeSelector, config.PodNodeSelector, step.Environment["CI_SYSTEM_PLATFORM"]),
Tolerations: tolerations(options.Tolerations),
SecurityContext: podSecurityContext(options.SecurityContext, config.SecurityContext, step.Privileged),
}
spec.Volumes, err = volumes(step.Volumes)
spec.Volumes, err = pvcVolumes(step.Volumes)
if err != nil {
return spec, err
}

log.Trace().Msgf("using the image pull secrets: %v", config.ImagePullSecretNames)
spec.ImagePullSecrets = secretsReferences(config.ImagePullSecretNames)

spec.Volumes = append(spec.Volumes, nsp.volumes...)

return spec, nil
}

func podContainer(step *types.Step, podName, goos string, options BackendOptions) (v1.Container, error) {
func podContainer(step *types.Step, podName, goos string, options BackendOptions, nsp nativeSecretsProcessor) (v1.Container, error) {
var err error
container := v1.Container{
Name: podName,
Expand Down Expand Up @@ -201,24 +211,28 @@ func podContainer(step *types.Step, podName, goos string, options BackendOptions
return container, err
}

container.EnvFrom = append(container.EnvFrom, nsp.envFromSources...)
container.Env = append(container.Env, nsp.envVars...)
container.VolumeMounts = append(container.VolumeMounts, nsp.mounts...)

return container, nil
}

func volumes(volumes []string) ([]v1.Volume, error) {
func pvcVolumes(volumes []string) ([]v1.Volume, error) {
var vols []v1.Volume

for _, v := range volumes {
volumeName, err := volumeName(v)
if err != nil {
return nil, err
}
vols = append(vols, volume(volumeName))
vols = append(vols, pvcVolume(volumeName))
}

return vols, nil
}

func volume(name string) v1.Volume {
func pvcVolume(name string) v1.Volume {
pvcSource := v1.PersistentVolumeClaimVolumeSource{
ClaimName: name,
ReadOnly: false,
Expand Down Expand Up @@ -285,22 +299,6 @@ func hostAlias(extraHost types.HostAlias) v1.HostAlias {
}
}

func imagePullSecretsReferences(imagePullSecretNames []string) []v1.LocalObjectReference {
log.Trace().Msgf("using the image pull secrets: %v", imagePullSecretNames)

secretReferences := make([]v1.LocalObjectReference, len(imagePullSecretNames))
for i, imagePullSecretName := range imagePullSecretNames {
secretReferences[i] = imagePullSecretsReference(imagePullSecretName)
}
return secretReferences
}

func imagePullSecretsReference(imagePullSecretName string) v1.LocalObjectReference {
return v1.LocalObjectReference{
Name: imagePullSecretName,
}
}

func resourceRequirements(resources Resources) (v1.ResourceRequirements, error) {
var err error
requirements := v1.ResourceRequirements{}
Expand Down
120 changes: 119 additions & 1 deletion pipeline/backend/kubernetes/pod_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -467,6 +467,124 @@ func TestScratchPod(t *testing.T) {
assert.NoError(t, err)

ja := jsonassert.New(t)
t.Log(string(podJSON))
ja.Assertf(string(podJSON), expected)
}

func TestSecrets(t *testing.T) {
expected := `
{
"metadata": {
"name": "wp-3kgk0qj36d2me01he8bebctabr-0",
"namespace": "woodpecker",
"creationTimestamp": null,
"labels": {
"step": "test-secrets"
}
},
"spec": {
"volumes": [
{
"name": "workspace",
"persistentVolumeClaim": {
"claimName": "workspace"
}
},
{
"name": "reg-cred",
"secret": {
"secretName": "reg-cred"
}
}
],
"containers": [
{
"name": "wp-3kgk0qj36d2me01he8bebctabr-0",
"image": "alpine",
"envFrom": [
{
"secretRef": {
"name": "ghcr-push-secret"
}
}
],
"env": [
{
"name": "CGO",
"value": "0"
},
{
"name": "AWS_ACCESS_KEY_ID",
"valueFrom": {
"secretKeyRef": {
"name": "aws-ecr",
"key": "AWS_ACCESS_KEY_ID"
}
}
},
{
"name": "AWS_SECRET_ACCESS_KEY",
"valueFrom": {
"secretKeyRef": {
"name": "aws-ecr",
"key": "access-key"
}
}
}
],
"resources": {},
"volumeMounts": [
{
"name": "workspace",
"mountPath": "/woodpecker/src"
},
{
"name": "reg-cred",
"mountPath": "~/.docker/config.json",
"subPath": ".dockerconfigjson",
"readOnly": true
}
]
}
],
"restartPolicy": "Never"
},
"status": {}
}`

pod, err := mkPod(&types.Step{
Name: "test-secrets",
Image: "alpine",
Environment: map[string]string{"CGO": "0"},
Volumes: []string{"workspace:/woodpecker/src"},
}, &config{
Namespace: "woodpecker",
NativeSecretsAllowFromStep: true,
}, "wp-3kgk0qj36d2me01he8bebctabr-0", "linux/amd64", BackendOptions{
Secrets: []SecretRef{
{
Name: "ghcr-push-secret",
},
{
Name: "aws-ecr",
Key: "AWS_ACCESS_KEY_ID",
},
{
Name: "aws-ecr",
Key: "access-key",
Target: SecretTarget{Env: "AWS_SECRET_ACCESS_KEY"},
},
{
Name: "reg-cred",
Key: ".dockerconfigjson",
Target: SecretTarget{File: "~/.docker/config.json"},
},
},
})
assert.NoError(t, err)

podJSON, err := json.Marshal(pod)
assert.NoError(t, err)

ja := jsonassert.New(t)
ja.Assertf(string(podJSON), expected)
}
Loading