From 01bf4833e994d99520fd5e8a761bb3808b8c1426 Mon Sep 17 00:00:00 2001 From: Samuel K Date: Sun, 3 May 2026 00:30:24 -0500 Subject: [PATCH] feat(config): add securityOpt passthrough to container create Wire securityOpt from devcontainer.json through to docker run as --security-opt flags. The dockerless code path was missing this field. Add unit tests for the security opt arg assembly and an E2E test validating securityOpt on docker containers. --- e2e/tests/up/provider_docker.go | 20 +++++++++ .../docker-securityopt/.devcontainer.json | 5 +++ pkg/devcontainer/single.go | 5 ++- pkg/driver/docker/docker_test.go | 41 +++++++++++++++++++ 4 files changed, 69 insertions(+), 2 deletions(-) create mode 100644 e2e/tests/up/testdata/docker-securityopt/.devcontainer.json diff --git a/e2e/tests/up/provider_docker.go b/e2e/tests/up/provider_docker.go index 6969d01a5..57d950a7d 100644 --- a/e2e/tests/up/provider_docker.go +++ b/e2e/tests/up/provider_docker.go @@ -508,6 +508,26 @@ var _ = ginkgo.Describe( ginkgo.SpecTimeout(framework.TimeoutShort()), ) + ginkgo.It("security options", func(ctx context.Context) { + tempDir, err := dtc.setupAndUp(ctx, "tests/up/testdata/docker-securityopt") + framework.ExpectNoError(err) + + workspace, err := dtc.f.FindWorkspace(ctx, tempDir) + framework.ExpectNoError(err) + + ids, err := dtc.findWorkspaceContainer(ctx, workspace) + framework.ExpectNoError(err) + gomega.Expect(ids).To(gomega.HaveLen(1)) + + var details []container.InspectResponse + err = dtc.dockerHelper.Inspect(ctx, ids, "container", &details) + framework.ExpectNoError(err) + gomega.Expect(details[0].HostConfig.SecurityOpt). + To(gomega.ContainElement("seccomp=unconfined")) + gomega.Expect(details[0].HostConfig.SecurityOpt). + To(gomega.ContainElement("apparmor=unconfined")) + }, ginkgo.SpecTimeout(framework.TimeoutShort())) + ginkgo.It("multi devcontainer selection", func(ctx context.Context) { tempDir, err := setupWorkspace( "tests/up/testdata/docker-multi-devcontainer", diff --git a/e2e/tests/up/testdata/docker-securityopt/.devcontainer.json b/e2e/tests/up/testdata/docker-securityopt/.devcontainer.json new file mode 100644 index 000000000..47be2d6d0 --- /dev/null +++ b/e2e/tests/up/testdata/docker-securityopt/.devcontainer.json @@ -0,0 +1,5 @@ +{ + "name": "Go", + "image": "ghcr.io/devsy-org/test-images/go:1", + "securityOpt": ["seccomp=unconfined", "apparmor=unconfined"] +} diff --git a/pkg/devcontainer/single.go b/pkg/devcontainer/single.go index 0b6a9ab88..0f41badbc 100644 --- a/pkg/devcontainer/single.go +++ b/pkg/devcontainer/single.go @@ -438,8 +438,9 @@ func (r *runner) getDockerlessRunOptions( "--cmd", GetStartScript(mergedConfig), "--user", buildInfo.Dockerless.User, }, - Env: env, - CapAdd: mergedConfig.CapAdd, + Env: env, + CapAdd: mergedConfig.CapAdd, + SecurityOpt: mergedConfig.SecurityOpt, Labels: []string{ metadata.ImageMetadataLabel + "=" + string(marshalled), config.UserLabel + "=" + buildInfo.Dockerless.User, diff --git a/pkg/driver/docker/docker_test.go b/pkg/driver/docker/docker_test.go index 44f7d0e06..37bc0de52 100644 --- a/pkg/driver/docker/docker_test.go +++ b/pkg/driver/docker/docker_test.go @@ -5,9 +5,15 @@ import ( "testing" "github.com/devsy-org/devsy/pkg/devcontainer/config" + "github.com/devsy-org/devsy/pkg/driver" "github.com/stretchr/testify/suite" ) +const ( + testSeccompUnconfined = "seccomp=unconfined" + testSecurityOptFlag = "--security-opt" +) + type DockerDriverTestSuite struct { suite.Suite driver *dockerDriver @@ -137,3 +143,38 @@ func (s *DockerDriverTestSuite) TestGatherUpdateRequirements_WithContainerUser() s.NotNil(localUser) s.Equal("container", containerUser) } + +func (s *DockerDriverTestSuite) TestAddCapabilityArgs_SingleSecurityOpt() { + opts := &driver.RunOptions{SecurityOpt: []string{testSeccompUnconfined}} + args := s.driver.addCapabilityArgs(nil, opts) + s.Equal([]string{testSecurityOptFlag, testSeccompUnconfined}, args) +} + +func (s *DockerDriverTestSuite) TestAddCapabilityArgs_MultipleSecurityOpts() { + opts := &driver.RunOptions{ + SecurityOpt: []string{testSeccompUnconfined, "apparmor=unconfined"}, + } + args := s.driver.addCapabilityArgs(nil, opts) + s.Equal([]string{ + testSecurityOptFlag, testSeccompUnconfined, + testSecurityOptFlag, "apparmor=unconfined", + }, args) +} + +func (s *DockerDriverTestSuite) TestAddCapabilityArgs_EmptySecurityOpt() { + opts := &driver.RunOptions{} + args := s.driver.addCapabilityArgs(nil, opts) + s.Nil(args) +} + +func (s *DockerDriverTestSuite) TestAddCapabilityArgs_CapAddAndSecurityOpt() { + opts := &driver.RunOptions{ + CapAdd: []string{"SYS_PTRACE"}, + SecurityOpt: []string{testSeccompUnconfined}, + } + args := s.driver.addCapabilityArgs(nil, opts) + s.Equal([]string{ + "--cap-add", "SYS_PTRACE", + testSecurityOptFlag, testSeccompUnconfined, + }, args) +}