mirror of
https://github.com/woodpecker-ci/woodpecker.git
synced 2026-09-05 20:07:25 +00:00
Gate Kubernetes nodeSelector backend step config behind agent config (#6809)
Co-authored-by: 6543 <6543@obermui.de> Co-authored-by: Lauris B <lauris@nix.lv>
This commit is contained in:
@@ -607,6 +607,15 @@ Additional node selector to apply to worker pods. Must be a YAML object, e.g. `{
|
||||
|
||||
---
|
||||
|
||||
### BACKEND_K8S_POD_NODE_SELECTOR_ALLOW_FROM_STEP
|
||||
|
||||
- Name: `WOODPECKER_BACKEND_K8S_POD_NODE_SELECTOR_ALLOW_FROM_STEP`
|
||||
- Default: `false`
|
||||
|
||||
Determines if the Pod `nodeSelector` can be defined from a step's backend options. Disabled by default, as it would otherwise let any user with push access pin pipeline pods onto chosen nodes.
|
||||
|
||||
---
|
||||
|
||||
### BACKEND_K8S_SECCTX_NONROOT <!-- cspell:ignore SECCTX NONROOT -->
|
||||
|
||||
- Name: `WOODPECKER_BACKEND_K8S_SECCTX_NONROOT`
|
||||
|
||||
@@ -91,6 +91,12 @@ var Flags = []cli.Flag{
|
||||
Usage: "whether to allow using tolerations from step's backend options",
|
||||
Value: true,
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Sources: cli.EnvVars("WOODPECKER_BACKEND_K8S_POD_NODE_SELECTOR_ALLOW_FROM_STEP"),
|
||||
Name: "backend-k8s-pod-node-selector-allow-from-step",
|
||||
Usage: "whether to allow using node selector from step's backend options",
|
||||
Value: false,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Sources: cli.EnvVars("WOODPECKER_BACKEND_K8S_POD_AFFINITY"),
|
||||
Name: "backend-k8s-pod-affinity",
|
||||
|
||||
@@ -69,6 +69,7 @@ type config struct {
|
||||
PodAnnotations map[string]string
|
||||
PodAnnotationsAllowFromStep bool
|
||||
PodNodeSelector map[string]string
|
||||
PodNodeSelectorAllowFromStep bool
|
||||
PodTolerationsAllowFromStep bool
|
||||
PodTolerations []Toleration
|
||||
PodAffinity *kube_core_v1.Affinity
|
||||
@@ -107,20 +108,21 @@ func configFromCliContext(ctx context.Context) (*config, error) {
|
||||
if ctx != nil {
|
||||
if c, ok := ctx.Value(types.CliCommand).(*cli.Command); ok {
|
||||
config := config{
|
||||
Namespace: c.String("backend-k8s-namespace"),
|
||||
EnableNamespacePerOrg: c.Bool("backend-k8s-namespace-per-org"),
|
||||
StorageClass: c.String("backend-k8s-storage-class"),
|
||||
VolumeSize: c.String("backend-k8s-volume-size"),
|
||||
StorageRwx: c.Bool("backend-k8s-storage-rwx"),
|
||||
PriorityClassName: c.String("backend-k8s-priority-class"),
|
||||
PodLabels: make(map[string]string), // just init empty map to prevent nil panic
|
||||
PodLabelsAllowFromStep: c.Bool("backend-k8s-pod-labels-allow-from-step"),
|
||||
PodAnnotations: make(map[string]string), // just init empty map to prevent nil panic
|
||||
PodAnnotationsAllowFromStep: c.Bool("backend-k8s-pod-annotations-allow-from-step"),
|
||||
PodTolerationsAllowFromStep: c.Bool("backend-k8s-pod-tolerations-allow-from-step"),
|
||||
PodNodeSelector: make(map[string]string), // just init empty map to prevent nil panic
|
||||
PodAffinityAllowFromStep: c.Bool("backend-k8s-pod-affinity-allow-from-step"),
|
||||
ImagePullSecretNames: c.StringSlice("backend-k8s-pod-image-pull-secret-names"),
|
||||
Namespace: c.String("backend-k8s-namespace"),
|
||||
EnableNamespacePerOrg: c.Bool("backend-k8s-namespace-per-org"),
|
||||
StorageClass: c.String("backend-k8s-storage-class"),
|
||||
VolumeSize: c.String("backend-k8s-volume-size"),
|
||||
StorageRwx: c.Bool("backend-k8s-storage-rwx"),
|
||||
PriorityClassName: c.String("backend-k8s-priority-class"),
|
||||
PodLabels: make(map[string]string), // just init empty map to prevent nil panic
|
||||
PodLabelsAllowFromStep: c.Bool("backend-k8s-pod-labels-allow-from-step"),
|
||||
PodAnnotations: make(map[string]string), // just init empty map to prevent nil panic
|
||||
PodAnnotationsAllowFromStep: c.Bool("backend-k8s-pod-annotations-allow-from-step"),
|
||||
PodTolerationsAllowFromStep: c.Bool("backend-k8s-pod-tolerations-allow-from-step"),
|
||||
PodNodeSelectorAllowFromStep: c.Bool("backend-k8s-pod-node-selector-allow-from-step"),
|
||||
PodNodeSelector: make(map[string]string), // just init empty map to prevent nil panic
|
||||
PodAffinityAllowFromStep: c.Bool("backend-k8s-pod-affinity-allow-from-step"),
|
||||
ImagePullSecretNames: c.StringSlice("backend-k8s-pod-image-pull-secret-names"),
|
||||
SecurityContext: SecurityContextConfig{
|
||||
RunAsNonRoot: c.Bool("backend-k8s-secctx-nonroot"), // cspell:words secctx nonroot
|
||||
FSGroup: newInt64(defaultFSGroup),
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
// Copyright 2026 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"
|
||||
|
||||
"go.woodpecker-ci.org/woodpecker/v3/pipeline/backend/types"
|
||||
)
|
||||
|
||||
func TestNodeSelectorAllowFromStep(t *testing.T) {
|
||||
step := &types.Step{
|
||||
Name: "ns-test",
|
||||
Image: "alpine",
|
||||
UUID: "01he8bebctabr3kgk0qj36d2me-0",
|
||||
}
|
||||
|
||||
// When disabled (default), a step-provided node selector must be ignored.
|
||||
pod, err := mkPod(step, &config{
|
||||
Namespace: "woodpecker",
|
||||
}, "wp-01he8bebctabr3kgk0qj36d2me-0", "linux/amd64", BackendOptions{
|
||||
NodeSelector: map[string]string{"attacker-target": "sensitive-node"},
|
||||
}, taskUUID)
|
||||
assert.NoError(t, err)
|
||||
assert.NotContains(t, pod.Spec.NodeSelector, "attacker-target")
|
||||
|
||||
// When explicitly enabled by the admin, the step value is honored.
|
||||
pod, err = mkPod(step, &config{
|
||||
Namespace: "woodpecker",
|
||||
PodNodeSelectorAllowFromStep: true,
|
||||
}, "wp-01he8bebctabr3kgk0qj36d2me-0", "linux/amd64", BackendOptions{
|
||||
NodeSelector: map[string]string{"attacker-target": "sensitive-node"},
|
||||
}, taskUUID)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "sensitive-node", pod.Spec.NodeSelector["attacker-target"])
|
||||
}
|
||||
@@ -193,7 +193,7 @@ func podSpec(step *types.Step, config *config, options BackendOptions, nsp nativ
|
||||
Hostname: getHostnameOrEmpty(step.Name),
|
||||
Subdomain: subdomain,
|
||||
DNSConfig: dnsConfig(config.GetNamespace(step.OrgID), subdomain),
|
||||
NodeSelector: nodeSelector(options.NodeSelector, config.PodNodeSelector, step.Environment["CI_SYSTEM_PLATFORM"]),
|
||||
NodeSelector: nodeSelector(options.NodeSelector, config.PodNodeSelector, config.PodNodeSelectorAllowFromStep, step.Environment["CI_SYSTEM_PLATFORM"]),
|
||||
Tolerations: tolerations(options.Tolerations),
|
||||
Affinity: affinity(options.Affinity, config.PodAffinity, config.PodAffinityAllowFromStep),
|
||||
SecurityContext: podSecurityContext(options.SecurityContext, config.SecurityContext, step.Privileged, options.HostUsers),
|
||||
@@ -512,7 +512,7 @@ func resourceList(resources map[string]string) (kube_core_v1.ResourceList, error
|
||||
return requestResources, nil
|
||||
}
|
||||
|
||||
func nodeSelector(backendNodeSelector, configNodeSelector map[string]string, platform string) map[string]string {
|
||||
func nodeSelector(backendNodeSelector, configNodeSelector map[string]string, allowFromStep bool, platform string) map[string]string {
|
||||
nodeSelector := make(map[string]string)
|
||||
|
||||
if platform != "" {
|
||||
@@ -527,8 +527,12 @@ func nodeSelector(backendNodeSelector, configNodeSelector map[string]string, pla
|
||||
}
|
||||
|
||||
if len(backendNodeSelector) > 0 {
|
||||
log.Trace().Msgf("appending labels to the node selector from the backend options: %v", backendNodeSelector)
|
||||
maps.Copy(nodeSelector, backendNodeSelector)
|
||||
if allowFromStep {
|
||||
log.Trace().Msgf("appending labels to the node selector from the backend options: %v", backendNodeSelector)
|
||||
maps.Copy(nodeSelector, backendNodeSelector)
|
||||
} else {
|
||||
log.Debug().Msg("Step node selector is disallowed by instance configuration, ignoring it")
|
||||
}
|
||||
}
|
||||
|
||||
return nodeSelector
|
||||
|
||||
@@ -466,6 +466,7 @@ func TestFullPod(t *testing.T) {
|
||||
PodAnnotationsAllowFromStep: true,
|
||||
PodTolerationsAllowFromStep: true,
|
||||
PodNodeSelector: map[string]string{"topology.kubernetes.io/region": "eu-central-1"},
|
||||
PodNodeSelectorAllowFromStep: true,
|
||||
SecurityContext: SecurityContextConfig{RunAsNonRoot: false},
|
||||
ServiceAccountNameAllowFromStep: true,
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user