mirror of
https://github.com/linuxkit/linuxkit.git
synced 2025-07-19 01:06:27 +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`.
|
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 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`
|
## `kernel`
|
||||||
|
|
||||||
The `kernel` section is only required if booting a VM. The files will be put into the `boot/`
|
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
|
- path: dir/name3
|
||||||
contents: "orange"
|
contents: "orange"
|
||||||
mode: "0644"
|
mode: "0644"
|
||||||
|
uid: 100
|
||||||
|
gid: 100
|
||||||
```
|
```
|
||||||
|
|
||||||
Specifying the `mode` is optional, and will default to `0600`. Leading directories will be
|
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.
|
- `readonly` sets the root filesystem to read only, and changes the other default filesystems to read only.
|
||||||
- `maskedPaths` sets paths which should be hidden.
|
- `maskedPaths` sets paths which should be hidden.
|
||||||
- `readonlyPaths` sets paths to read only.
|
- `readonlyPaths` sets paths to read only.
|
||||||
- `uid` sets the user id of the process. Only numbers are accepted.
|
- `uid` sets the user id of the process.
|
||||||
- `gid` sets the group id of the process. Only numbers are accepted.
|
- `gid` sets the group id of the process.
|
||||||
- `additionalGids` sets additional groups for the process. A list of numbers is accepted.
|
- `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.
|
- `noNewPrivileges` is `true` means no additional capabilities can be acquired and `suid` binaries do not work.
|
||||||
- `hostname` sets the hostname inside the image.
|
- `hostname` sets the hostname inside the image.
|
||||||
- `oomScoreAdj` changes the OOM score.
|
- `oomScoreAdj` changes the OOM score.
|
||||||
|
@ -125,6 +125,18 @@ func Build(m Moby, w io.Writer, pull bool, tp string) error {
|
|||||||
// add additions
|
// add additions
|
||||||
addition := additions[tp]
|
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 != "" {
|
if m.Kernel.Image != "" {
|
||||||
// get kernel and initrd tarball from container
|
// get kernel and initrd tarball from container
|
||||||
log.Infof("Extract kernel image: %s", m.Kernel.Image)
|
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 {
|
for i, image := range m.Onboot {
|
||||||
log.Infof(" Create OCI config for %s", image.Image)
|
log.Infof(" Create OCI config for %s", image.Image)
|
||||||
useTrust := enforceContentTrust(image.Image, &m.Trust)
|
useTrust := enforceContentTrust(image.Image, &m.Trust)
|
||||||
config, err := ConfigToOCI(image, useTrust)
|
config, err := ConfigToOCI(image, useTrust, idMap)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Failed to create config.json for %s: %v", image.Image, err)
|
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 {
|
for _, image := range m.Services {
|
||||||
log.Infof(" Create OCI config for %s", image.Image)
|
log.Infof(" Create OCI config for %s", image.Image)
|
||||||
useTrust := enforceContentTrust(image.Image, &m.Trust)
|
useTrust := enforceContentTrust(image.Image, &m.Trust)
|
||||||
config, err := ConfigToOCI(image, useTrust)
|
config, err := ConfigToOCI(image, useTrust, idMap)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Failed to create config.json for %s: %v", image.Image, err)
|
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
|
// add files
|
||||||
err := filesystem(m, iw)
|
err := filesystem(m, iw, idMap)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to add filesystem parts: %v", err)
|
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
|
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
|
// TODO also include the files added in other parts of the build
|
||||||
var addedFiles = map[string]bool{}
|
var addedFiles = map[string]bool{}
|
||||||
|
|
||||||
@ -372,6 +384,16 @@ func filesystem(m Moby, tw *tar.Writer) error {
|
|||||||
if dirMode&0007 != 0 {
|
if dirMode&0007 != 0 {
|
||||||
dirMode |= 0001
|
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
|
var contents []byte
|
||||||
if f.Contents != nil {
|
if f.Contents != nil {
|
||||||
contents = []byte(*f.Contents)
|
contents = []byte(*f.Contents)
|
||||||
@ -414,8 +436,8 @@ func filesystem(m Moby, tw *tar.Writer) error {
|
|||||||
Name: root,
|
Name: root,
|
||||||
Typeflag: tar.TypeDir,
|
Typeflag: tar.TypeDir,
|
||||||
Mode: dirMode,
|
Mode: dirMode,
|
||||||
Uid: int(f.UID),
|
Uid: int(uid),
|
||||||
Gid: int(f.GID),
|
Gid: int(gid),
|
||||||
}
|
}
|
||||||
err := tw.WriteHeader(hdr)
|
err := tw.WriteHeader(hdr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -428,8 +450,8 @@ func filesystem(m Moby, tw *tar.Writer) error {
|
|||||||
hdr := &tar.Header{
|
hdr := &tar.Header{
|
||||||
Name: f.Path,
|
Name: f.Path,
|
||||||
Mode: mode,
|
Mode: mode,
|
||||||
Uid: int(f.UID),
|
Uid: int(uid),
|
||||||
Gid: int(f.GID),
|
Gid: int(gid),
|
||||||
}
|
}
|
||||||
if f.Directory {
|
if f.Directory {
|
||||||
if f.Contents != nil {
|
if f.Contents != nil {
|
||||||
|
@ -45,8 +45,8 @@ type File struct {
|
|||||||
Source string
|
Source string
|
||||||
Optional bool
|
Optional bool
|
||||||
Mode string
|
Mode string
|
||||||
UID uint32 `yaml:"uid" json:"uid"`
|
UID string `yaml:"uid" json:"uid"`
|
||||||
GID uint32 `yaml:"gid" json:"gid"`
|
GID string `yaml:"gid" json:"gid"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Image is the type of an image config
|
// Image is the type of an image config
|
||||||
@ -69,9 +69,9 @@ type Image struct {
|
|||||||
Readonly *bool `yaml:"readonly" json:"readonly,omitempty"`
|
Readonly *bool `yaml:"readonly" json:"readonly,omitempty"`
|
||||||
MaskedPaths *[]string `yaml:"maskedPaths" json:"maskedPaths,omitempty"`
|
MaskedPaths *[]string `yaml:"maskedPaths" json:"maskedPaths,omitempty"`
|
||||||
ReadonlyPaths *[]string `yaml:"readonlyPaths" json:"readonlyPaths,omitempty"`
|
ReadonlyPaths *[]string `yaml:"readonlyPaths" json:"readonlyPaths,omitempty"`
|
||||||
UID *uint32 `yaml:"uid" json:"uid,omitempty"`
|
UID *string `yaml:"uid" json:"uid,omitempty"`
|
||||||
GID *uint32 `yaml:"gid" json:"gid,omitempty"`
|
GID *string `yaml:"gid" json:"gid,omitempty"`
|
||||||
AdditionalGids *[]uint32 `yaml:"additionalGids" json:"additionalGids,omitempty"`
|
AdditionalGids *[]string `yaml:"additionalGids" json:"additionalGids,omitempty"`
|
||||||
NoNewPrivileges *bool `yaml:"noNewPrivileges" json:"noNewPrivileges,omitempty"`
|
NoNewPrivileges *bool `yaml:"noNewPrivileges" json:"noNewPrivileges,omitempty"`
|
||||||
OOMScoreAdj *int `yaml:"oomScoreAdj" json:"oomScoreAdj,omitempty"`
|
OOMScoreAdj *int `yaml:"oomScoreAdj" json:"oomScoreAdj,omitempty"`
|
||||||
DisableOOMKiller *bool `yaml:"disableOOMKiller" json:"disableOOMKiller,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
|
// 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
|
// TODO pass through same docker client to all functions
|
||||||
cli, err := dockerClient()
|
cli, err := dockerClient()
|
||||||
@ -239,7 +239,7 @@ func ConfigToOCI(image Image, trust bool) ([]byte, error) {
|
|||||||
return []byte{}, err
|
return []byte{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
oci, err := ConfigInspectToOCI(image, inspect)
|
oci, err := ConfigInspectToOCI(image, inspect, idMap)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return []byte{}, err
|
return []byte{}, err
|
||||||
}
|
}
|
||||||
@ -467,8 +467,24 @@ var allCaps = []string{
|
|||||||
"CAP_WAKE_ALARM",
|
"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
|
// 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{}
|
oci := specs.Spec{}
|
||||||
|
|
||||||
var inspectConfig container.Config
|
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.Version = specs.Version
|
||||||
|
|
||||||
oci.Platform = specs.Platform{
|
oci.Platform = specs.Platform{
|
||||||
@ -737,9 +774,9 @@ func ConfigInspectToOCI(yaml Image, inspect types.ImageInspect) (specs.Spec, err
|
|||||||
Terminal: false,
|
Terminal: false,
|
||||||
//ConsoleSize
|
//ConsoleSize
|
||||||
User: specs.User{
|
User: specs.User{
|
||||||
UID: assignUint32(label.UID, yaml.UID),
|
UID: uid,
|
||||||
GID: assignUint32(label.GID, yaml.GID),
|
GID: gid,
|
||||||
AdditionalGids: assignUint32Array(label.AdditionalGids, yaml.AdditionalGids),
|
AdditionalGids: additionalGroups,
|
||||||
// Username (Windows)
|
// Username (Windows)
|
||||||
},
|
},
|
||||||
Args: args,
|
Args: args,
|
||||||
|
@ -25,6 +25,8 @@ func setupInspect(t *testing.T, label Image) types.ImageInspect {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestOverrides(t *testing.T) {
|
func TestOverrides(t *testing.T) {
|
||||||
|
idMap := map[string]uint32{}
|
||||||
|
|
||||||
var yamlCaps = []string{"CAP_SYS_ADMIN"}
|
var yamlCaps = []string{"CAP_SYS_ADMIN"}
|
||||||
|
|
||||||
var yaml = Image{
|
var yaml = Image{
|
||||||
@ -42,7 +44,7 @@ func TestOverrides(t *testing.T) {
|
|||||||
|
|
||||||
inspect := setupInspect(t, label)
|
inspect := setupInspect(t, label)
|
||||||
|
|
||||||
oci, err := ConfigInspectToOCI(yaml, inspect)
|
oci, err := ConfigInspectToOCI(yaml, inspect, idMap)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
@ -56,6 +58,8 @@ func TestOverrides(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestInvalidCap(t *testing.T) {
|
func TestInvalidCap(t *testing.T) {
|
||||||
|
idMap := map[string]uint32{}
|
||||||
|
|
||||||
yaml := Image{
|
yaml := Image{
|
||||||
Name: "test",
|
Name: "test",
|
||||||
Image: "testimage",
|
Image: "testimage",
|
||||||
@ -68,8 +72,38 @@ func TestInvalidCap(t *testing.T) {
|
|||||||
|
|
||||||
inspect := setupInspect(t, label)
|
inspect := setupInspect(t, label)
|
||||||
|
|
||||||
_, err := ConfigInspectToOCI(yaml, inspect)
|
_, err := ConfigInspectToOCI(yaml, inspect, idMap)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Error("expected error, got valid OCI config")
|
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"},
|
"source": {"type": "string"},
|
||||||
"optional": {"type": "boolean"},
|
"optional": {"type": "boolean"},
|
||||||
"mode": {"type": "string"},
|
"mode": {"type": "string"},
|
||||||
"uid": {"type": "integer"},
|
"uid": {"type": "string"},
|
||||||
"gid": {"type": "integer"}
|
"gid": {"type": "string"}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"files": {
|
"files": {
|
||||||
@ -81,11 +81,11 @@ var schema = string(`
|
|||||||
"readonly": { "type": "boolean"},
|
"readonly": { "type": "boolean"},
|
||||||
"maskedPaths": { "$ref": "#/definitions/strings" },
|
"maskedPaths": { "$ref": "#/definitions/strings" },
|
||||||
"readonlyPaths": { "$ref": "#/definitions/strings" },
|
"readonlyPaths": { "$ref": "#/definitions/strings" },
|
||||||
"uid": {"type": "integer"},
|
"uid": {"type": "string"},
|
||||||
"gid": {"type": "integer"},
|
"gid": {"type": "string"},
|
||||||
"additionalGids": {
|
"additionalGids": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": { "type": "integer" }
|
"items": { "type": "string" }
|
||||||
},
|
},
|
||||||
"noNewPrivileges": {"type": "boolean"},
|
"noNewPrivileges": {"type": "boolean"},
|
||||||
"hostname": {"type": "string"},
|
"hostname": {"type": "string"},
|
||||||
|
Loading…
Reference in New Issue
Block a user