Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b6dbe0a7b9 | ||
|
|
07bdad9e94 |
@@ -1583,6 +1583,11 @@ func containerSpecReading(r *declaration.Container, declares, reads map[string]s
|
||||
for _, n := range r.Networks {
|
||||
b.WriteString("also-on " + n + "\n")
|
||||
}
|
||||
// And the capabilities it was granted (ADR 0169): one gained or dropped is a different
|
||||
// container, and the runtime cannot change a running one's.
|
||||
for _, c := range r.Capabilities {
|
||||
b.WriteString("cap " + c + "\n")
|
||||
}
|
||||
// The cadence is part of what was declared, so a changed schedule is a changed spec — the marker
|
||||
// moves and the install is reported "updated" and re-established. Added only when present, so no
|
||||
// ordinary container's or run-once step's digest moves for a field it does not set.
|
||||
@@ -1777,6 +1782,9 @@ func applyContainer(ctx context.Context, r *declaration.Container, run Runner,
|
||||
if r.Network != "" {
|
||||
args = append(args, "--network", r.Network)
|
||||
}
|
||||
for _, c := range r.Capabilities {
|
||||
args = append(args, "--cap-add", c)
|
||||
}
|
||||
for _, d := range r.Dns {
|
||||
args = append(args, "--dns", d)
|
||||
}
|
||||
|
||||
@@ -134,3 +134,42 @@ func TestALeftOutModuleIsNeitherRemovedNorForgotten(t *testing.T) {
|
||||
t.Fatalf("keeping the left-out module's container was not said: %+v", report.Outcomes)
|
||||
}
|
||||
}
|
||||
|
||||
// A container's capabilities reach the runtime and are part of its spec (novox/hq ADR 0169).
|
||||
func TestACapabilityReachesTheRuntimeAndTheSpec(t *testing.T) {
|
||||
var ran []string
|
||||
run := func(_ context.Context, name string, args ...string) (string, error) {
|
||||
if name != "docker" {
|
||||
return "", errors.New("not installed")
|
||||
}
|
||||
switch args[0] {
|
||||
case "info":
|
||||
return "29.0.0\n", nil
|
||||
case "container":
|
||||
return "false\t\n", errors.New("no such container")
|
||||
case "run":
|
||||
ran = args
|
||||
return "deadbeef\n", nil
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
d := parseTrusted(t, `{"declaration":1,"resources":[
|
||||
{"id":"fw","type":"container","name":"fw","image":"`+pinned+`","network":"host","capabilities":["NET_ADMIN"]}
|
||||
]}`)
|
||||
_, _, _ = Apply(context.Background(), archHost(t), d, store.State{}, store.OriginCarried, run, nil, nil)
|
||||
granted := false
|
||||
for i, a := range ran {
|
||||
if a == "--cap-add" && i+1 < len(ran) && ran[i+1] == "NET_ADMIN" {
|
||||
granted = true
|
||||
}
|
||||
}
|
||||
if !granted {
|
||||
t.Fatalf("the capability was not granted: %v", ran)
|
||||
}
|
||||
with := d.Resources[0].(*declaration.Container)
|
||||
without := *with
|
||||
without.Capabilities = nil
|
||||
if containerSpec(with, inputs{}) == containerSpec(&without, inputs{}) {
|
||||
t.Fatal("a capability is not part of the container's spec")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -940,6 +940,12 @@ type Container struct {
|
||||
// its siblings can name before any of them can resolve anything.
|
||||
Dns []string `json:"dns,omitempty"`
|
||||
|
||||
// Capabilities are the Linux capabilities this container is granted beyond the runtime's
|
||||
// default set, by name (novox/hq ADR 0169): a holder's runtime that changes the machine's packet
|
||||
// filter asks for NET_ADMIN. Exactly these, named in the spec so a change recreates the
|
||||
// container; a privileged container stays undeclarable.
|
||||
Capabilities []string `json:"capabilities,omitempty"`
|
||||
|
||||
// Networks are networks this container also joins once created, by name — a found network a
|
||||
// per-machine setting keeps for a taken container (novox/hq ADR 0163, rule 4), so a
|
||||
// neighbour that resolves it there keeps resolving it until the neighbour is taken too.
|
||||
@@ -1039,6 +1045,12 @@ func (c *Container) validate(where string, _ bool) []string {
|
||||
"static address anywhere but a user-defined one")
|
||||
}
|
||||
}
|
||||
for _, cap := range c.Capabilities {
|
||||
if !capabilityName.MatchString(cap) {
|
||||
problems = append(problems, where+": capabilities names "+strconv.Quote(cap)+", which is not a "+
|
||||
"capability's name (CAP_NET_ADMIN or NET_ADMIN)")
|
||||
}
|
||||
}
|
||||
for _, n := range c.Networks {
|
||||
problems = append(problems, (&Network{Name: n}).validate(where+": networks", false)...)
|
||||
if n == c.Network {
|
||||
@@ -1209,6 +1221,10 @@ type Adoption struct {
|
||||
Untaken map[string][]string `json:"untaken,omitempty"`
|
||||
}
|
||||
|
||||
// capabilityName is what a Linux capability is called: upper case, underscores, an optional CAP_
|
||||
// prefix. The runtime accepts either spelling.
|
||||
var capabilityName = regexp.MustCompile(`^(CAP_)?[A-Z][A-Z0-9_]*$`)
|
||||
|
||||
// AdoptionPrefix is the id prefix of what the mesh itself declares because a node is adopted —
|
||||
// its openings and its guard. Nothing under it belongs to a module, so none of it is ever held.
|
||||
const AdoptionPrefix = "adoption."
|
||||
|
||||
@@ -504,3 +504,23 @@ func TestKeptNetworksAndLeftOutModulesAreReadStrictly(t *testing.T) {
|
||||
t.Fatalf("a carried bundle leaving modules out was accepted: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// A container may ask for a capability by name, and nothing else (novox/hq ADR 0169).
|
||||
func TestACapabilityIsNamedOrRefused(t *testing.T) {
|
||||
image := "postgres@sha256:" + strings.Repeat("a", 64)
|
||||
d, err := Parse([]byte(`{"declaration":1,"resources":[
|
||||
{"id":"fw","type":"container","name":"fw","image":"` + image + `","network":"host","capabilities":["NET_ADMIN","CAP_NET_RAW"]}
|
||||
]}`))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got := d.Resources[0].(*Container).Capabilities; len(got) != 2 || got[0] != "NET_ADMIN" {
|
||||
t.Fatalf("capabilities read as %v", got)
|
||||
}
|
||||
for _, bad := range []string{`"net_admin"`, `"ALL;rm -rf /"`, `"privileged"`} {
|
||||
if _, err := Parse([]byte(`{"declaration":1,"resources":[
|
||||
{"id":"fw","type":"container","name":"fw","image":"` + image + `","capabilities":[` + bad + `]}]}`)); err == nil {
|
||||
t.Errorf("%s was accepted as a capability", bad)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user