Skip to content

Commit 9b9f49b

Browse files
runtime/kubernetes: Support specifying additional capabilities for containers.
Also make sure that when a container is marked privileged it is allowed to run as root.
1 parent bdef7bb commit 9b9f49b

File tree

10 files changed

+99
-41
lines changed

10 files changed

+99
-41
lines changed

internal/frontend/cuefrontendopaque/container.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ type cueContainer struct {
2323

2424
Requests *schema.Container_ResourceLimits `json:"resourceRequests"`
2525
Limits *schema.Container_ResourceLimits `json:"resourceLimits"`
26+
Security *cueServerSecurity `json:"security,omitempty"`
2627
}
2728

2829
type parsedCueContainer struct {
@@ -53,6 +54,18 @@ func parseCueContainer(ctx context.Context, env *schema.Environment, pl parsing.
5354
},
5455
}
5556

57+
if bits.Security != nil {
58+
if err := parsing.RequireFeature(loc.Module, "experimental/container/security"); err != nil {
59+
return nil, fnerrors.AttachLocation(loc, err)
60+
}
61+
62+
out.container.Security = &schema.Container_Security{
63+
Privileged: bits.Security.Privileged,
64+
HostNetwork: bits.Security.HostNetwork,
65+
Capabilities: bits.Security.Capabilities,
66+
}
67+
}
68+
5669
if mounts := v.LookupPath("mounts"); mounts.Exists() {
5770
var err error
5871
out.container.Mount, out.volumes, err = cuefrontend.ParseMounts(ctx, pl, loc, mounts)

internal/frontend/cuefrontendopaque/phase0.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ var packageFields = []string{
2626
}
2727

2828
var sidecarFields = []string{
29-
"args", "env", "mounts", "image", "imageFrom", "init",
29+
"args", "env", "mounts", "image", "imageFrom", "init", "security",
3030
}
3131

3232
type Frontend struct {

internal/frontend/cuefrontendopaque/server.go

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -79,8 +79,9 @@ type cuePermissions struct {
7979
}
8080

8181
type cueServerSecurity struct {
82-
Privileged bool `json:"privileged"`
83-
HostNetwork bool `json:"hostNetwork"`
82+
Privileged bool `json:"privileged"`
83+
HostNetwork bool `json:"hostNetwork"`
84+
Capabilities []string `json:"capabilities"`
8485
}
8586

8687
// TODO: converge the relevant parts with parseCueContainer.
@@ -347,8 +348,9 @@ func parseServerExtension(ctx context.Context, env *schema.Environment, pl parsi
347348
}
348349

349350
out.MainContainer.Security = &schema.Container_Security{
350-
Privileged: bits.Security.Privileged,
351-
HostNetwork: bits.Security.HostNetwork,
351+
Privileged: bits.Security.Privileged,
352+
HostNetwork: bits.Security.HostNetwork,
353+
Capabilities: bits.Security.Capabilities,
352354
}
353355
}
354356

internal/planning/deploy/deploy.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -935,6 +935,7 @@ func PrepareRunOpts(ctx context.Context, stack *planning.Stack, srv planning.Pla
935935
out.MainContainer.Args = append(out.MainContainer.Args, main.Args...)
936936
out.MainContainer.Privileged = main.GetSecurity().GetPrivileged()
937937
out.MainContainer.HostNetwork = main.GetSecurity().GetHostNetwork()
938+
out.MainContainer.Capabilities = main.GetSecurity().GetCapabilities()
938939
out.MainContainer.ResourceLimits = main.Limits
939940
out.MainContainer.ResourceRequests = main.Requests
940941
out.MainContainer.ContainerPorts = append(out.MainContainer.ContainerPorts, main.ContainerPort...)
@@ -984,6 +985,7 @@ func prepareContainerRunOpts(containers []*schema.Container, resolved ResolvedSe
984985
ResourceLimits: container.Limits,
985986
ResourceRequests: container.Requests,
986987
ContainerPorts: container.ContainerPort,
988+
Capabilities: container.GetSecurity().GetCapabilities(),
987989
},
988990
}
989991

internal/runtime/interface.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,7 @@ type ContainerRunOpts struct {
318318
ReadOnlyFilesystem bool
319319
Privileged bool
320320
HostNetwork bool
321+
Capabilities []string
321322
Mounts []*schema.Mount
322323
ContainerPorts []*schema.Endpoint_Port
323324
ResourceLimits *schema.Container_ResourceLimits

internal/runtime/kubernetes/deployment.go

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import (
3030
"namespacelabs.dev/foundation/framework/kubernetes/kubedef"
3131
"namespacelabs.dev/foundation/framework/kubernetes/kubenaming"
3232
"namespacelabs.dev/foundation/framework/secrets"
33+
"namespacelabs.dev/foundation/internal/console"
3334
"namespacelabs.dev/foundation/internal/fnerrors"
3435
"namespacelabs.dev/foundation/internal/protos"
3536
"namespacelabs.dev/foundation/internal/runtime"
@@ -98,7 +99,7 @@ func prepareDeployment(ctx context.Context, target BoundNamespace, deployable ru
9899
return fnerrors.InternalError("kubernetes: no repository defined in image: %v", deployable.MainContainer.Image)
99100
}
100101

101-
secCtx, err := makeSecurityContext(deployable.MainContainer)
102+
secCtx, err := makeSecurityContext(deployable.MainContainer, deployable.Name, console.Debug(ctx))
102103
if err != nil {
103104
return err
104105
}
@@ -760,7 +761,7 @@ func prepareDeployment(ctx context.Context, target BoundNamespace, deployable ru
760761

761762
containers = append(containers, name)
762763

763-
sidecarSecCtx, err := makeSecurityContext(sidecar.ContainerRunOpts)
764+
sidecarSecCtx, err := makeSecurityContext(sidecar.ContainerRunOpts, sidecar.Name, console.Debug(ctx))
764765
if err != nil {
765766
return fnerrors.AttachLocation(deployable.ErrorLocation, err)
766767
}
@@ -865,10 +866,6 @@ func prepareDeployment(ctx context.Context, target BoundNamespace, deployable ru
865866
return fnerrors.AttachLocation(deployable.ErrorLocation, err)
866867
}
867868

868-
if deployable.MainContainer.Privileged {
869-
podSecCtx = podSecCtx.WithRunAsUser(0).WithRunAsGroup(0)
870-
}
871-
872869
if deployable.MainContainer.HostNetwork {
873870
spec = spec.WithHostNetwork(true).WithDNSPolicy(corev1.DNSClusterFirstWithHostNet)
874871
}

internal/runtime/kubernetes/securitycontext.go

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,33 @@
55
package kubernetes
66

77
import (
8+
"fmt"
9+
"io"
810
"io/fs"
911
"strconv"
1012

13+
k8sv1 "k8s.io/api/core/v1"
1114
applycorev1 "k8s.io/client-go/applyconfigurations/core/v1"
1215
"namespacelabs.dev/foundation/internal/fnerrors"
1316
"namespacelabs.dev/foundation/internal/runtime"
1417
"sigs.k8s.io/yaml"
1518
)
1619

17-
func makeSecurityContext(opts runtime.ContainerRunOpts) (*applycorev1.SecurityContextApplyConfiguration, error) {
20+
// Effective rules:
21+
//
22+
// PodSecurityContext
23+
// * defaults/pod.podsecuritycontext.yaml
24+
// * kubedef.SpecExtension
25+
// * MainContainer.RunAs
26+
//
27+
// SecurityContext (main or sidecar):
28+
// * defaults/container.securitycontext.yaml
29+
// * Container.Privileged -> RunAsUser=RunAsGroup=0, RunAsNonRoot=false
30+
// * Container.Capabilities
31+
//
32+
// Sidecar.RunAs ignored.
33+
34+
func makeSecurityContext(opts runtime.ContainerRunOpts, containerName string, debugLog io.Writer) (*applycorev1.SecurityContextApplyConfiguration, error) {
1835
secCtx := applycorev1.SecurityContext()
1936

2037
const path = "defaults/container.securitycontext.yaml"
@@ -28,9 +45,23 @@ func makeSecurityContext(opts runtime.ContainerRunOpts) (*applycorev1.SecurityCo
2845
}
2946

3047
if opts.Privileged {
31-
secCtx = secCtx.WithPrivileged(true).WithAllowPrivilegeEscalation(true)
48+
fmt.Fprintf(debugLog, "privileged container %q will run as root\n", containerName)
49+
secCtx = secCtx.
50+
WithPrivileged(true).
51+
WithAllowPrivilegeEscalation(true).
52+
WithRunAsUser(0).
53+
WithRunAsGroup(0).
54+
WithRunAsNonRoot(false)
3255
}
3356

57+
var caps []k8sv1.Capability
58+
for _, cap := range opts.Capabilities {
59+
caps = append(caps, k8sv1.Capability(cap))
60+
}
61+
secCtx = secCtx.WithCapabilities(&applycorev1.CapabilitiesApplyConfiguration{
62+
Add: caps,
63+
})
64+
3465
if opts.ReadOnlyFilesystem {
3566
secCtx = secCtx.WithReadOnlyRootFilesystem(true)
3667
}

internal/versions/versions.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"api_version": 100,
2+
"api_version": 101,
33
"minimum_api_version": 40,
44
"cache_version": 1
5-
}
5+
}

schema/container.pb.go

Lines changed: 37 additions & 26 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

schema/container.proto

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ message Container {
3838
message Security {
3939
bool privileged = 1;
4040
bool host_network = 2;
41+
repeated string capabilities = 3;
4142
}
4243

4344
message ResourceLimits {

0 commit comments

Comments
 (0)