A container may log to the journal (hq ADR 0179)
A jail reads a log; a container's output went to a file of the runtime's own under a path that changes on recreate, so no jail could read a container's service. logging: journald runs the container with the journal as its driver, named in the spec so moving it recreates it; any other place is refused.
This commit is contained in:
@@ -1607,6 +1607,10 @@ func containerSpecReading(r *declaration.Container, declares, reads map[string]s
|
|||||||
for _, c := range r.Capabilities {
|
for _, c := range r.Capabilities {
|
||||||
b.WriteString("cap " + c + "\n")
|
b.WriteString("cap " + c + "\n")
|
||||||
}
|
}
|
||||||
|
// And where it logs (ADR 0179): the runtime cannot move a running container's output.
|
||||||
|
if r.Logging != "" {
|
||||||
|
b.WriteString("log " + r.Logging + "\n")
|
||||||
|
}
|
||||||
// The cadence is part of what was declared, so a changed schedule is a changed spec — the marker
|
// 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
|
// 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.
|
// ordinary container's or run-once step's digest moves for a field it does not set.
|
||||||
@@ -1804,6 +1808,11 @@ func applyContainer(ctx context.Context, r *declaration.Container, run Runner,
|
|||||||
for _, c := range r.Capabilities {
|
for _, c := range r.Capabilities {
|
||||||
args = append(args, "--cap-add", c)
|
args = append(args, "--cap-add", c)
|
||||||
}
|
}
|
||||||
|
if r.Logging != "" {
|
||||||
|
// The journal keeps the container's name on every line (CONTAINER_NAME), which is what a
|
||||||
|
// jail matches on (novox/hq ADR 0179); `docker logs` keeps working against the journal.
|
||||||
|
args = append(args, "--log-driver", r.Logging)
|
||||||
|
}
|
||||||
for _, d := range r.Dns {
|
for _, d := range r.Dns {
|
||||||
args = append(args, "--dns", d)
|
args = append(args, "--dns", d)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
package apply
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/novox/mesh-host/internal/declaration"
|
||||||
|
"github.com/novox/mesh-host/internal/store"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A container declared to log to the journal is run with the journal as its log driver, and the
|
||||||
|
// place it logs is part of its spec, so moving it recreates the container (novox/hq ADR 0179).
|
||||||
|
func TestAContainerLoggingToTheJournalIsRunThatWayAndRecreatedWhenMoved(t *testing.T) {
|
||||||
|
pinned := "postgres@sha256:" + strings.Repeat("a", 64)
|
||||||
|
var ran []string
|
||||||
|
run := func(_ context.Context, cmd string, args ...string) (string, error) {
|
||||||
|
if cmd == "docker" && len(args) > 0 && args[0] == "run" {
|
||||||
|
ran = args
|
||||||
|
return "deadbeef\n", nil
|
||||||
|
}
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
d := parseTrusted(t, `{"declaration":1,"resources":[
|
||||||
|
{"id":"front","type":"container","name":"front","image":"`+pinned+`","logging":"journald"}
|
||||||
|
]}`)
|
||||||
|
_, _, _ = Apply(context.Background(), archHost(t), d, store.State{}, store.OriginCarried, run, nil, nil)
|
||||||
|
sent := false
|
||||||
|
for i, a := range ran {
|
||||||
|
if a == "--log-driver" && i+1 < len(ran) && ran[i+1] == "journald" {
|
||||||
|
sent = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !sent {
|
||||||
|
t.Fatalf("the container's output was not sent to the journal: %v", ran)
|
||||||
|
}
|
||||||
|
with := d.Resources[0].(*declaration.Container)
|
||||||
|
without := *with
|
||||||
|
without.Logging = ""
|
||||||
|
if containerSpec(with, inputs{}) == containerSpec(&without, inputs{}) {
|
||||||
|
t.Fatal("where a container logs is not part of its spec, so moving it would not recreate it")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -952,6 +952,14 @@ type Container struct {
|
|||||||
// container; a privileged container stays undeclarable.
|
// container; a privileged container stays undeclarable.
|
||||||
Capabilities []string `json:"capabilities,omitempty"`
|
Capabilities []string `json:"capabilities,omitempty"`
|
||||||
|
|
||||||
|
// Logging names where the runtime sends this container's output: "journald" sends it to the
|
||||||
|
// machine's journal, under the container's name, where what reads the machine's logs — its
|
||||||
|
// intrusion prevention first of all (novox/hq ADR 0179) — can read it the way it reads the
|
||||||
|
// machine's own services. Empty keeps the runtime's default, which is a file of the runtime's
|
||||||
|
// own that nothing but the runtime reads. Part of the spec: a container that logs elsewhere
|
||||||
|
// is a different container, and the runtime cannot change a running one's driver.
|
||||||
|
Logging string `json:"logging,omitempty"`
|
||||||
|
|
||||||
// Networks are networks this container also joins once created, by name — a found network a
|
// 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
|
// 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.
|
// neighbour that resolves it there keeps resolving it until the neighbour is taken too.
|
||||||
@@ -1057,6 +1065,10 @@ func (c *Container) validate(where string, _ bool) []string {
|
|||||||
"capability's name (CAP_NET_ADMIN or NET_ADMIN)")
|
"capability's name (CAP_NET_ADMIN or NET_ADMIN)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if c.Logging != "" && c.Logging != "journald" {
|
||||||
|
problems = append(problems, where+": logging is "+strconv.Quote(c.Logging)+", and the only place a "+
|
||||||
|
"container's output can be sent besides the runtime's own file is \"journald\"")
|
||||||
|
}
|
||||||
for _, n := range c.Networks {
|
for _, n := range c.Networks {
|
||||||
problems = append(problems, (&Network{Name: n}).validate(where+": networks", false)...)
|
problems = append(problems, (&Network{Name: n}).validate(where+": networks", false)...)
|
||||||
if n == c.Network {
|
if n == c.Network {
|
||||||
|
|||||||
@@ -524,3 +524,24 @@ func TestACapabilityIsNamedOrRefused(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A container may send its output to the machine's journal, and nowhere else but the runtime's own
|
||||||
|
// file (novox/hq ADR 0179): what reads the machine's logs then reads the container's too.
|
||||||
|
func TestAContainerMayLogToTheJournalAndNowhereElse(t *testing.T) {
|
||||||
|
image := "postgres@sha256:" + strings.Repeat("a", 64)
|
||||||
|
d, err := Parse([]byte(`{"declaration":1,"resources":[
|
||||||
|
{"id":"front","type":"container","name":"front","image":"` + image + `","logging":"journald"}
|
||||||
|
]}`))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if got := d.Resources[0].(*Container).Logging; got != "journald" {
|
||||||
|
t.Fatalf("logging read as %q", got)
|
||||||
|
}
|
||||||
|
for _, bad := range []string{`"syslog"`, `"none"`, `"json-file"`} {
|
||||||
|
if _, err := Parse([]byte(`{"declaration":1,"resources":[
|
||||||
|
{"id":"front","type":"container","name":"front","image":"` + image + `","logging":` + bad + `}]}`)); err == nil {
|
||||||
|
t.Errorf("%s was accepted as a place to log", bad)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user