Service cap-add/cap-drop: improve handling of combinations and special "ALL" value
When creating and updating services, we need to avoid unneeded service churn.
The interaction of separate lists to "add" and "drop" capabilities, a special
("ALL") capability, as well as a "relaxed" format for accepted capabilities
(case-insensitive, `CAP_` prefix optional) make this rather involved.
This patch updates how we handle `--cap-add` / `--cap-drop` when _creating_ as
well as _updating_, with the following rules/assumptions applied:
- both existing (service spec) and new (values passed through flags or in
the compose-file) are normalized and de-duplicated before use.
- the special "ALL" capability is equivalent to "all capabilities" and taken
into account when normalizing capabilities. Combining "ALL" capabilities
and other capabilities is therefore equivalent to just specifying "ALL".
- adding capabilities takes precedence over dropping, which means that if
a capability is both set to be "dropped" and to be "added", it is removed
from the list to "drop".
- the final lists should be sorted and normalized to reduce service churn
- no validation of capabilities is handled by the client. Validation is
delegated to the daemon/server.
When deploying a service using a docker-compose file, the docker-compose file
is *mostly* handled as being "declarative". However, many of the issues outlined
above also apply to compose-files, so similar handling is applied to compose
files as well to prevent service churn.
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2020-08-25 07:03:06 -04:00
|
|
|
package opts
|
|
|
|
|
|
|
|
import (
|
|
|
|
"sort"
|
|
|
|
"strings"
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
// AllCapabilities is a special value to add or drop all capabilities
|
|
|
|
AllCapabilities = "ALL"
|
2020-08-26 11:50:14 -04:00
|
|
|
|
|
|
|
// ResetCapabilities is a special value to reset capabilities when updating.
|
|
|
|
// This value should only be used when updating, not used on "create".
|
|
|
|
ResetCapabilities = "RESET"
|
Service cap-add/cap-drop: improve handling of combinations and special "ALL" value
When creating and updating services, we need to avoid unneeded service churn.
The interaction of separate lists to "add" and "drop" capabilities, a special
("ALL") capability, as well as a "relaxed" format for accepted capabilities
(case-insensitive, `CAP_` prefix optional) make this rather involved.
This patch updates how we handle `--cap-add` / `--cap-drop` when _creating_ as
well as _updating_, with the following rules/assumptions applied:
- both existing (service spec) and new (values passed through flags or in
the compose-file) are normalized and de-duplicated before use.
- the special "ALL" capability is equivalent to "all capabilities" and taken
into account when normalizing capabilities. Combining "ALL" capabilities
and other capabilities is therefore equivalent to just specifying "ALL".
- adding capabilities takes precedence over dropping, which means that if
a capability is both set to be "dropped" and to be "added", it is removed
from the list to "drop".
- the final lists should be sorted and normalized to reduce service churn
- no validation of capabilities is handled by the client. Validation is
delegated to the daemon/server.
When deploying a service using a docker-compose file, the docker-compose file
is *mostly* handled as being "declarative". However, many of the issues outlined
above also apply to compose-files, so similar handling is applied to compose
files as well to prevent service churn.
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2020-08-25 07:03:06 -04:00
|
|
|
)
|
|
|
|
|
|
|
|
// NormalizeCapability normalizes a capability by upper-casing, trimming white space
|
|
|
|
// and adding a CAP_ prefix (if not yet present). This function also accepts the
|
|
|
|
// "ALL" magic-value, as used by CapAdd/CapDrop.
|
|
|
|
//
|
|
|
|
// This function only handles rudimentary formatting; no validation is performed,
|
|
|
|
// as the list of available capabilities can be updated over time, thus should be
|
|
|
|
// handled by the daemon.
|
2023-03-29 10:01:56 -04:00
|
|
|
func NormalizeCapability(capability string) string {
|
|
|
|
capability = strings.ToUpper(strings.TrimSpace(capability))
|
|
|
|
if capability == AllCapabilities || capability == ResetCapabilities {
|
|
|
|
return capability
|
Service cap-add/cap-drop: improve handling of combinations and special "ALL" value
When creating and updating services, we need to avoid unneeded service churn.
The interaction of separate lists to "add" and "drop" capabilities, a special
("ALL") capability, as well as a "relaxed" format for accepted capabilities
(case-insensitive, `CAP_` prefix optional) make this rather involved.
This patch updates how we handle `--cap-add` / `--cap-drop` when _creating_ as
well as _updating_, with the following rules/assumptions applied:
- both existing (service spec) and new (values passed through flags or in
the compose-file) are normalized and de-duplicated before use.
- the special "ALL" capability is equivalent to "all capabilities" and taken
into account when normalizing capabilities. Combining "ALL" capabilities
and other capabilities is therefore equivalent to just specifying "ALL".
- adding capabilities takes precedence over dropping, which means that if
a capability is both set to be "dropped" and to be "added", it is removed
from the list to "drop".
- the final lists should be sorted and normalized to reduce service churn
- no validation of capabilities is handled by the client. Validation is
delegated to the daemon/server.
When deploying a service using a docker-compose file, the docker-compose file
is *mostly* handled as being "declarative". However, many of the issues outlined
above also apply to compose-files, so similar handling is applied to compose
files as well to prevent service churn.
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2020-08-25 07:03:06 -04:00
|
|
|
}
|
2023-03-29 10:01:56 -04:00
|
|
|
if !strings.HasPrefix(capability, "CAP_") {
|
|
|
|
capability = "CAP_" + capability
|
Service cap-add/cap-drop: improve handling of combinations and special "ALL" value
When creating and updating services, we need to avoid unneeded service churn.
The interaction of separate lists to "add" and "drop" capabilities, a special
("ALL") capability, as well as a "relaxed" format for accepted capabilities
(case-insensitive, `CAP_` prefix optional) make this rather involved.
This patch updates how we handle `--cap-add` / `--cap-drop` when _creating_ as
well as _updating_, with the following rules/assumptions applied:
- both existing (service spec) and new (values passed through flags or in
the compose-file) are normalized and de-duplicated before use.
- the special "ALL" capability is equivalent to "all capabilities" and taken
into account when normalizing capabilities. Combining "ALL" capabilities
and other capabilities is therefore equivalent to just specifying "ALL".
- adding capabilities takes precedence over dropping, which means that if
a capability is both set to be "dropped" and to be "added", it is removed
from the list to "drop".
- the final lists should be sorted and normalized to reduce service churn
- no validation of capabilities is handled by the client. Validation is
delegated to the daemon/server.
When deploying a service using a docker-compose file, the docker-compose file
is *mostly* handled as being "declarative". However, many of the issues outlined
above also apply to compose-files, so similar handling is applied to compose
files as well to prevent service churn.
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2020-08-25 07:03:06 -04:00
|
|
|
}
|
2023-03-29 10:01:56 -04:00
|
|
|
return capability
|
Service cap-add/cap-drop: improve handling of combinations and special "ALL" value
When creating and updating services, we need to avoid unneeded service churn.
The interaction of separate lists to "add" and "drop" capabilities, a special
("ALL") capability, as well as a "relaxed" format for accepted capabilities
(case-insensitive, `CAP_` prefix optional) make this rather involved.
This patch updates how we handle `--cap-add` / `--cap-drop` when _creating_ as
well as _updating_, with the following rules/assumptions applied:
- both existing (service spec) and new (values passed through flags or in
the compose-file) are normalized and de-duplicated before use.
- the special "ALL" capability is equivalent to "all capabilities" and taken
into account when normalizing capabilities. Combining "ALL" capabilities
and other capabilities is therefore equivalent to just specifying "ALL".
- adding capabilities takes precedence over dropping, which means that if
a capability is both set to be "dropped" and to be "added", it is removed
from the list to "drop".
- the final lists should be sorted and normalized to reduce service churn
- no validation of capabilities is handled by the client. Validation is
delegated to the daemon/server.
When deploying a service using a docker-compose file, the docker-compose file
is *mostly* handled as being "declarative". However, many of the issues outlined
above also apply to compose-files, so similar handling is applied to compose
files as well to prevent service churn.
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2020-08-25 07:03:06 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// CapabilitiesMap normalizes the given capabilities and converts them to a map.
|
|
|
|
func CapabilitiesMap(caps []string) map[string]bool {
|
|
|
|
normalized := make(map[string]bool)
|
|
|
|
for _, c := range caps {
|
|
|
|
normalized[NormalizeCapability(c)] = true
|
|
|
|
}
|
|
|
|
return normalized
|
|
|
|
}
|
|
|
|
|
|
|
|
// EffectiveCapAddCapDrop normalizes and sorts capabilities to "add" and "drop",
|
|
|
|
// and returns the effective capabilities to include in both.
|
|
|
|
//
|
|
|
|
// "CapAdd" takes precedence over "CapDrop", so capabilities included in both
|
|
|
|
// lists are removed from the list of capabilities to drop. The special "ALL"
|
|
|
|
// capability is also taken into account.
|
|
|
|
//
|
2020-08-26 11:50:14 -04:00
|
|
|
// Note that the special "RESET" value is only used when updating an existing
|
|
|
|
// service, and will be ignored.
|
|
|
|
//
|
Service cap-add/cap-drop: improve handling of combinations and special "ALL" value
When creating and updating services, we need to avoid unneeded service churn.
The interaction of separate lists to "add" and "drop" capabilities, a special
("ALL") capability, as well as a "relaxed" format for accepted capabilities
(case-insensitive, `CAP_` prefix optional) make this rather involved.
This patch updates how we handle `--cap-add` / `--cap-drop` when _creating_ as
well as _updating_, with the following rules/assumptions applied:
- both existing (service spec) and new (values passed through flags or in
the compose-file) are normalized and de-duplicated before use.
- the special "ALL" capability is equivalent to "all capabilities" and taken
into account when normalizing capabilities. Combining "ALL" capabilities
and other capabilities is therefore equivalent to just specifying "ALL".
- adding capabilities takes precedence over dropping, which means that if
a capability is both set to be "dropped" and to be "added", it is removed
from the list to "drop".
- the final lists should be sorted and normalized to reduce service churn
- no validation of capabilities is handled by the client. Validation is
delegated to the daemon/server.
When deploying a service using a docker-compose file, the docker-compose file
is *mostly* handled as being "declarative". However, many of the issues outlined
above also apply to compose-files, so similar handling is applied to compose
files as well to prevent service churn.
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2020-08-25 07:03:06 -04:00
|
|
|
// Duplicates are removed, and the resulting lists are sorted.
|
|
|
|
func EffectiveCapAddCapDrop(add, drop []string) (capAdd, capDrop []string) {
|
|
|
|
var (
|
|
|
|
addCaps = CapabilitiesMap(add)
|
|
|
|
dropCaps = CapabilitiesMap(drop)
|
|
|
|
)
|
|
|
|
|
|
|
|
if addCaps[AllCapabilities] {
|
|
|
|
// Special case: "ALL capabilities" trumps any other capability added.
|
|
|
|
addCaps = map[string]bool{AllCapabilities: true}
|
|
|
|
}
|
|
|
|
if dropCaps[AllCapabilities] {
|
|
|
|
// Special case: "ALL capabilities" trumps any other capability added.
|
|
|
|
dropCaps = map[string]bool{AllCapabilities: true}
|
|
|
|
}
|
|
|
|
for c := range dropCaps {
|
|
|
|
if addCaps[c] {
|
|
|
|
// Adding a capability takes precedence, so skip dropping
|
|
|
|
continue
|
|
|
|
}
|
2020-08-26 11:50:14 -04:00
|
|
|
if c != ResetCapabilities {
|
|
|
|
capDrop = append(capDrop, c)
|
|
|
|
}
|
Service cap-add/cap-drop: improve handling of combinations and special "ALL" value
When creating and updating services, we need to avoid unneeded service churn.
The interaction of separate lists to "add" and "drop" capabilities, a special
("ALL") capability, as well as a "relaxed" format for accepted capabilities
(case-insensitive, `CAP_` prefix optional) make this rather involved.
This patch updates how we handle `--cap-add` / `--cap-drop` when _creating_ as
well as _updating_, with the following rules/assumptions applied:
- both existing (service spec) and new (values passed through flags or in
the compose-file) are normalized and de-duplicated before use.
- the special "ALL" capability is equivalent to "all capabilities" and taken
into account when normalizing capabilities. Combining "ALL" capabilities
and other capabilities is therefore equivalent to just specifying "ALL".
- adding capabilities takes precedence over dropping, which means that if
a capability is both set to be "dropped" and to be "added", it is removed
from the list to "drop".
- the final lists should be sorted and normalized to reduce service churn
- no validation of capabilities is handled by the client. Validation is
delegated to the daemon/server.
When deploying a service using a docker-compose file, the docker-compose file
is *mostly* handled as being "declarative". However, many of the issues outlined
above also apply to compose-files, so similar handling is applied to compose
files as well to prevent service churn.
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2020-08-25 07:03:06 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
for c := range addCaps {
|
2020-08-26 11:50:14 -04:00
|
|
|
if c != ResetCapabilities {
|
|
|
|
capAdd = append(capAdd, c)
|
|
|
|
}
|
Service cap-add/cap-drop: improve handling of combinations and special "ALL" value
When creating and updating services, we need to avoid unneeded service churn.
The interaction of separate lists to "add" and "drop" capabilities, a special
("ALL") capability, as well as a "relaxed" format for accepted capabilities
(case-insensitive, `CAP_` prefix optional) make this rather involved.
This patch updates how we handle `--cap-add` / `--cap-drop` when _creating_ as
well as _updating_, with the following rules/assumptions applied:
- both existing (service spec) and new (values passed through flags or in
the compose-file) are normalized and de-duplicated before use.
- the special "ALL" capability is equivalent to "all capabilities" and taken
into account when normalizing capabilities. Combining "ALL" capabilities
and other capabilities is therefore equivalent to just specifying "ALL".
- adding capabilities takes precedence over dropping, which means that if
a capability is both set to be "dropped" and to be "added", it is removed
from the list to "drop".
- the final lists should be sorted and normalized to reduce service churn
- no validation of capabilities is handled by the client. Validation is
delegated to the daemon/server.
When deploying a service using a docker-compose file, the docker-compose file
is *mostly* handled as being "declarative". However, many of the issues outlined
above also apply to compose-files, so similar handling is applied to compose
files as well to prevent service churn.
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2020-08-25 07:03:06 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
sort.Strings(capAdd)
|
|
|
|
sort.Strings(capDrop)
|
|
|
|
|
|
|
|
return capAdd, capDrop
|
|
|
|
}
|