214 lines
6.9 KiB
Go
214 lines
6.9 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
package capabilities
|
|
|
|
import (
|
|
"fmt"
|
|
"regexp"
|
|
|
|
"github.com/syndtr/gocapability/capability"
|
|
)
|
|
|
|
const (
|
|
// HCLSpecLiteral is an equivalent list to NomadDefaults, expressed as a literal
|
|
// HCL string for use in HCL config parsing.
|
|
HCLSpecLiteral = `["AUDIT_WRITE","CHOWN","DAC_OVERRIDE","FOWNER","FSETID","KILL","MKNOD","NET_BIND_SERVICE","SETFCAP","SETGID","SETPCAP","SETUID","SYS_CHROOT"]`
|
|
)
|
|
|
|
var (
|
|
extractLiteral = regexp.MustCompile(`([\w]+)`)
|
|
)
|
|
|
|
// NomadDefaults is the set of Linux capabilities that Nomad enables by
|
|
// default. This list originates from what Docker enabled by default, but then
|
|
// excludes NET_RAW for security reasons.
|
|
//
|
|
// This set is use in the as HCL configuration default, described by HCLSpecLiteral.
|
|
func NomadDefaults() *Set {
|
|
return New(extractLiteral.FindAllString(HCLSpecLiteral, -1))
|
|
}
|
|
|
|
// DockerDefaults is a list of Linux capabilities enabled by Docker by default
|
|
// and is used to compute the set of capabilities to add/drop given docker driver
|
|
// configuration.
|
|
//
|
|
// https://docs.docker.com/engine/reference/run/#runtime-privilege-and-linux-capabilities
|
|
func DockerDefaults() *Set {
|
|
defaults := NomadDefaults()
|
|
defaults.Add("NET_RAW")
|
|
return defaults
|
|
}
|
|
|
|
// Supported returns the set of capabilities supported by the operating system.
|
|
//
|
|
// This set will expand over time as new capabilities are introduced to the kernel
|
|
// and the capability library is updated (which tends to happen to keep up with
|
|
// run-container libraries).
|
|
//
|
|
// Defers to a library generated from
|
|
// https://github.com/torvalds/linux/blob/master/include/uapi/linux/capability.h
|
|
func Supported() *Set {
|
|
s := New(nil)
|
|
|
|
last := capability.CAP_LAST_CAP
|
|
|
|
// workaround for RHEL6 which has no /proc/sys/kernel/cap_last_cap
|
|
if last == capability.Cap(63) {
|
|
last = capability.CAP_BLOCK_SUSPEND
|
|
}
|
|
|
|
// accumulate every capability supported by this system
|
|
for _, c := range capability.List() {
|
|
if c > last {
|
|
continue
|
|
}
|
|
s.Add(c.String())
|
|
}
|
|
|
|
return s
|
|
}
|
|
|
|
// LegacySupported returns the historical set of capabilities used when a task is
|
|
// configured to run as root using the exec task driver. Older versions of Nomad
|
|
// always allowed the root user to make use of any capability. Now that the exec
|
|
// task driver supports configuring the allowed capabilities, operators are
|
|
// encouraged to explicitly opt-in to capabilities beyond this legacy set. We
|
|
// maintain the legacy list here, because previous versions of Nomad deferred to
|
|
// the capability.List library function, which adds new capabilities over time.
|
|
//
|
|
// https://github.com/hashicorp/nomad/blob/v1.0.4/vendor/github.com/syndtr/gocapability/capability/enum_gen.go#L88
|
|
func LegacySupported() *Set {
|
|
return New([]string{
|
|
"CAP_CHOWN",
|
|
"CAP_DAC_OVERRIDE",
|
|
"CAP_DAC_READ_SEARCH",
|
|
"CAP_FOWNER",
|
|
"CAP_FSETID",
|
|
"CAP_KILL",
|
|
"CAP_SETGID",
|
|
"CAP_SETUID",
|
|
"CAP_SETPCAP",
|
|
"CAP_LINUX_IMMUTABLE",
|
|
"CAP_NET_BIND_SERVICE",
|
|
"CAP_NET_BROADCAST",
|
|
"CAP_NET_ADMIN",
|
|
"CAP_NET_RAW",
|
|
"CAP_IPC_LOCK",
|
|
"CAP_IPC_OWNER",
|
|
"CAP_SYS_MODULE",
|
|
"CAP_SYS_RAWIO",
|
|
"CAP_SYS_CHROOT",
|
|
"CAP_SYS_PTRACE",
|
|
"CAP_SYS_PACCT",
|
|
"CAP_SYS_ADMIN",
|
|
"CAP_SYS_BOOT",
|
|
"CAP_SYS_NICE",
|
|
"CAP_SYS_RESOURCE",
|
|
"CAP_SYS_TIME",
|
|
"CAP_SYS_TTY_CONFIG",
|
|
"CAP_MKNOD",
|
|
"CAP_LEASE",
|
|
"CAP_AUDIT_WRITE",
|
|
"CAP_AUDIT_CONTROL",
|
|
"CAP_SETFCAP",
|
|
"CAP_MAC_OVERRIDE",
|
|
"CAP_MAC_ADMIN",
|
|
"CAP_SYSLOG",
|
|
"CAP_WAKE_ALARM",
|
|
"CAP_BLOCK_SUSPEND",
|
|
"CAP_AUDIT_READ",
|
|
})
|
|
}
|
|
|
|
// Calculate the resulting set of linux capabilities to enable for a task, taking
|
|
// into account:
|
|
// - default capability basis
|
|
// - driver allowable capabilities
|
|
// - task capability drops
|
|
// - task capability adds
|
|
//
|
|
// Nomad establishes a standard set of enabled capabilities allowed by the task
|
|
// driver if allow_caps is not set. This is the same set that the task will be
|
|
// enabled with by default if allow_caps does not further reduce permissions,
|
|
// in which case the task capabilities will also be reduced accordingly.
|
|
//
|
|
// The task will drop any capabilities specified in cap_drop, and add back
|
|
// capabilities specified in cap_add. The task will not be allowed to add capabilities
|
|
// not set in the the allow_caps setting (which by default is the same as the basis).
|
|
//
|
|
// cap_add takes precedence over cap_drop, enabling the common pattern of dropping
|
|
// all capabilities, then adding back the desired smaller set. e.g.
|
|
//
|
|
// cap_drop = ["all"]
|
|
// cap_add = ["chown", "kill"]
|
|
//
|
|
// Note that the resulting capability names are upper-cased and prefixed with
|
|
// "CAP_", which is the expected input for the exec/java driver implementation.
|
|
func Calculate(basis *Set, allowCaps, capAdd, capDrop []string) ([]string, error) {
|
|
allow := New(allowCaps)
|
|
adds := New(capAdd)
|
|
|
|
// determine caps the task wants that are not allowed
|
|
missing := allow.Difference(adds)
|
|
if !missing.Empty() {
|
|
return nil, fmt.Errorf("driver does not allow the following capabilities: %s", missing)
|
|
}
|
|
|
|
// the realized enabled capabilities starts with what is allowed both by driver
|
|
// config AND is a member of the basis (i.e. nomad defaults)
|
|
result := basis.Intersect(allow)
|
|
|
|
// then remove capabilities the task explicitly drops
|
|
result.Remove(capDrop)
|
|
|
|
// then add back capabilities the task explicitly adds
|
|
return result.Union(adds).Slice(true), nil
|
|
}
|
|
|
|
// Delta calculates the set of capabilities that must be added and dropped relative
|
|
// to a basis to achieve a desired result. The use case is that the docker driver
|
|
// assumes a default set (DockerDefault), and we must calculate what to pass into
|
|
// --cap-add and --cap-drop on container creation given the inputs of the docker
|
|
// plugin config for allow_caps, and the docker task configuration for cap_add and
|
|
// cap_drop. Note that the user provided cap_add and cap_drop settings are always
|
|
// included, even if they are redundant with the basis (maintaining existing
|
|
// behavior, working with existing tests).
|
|
//
|
|
// Note that the resulting capability names are lower-cased and not prefixed with
|
|
// "CAP_", which is the existing style used with the docker driver implementation.
|
|
func Delta(basis *Set, allowCaps, capAdd, capDrop []string) ([]string, []string, error) {
|
|
all := func(caps []string) bool {
|
|
for _, c := range caps {
|
|
if normalize(c) == "all" {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// set of caps allowed by driver
|
|
allow := New(allowCaps)
|
|
|
|
// determine caps the task wants that are not allowed
|
|
missing := allow.Difference(New(capAdd))
|
|
if !missing.Empty() {
|
|
return nil, nil, fmt.Errorf("driver does not allow the following capabilities: %s", missing)
|
|
}
|
|
|
|
// add what the task is asking for
|
|
add := New(capAdd).Slice(false)
|
|
if all(capAdd) {
|
|
add = []string{"all"}
|
|
}
|
|
|
|
// drop what the task removes plus whatever is in the basis that is not
|
|
// in the driver allow configuration
|
|
drop := New(allowCaps).Difference(basis).Union(New(capDrop)).Slice(false)
|
|
if all(capDrop) {
|
|
drop = []string{"all"}
|
|
}
|
|
|
|
return add, drop, nil
|
|
}
|