You've already forked woodpecker
mirror of
https://github.com/woodpecker-ci/woodpecker.git
synced 2025-11-29 21:48:14 +02:00
kube backend: prevent secrets from leaking to Kubernetes apiserver logs (#5196)
This commit is contained in:
@@ -233,6 +233,13 @@ func (e *kube) StartStep(ctx context.Context, step *types.Step, taskUUID string)
|
||||
}
|
||||
}
|
||||
|
||||
if needsStepSecret(step) {
|
||||
err = startStepSecret(ctx, e, step)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
log.Trace().Str("taskUUID", taskUUID).Msgf("starting step: %s", step.Name)
|
||||
_, err = startPod(ctx, e, step, options)
|
||||
return err
|
||||
@@ -398,6 +405,13 @@ func (e *kube) DestroyStep(ctx context.Context, step *types.Step, taskUUID strin
|
||||
}
|
||||
}
|
||||
|
||||
if needsStepSecret(step) {
|
||||
err := stopStepSecret(ctx, e, step, defaultDeleteOptions)
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
|
||||
err := stopPod(ctx, e, step, defaultDeleteOptions)
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
|
||||
@@ -235,7 +235,15 @@ func podContainer(step *types.Step, podName, goos string, options BackendOptions
|
||||
container.Command = step.Entrypoint
|
||||
}
|
||||
|
||||
container.Env = mapToEnvVars(step.Environment)
|
||||
stepSecret, err := stepSecretName(step)
|
||||
if err != nil {
|
||||
return container, err
|
||||
}
|
||||
|
||||
// filter environment variables to non-secrets and secrets, refer secrets from step secrets
|
||||
envs, secs := filterSecrets(step.Environment, step.SecretMapping)
|
||||
envsFromSecrets := mapToEnvVarsFromStepSecrets(secs, stepSecret)
|
||||
container.Env = append(mapToEnvVars(envs), envsFromSecrets...)
|
||||
|
||||
container.Resources, err = resourceRequirements(options.Resources)
|
||||
if err != nil {
|
||||
@@ -254,6 +262,38 @@ func podContainer(step *types.Step, podName, goos string, options BackendOptions
|
||||
return container, nil
|
||||
}
|
||||
|
||||
func mapToEnvVarsFromStepSecrets(secs []string, stepSecretName string) []v1.EnvVar {
|
||||
var ev []v1.EnvVar
|
||||
for _, key := range secs {
|
||||
ev = append(ev, v1.EnvVar{
|
||||
Name: key,
|
||||
ValueFrom: &v1.EnvVarSource{
|
||||
SecretKeyRef: &v1.SecretKeySelector{
|
||||
LocalObjectReference: v1.LocalObjectReference{
|
||||
Name: stepSecretName,
|
||||
},
|
||||
Key: key,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
return ev
|
||||
}
|
||||
|
||||
func filterSecrets(environment, secrets map[string]string) (map[string]string, []string) {
|
||||
ev := map[string]string{}
|
||||
var secs []string
|
||||
|
||||
for k, v := range environment {
|
||||
if _, found := secrets[k]; found {
|
||||
secs = append(secs, k)
|
||||
} else {
|
||||
ev[k] = v
|
||||
}
|
||||
}
|
||||
return ev, secs
|
||||
}
|
||||
|
||||
func pvcVolumes(volumes []string) ([]v1.Volume, error) {
|
||||
var vols []v1.Volume
|
||||
|
||||
|
||||
@@ -177,6 +177,7 @@ func TestTinyPod(t *testing.T) {
|
||||
pod, err := mkPod(&types.Step{
|
||||
Name: "build-via-gradle",
|
||||
Image: "gradle:8.4.0-jdk21",
|
||||
UUID: "01he8bebctabr3kgk0qj36d2me-0",
|
||||
WorkingDir: "/woodpecker/src",
|
||||
Pull: false,
|
||||
Privileged: false,
|
||||
@@ -415,6 +416,7 @@ func TestPodPrivilege(t *testing.T) {
|
||||
return mkPod(&types.Step{
|
||||
Name: "go-test",
|
||||
Image: "golang:1.16",
|
||||
UUID: "01he8bebctabr3kgk0qj36d2me-0",
|
||||
Privileged: stepPrivileged,
|
||||
}, &config{
|
||||
Namespace: "woodpecker",
|
||||
@@ -525,6 +527,7 @@ func TestScratchPod(t *testing.T) {
|
||||
pod, err := mkPod(&types.Step{
|
||||
Name: "curl-google",
|
||||
Image: "quay.io/curl/curl",
|
||||
UUID: "01he8bebctabr3kgk0qj36d2me-0",
|
||||
Entrypoint: []string{"/usr/bin/curl", "-v", "google.com"},
|
||||
}, &config{
|
||||
Namespace: "woodpecker",
|
||||
@@ -623,6 +626,7 @@ func TestSecrets(t *testing.T) {
|
||||
pod, err := mkPod(&types.Step{
|
||||
Name: "test-secrets",
|
||||
Image: "alpine",
|
||||
UUID: "01he8bebctabr3kgk0qj36d2me-0",
|
||||
Environment: map[string]string{"CGO": "0"},
|
||||
Volumes: []string{"workspace:/woodpecker/src"},
|
||||
}, &config{
|
||||
@@ -657,3 +661,35 @@ func TestSecrets(t *testing.T) {
|
||||
ja := jsonassert.New(t)
|
||||
ja.Assertf(string(podJSON), expected)
|
||||
}
|
||||
|
||||
func TestStepSecret(t *testing.T) {
|
||||
const expected = `{
|
||||
"metadata": {
|
||||
"name": "wp-01he8bebctabr3kgk0qj36d2me-0-step-secret",
|
||||
"namespace": "woodpecker",
|
||||
"creationTimestamp": null
|
||||
},
|
||||
"type": "Opaque",
|
||||
"stringData": {
|
||||
"VERY_SECRET": "secret_value"
|
||||
}
|
||||
}`
|
||||
|
||||
secret, err := mkStepSecret(&types.Step{
|
||||
UUID: "01he8bebctabr3kgk0qj36d2me-0",
|
||||
Name: "go-test",
|
||||
Image: "meltwater/drone-cache",
|
||||
SecretMapping: map[string]string{
|
||||
"VERY_SECRET": "secret_value",
|
||||
},
|
||||
}, &config{
|
||||
Namespace: "woodpecker",
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
|
||||
secretJSON, err := json.Marshal(secret)
|
||||
assert.NoError(t, err)
|
||||
|
||||
ja := jsonassert.New(t)
|
||||
ja.Assertf(string(secretJSON), expected)
|
||||
}
|
||||
|
||||
@@ -308,3 +308,58 @@ func stopRegistrySecret(ctx context.Context, engine *kube, step *types.Step, del
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func needsStepSecret(step *types.Step) bool {
|
||||
return len(step.SecretMapping) > 0
|
||||
}
|
||||
|
||||
func startStepSecret(ctx context.Context, e *kube, step *types.Step) error {
|
||||
secret, err := mkStepSecret(step, e.config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Trace().Msgf("creating secret: %s", secret.Name)
|
||||
_, err = e.client.CoreV1().Secrets(e.config.Namespace).Create(ctx, secret, meta_v1.CreateOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func mkStepSecret(step *types.Step, config *config) (*v1.Secret, error) {
|
||||
name, err := stepSecretName(step)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &v1.Secret{
|
||||
ObjectMeta: meta_v1.ObjectMeta{
|
||||
Namespace: config.Namespace,
|
||||
Name: name,
|
||||
},
|
||||
Type: v1.SecretTypeOpaque,
|
||||
StringData: step.SecretMapping,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func stepSecretName(step *types.Step) (string, error) {
|
||||
name, err := stepToPodName(step)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return fmt.Sprintf("%s-step-secret", name), nil
|
||||
}
|
||||
|
||||
func stopStepSecret(ctx context.Context, engine *kube, step *types.Step, deleteOpts meta_v1.DeleteOptions) error {
|
||||
name, err := stepSecretName(step)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Trace().Str("name", name).Msg("deleting secret")
|
||||
|
||||
err = engine.client.CoreV1().Secrets(engine.config.Namespace).Delete(ctx, name, deleteOpts)
|
||||
if errors.IsNotFound(err) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user