Skip to content

Commit

Permalink
Secured kubernetes backend configuration (woodpecker-ci#3204)
Browse files Browse the repository at this point in the history
Follow up of woodpecker-ci#3165
  • Loading branch information
zc-devs authored and fernandrone committed Feb 1, 2024
1 parent 524327b commit 6d889ba
Show file tree
Hide file tree
Showing 7 changed files with 111 additions and 26 deletions.
13 changes: 13 additions & 0 deletions pipeline/backend/kubernetes/kubernetes.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,10 @@ import (
"context"
"fmt"
"io"
"maps"
"os"
"runtime"
"slices"
"time"

"github.com/rs/zerolog/log"
Expand Down Expand Up @@ -155,6 +157,17 @@ func (e *kube) Load(ctx context.Context) (*types.BackendInfo, error) {
}, nil
}

func (e *kube) getConfig() *config {
if e.config == nil {
return nil
}
c := *e.config
c.PodLabels = maps.Clone(e.config.PodLabels)
c.PodAnnotations = maps.Clone(e.config.PodLabels)
c.ImagePullSecretNames = slices.Clone(e.config.ImagePullSecretNames)
return &c
}

// Setup the pipeline environment.
func (e *kube) SetupWorkflow(ctx context.Context, conf *types.Config, taskUUID string) error {
log.Trace().Str("taskUUID", taskUUID).Msgf("Setting up Kubernetes primitives")
Expand Down
53 changes: 53 additions & 0 deletions pipeline/backend/kubernetes/kubernetes_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// Copyright 2024 Woodpecker Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package kubernetes

import (
"testing"

"github.com/stretchr/testify/assert"
)

func TestGettingConfig(t *testing.T) {
engine := kube{
config: &config{
Namespace: "default",
StorageClass: "hdd",
VolumeSize: "1G",
StorageRwx: false,
PodLabels: map[string]string{"l1": "v1"},
PodAnnotations: map[string]string{"a1": "v1"},
ImagePullSecretNames: []string{"regcred"},
SecurityContext: SecurityContextConfig{RunAsNonRoot: false},
},
}
config := engine.getConfig()
config.Namespace = "wp"
config.StorageClass = "ssd"
config.StorageRwx = true
config.PodLabels = nil
config.PodAnnotations["a2"] = "v2"
config.ImagePullSecretNames = append(config.ImagePullSecretNames, "docker.io")
config.SecurityContext.RunAsNonRoot = true

assert.Equal(t, "default", engine.config.Namespace)
assert.Equal(t, "hdd", engine.config.StorageClass)
assert.Equal(t, "1G", engine.config.VolumeSize)
assert.Equal(t, false, engine.config.StorageRwx)
assert.Equal(t, 1, len(engine.config.PodLabels))
assert.Equal(t, 1, len(engine.config.PodAnnotations))
assert.Equal(t, 1, len(engine.config.ImagePullSecretNames))
assert.Equal(t, false, engine.config.SecurityContext.RunAsNonRoot)
}
22 changes: 12 additions & 10 deletions pipeline/backend/kubernetes/pod.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,15 +74,16 @@ func podMeta(step *types.Step, config *config, podName string) metav1.ObjectMeta
Namespace: config.Namespace,
}

labels := make(map[string]string, len(config.PodLabels)+1)
// copy to not alter the engine config
maps.Copy(labels, config.PodLabels)
labels[StepLabel] = step.Name
meta.Labels = labels
meta.Labels = config.PodLabels
if meta.Labels == nil {
meta.Labels = make(map[string]string, 1)
}
meta.Labels[StepLabel] = step.Name

// copy to not alter the engine config
meta.Annotations = make(map[string]string, len(config.PodAnnotations))
maps.Copy(meta.Annotations, config.PodAnnotations)
meta.Annotations = config.PodAnnotations
if meta.Annotations == nil {
meta.Annotations = make(map[string]string)
}

securityContext := step.BackendOptions.Kubernetes.SecurityContext
if securityContext != nil {
Expand Down Expand Up @@ -442,13 +443,14 @@ func startPod(ctx context.Context, engine *kube, step *types.Step) (*v1.Pod, err
if err != nil {
return nil, err
}
pod, err := mkPod(step, engine.config, podName, engine.goos)
engineConfig := engine.getConfig()
pod, err := mkPod(step, engineConfig, podName, engine.goos)
if err != nil {
return nil, err
}

log.Trace().Msgf("creating pod: %s", pod.Name)
return engine.client.CoreV1().Pods(engine.config.Namespace).Create(ctx, pod, metav1.CreateOptions{})
return engine.client.CoreV1().Pods(engineConfig.Namespace).Create(ctx, pod, metav1.CreateOptions{})
}

func stopPod(ctx context.Context, engine *kube, step *types.Step, deleteOpts metav1.DeleteOptions) error {
Expand Down
9 changes: 5 additions & 4 deletions pipeline/backend/kubernetes/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ const (
ServiceLabel = "service"
)

func mkService(step *types.Step, namespace string) (*v1.Service, error) {
func mkService(step *types.Step, config *config) (*v1.Service, error) {
name, err := serviceName(step)
if err != nil {
return nil, err
Expand All @@ -51,7 +51,7 @@ func mkService(step *types.Step, namespace string) (*v1.Service, error) {
return &v1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: namespace,
Namespace: config.Namespace,
},
Spec: v1.ServiceSpec{
Type: v1.ServiceTypeClusterIP,
Expand All @@ -77,13 +77,14 @@ func servicePort(port types.Port) v1.ServicePort {
}

func startService(ctx context.Context, engine *kube, step *types.Step) (*v1.Service, error) {
svc, err := mkService(step, engine.config.Namespace)
engineConfig := engine.getConfig()
svc, err := mkService(step, engineConfig)
if err != nil {
return nil, err
}

log.Trace().Str("name", svc.Name).Interface("selector", svc.Spec.Selector).Interface("ports", svc.Spec.Ports).Msg("creating service")
return engine.client.CoreV1().Services(engine.config.Namespace).Create(ctx, svc, metav1.CreateOptions{})
return engine.client.CoreV1().Services(engineConfig.Namespace).Create(ctx, svc, metav1.CreateOptions{})
}

func stopService(ctx context.Context, engine *kube, step *types.Step, deleteOpts metav1.DeleteOptions) error {
Expand Down
2 changes: 1 addition & 1 deletion pipeline/backend/kubernetes/service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ func TestService(t *testing.T) {
s, err := mkService(&types.Step{
Name: "bar",
Ports: ports,
}, "foo")
}, &config{Namespace: "foo"})
assert.NoError(t, err)
j, err := json.Marshal(s)
assert.NoError(t, err)
Expand Down
17 changes: 9 additions & 8 deletions pipeline/backend/kubernetes/volume.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,15 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

func mkPersistentVolumeClaim(namespace, name, storageClass, size string, storageRwx bool) (*v1.PersistentVolumeClaim, error) {
_storageClass := &storageClass
if storageClass == "" {
func mkPersistentVolumeClaim(config *config, name string) (*v1.PersistentVolumeClaim, error) {
_storageClass := &config.StorageClass
if config.StorageClass == "" {
_storageClass = nil
}

var accessMode v1.PersistentVolumeAccessMode

if storageRwx {
if config.StorageRwx {
accessMode = v1.ReadWriteMany
} else {
accessMode = v1.ReadWriteOnce
Expand All @@ -47,14 +47,14 @@ func mkPersistentVolumeClaim(namespace, name, storageClass, size string, storage
pvc := &v1.PersistentVolumeClaim{
ObjectMeta: metav1.ObjectMeta{
Name: volumeName,
Namespace: namespace,
Namespace: config.Namespace,
},
Spec: v1.PersistentVolumeClaimSpec{
AccessModes: []v1.PersistentVolumeAccessMode{accessMode},
StorageClassName: _storageClass,
Resources: v1.VolumeResourceRequirements{
Requests: v1.ResourceList{
v1.ResourceStorage: resource.MustParse(size),
v1.ResourceStorage: resource.MustParse(config.VolumeSize),
},
},
},
Expand All @@ -76,13 +76,14 @@ func volumeMountPath(name string) string {
}

func startVolume(ctx context.Context, engine *kube, name string) (*v1.PersistentVolumeClaim, error) {
pvc, err := mkPersistentVolumeClaim(engine.config.Namespace, name, engine.config.StorageClass, engine.config.VolumeSize, engine.config.StorageRwx)
engineConfig := engine.getConfig()
pvc, err := mkPersistentVolumeClaim(engineConfig, name)
if err != nil {
return nil, err
}

log.Trace().Msgf("creating volume: %s", pvc.Name)
return engine.client.CoreV1().PersistentVolumeClaims(engine.config.Namespace).Create(ctx, pvc, metav1.CreateOptions{})
return engine.client.CoreV1().PersistentVolumeClaims(engineConfig.Namespace).Create(ctx, pvc, metav1.CreateOptions{})
}

func stopVolume(ctx context.Context, engine *kube, name string, deleteOpts metav1.DeleteOptions) error {
Expand Down
21 changes: 18 additions & 3 deletions pipeline/backend/kubernetes/volume_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,20 +84,35 @@ func TestPersistentVolumeClaim(t *testing.T) {
"status": {}
}`

pvc, err := mkPersistentVolumeClaim("someNamespace", "somename", "local-storage", "1Gi", true)
pvc, err := mkPersistentVolumeClaim(&config{
Namespace: "someNamespace",
StorageClass: "local-storage",
VolumeSize: "1Gi",
StorageRwx: true,
}, "somename")
assert.NoError(t, err)

j, err := json.Marshal(pvc)
assert.NoError(t, err)
assert.JSONEq(t, expectedRwx, string(j))

pvc, err = mkPersistentVolumeClaim("someNamespace", "somename", "local-storage", "1Gi", false)
pvc, err = mkPersistentVolumeClaim(&config{
Namespace: "someNamespace",
StorageClass: "local-storage",
VolumeSize: "1Gi",
StorageRwx: false,
}, "somename")
assert.NoError(t, err)

j, err = json.Marshal(pvc)
assert.NoError(t, err)
assert.JSONEq(t, expectedRwo, string(j))

_, err = mkPersistentVolumeClaim("someNamespace", "some0..INVALID3name", "local-storage", "1Gi", false)
_, err = mkPersistentVolumeClaim(&config{
Namespace: "someNamespace",
StorageClass: "local-storage",
VolumeSize: "1Gi",
StorageRwx: false,
}, "some0..INVALID3name")
assert.Error(t, err)
}

0 comments on commit 6d889ba

Please sign in to comment.