mirror of
https://github.com/moby/moby.git
synced 2026-01-11 18:51:37 +00:00
client: ContainerCreate: normalize CapAdd, CapDrop capabilities
Before this change, capabilities would be sent un-normalized, un-sorted,
and could contain duplicates;
docker create --name foo --cap-add SYS_ADMIN --cap-add sys_admin --cap-add cap_sys_admin --cap-add ALL busybox
docker container inspect --format '{{json .HostConfig.CapAdd }}' foo
["SYS_ADMIN","sys_admin","cap_sys_admin","ALL"]
After this change, capabilities are sent in their normalized form, sorted,
and with duplicates removed;
docker create --name foo --cap-add SYS_ADMIN --cap-add sys_admin --cap-add cap_sys_admin --cap-add ALL busybox
docker container inspect --format '{{json .HostConfig.CapAdd }}' foo
["ALL", "CAP_SYS_ADMIN"]
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
This commit is contained in:
@@ -5,6 +5,8 @@ import (
|
||||
"encoding/json"
|
||||
"net/url"
|
||||
"path"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types/network"
|
||||
@@ -52,6 +54,9 @@ func (cli *Client) ContainerCreate(ctx context.Context, config *container.Config
|
||||
// When using API under 1.42, the Linux daemon doesn't respect the ConsoleSize
|
||||
hostConfig.ConsoleSize = [2]uint{0, 0}
|
||||
}
|
||||
|
||||
hostConfig.CapAdd = normalizeCapabilities(hostConfig.CapAdd)
|
||||
hostConfig.CapDrop = normalizeCapabilities(hostConfig.CapDrop)
|
||||
}
|
||||
|
||||
// Since API 1.44, the container-wide MacAddress is deprecated and will trigger a WARNING if it's specified.
|
||||
@@ -108,3 +113,42 @@ func hasEndpointSpecificMacAddress(networkingConfig *network.NetworkingConfig) b
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// allCapabilities is a magic value for "all capabilities"
|
||||
const allCapabilities = "ALL"
|
||||
|
||||
// normalizeCapabilities normalizes capabilities to their canonical form,
|
||||
// removes duplicates, and sorts the results.
|
||||
//
|
||||
// It is similar to [github.com/docker/docker/oci/caps.NormalizeLegacyCapabilities],
|
||||
// but performs no validation based on supported capabilities.
|
||||
func normalizeCapabilities(caps []string) []string {
|
||||
var normalized []string
|
||||
|
||||
unique := make(map[string]struct{})
|
||||
for _, c := range caps {
|
||||
c = normalizeCap(c)
|
||||
if _, ok := unique[c]; ok {
|
||||
continue
|
||||
}
|
||||
unique[c] = struct{}{}
|
||||
normalized = append(normalized, c)
|
||||
}
|
||||
|
||||
sort.Strings(normalized)
|
||||
return normalized
|
||||
}
|
||||
|
||||
// normalizeCap normalizes a capability to its canonical format by upper-casing
|
||||
// and adding a "CAP_" prefix (if not yet present). It also accepts the "ALL"
|
||||
// magic-value.
|
||||
func normalizeCap(cap string) string {
|
||||
cap = strings.ToUpper(cap)
|
||||
if cap == allCapabilities {
|
||||
return cap
|
||||
}
|
||||
if !strings.HasPrefix(cap, "CAP_") {
|
||||
cap = "CAP_" + cap
|
||||
}
|
||||
return cap
|
||||
}
|
||||
|
||||
@@ -125,3 +125,52 @@ func TestContainerCreateConnectionError(t *testing.T) {
|
||||
_, err = client.ContainerCreate(context.Background(), nil, nil, nil, nil, "")
|
||||
assert.Check(t, is.ErrorType(err, IsErrConnectionFailed))
|
||||
}
|
||||
|
||||
// TestContainerCreateCapabilities verifies that CapAdd and CapDrop capabilities
|
||||
// are normalized to their canonical form.
|
||||
func TestContainerCreateCapabilities(t *testing.T) {
|
||||
inputCaps := []string{
|
||||
"all",
|
||||
"ALL",
|
||||
"capability_b",
|
||||
"capability_a",
|
||||
"capability_c",
|
||||
"CAPABILITY_D",
|
||||
"CAP_CAPABILITY_D",
|
||||
}
|
||||
|
||||
expectedCaps := []string{
|
||||
"ALL",
|
||||
"CAP_CAPABILITY_A",
|
||||
"CAP_CAPABILITY_B",
|
||||
"CAP_CAPABILITY_C",
|
||||
"CAP_CAPABILITY_D",
|
||||
}
|
||||
|
||||
client := &Client{
|
||||
client: newMockClient(func(req *http.Request) (*http.Response, error) {
|
||||
var config container.CreateRequest
|
||||
|
||||
if err := json.NewDecoder(req.Body).Decode(&config); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
assert.Check(t, is.DeepEqual([]string(config.HostConfig.CapAdd), expectedCaps))
|
||||
assert.Check(t, is.DeepEqual([]string(config.HostConfig.CapDrop), expectedCaps))
|
||||
|
||||
b, err := json.Marshal(container.CreateResponse{
|
||||
ID: "container_id",
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &http.Response{
|
||||
StatusCode: http.StatusOK,
|
||||
Body: io.NopCloser(bytes.NewReader(b)),
|
||||
}, nil
|
||||
}),
|
||||
version: "1.24",
|
||||
}
|
||||
|
||||
_, err := client.ContainerCreate(context.Background(), nil, &container.HostConfig{CapAdd: inputCaps, CapDrop: inputCaps}, nil, nil, "")
|
||||
assert.NilError(t, err)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user