Merge pull request #16837 from giuseppe/idmap-oci
libpod: use OCI idmappings for mounts
This commit is contained in:
		
						commit
						76cf5e18cb
					
				|  | @ -37,6 +37,11 @@ Current supported mount TYPEs are **bind**, **volume**, **image**, **tmpfs** and | |||
| 	      . U, chown: true or false (default). Change recursively the owner and group of the source volume based on the UID and GID of the container. | ||||
| 
 | ||||
| 	      · idmap: true or false (default).  If specified, create an idmapped mount to the target user namespace in the container. | ||||
|           The idmap option supports a custom mapping that can be different than the user namespace used by the container. | ||||
|           The mapping can be specified after the idmap option like: idmap=uids=0-1-10#10-11-10;gids=0-100-10.  For each triplet, the first value is the | ||||
|           start of the backing file system IDs that are mapped to the second value on the host.  The length of this mapping is given in the third value. | ||||
| 
 | ||||
|        Multiple ranges are separated with #. | ||||
| 
 | ||||
|        Options specific to image: | ||||
| 
 | ||||
|  |  | |||
|  | @ -47,6 +47,7 @@ import ( | |||
| 	"github.com/containers/storage/pkg/archive" | ||||
| 	"github.com/containers/storage/pkg/idtools" | ||||
| 	"github.com/containers/storage/pkg/lockfile" | ||||
| 	stypes "github.com/containers/storage/types" | ||||
| 	securejoin "github.com/cyphar/filepath-securejoin" | ||||
| 	runcuser "github.com/opencontainers/runc/libcontainer/user" | ||||
| 	spec "github.com/opencontainers/runtime-spec/specs-go" | ||||
|  | @ -56,6 +57,66 @@ import ( | |||
| 	"github.com/sirupsen/logrus" | ||||
| ) | ||||
| 
 | ||||
| func parseOptionIDs(option string) ([]idtools.IDMap, error) { | ||||
| 	ranges := strings.Split(option, "#") | ||||
| 	ret := make([]idtools.IDMap, len(ranges)) | ||||
| 	for i, m := range ranges { | ||||
| 		var v idtools.IDMap | ||||
| 		_, err := fmt.Sscanf(m, "%d-%d-%d", &v.ContainerID, &v.HostID, &v.Size) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 		if v.ContainerID < 0 || v.HostID < 0 || v.Size < 1 { | ||||
| 			return nil, fmt.Errorf("invalid value for %q", option) | ||||
| 		} | ||||
| 		ret[i] = v | ||||
| 	} | ||||
| 	return ret, nil | ||||
| } | ||||
| 
 | ||||
| func parseIDMapMountOption(idMappings stypes.IDMappingOptions, option string) ([]spec.LinuxIDMapping, []spec.LinuxIDMapping, error) { | ||||
| 	uidMap := idMappings.UIDMap | ||||
| 	gidMap := idMappings.GIDMap | ||||
| 	if strings.HasPrefix(option, "idmap=") { | ||||
| 		var err error | ||||
| 		options := strings.Split(strings.SplitN(option, "=", 2)[1], ";") | ||||
| 		for _, i := range options { | ||||
| 			switch { | ||||
| 			case strings.HasPrefix(i, "uids="): | ||||
| 				uidMap, err = parseOptionIDs(strings.Replace(i, "uids=", "", 1)) | ||||
| 				if err != nil { | ||||
| 					return nil, nil, err | ||||
| 				} | ||||
| 			case strings.HasPrefix(i, "gids="): | ||||
| 				gidMap, err = parseOptionIDs(strings.Replace(i, "gids=", "", 1)) | ||||
| 				if err != nil { | ||||
| 					return nil, nil, err | ||||
| 				} | ||||
| 			default: | ||||
| 				return nil, nil, fmt.Errorf("unknown option %q", i) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	uidMappings := make([]spec.LinuxIDMapping, len(uidMap)) | ||||
| 	gidMappings := make([]spec.LinuxIDMapping, len(gidMap)) | ||||
| 	for i, uidmap := range uidMap { | ||||
| 		uidMappings[i] = spec.LinuxIDMapping{ | ||||
| 			HostID:      uint32(uidmap.ContainerID), | ||||
| 			ContainerID: uint32(uidmap.HostID), | ||||
| 			Size:        uint32(uidmap.Size), | ||||
| 		} | ||||
| 	} | ||||
| 	for i, gidmap := range gidMap { | ||||
| 		gidMappings[i] = spec.LinuxIDMapping{ | ||||
| 			HostID:      uint32(gidmap.ContainerID), | ||||
| 			ContainerID: uint32(gidmap.HostID), | ||||
| 			Size:        uint32(gidmap.Size), | ||||
| 		} | ||||
| 	} | ||||
| 	return uidMappings, gidMappings, nil | ||||
| } | ||||
| 
 | ||||
| // Internal only function which returns upper and work dir from
 | ||||
| // overlay options.
 | ||||
| func getOverlayUpperAndWorkDir(options []string) (string, string, error) { | ||||
|  | @ -217,13 +278,22 @@ func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) { | |||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	// Check if the spec file mounts contain the options z, Z or U.
 | ||||
| 	// Check if the spec file mounts contain the options z, Z, U or idmap.
 | ||||
| 	// If they have z or Z, relabel the source directory and then remove the option.
 | ||||
| 	// If they have U, chown the source directory and them remove the option.
 | ||||
| 	// If they have idmap, then calculate the mappings to use in the OCI config file.
 | ||||
| 	for i := range g.Config.Mounts { | ||||
| 		m := &g.Config.Mounts[i] | ||||
| 		var options []string | ||||
| 		for _, o := range m.Options { | ||||
| 			if o == "idmap" || strings.HasPrefix(o, "idmap=") { | ||||
| 				var err error | ||||
| 				m.UIDMappings, m.GIDMappings, err = parseIDMapMountOption(c.config.IDMappings, o) | ||||
| 				if err != nil { | ||||
| 					return nil, err | ||||
| 				} | ||||
| 				continue | ||||
| 			} | ||||
| 			switch o { | ||||
| 			case "U": | ||||
| 				if m.Type == "tmpfs" { | ||||
|  |  | |||
|  | @ -8,6 +8,8 @@ import ( | |||
| 	"runtime" | ||||
| 	"testing" | ||||
| 
 | ||||
| 	"github.com/containers/storage/pkg/idtools" | ||||
| 	stypes "github.com/containers/storage/types" | ||||
| 	rspec "github.com/opencontainers/runtime-spec/specs-go" | ||||
| 	"github.com/stretchr/testify/assert" | ||||
| ) | ||||
|  | @ -15,6 +17,100 @@ import ( | |||
| // hookPath is the path to an example hook executable.
 | ||||
| var hookPath string | ||||
| 
 | ||||
| func TestParseOptionIDs(t *testing.T) { | ||||
| 	_, err := parseOptionIDs("uids=100-200-2") | ||||
| 	assert.NotNil(t, err) | ||||
| 
 | ||||
| 	mappings, err := parseOptionIDs("100-200-2") | ||||
| 	assert.Nil(t, err) | ||||
| 	assert.NotNil(t, mappings) | ||||
| 
 | ||||
| 	assert.Equal(t, len(mappings), 1) | ||||
| 
 | ||||
| 	assert.Equal(t, mappings[0].ContainerID, 100) | ||||
| 	assert.Equal(t, mappings[0].HostID, 200) | ||||
| 	assert.Equal(t, mappings[0].Size, 2) | ||||
| 
 | ||||
| 	mappings, err = parseOptionIDs("100-200-2#300-400-5") | ||||
| 	assert.Nil(t, err) | ||||
| 	assert.NotNil(t, mappings) | ||||
| 
 | ||||
| 	assert.Equal(t, len(mappings), 2) | ||||
| 
 | ||||
| 	assert.Equal(t, mappings[0].ContainerID, 100) | ||||
| 	assert.Equal(t, mappings[0].HostID, 200) | ||||
| 	assert.Equal(t, mappings[0].Size, 2) | ||||
| 
 | ||||
| 	assert.Equal(t, mappings[1].ContainerID, 300) | ||||
| 	assert.Equal(t, mappings[1].HostID, 400) | ||||
| 	assert.Equal(t, mappings[1].Size, 5) | ||||
| } | ||||
| 
 | ||||
| func TestParseIDMapMountOption(t *testing.T) { | ||||
| 	uidMap := []idtools.IDMap{ | ||||
| 		{ | ||||
| 			ContainerID: 0, | ||||
| 			HostID:      1000, | ||||
| 			Size:        10000, | ||||
| 		}, | ||||
| 	} | ||||
| 	gidMap := []idtools.IDMap{ | ||||
| 		{ | ||||
| 			ContainerID: 0, | ||||
| 			HostID:      2000, | ||||
| 			Size:        10000, | ||||
| 		}, | ||||
| 	} | ||||
| 	options := stypes.IDMappingOptions{ | ||||
| 		UIDMap: uidMap, | ||||
| 		GIDMap: gidMap, | ||||
| 	} | ||||
| 	uids, gids, err := parseIDMapMountOption(options, "idmap") | ||||
| 	assert.Nil(t, err) | ||||
| 	assert.Equal(t, len(uids), 1) | ||||
| 	assert.Equal(t, len(gids), 1) | ||||
| 
 | ||||
| 	assert.Equal(t, uids[0].ContainerID, uint32(1000)) | ||||
| 	assert.Equal(t, uids[0].HostID, uint32(0)) | ||||
| 	assert.Equal(t, uids[0].Size, uint32(10000)) | ||||
| 
 | ||||
| 	assert.Equal(t, gids[0].ContainerID, uint32(2000)) | ||||
| 	assert.Equal(t, gids[0].HostID, uint32(0)) | ||||
| 	assert.Equal(t, gids[0].Size, uint32(10000)) | ||||
| 
 | ||||
| 	uids, gids, err = parseIDMapMountOption(options, "idmap=uids=0-1-10#10-11-10;gids=0-3-10") | ||||
| 	assert.Nil(t, err) | ||||
| 	assert.Equal(t, len(uids), 2) | ||||
| 	assert.Equal(t, len(gids), 1) | ||||
| 
 | ||||
| 	assert.Equal(t, uids[0].ContainerID, uint32(1)) | ||||
| 	assert.Equal(t, uids[0].HostID, uint32(0)) | ||||
| 	assert.Equal(t, uids[0].Size, uint32(10)) | ||||
| 
 | ||||
| 	assert.Equal(t, uids[1].ContainerID, uint32(11)) | ||||
| 	assert.Equal(t, uids[1].HostID, uint32(10)) | ||||
| 	assert.Equal(t, uids[1].Size, uint32(10)) | ||||
| 
 | ||||
| 	assert.Equal(t, gids[0].ContainerID, uint32(3)) | ||||
| 	assert.Equal(t, gids[0].HostID, uint32(0)) | ||||
| 	assert.Equal(t, gids[0].Size, uint32(10)) | ||||
| 
 | ||||
| 	_, _, err = parseIDMapMountOption(options, "idmap=uids=0-1-10#10-11-10;gids=0-3-10;foobar=bar") | ||||
| 	assert.NotNil(t, err) | ||||
| 
 | ||||
| 	_, _, err = parseIDMapMountOption(options, "idmap=uids=0-1-10#10-11-10;gids=0-3-10#0-12") | ||||
| 	assert.NotNil(t, err) | ||||
| 
 | ||||
| 	_, _, err = parseIDMapMountOption(options, "idmap=uids=0-1-10#10-11-10;gids=0-3-10#0-12--12") | ||||
| 	assert.NotNil(t, err) | ||||
| 
 | ||||
| 	_, _, err = parseIDMapMountOption(options, "idmap=uids=0-1-10#10-11-10;gids=0-3-10#-1-12-12") | ||||
| 	assert.NotNil(t, err) | ||||
| 
 | ||||
| 	_, _, err = parseIDMapMountOption(options, "idmap=uids=0-1-10#10-11-10;gids=0-3-10#0--12-0") | ||||
| 	assert.NotNil(t, err) | ||||
| } | ||||
| 
 | ||||
| func TestPostDeleteHooks(t *testing.T) { | ||||
| 	ctx := context.Background() | ||||
| 	dir := t.TempDir() | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue