mirror of
https://github.com/linuxkit/linuxkit.git
synced 2025-07-18 17:01:07 +00:00
Merge pull request #98 from justincormack/named-uids
Assign each container a uid and gid it can use
This commit is contained in:
commit
c7c4c9ef2a
29
docs/yaml.md
29
docs/yaml.md
@ -13,6 +13,27 @@ so it can be tested reliably for continuous delivery.
|
||||
The configuration file is processed in the order `kernel`, `init`, `onboot`, `services`, `files`.
|
||||
Each section adds file to the root file system. Sections may be omitted.
|
||||
|
||||
Each container that is specified is allocated a unique `uid` and `gid` that it may use if it
|
||||
wishes to run as an isolated user (or user namespace). Anywhere you specify a `uid` or `gid`
|
||||
field you specify a string that can either be the numeric id, or if you use a name it will
|
||||
refer to the id allocated to the container with that name.
|
||||
|
||||
```
|
||||
services:
|
||||
- name: redis
|
||||
image: redis:latest
|
||||
uid: redis
|
||||
gid: redis
|
||||
binds:
|
||||
- /etc/redis:/etc/redis
|
||||
files:
|
||||
- path: /etc/redis/redis.conf
|
||||
contents: "..."
|
||||
uid: redis
|
||||
gid: redis
|
||||
mode: "0600"
|
||||
```
|
||||
|
||||
## `kernel`
|
||||
|
||||
The `kernel` section is only required if booting a VM. The files will be put into the `boot/`
|
||||
@ -64,6 +85,8 @@ files:
|
||||
- path: dir/name3
|
||||
contents: "orange"
|
||||
mode: "0644"
|
||||
uid: 100
|
||||
gid: 100
|
||||
```
|
||||
|
||||
Specifying the `mode` is optional, and will default to `0600`. Leading directories will be
|
||||
@ -123,9 +146,9 @@ bind mounted into a container.
|
||||
- `readonly` sets the root filesystem to read only, and changes the other default filesystems to read only.
|
||||
- `maskedPaths` sets paths which should be hidden.
|
||||
- `readonlyPaths` sets paths to read only.
|
||||
- `uid` sets the user id of the process. Only numbers are accepted.
|
||||
- `gid` sets the group id of the process. Only numbers are accepted.
|
||||
- `additionalGids` sets additional groups for the process. A list of numbers is accepted.
|
||||
- `uid` sets the user id of the process.
|
||||
- `gid` sets the group id of the process.
|
||||
- `additionalGids` sets a list of additional groups for the process.
|
||||
- `noNewPrivileges` is `true` means no additional capabilities can be acquired and `suid` binaries do not work.
|
||||
- `hostname` sets the hostname inside the image.
|
||||
- `oomScoreAdj` changes the OOM score.
|
||||
|
@ -125,6 +125,18 @@ func Build(m Moby, w io.Writer, pull bool, tp string) error {
|
||||
// add additions
|
||||
addition := additions[tp]
|
||||
|
||||
// allocate each container a uid, gid that can be referenced by name
|
||||
idMap := map[string]uint32{}
|
||||
id := uint32(100)
|
||||
for _, image := range m.Onboot {
|
||||
idMap[image.Name] = id
|
||||
id++
|
||||
}
|
||||
for _, image := range m.Services {
|
||||
idMap[image.Name] = id
|
||||
id++
|
||||
}
|
||||
|
||||
if m.Kernel.Image != "" {
|
||||
// get kernel and initrd tarball from container
|
||||
log.Infof("Extract kernel image: %s", m.Kernel.Image)
|
||||
@ -157,7 +169,7 @@ func Build(m Moby, w io.Writer, pull bool, tp string) error {
|
||||
for i, image := range m.Onboot {
|
||||
log.Infof(" Create OCI config for %s", image.Image)
|
||||
useTrust := enforceContentTrust(image.Image, &m.Trust)
|
||||
config, err := ConfigToOCI(image, useTrust)
|
||||
config, err := ConfigToOCI(image, useTrust, idMap)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to create config.json for %s: %v", image.Image, err)
|
||||
}
|
||||
@ -175,7 +187,7 @@ func Build(m Moby, w io.Writer, pull bool, tp string) error {
|
||||
for _, image := range m.Services {
|
||||
log.Infof(" Create OCI config for %s", image.Image)
|
||||
useTrust := enforceContentTrust(image.Image, &m.Trust)
|
||||
config, err := ConfigToOCI(image, useTrust)
|
||||
config, err := ConfigToOCI(image, useTrust, idMap)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to create config.json for %s: %v", image.Image, err)
|
||||
}
|
||||
@ -187,7 +199,7 @@ func Build(m Moby, w io.Writer, pull bool, tp string) error {
|
||||
}
|
||||
|
||||
// add files
|
||||
err := filesystem(m, iw)
|
||||
err := filesystem(m, iw, idMap)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to add filesystem parts: %v", err)
|
||||
}
|
||||
@ -335,7 +347,7 @@ func tarAppend(iw *tar.Writer, tr *tar.Reader) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func filesystem(m Moby, tw *tar.Writer) error {
|
||||
func filesystem(m Moby, tw *tar.Writer, idMap map[string]uint32) error {
|
||||
// TODO also include the files added in other parts of the build
|
||||
var addedFiles = map[string]bool{}
|
||||
|
||||
@ -372,6 +384,16 @@ func filesystem(m Moby, tw *tar.Writer) error {
|
||||
if dirMode&0007 != 0 {
|
||||
dirMode |= 0001
|
||||
}
|
||||
|
||||
uid, err := idNumeric(f.UID, idMap)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
gid, err := idNumeric(f.GID, idMap)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var contents []byte
|
||||
if f.Contents != nil {
|
||||
contents = []byte(*f.Contents)
|
||||
@ -414,8 +436,8 @@ func filesystem(m Moby, tw *tar.Writer) error {
|
||||
Name: root,
|
||||
Typeflag: tar.TypeDir,
|
||||
Mode: dirMode,
|
||||
Uid: int(f.UID),
|
||||
Gid: int(f.GID),
|
||||
Uid: int(uid),
|
||||
Gid: int(gid),
|
||||
}
|
||||
err := tw.WriteHeader(hdr)
|
||||
if err != nil {
|
||||
@ -428,8 +450,8 @@ func filesystem(m Moby, tw *tar.Writer) error {
|
||||
hdr := &tar.Header{
|
||||
Name: f.Path,
|
||||
Mode: mode,
|
||||
Uid: int(f.UID),
|
||||
Gid: int(f.GID),
|
||||
Uid: int(uid),
|
||||
Gid: int(gid),
|
||||
}
|
||||
if f.Directory {
|
||||
if f.Contents != nil {
|
||||
|
@ -45,8 +45,8 @@ type File struct {
|
||||
Source string
|
||||
Optional bool
|
||||
Mode string
|
||||
UID uint32 `yaml:"uid" json:"uid"`
|
||||
GID uint32 `yaml:"gid" json:"gid"`
|
||||
UID string `yaml:"uid" json:"uid"`
|
||||
GID string `yaml:"gid" json:"gid"`
|
||||
}
|
||||
|
||||
// Image is the type of an image config
|
||||
@ -69,9 +69,9 @@ type Image struct {
|
||||
Readonly *bool `yaml:"readonly" json:"readonly,omitempty"`
|
||||
MaskedPaths *[]string `yaml:"maskedPaths" json:"maskedPaths,omitempty"`
|
||||
ReadonlyPaths *[]string `yaml:"readonlyPaths" json:"readonlyPaths,omitempty"`
|
||||
UID *uint32 `yaml:"uid" json:"uid,omitempty"`
|
||||
GID *uint32 `yaml:"gid" json:"gid,omitempty"`
|
||||
AdditionalGids *[]uint32 `yaml:"additionalGids" json:"additionalGids,omitempty"`
|
||||
UID *string `yaml:"uid" json:"uid,omitempty"`
|
||||
GID *string `yaml:"gid" json:"gid,omitempty"`
|
||||
AdditionalGids *[]string `yaml:"additionalGids" json:"additionalGids,omitempty"`
|
||||
NoNewPrivileges *bool `yaml:"noNewPrivileges" json:"noNewPrivileges,omitempty"`
|
||||
OOMScoreAdj *int `yaml:"oomScoreAdj" json:"oomScoreAdj,omitempty"`
|
||||
DisableOOMKiller *bool `yaml:"disableOOMKiller" json:"disableOOMKiller,omitempty"`
|
||||
@ -226,7 +226,7 @@ func NewImage(config []byte) (Image, error) {
|
||||
}
|
||||
|
||||
// ConfigToOCI converts a config specification to an OCI config file
|
||||
func ConfigToOCI(image Image, trust bool) ([]byte, error) {
|
||||
func ConfigToOCI(image Image, trust bool, idMap map[string]uint32) ([]byte, error) {
|
||||
|
||||
// TODO pass through same docker client to all functions
|
||||
cli, err := dockerClient()
|
||||
@ -239,7 +239,7 @@ func ConfigToOCI(image Image, trust bool) ([]byte, error) {
|
||||
return []byte{}, err
|
||||
}
|
||||
|
||||
oci, err := ConfigInspectToOCI(image, inspect)
|
||||
oci, err := ConfigInspectToOCI(image, inspect, idMap)
|
||||
if err != nil {
|
||||
return []byte{}, err
|
||||
}
|
||||
@ -467,8 +467,24 @@ var allCaps = []string{
|
||||
"CAP_WAKE_ALARM",
|
||||
}
|
||||
|
||||
func idNumeric(id string, idMap map[string]uint32) (uint32, error) {
|
||||
if id == "" || id == "root" {
|
||||
return 0, nil
|
||||
}
|
||||
for k, v := range idMap {
|
||||
if id == k {
|
||||
return v, nil
|
||||
}
|
||||
}
|
||||
v, err := strconv.ParseUint(id, 10, 32)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("Cannot find or parse id (%s): %v", id, err)
|
||||
}
|
||||
return uint32(v), nil
|
||||
}
|
||||
|
||||
// ConfigInspectToOCI converts a config and the output of image inspect to an OCI config
|
||||
func ConfigInspectToOCI(yaml Image, inspect types.ImageInspect) (specs.Spec, error) {
|
||||
func ConfigInspectToOCI(yaml Image, inspect types.ImageInspect, idMap map[string]uint32) (specs.Spec, error) {
|
||||
oci := specs.Spec{}
|
||||
|
||||
var inspectConfig container.Config
|
||||
@ -726,6 +742,27 @@ func ConfigInspectToOCI(yaml Image, inspect types.ImageInspect) (specs.Spec, err
|
||||
}
|
||||
}
|
||||
|
||||
// handle mapping of named uid, gid to numbers
|
||||
uidString := assignString(label.UID, yaml.UID)
|
||||
gidString := assignString(label.GID, yaml.GID)
|
||||
agStrings := assignStrings(label.AdditionalGids, yaml.AdditionalGids)
|
||||
uid, err := idNumeric(uidString, idMap)
|
||||
if err != nil {
|
||||
return oci, err
|
||||
}
|
||||
gid, err := idNumeric(gidString, idMap)
|
||||
if err != nil {
|
||||
return oci, err
|
||||
}
|
||||
additionalGroups := []uint32{}
|
||||
for _, id := range agStrings {
|
||||
ag, err := idNumeric(id, idMap)
|
||||
if err != nil {
|
||||
return oci, err
|
||||
}
|
||||
additionalGroups = append(additionalGroups, ag)
|
||||
}
|
||||
|
||||
oci.Version = specs.Version
|
||||
|
||||
oci.Platform = specs.Platform{
|
||||
@ -737,9 +774,9 @@ func ConfigInspectToOCI(yaml Image, inspect types.ImageInspect) (specs.Spec, err
|
||||
Terminal: false,
|
||||
//ConsoleSize
|
||||
User: specs.User{
|
||||
UID: assignUint32(label.UID, yaml.UID),
|
||||
GID: assignUint32(label.GID, yaml.GID),
|
||||
AdditionalGids: assignUint32Array(label.AdditionalGids, yaml.AdditionalGids),
|
||||
UID: uid,
|
||||
GID: gid,
|
||||
AdditionalGids: additionalGroups,
|
||||
// Username (Windows)
|
||||
},
|
||||
Args: args,
|
||||
|
@ -25,6 +25,8 @@ func setupInspect(t *testing.T, label Image) types.ImageInspect {
|
||||
}
|
||||
|
||||
func TestOverrides(t *testing.T) {
|
||||
idMap := map[string]uint32{}
|
||||
|
||||
var yamlCaps = []string{"CAP_SYS_ADMIN"}
|
||||
|
||||
var yaml = Image{
|
||||
@ -42,7 +44,7 @@ func TestOverrides(t *testing.T) {
|
||||
|
||||
inspect := setupInspect(t, label)
|
||||
|
||||
oci, err := ConfigInspectToOCI(yaml, inspect)
|
||||
oci, err := ConfigInspectToOCI(yaml, inspect, idMap)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
@ -56,6 +58,8 @@ func TestOverrides(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestInvalidCap(t *testing.T) {
|
||||
idMap := map[string]uint32{}
|
||||
|
||||
yaml := Image{
|
||||
Name: "test",
|
||||
Image: "testimage",
|
||||
@ -68,8 +72,38 @@ func TestInvalidCap(t *testing.T) {
|
||||
|
||||
inspect := setupInspect(t, label)
|
||||
|
||||
_, err := ConfigInspectToOCI(yaml, inspect)
|
||||
_, err := ConfigInspectToOCI(yaml, inspect, idMap)
|
||||
if err == nil {
|
||||
t.Error("expected error, got valid OCI config")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIdMap(t *testing.T) {
|
||||
idMap := map[string]uint32{"test": 199}
|
||||
|
||||
uid := "test"
|
||||
gid := "76"
|
||||
|
||||
yaml := Image{
|
||||
Name: "test",
|
||||
Image: "testimage",
|
||||
UID: &uid,
|
||||
GID: &gid,
|
||||
}
|
||||
|
||||
var label = Image{}
|
||||
|
||||
inspect := setupInspect(t, label)
|
||||
|
||||
oci, err := ConfigInspectToOCI(yaml, inspect, idMap)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if oci.Process.User.UID != 199 {
|
||||
t.Error("Expected named uid to work")
|
||||
}
|
||||
if oci.Process.User.GID != 76 {
|
||||
t.Error("Expected numerical gid to work")
|
||||
}
|
||||
}
|
||||
|
@ -25,8 +25,8 @@ var schema = string(`
|
||||
"source": {"type": "string"},
|
||||
"optional": {"type": "boolean"},
|
||||
"mode": {"type": "string"},
|
||||
"uid": {"type": "integer"},
|
||||
"gid": {"type": "integer"}
|
||||
"uid": {"type": "string"},
|
||||
"gid": {"type": "string"}
|
||||
}
|
||||
},
|
||||
"files": {
|
||||
@ -81,11 +81,11 @@ var schema = string(`
|
||||
"readonly": { "type": "boolean"},
|
||||
"maskedPaths": { "$ref": "#/definitions/strings" },
|
||||
"readonlyPaths": { "$ref": "#/definitions/strings" },
|
||||
"uid": {"type": "integer"},
|
||||
"gid": {"type": "integer"},
|
||||
"uid": {"type": "string"},
|
||||
"gid": {"type": "string"},
|
||||
"additionalGids": {
|
||||
"type": "array",
|
||||
"items": { "type": "integer" }
|
||||
"items": { "type": "string" }
|
||||
},
|
||||
"noNewPrivileges": {"type": "boolean"},
|
||||
"hostname": {"type": "string"},
|
||||
|
Loading…
Reference in New Issue
Block a user