Skip to content

Commit dde482c

Browse files
authored
feat: Add PidMode, Ipcmode and GroupAdd options (runfinch#232)
feat: Add PidMode, Ipc mode Signed-off-by: Arjun Raja Yogidas <[email protected]>
1 parent 18434d7 commit dde482c

File tree

4 files changed

+217
-6
lines changed

4 files changed

+217
-6
lines changed

api/handlers/container/create.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,10 @@ func (h *handler) create(w http.ResponseWriter, r *http.Request) {
175175
if req.HostConfig.Tmpfs != nil {
176176
tmpfs = translateTmpfs(req.HostConfig.Tmpfs)
177177
}
178+
groupAdd := []string{}
179+
if req.HostConfig.GroupAdd != nil {
180+
groupAdd = req.HostConfig.GroupAdd
181+
}
178182

179183
globalOpt := ncTypes.GlobalCommandOptions(*h.Config)
180184
createOpt := ncTypes.ContainerCreateOptions{
@@ -193,6 +197,7 @@ func (h *handler) create(w http.ResponseWriter, r *http.Request) {
193197
StopTimeout: stopTimeout,
194198
CidFile: req.HostConfig.ContainerIDFile, // CidFile write the container ID to the file
195199
OomKillDisable: req.HostConfig.OomKillDisable,
200+
Pid: req.HostConfig.PidMode, // Pid namespace to use
196201
// #endregion
197202

198203
// #region for platform flags
@@ -222,10 +227,12 @@ func (h *handler) create(w http.ResponseWriter, r *http.Request) {
222227
BlkioDeviceWriteBps: throttleDevicesToStrings(req.HostConfig.BlkioDeviceWriteBps),
223228
BlkioDeviceReadIOps: throttleDevicesToStrings(req.HostConfig.BlkioDeviceReadIOps),
224229
BlkioDeviceWriteIOps: throttleDevicesToStrings(req.HostConfig.BlkioDeviceWriteIOps),
230+
IPC: req.HostConfig.IpcMode, // IPC namespace to use
225231
// #endregion
226232

227233
// #region for user flags
228-
User: req.User,
234+
User: req.User,
235+
GroupAdd: groupAdd,
229236
// #endregion
230237

231238
// #region for security flags

api/handlers/container/create_test.go

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -482,6 +482,27 @@ var _ = Describe("Container Create API ", func() {
482482
Expect(rr.Body).Should(MatchJSON(jsonResponse))
483483
})
484484

485+
It("should set GroupAdd option", func() {
486+
body := []byte(`{
487+
"Image": "test-image",
488+
"HostConfig": {
489+
"GroupAdd": ["someGroup"]
490+
}
491+
}`)
492+
req, _ := http.NewRequest(http.MethodPost, "/containers/create", bytes.NewReader(body))
493+
494+
// expected create options
495+
createOpt.GroupAdd = []string{"someGroup"}
496+
497+
service.EXPECT().Create(gomock.Any(), "test-image", nil, equalTo(createOpt), equalTo(netOpt)).Return(
498+
cid, nil)
499+
500+
// handler should return success message with 201 status code.
501+
h.create(rr, req)
502+
Expect(rr).Should(HaveHTTPStatus(http.StatusCreated))
503+
Expect(rr.Body).Should(MatchJSON(jsonResponse))
504+
})
505+
485506
It("should set Privileged option", func() {
486507
body := []byte(`{
487508
"Image": "test-image",
@@ -817,6 +838,48 @@ var _ = Describe("Container Create API ", func() {
817838
Expect(rr.Body).Should(MatchJSON(jsonResponse))
818839
})
819840

841+
It("should set PidMode option", func() {
842+
body := []byte(`{
843+
"Image": "test-image",
844+
"HostConfig": {
845+
"PidMode": "host"
846+
}
847+
}`)
848+
req, _ := http.NewRequest(http.MethodPost, "/containers/create", bytes.NewReader(body))
849+
850+
// expected create options
851+
createOpt.Pid = "host"
852+
853+
service.EXPECT().Create(gomock.Any(), "test-image", nil, equalTo(createOpt), equalTo(netOpt)).Return(
854+
cid, nil)
855+
856+
// handler should return success message with 201 status code.
857+
h.create(rr, req)
858+
Expect(rr).Should(HaveHTTPStatus(http.StatusCreated))
859+
Expect(rr.Body).Should(MatchJSON(jsonResponse))
860+
})
861+
862+
It("should set IPC option", func() {
863+
body := []byte(`{
864+
"Image": "test-image",
865+
"HostConfig": {
866+
"IpcMode": "host"
867+
}
868+
}`)
869+
req, _ := http.NewRequest(http.MethodPost, "/containers/create", bytes.NewReader(body))
870+
871+
// expected create options
872+
createOpt.IPC = "host"
873+
874+
service.EXPECT().Create(gomock.Any(), "test-image", nil, equalTo(createOpt), equalTo(netOpt)).Return(
875+
cid, nil)
876+
877+
// handler should return success message with 201 status code.
878+
h.create(rr, req)
879+
Expect(rr).Should(HaveHTTPStatus(http.StatusCreated))
880+
Expect(rr.Body).Should(MatchJSON(jsonResponse))
881+
})
882+
820883
Context("translate port mappings", func() {
821884
It("should return empty if port mappings is nil", func() {
822885
Expect(translatePortMappings(nil)).Should(BeEmpty())
@@ -992,6 +1055,7 @@ func getDefaultCreateOpt(conf config.Config) types.ContainerCreateOptions {
9921055
CapAdd: []string{}, // nerdctl default.
9931056
CapDrop: []string{}, // nerdctl default.
9941057
Privileged: false,
1058+
GroupAdd: []string{}, // nerdctl default.
9951059
// #endregion
9961060

9971061
// #region for runtime flags

api/types/container_types.go

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -79,14 +79,15 @@ type ContainerHostConfig struct {
7979
DNSOptions []string `json:"DnsOptions"` // List of DNSOption to look for
8080
DNSSearch []string `json:"DnsSearch"` // List of DNSSearch to look for
8181
ExtraHosts []string // List of extra hosts
82-
// TODO: GroupAdd []string // List of additional groups that the container process will run as
83-
// TODO: IpcMode IpcMode // IPC namespace to use for the container
82+
GroupAdd []string // List of additional groups that the container process will run as
83+
IpcMode string // IPC namespace to use for the container
8484
// TODO: Cgroup CgroupSpec // Cgroup to use for the container
8585
// TODO: Links []string // List of links (in the name:alias form)
8686
OomKillDisable bool // specifies whether to disable OOM Killer
87-
// TODO: OomScoreAdj int // specifies the tune container’s OOM preferences (-1000 to 1000, rootless: 100 to 1000)
88-
// TODO: PidMode string // PID namespace to use for the container
89-
Privileged bool // Is the container in privileged mode
87+
// TODO: OomScoreAdj int // specifies the tune container’s OOM preferences (-1000 to 1000, rootless: 100 to 1000)
88+
// TODO: OomScoreAdjChanged bool // OomScoreAdjChanged specifies whether the OOM preferences
89+
PidMode string // PID namespace to use for the container
90+
Privileged bool // Is the container in privileged mode
9091
// TODO: ReadonlyRootfs bool // Is the container root filesystem in read-only
9192
// TODO: SecurityOpt []string // List of string values to customize labels for MLS systems, such as SELinux. (["key=value"])
9293
Tmpfs map[string]string `json:",omitempty"` // List of tmpfs (mounts) used for the container

e2e/tests/container_create.go

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -617,6 +617,7 @@ func ContainerCreate(opt *option.Option) {
617617
command.Run(opt, "start", testContainerName)
618618
verifyNetworkSettings(opt, testContainerName, "bridge")
619619
})
620+
620621
It("should create a container with specified restart options", func() {
621622
// define options
622623
options.Cmd = []string{"sleep", "Infinity"}
@@ -1126,6 +1127,144 @@ func ContainerCreate(opt *option.Option) {
11261127
}
11271128
Expect(foundUTSNamespace).Should(BeFalse())
11281129
})
1130+
1131+
It("should create a container with specified PidMode", func() {
1132+
// First create a container that will be referenced in pid mode
1133+
hostOptions := types.ContainerCreateRequest{}
1134+
hostOptions.Image = defaultImage
1135+
hostOptions.Cmd = []string{"sleep", "Infinity"}
1136+
statusCode, hostCtr := createContainer(uClient, url, "host-container", hostOptions)
1137+
Expect(statusCode).Should(Equal(http.StatusCreated))
1138+
Expect(hostCtr.ID).ShouldNot(BeEmpty())
1139+
command.Run(opt, "start", "host-container")
1140+
1141+
// Define options for the container with pid mode
1142+
options.Cmd = []string{"sleep", "Infinity"}
1143+
options.HostConfig.PidMode = "container:host-container"
1144+
1145+
// Create container
1146+
statusCode, ctr := createContainer(uClient, url, testContainerName, options)
1147+
Expect(statusCode).Should(Equal(http.StatusCreated))
1148+
Expect(ctr.ID).ShouldNot(BeEmpty())
1149+
1150+
// Inspect container using Docker-compatible format
1151+
resp := command.Stdout(opt, "inspect", testContainerName)
1152+
var inspect []*dockercompat.Container
1153+
err := json.Unmarshal(resp, &inspect)
1154+
Expect(err).Should(BeNil())
1155+
Expect(inspect).Should(HaveLen(1))
1156+
1157+
// Verify PidMode configuration
1158+
Expect(inspect[0].HostConfig.PidMode).Should(Equal(hostCtr.ID))
1159+
1160+
// Cleanup
1161+
command.Run(opt, "rm", "-f", "host-container")
1162+
})
1163+
1164+
It("should create a container with private IPC mode", func() {
1165+
options.Cmd = []string{"sleep", "Infinity"}
1166+
options.HostConfig.IpcMode = "private"
1167+
1168+
statusCode, ctr := createContainer(uClient, url, testContainerName, options)
1169+
Expect(statusCode).Should(Equal(http.StatusCreated))
1170+
Expect(ctr.ID).ShouldNot(BeEmpty())
1171+
1172+
command.Run(opt, "start", testContainerName)
1173+
1174+
nativeResp := command.Stdout(opt, "inspect", "--mode=native", testContainerName)
1175+
var nativeInspect []map[string]interface{}
1176+
err := json.Unmarshal(nativeResp, &nativeInspect)
1177+
Expect(err).Should(BeNil())
1178+
Expect(nativeInspect).Should(HaveLen(1))
1179+
1180+
spec, ok := nativeInspect[0]["Spec"].(map[string]interface{})
1181+
Expect(ok).Should(BeTrue())
1182+
linux, ok := spec["linux"].(map[string]interface{})
1183+
Expect(ok).Should(BeTrue())
1184+
namespaces, ok := linux["namespaces"].([]interface{})
1185+
Expect(ok).Should(BeTrue())
1186+
1187+
// For private IPC mode, verify IPC namespace is present
1188+
foundIpcNamespace := false
1189+
for _, ns := range namespaces {
1190+
namespace := ns.(map[string]interface{})
1191+
if namespace["type"] == "ipc" {
1192+
foundIpcNamespace = true
1193+
break
1194+
}
1195+
}
1196+
Expect(foundIpcNamespace).Should(BeTrue())
1197+
})
1198+
1199+
It("should create a container with privileged mode", func() {
1200+
// Define options
1201+
options.Cmd = []string{"sleep", "Infinity"}
1202+
options.HostConfig.Privileged = true
1203+
1204+
// Create container
1205+
statusCode, ctr := createContainer(uClient, url, testContainerName, options)
1206+
Expect(statusCode).Should(Equal(http.StatusCreated))
1207+
Expect(ctr.ID).ShouldNot(BeEmpty())
1208+
1209+
// Start container
1210+
command.Run(opt, "start", testContainerName)
1211+
1212+
// Inspect the container using native format
1213+
nativeResp := command.Stdout(opt, "inspect", "--mode=native", testContainerName)
1214+
var nativeInspect []map[string]interface{}
1215+
err := json.Unmarshal(nativeResp, &nativeInspect)
1216+
Expect(err).Should(BeNil())
1217+
Expect(nativeInspect).Should(HaveLen(1))
1218+
1219+
// Navigate to the process capabilities section
1220+
spec, ok := nativeInspect[0]["Spec"].(map[string]interface{})
1221+
Expect(ok).Should(BeTrue())
1222+
process, ok := spec["process"].(map[string]interface{})
1223+
Expect(ok).Should(BeTrue())
1224+
capabilities, ok := process["capabilities"].(map[string]interface{})
1225+
Expect(ok).Should(BeTrue())
1226+
1227+
// Verify privileged capabilities
1228+
// In privileged mode, the container should have extensive capabilities
1229+
expectedCaps := []string{
1230+
"CAP_SYS_ADMIN",
1231+
"CAP_NET_ADMIN",
1232+
"CAP_SYS_MODULE",
1233+
}
1234+
1235+
for _, capType := range []string{"bounding", "effective", "permitted"} {
1236+
caps, ok := capabilities[capType].([]interface{})
1237+
Expect(ok).Should(BeTrue())
1238+
capsList := make([]string, len(caps))
1239+
for i, cap := range caps {
1240+
capsList[i] = cap.(string)
1241+
}
1242+
for _, expectedCap := range expectedCaps {
1243+
Expect(capsList).Should(ContainElement(expectedCap))
1244+
}
1245+
}
1246+
1247+
// Also verify that devices are allowed in privileged mode
1248+
linux, ok := spec["linux"].(map[string]interface{})
1249+
Expect(ok).Should(BeTrue())
1250+
resources, ok := linux["resources"].(map[string]interface{})
1251+
Expect(ok).Should(BeTrue())
1252+
devices, ok := resources["devices"].([]interface{})
1253+
Expect(ok).Should(BeTrue())
1254+
1255+
// In privileged mode, there should be a device rule that allows all devices
1256+
foundAllowAllDevices := false
1257+
for _, device := range devices {
1258+
dev := device.(map[string]interface{})
1259+
if dev["allow"] == true && dev["access"] == "rwm" {
1260+
if _, hasType := dev["type"]; !hasType {
1261+
foundAllowAllDevices = true
1262+
break
1263+
}
1264+
}
1265+
}
1266+
Expect(foundAllowAllDevices).Should(BeTrue())
1267+
})
11291268
})
11301269
}
11311270

0 commit comments

Comments
 (0)