Skip to content

Commit c753be1

Browse files
authored
Merge pull request #135 from champtar/adjustement-namespaces
Allow to adjust LinuxNamespaces
2 parents 7de1160 + 7d06097 commit c753be1

File tree

12 files changed

+667
-331
lines changed

12 files changed

+667
-331
lines changed

README.md

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,7 @@ container parameters:
254254
- Block I/O class
255255
- RDT class
256256
- Linux seccomp policy
257+
- Linux namespaces
257258

258259
### Container Updates
259260

@@ -349,11 +350,14 @@ for rejecting adjustment of the seccomp policy profile based on the type of poli
349350
profile set for the container. These types include the runtime default seccomp
350351
policy profile, a custom policy profile, and unconfined security profiles.
351352

352-
3. Verify global mandatory plugins: Verify that all configured mandatory
353+
3. Reject Linux Namespace adjustment: Reject any adjustment which tries to
354+
alter Linux namespaces of a container.
355+
356+
4. Verify global mandatory plugins: Verify that all configured mandatory
353357
plugins are present and have processed a container. Otherwise reject the
354358
creation of the container.
355359

356-
4. Verify annotated mandatory plugins: Verify that an annotated set of
360+
5. Verify annotated mandatory plugins: Verify that an annotated set of
357361
container-specific mandatory plugins are present and have processed a
358362
container. Otherwise reject the creation of the container.
359363

@@ -362,11 +366,11 @@ allows one to deploy mandatory plugins as containers themselves.
362366

363367
#### Default Validation Scope
364368

365-
Currently only OCI hook injection and Linux seccomp policy can be restricted
366-
using the default validator. However, this probably will change in the future.
367-
Especially when NRI is extended with control over more container parameters.
368-
If newly added controls will have security implications, expect corresponding
369-
configurable restrictions in the default validator.
369+
Currently only OCI hook injection, Linux seccomp policy and Linux namespace
370+
adjustment can be restricted using the default validator. However, this probably
371+
will change in the future. Especially when NRI is extended with control over more
372+
container parameters. If newly added controls will have security implications,
373+
expect corresponding configurable restrictions in the default validator.
370374

371375
## Runtime Adaptation
372376

pkg/adaptation/adaptation_suite_test.go

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -505,6 +505,12 @@ var _ = Describe("Plugin container creation adjustments", func() {
505505
}
506506
a.AddDevice(dev)
507507

508+
case "namespace":
509+
ns := &api.LinuxNamespace{
510+
Type: "cgroup",
511+
}
512+
a.AddOrReplaceNamespace(ns)
513+
508514
case "rlimit":
509515
a.AddRlimit("nofile", 456, 123)
510516

@@ -716,6 +722,17 @@ var _ = Describe("Plugin container creation adjustments", func() {
716722
},
717723
},
718724
),
725+
Entry("adjust namespace", "namespace",
726+
&api.ContainerAdjustment{
727+
Linux: &api.LinuxContainerAdjustment{
728+
Namespaces: []*api.LinuxNamespace{
729+
{
730+
Type: "cgroup",
731+
},
732+
},
733+
},
734+
},
735+
),
719736
Entry("adjust rlimits", "rlimit",
720737
&api.ContainerAdjustment{
721738
Rlimits: []*api.POSIXRlimit{{Type: "nofile", Soft: 123, Hard: 456}},
@@ -1800,6 +1817,94 @@ var _ = Describe("Plugin container creation adjustments", func() {
18001817
})
18011818
})
18021819

1820+
When("the default validator is enabled and namespace adjustment is disabled", func() {
1821+
BeforeEach(func() {
1822+
s.Prepare(
1823+
&mockRuntime{
1824+
options: []nri.Option{
1825+
nri.WithDefaultValidator(
1826+
&validator.DefaultValidatorConfig{
1827+
Enable: true,
1828+
RejectNamespaceAdjustment: true,
1829+
},
1830+
),
1831+
},
1832+
},
1833+
&mockPlugin{idx: "00", name: "foo"},
1834+
&mockPlugin{idx: "10", name: "validator1"},
1835+
&mockPlugin{idx: "20", name: "validator2"},
1836+
)
1837+
})
1838+
1839+
It("should reject namespace adjustment", func() {
1840+
var (
1841+
create = func(_ *mockPlugin, _ *api.PodSandbox, ctr *api.Container) (*api.ContainerAdjustment, []*api.ContainerUpdate, error) {
1842+
a := &api.ContainerAdjustment{}
1843+
if ctr.GetName() == "ctr1" {
1844+
a.AddOrReplaceNamespace(
1845+
&api.LinuxNamespace{
1846+
Type: "cgroup",
1847+
Path: "/",
1848+
},
1849+
)
1850+
}
1851+
return a, nil, nil
1852+
}
1853+
1854+
validate = func(_ *mockPlugin, _ *api.ValidateContainerAdjustmentRequest) error {
1855+
return nil
1856+
}
1857+
1858+
runtime = s.runtime
1859+
plugins = s.plugins
1860+
ctx = context.Background()
1861+
1862+
pod = &api.PodSandbox{
1863+
Id: "pod0",
1864+
Name: "pod0",
1865+
Uid: "uid0",
1866+
Namespace: "default",
1867+
}
1868+
ctr0 = &api.Container{
1869+
Id: "ctr0",
1870+
PodSandboxId: "pod0",
1871+
Name: "ctr0",
1872+
State: api.ContainerState_CONTAINER_CREATED,
1873+
}
1874+
ctr1 = &api.Container{
1875+
Id: "ctr1",
1876+
PodSandboxId: "pod0",
1877+
Name: "ctr1",
1878+
State: api.ContainerState_CONTAINER_CREATED,
1879+
}
1880+
)
1881+
1882+
plugins[0].createContainer = create
1883+
plugins[1].validateAdjustment = validate
1884+
plugins[2].validateAdjustment = validate
1885+
1886+
s.Startup()
1887+
podReq := &api.RunPodSandboxRequest{Pod: pod}
1888+
Expect(runtime.RunPodSandbox(ctx, podReq)).To(Succeed())
1889+
1890+
ctrReq := &api.CreateContainerRequest{
1891+
Pod: pod,
1892+
Container: ctr0,
1893+
}
1894+
reply, err := runtime.CreateContainer(ctx, ctrReq)
1895+
Expect(reply).ToNot(BeNil())
1896+
Expect(err).To(BeNil())
1897+
1898+
ctrReq = &api.CreateContainerRequest{
1899+
Pod: pod,
1900+
Container: ctr1,
1901+
}
1902+
reply, err = runtime.CreateContainer(ctx, ctrReq)
1903+
Expect(err).ToNot(BeNil())
1904+
Expect(reply).To(BeNil())
1905+
})
1906+
})
1907+
18031908
When("the default validator is enabled with some required plugins", func() {
18041909
const AnnotationDomain = plugin.AnnotationDomain
18051910
BeforeEach(func() {

pkg/adaptation/result.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package adaptation
1818

1919
import (
2020
"fmt"
21+
"maps"
2122
"slices"
2223
"strings"
2324

@@ -78,6 +79,9 @@ func collectCreateContainerResult(request *CreateContainerRequest) *result {
7879
if request.Container.Linux.Resources.Unified == nil {
7980
request.Container.Linux.Resources.Unified = map[string]string{}
8081
}
82+
if request.Container.Linux.Namespaces == nil {
83+
request.Container.Linux.Namespaces = []*LinuxNamespace{}
84+
}
8185

8286
return &result{
8387
request: resultRequest{
@@ -99,6 +103,7 @@ func collectCreateContainerResult(request *CreateContainerRequest) *result {
99103
HugepageLimits: []*HugepageLimit{},
100104
Unified: map[string]string{},
101105
},
106+
Namespaces: []*LinuxNamespace{},
102107
},
103108
},
104109
},
@@ -227,6 +232,9 @@ func (r *result) adjust(rpl *ContainerAdjustment, plugin string) error {
227232
if err := r.adjustSeccompPolicy(rpl.Linux.SeccompPolicy, plugin); err != nil {
228233
return err
229234
}
235+
if err := r.adjustNamespaces(rpl.Linux.Namespaces, plugin); err != nil {
236+
return err
237+
}
230238
}
231239
if err := r.adjustRlimits(rpl.Rlimits, plugin); err != nil {
232240
return err
@@ -410,6 +418,39 @@ func (r *result) adjustDevices(devices []*LinuxDevice, plugin string) error {
410418
return nil
411419
}
412420

421+
func (r *result) adjustNamespaces(namespaces []*LinuxNamespace, plugin string) error {
422+
if len(namespaces) == 0 {
423+
return nil
424+
}
425+
426+
create, id := r.request.create, r.request.create.Container.Id
427+
428+
creatensmap := map[string]*LinuxNamespace{}
429+
for _, n := range create.Container.Linux.Namespaces {
430+
creatensmap[n.Type] = n
431+
}
432+
433+
for _, n := range namespaces {
434+
if n == nil {
435+
continue
436+
}
437+
key, marked := n.IsMarkedForRemoval()
438+
if err := r.owners.ClaimNamespace(id, key, plugin); err != nil {
439+
return err
440+
}
441+
if marked {
442+
delete(creatensmap, key)
443+
} else {
444+
creatensmap[key] = n
445+
}
446+
r.reply.adjust.Linux.Namespaces = append(r.reply.adjust.Linux.Namespaces, n)
447+
}
448+
449+
create.Container.Linux.Namespaces = slices.Collect(maps.Values(creatensmap))
450+
451+
return nil
452+
}
453+
413454
func (r *result) adjustCDIDevices(devices []*CDIDevice, plugin string) error {
414455
if len(devices) == 0 {
415456
return nil

pkg/api/adjustment.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,20 @@ func (a *ContainerAdjustment) AddCDIDevice(d *CDIDevice) {
147147
a.CDIDevices = append(a.CDIDevices, d) // TODO: should we dup d here ?
148148
}
149149

150+
// AddOrReplaceNamespace records the addition or replacement of the given namespace to a container.
151+
func (a *ContainerAdjustment) AddOrReplaceNamespace(n *LinuxNamespace) {
152+
a.initLinuxNamespaces()
153+
a.Linux.Namespaces = append(a.Linux.Namespaces, n) // TODO: should we dup n here ?
154+
}
155+
156+
// RemoveNamespace records the removal of the given namespace from a container.
157+
func (a *ContainerAdjustment) RemoveNamespace(n *LinuxNamespace) {
158+
a.initLinuxNamespaces()
159+
a.Linux.Namespaces = append(a.Linux.Namespaces, &LinuxNamespace{
160+
Type: MarkForRemoval(n.Type),
161+
})
162+
}
163+
150164
// SetLinuxMemoryLimit records setting the memory limit for a container.
151165
func (a *ContainerAdjustment) SetLinuxMemoryLimit(value int64) {
152166
a.initLinuxResourcesMemory()
@@ -323,6 +337,13 @@ func (a *ContainerAdjustment) initLinux() {
323337
}
324338
}
325339

340+
func (a *ContainerAdjustment) initLinuxNamespaces() {
341+
a.initLinux()
342+
if a.Linux.Namespaces == nil {
343+
a.Linux.Namespaces = []*LinuxNamespace{}
344+
}
345+
}
346+
326347
func (a *ContainerAdjustment) initLinuxResources() {
327348
a.initLinux()
328349
if a.Linux.Resources == nil {

0 commit comments

Comments
 (0)