Compare commits
6
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2a332b0e6e | ||
|
|
b94e0f9a77 | ||
|
|
b840ce0a77 | ||
|
|
286865dfa7 | ||
|
|
ca7c4a5915 | ||
|
|
b30d9c5b5a |
+47
-32
@@ -58,8 +58,6 @@ type Outcome struct {
|
|||||||
kept string
|
kept string
|
||||||
// stateless is a service whose unit's lifecycle is the machine's (novox/hq ADR 0117).
|
// stateless is a service whose unit's lifecycle is the machine's (novox/hq ADR 0117).
|
||||||
stateless bool
|
stateless bool
|
||||||
// scope and user are, for a service, whose manager it was applied through (novox/hq ADR 0177).
|
|
||||||
scope, user string
|
|
||||||
// found is, for a service, its unit as the host first found it (novox/hq ADR 0118).
|
// found is, for a service, its unit as the host first found it (novox/hq ADR 0118).
|
||||||
found *store.FoundUnit
|
found *store.FoundUnit
|
||||||
// reads is, for a container, the digest of each file it was created reading, by path — so
|
// reads is, for a container, the digest of each file it was created reading, by path — so
|
||||||
@@ -565,8 +563,6 @@ func ApplyKeeping(
|
|||||||
Kept: kept,
|
Kept: kept,
|
||||||
Reads: outcome.reads,
|
Reads: outcome.reads,
|
||||||
Stateless: outcome.stateless,
|
Stateless: outcome.stateless,
|
||||||
Scope: outcome.scope,
|
|
||||||
User: outcome.user,
|
|
||||||
Found: outcome.found,
|
Found: outcome.found,
|
||||||
Holds: holds(resource),
|
Holds: holds(resource),
|
||||||
})
|
})
|
||||||
@@ -1045,14 +1041,44 @@ type unitReloader interface {
|
|||||||
ReloadUnits(ctx context.Context, run system.Runner) error
|
ReloadUnits(ctx context.Context, run system.Runner) error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// serviceSettle is how long the host waits before looking at a unit a second time. A test sets it
|
||||||
|
// to nothing; on a machine it is the window in which a daemon that refuses its configuration dies.
|
||||||
|
var serviceSettle = 2 * time.Second
|
||||||
|
|
||||||
|
// stayedRunning is the state of a unit the host has just asked to run, read twice.
|
||||||
|
//
|
||||||
|
// **Because the first read races the failure.** A service manager returns when it has started the
|
||||||
|
// process, and the unit is "activating" or "active" at that instant whatever the process is about
|
||||||
|
// to do. A daemon that reads its configuration, refuses it and exits does so a fraction of a second
|
||||||
|
// later — fail2ban took 221 milliseconds the day this was written — so a single read back says
|
||||||
|
// running about a machine whose daemon is already gone, and the apply reports "restarted" for a
|
||||||
|
// service that is dead. Every ban on both public machines was lost that way while every check
|
||||||
|
// passed (novox/hq ADR 0184), which is the one shape of failure this host exists to refuse.
|
||||||
|
//
|
||||||
|
// So it looks again, after the moment in which that happens. It does not wait for a slow unit to
|
||||||
|
// finish starting: a unit still coming up reads as running both times and is accepted, as before.
|
||||||
|
// What this catches is a unit that was running and is not any more.
|
||||||
|
func stayedRunning(ctx context.Context, sys system.System, run Runner, unit string) (string, error) {
|
||||||
|
state, err := sys.ServiceState(ctx, run, unit)
|
||||||
|
if err != nil || state != "running" {
|
||||||
|
return state, err
|
||||||
|
}
|
||||||
|
timer := time.NewTimer(serviceSettle)
|
||||||
|
defer timer.Stop()
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return state, ctx.Err()
|
||||||
|
case <-timer.C:
|
||||||
|
}
|
||||||
|
return sys.ServiceState(ctx, run, unit)
|
||||||
|
}
|
||||||
|
|
||||||
func applyService(ctx context.Context, sys system.System, r *declaration.Service, run Runner,
|
func applyService(ctx context.Context, sys system.System, r *declaration.Service, run Runner,
|
||||||
changed map[string]bool, previous store.Applied) (Outcome, error) {
|
changed map[string]bool, previous store.Applied) (Outcome, error) {
|
||||||
run = managerFor(r.Scope, r.User, run)
|
|
||||||
if r.Stateless() {
|
if r.Stateless() {
|
||||||
return reflectOnly(ctx, sys, r, run, changed)
|
return reflectOnly(ctx, sys, r, run, changed)
|
||||||
}
|
}
|
||||||
out := begin(r)
|
out := begin(r)
|
||||||
out.scope, out.user = r.Scope, r.User
|
|
||||||
var changes []string
|
var changes []string
|
||||||
|
|
||||||
// A file the service reflects changed, and it may be the unit's own file or a drop-in: the
|
// A file the service reflects changed, and it may be the unit's own file or a drop-in: the
|
||||||
@@ -1126,6 +1152,9 @@ func applyService(ctx context.Context, sys system.System, r *declaration.Service
|
|||||||
// Read back. A service manager accepting a command says the transaction was accepted,
|
// Read back. A service manager accepting a command says the transaction was accepted,
|
||||||
// not that the unit is running — one that starts and immediately dies satisfies it.
|
// not that the unit is running — one that starts and immediately dies satisfies it.
|
||||||
after, err := sys.ServiceState(ctx, run, r.Unit)
|
after, err := sys.ServiceState(ctx, run, r.Unit)
|
||||||
|
if err == nil && r.State == "running" {
|
||||||
|
after, err = stayedRunning(ctx, sys, run, r.Unit)
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return out, err
|
return out, err
|
||||||
}
|
}
|
||||||
@@ -1146,7 +1175,7 @@ func applyService(ctx context.Context, sys system.System, r *declaration.Service
|
|||||||
}
|
}
|
||||||
// Read back, for the same reason as above: a unit that starts and immediately dies
|
// Read back, for the same reason as above: a unit that starts and immediately dies
|
||||||
// satisfies a service manager and nothing else.
|
// satisfies a service manager and nothing else.
|
||||||
after, err := sys.ServiceState(ctx, run, r.Unit)
|
after, err := stayedRunning(ctx, sys, run, r.Unit)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return out, err
|
return out, err
|
||||||
}
|
}
|
||||||
@@ -1192,9 +1221,7 @@ func applyService(ctx context.Context, sys system.System, r *declaration.Service
|
|||||||
// a machine that uses another — and it reads the change when whatever starts it does.
|
// a machine that uses another — and it reads the change when whatever starts it does.
|
||||||
func reflectOnly(ctx context.Context, sys system.System, r *declaration.Service, run Runner,
|
func reflectOnly(ctx context.Context, sys system.System, r *declaration.Service, run Runner,
|
||||||
changed map[string]bool) (Outcome, error) {
|
changed map[string]bool) (Outcome, error) {
|
||||||
run = managerFor(r.Scope, r.User, run)
|
|
||||||
out := begin(r)
|
out := begin(r)
|
||||||
out.scope, out.user = r.Scope, r.User
|
|
||||||
out.stateless = true
|
out.stateless = true
|
||||||
restart := reflected(r, changed)
|
restart := reflected(r, changed)
|
||||||
reload := restartedBy(r.ReloadOn, changed)
|
reload := restartedBy(r.ReloadOn, changed)
|
||||||
@@ -1238,7 +1265,7 @@ func reflectOnly(ctx context.Context, sys system.System, r *declaration.Service,
|
|||||||
}
|
}
|
||||||
// Read back: it was running, and a restart or reload that left it otherwise is a failure —
|
// Read back: it was running, and a restart or reload that left it otherwise is a failure —
|
||||||
// the machine's network manager down is not a change to report and move past.
|
// the machine's network manager down is not a change to report and move past.
|
||||||
after, err := sys.ServiceState(ctx, run, r.Unit)
|
after, err := stayedRunning(ctx, sys, run, r.Unit)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return out, err
|
return out, err
|
||||||
}
|
}
|
||||||
@@ -1416,7 +1443,7 @@ func applyPackage(ctx context.Context, sys system.System, r *declaration.Package
|
|||||||
return out, err
|
return out, err
|
||||||
}
|
}
|
||||||
if r.Absent {
|
if r.Absent {
|
||||||
// Declared absent (novox/hq ADR 0175): removed when it is here, left alone when it is not.
|
// Declared absent (novox/hq ADR 0180): removed when it is here, left alone when it is not.
|
||||||
if !installed {
|
if !installed {
|
||||||
out.Action = "unchanged"
|
out.Action = "unchanged"
|
||||||
out.Detail = "not installed, as declared"
|
out.Detail = "not installed, as declared"
|
||||||
@@ -1615,6 +1642,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.
|
||||||
@@ -1812,6 +1843,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)
|
||||||
}
|
}
|
||||||
@@ -2243,7 +2279,6 @@ func declaredDigest(r declaration.Resource) string {
|
|||||||
// what was actually done — a unit already as it was found is forgotten, not "restored".
|
// what was actually done — a unit already as it was found is forgotten, not "restored".
|
||||||
func removeService(ctx context.Context, sys system.System, a store.Applied, run Runner,
|
func removeService(ctx context.Context, sys system.System, a store.Applied, run Runner,
|
||||||
made bool) (string, string, error) {
|
made bool) (string, string, error) {
|
||||||
run = managerFor(a.Scope, a.User, run)
|
|
||||||
if a.Stateless {
|
if a.Stateless {
|
||||||
// Declared with no state (novox/hq ADR 0117): its lifecycle was never the mesh's, and the
|
// Declared with no state (novox/hq ADR 0117): its lifecycle was never the mesh's, and the
|
||||||
// declaration that said so is the operator's word to hold to, a file of the mesh's or not.
|
// declaration that said so is the operator's word to hold to, a file of the mesh's or not.
|
||||||
@@ -2418,23 +2453,3 @@ func moduleOf(identity string) (string, bool) {
|
|||||||
}
|
}
|
||||||
return identity[:at], true
|
return identity[:at], true
|
||||||
}
|
}
|
||||||
|
|
||||||
// managerFor routes a unit's commands to the manager it belongs to (novox/hq ADR 0177). A system
|
|
||||||
// unit's go to the machine's manager as they always did. A user-scoped unit's go to the account's
|
|
||||||
// own: `systemctl --user --machine=<account>@`, which reaches that manager from the host's own
|
|
||||||
// process without an environment to forge or a user to switch to — and which only answers while
|
|
||||||
// the account's manager runs (a login, or lingering enabled for the account). Done on the runner
|
|
||||||
// rather than in each system: every system's reading of a unit already goes through `systemctl`,
|
|
||||||
// so this is one place instead of one per system and one per method.
|
|
||||||
func managerFor(scope, user string, run Runner) Runner {
|
|
||||||
if scope != declaration.ScopeUser || user == "" {
|
|
||||||
return run
|
|
||||||
}
|
|
||||||
return func(ctx context.Context, name string, args ...string) (string, error) {
|
|
||||||
if name != "systemctl" {
|
|
||||||
return run(ctx, name, args...)
|
|
||||||
}
|
|
||||||
scoped := append([]string{"--user", "--machine=" + user + "@"}, args...)
|
|
||||||
return run(ctx, name, scoped...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -174,7 +174,7 @@ func TestACapabilityReachesTheRuntimeAndTheSpec(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// A package may be declared absent (novox/hq ADR 0175): removed when it is installed, read back,
|
// A package may be declared absent (novox/hq ADR 0180): removed when it is installed, read back,
|
||||||
// left alone when it is not.
|
// left alone when it is not.
|
||||||
func TestAPackageDeclaredAbsentIsRemovedWhenPresentAndLeftWhenNot(t *testing.T) {
|
func TestAPackageDeclaredAbsentIsRemovedWhenPresentAndLeftWhenNot(t *testing.T) {
|
||||||
installed := true
|
installed := true
|
||||||
|
|||||||
@@ -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")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -77,7 +77,7 @@ func retireFirewall(ctx context.Context, d *declaration.Declaration, origin stri
|
|||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
if !firewall.Installed(ctx, run) {
|
if !firewall.Installed(ctx, run) {
|
||||||
// Uninstalled (novox/hq ADR 0175): retired for good, by the module that replaced it. Said
|
// Uninstalled (novox/hq ADR 0180): retired for good, by the module that replaced it. Said
|
||||||
// once, and nothing is asked of a command that is not there.
|
// once, and nothing is asked of a command that is not there.
|
||||||
if rec.RetiredBy != firewall.RetiredRemoved {
|
if rec.RetiredBy != firewall.RetiredRemoved {
|
||||||
rec.RetiredBy = firewall.RetiredRemoved
|
rec.RetiredBy = firewall.RetiredRemoved
|
||||||
|
|||||||
@@ -525,7 +525,7 @@ func TestUfwIsNotRetiredUntilTheMeshsOwnFilterIsLoaded(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// A front end that is no longer installed is recorded as removed, said once, and asked nothing of
|
// A front end that is no longer installed is recorded as removed, said once, and asked nothing of
|
||||||
// (novox/hq ADR 0175).
|
// (novox/hq ADR 0180).
|
||||||
func TestAnUninstalledFrontEndIsRetiredForGood(t *testing.T) {
|
func TestAnUninstalledFrontEndIsRetiredForGood(t *testing.T) {
|
||||||
dir := t.TempDir()
|
dir := t.TempDir()
|
||||||
u := &ufwMachine{installed: false, ruleset: "table inet mesh\n"}
|
u := &ufwMachine{installed: false, ruleset: "table inet mesh\n"}
|
||||||
|
|||||||
@@ -0,0 +1,100 @@
|
|||||||
|
package apply
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/novox/mesh-host/internal/store"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A daemon that reads a configuration it refuses dies a fraction of a second after the service
|
||||||
|
// manager has reported it started — fail2ban took 221 milliseconds on the control node the day this
|
||||||
|
// was written. One read back catches nothing: the unit is active at that instant. The mesh reported
|
||||||
|
// the service "restarted" while every ban on two public machines was gone, and every check passed
|
||||||
|
// (novox/hq ADR 0184). The host looks again, after the moment in which that happens.
|
||||||
|
func TestAServiceThatDiesJustAfterItsRestartIsNotReportedRestarted(t *testing.T) {
|
||||||
|
serviceSettle = 0
|
||||||
|
// Alive at the first look after starting, dead at the second — the shape of a daemon that
|
||||||
|
// refuses what it was just given.
|
||||||
|
started, looks := false, 0
|
||||||
|
run := func(ctx context.Context, name string, args ...string) (string, error) {
|
||||||
|
if args[0] == "show" {
|
||||||
|
state := "active"
|
||||||
|
if started {
|
||||||
|
if looks++; looks >= 2 {
|
||||||
|
state = "failed"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "LoadState=loaded\nActiveState=" + state + "\n", nil
|
||||||
|
}
|
||||||
|
if args[0] == "start" {
|
||||||
|
started = true
|
||||||
|
}
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
dir := t.TempDir()
|
||||||
|
d := parse(t, `{"declaration":1,"resources":[
|
||||||
|
{"id":"conf","type":"file","path":"`+dir+`/jail.conf","content":"[sshd]\n","mode":"0644"},
|
||||||
|
{"id":"run","type":"service","unit":"fail2ban.service","state":"running","restart-on":["conf"]}
|
||||||
|
]}`)
|
||||||
|
_, _, err := Apply(context.Background(), archHost(t), d, store.State{}, store.OriginCarried, run, nil, nil)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("a service that died just after being restarted was reported as restarted")
|
||||||
|
}
|
||||||
|
if !strings.Contains(err.Error(), "fail2ban.service") || !strings.Contains(err.Error(), "is stopped") {
|
||||||
|
t.Errorf("the failure does not name the unit and what it is now: %v", err)
|
||||||
|
}
|
||||||
|
if looks < 2 {
|
||||||
|
t.Errorf("the host looked at the unit %d time(s) after starting it; it must look again", looks)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A unit still coming up reads as running at both looks and is accepted: the second look is for a
|
||||||
|
// unit that WAS running and is not any more, never a wait for a slow one to finish starting.
|
||||||
|
func TestAUnitStillStartingIsNotAFailure(t *testing.T) {
|
||||||
|
serviceSettle = 0
|
||||||
|
run := func(ctx context.Context, name string, args ...string) (string, error) {
|
||||||
|
if args[0] == "show" {
|
||||||
|
return "LoadState=loaded\nActiveState=activating\n", nil
|
||||||
|
}
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
d := parse(t, `{"declaration":1,"resources":[
|
||||||
|
{"id":"s","type":"service","unit":"slow.service","state":"running"}
|
||||||
|
]}`)
|
||||||
|
if _, _, err := Apply(context.Background(), archHost(t), d, store.State{}, store.OriginCarried, run, nil, nil); err != nil {
|
||||||
|
t.Fatalf("a unit still starting was reported as a failure: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// And a service the declaration asks to be stopped is not waited on at all.
|
||||||
|
func TestAServiceAskedToStopIsNotWaitedOn(t *testing.T) {
|
||||||
|
serviceSettle = 0
|
||||||
|
shows := 0
|
||||||
|
run := func(ctx context.Context, name string, args ...string) (string, error) {
|
||||||
|
if args[0] == "show" {
|
||||||
|
shows++
|
||||||
|
if shows == 1 {
|
||||||
|
return "LoadState=loaded\nActiveState=active\n", nil
|
||||||
|
}
|
||||||
|
return "LoadState=loaded\nActiveState=inactive\n", nil
|
||||||
|
}
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
d := parse(t, `{"declaration":1,"resources":[
|
||||||
|
{"id":"s","type":"service","unit":"off.service","state":"stopped"}
|
||||||
|
]}`)
|
||||||
|
if _, _, err := Apply(context.Background(), archHost(t), d, store.State{}, store.OriginCarried, run, nil, nil); err != nil {
|
||||||
|
t.Fatalf("stopping a service was reported as a failure: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The settle between a unit's two read-backs is a real pause on a machine and nothing in a test:
|
||||||
|
// no test here drives a service manager that takes time, so paying it would only slow the suite
|
||||||
|
// (novox/hq ADR 0184).
|
||||||
|
func TestMain(m *testing.M) {
|
||||||
|
serviceSettle = 0
|
||||||
|
os.Exit(m.Run())
|
||||||
|
}
|
||||||
@@ -1,99 +0,0 @@
|
|||||||
package apply
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/novox/mesh-host/internal/store"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Defends novox/hq ADR 0177: a unit in the operator account's own service manager is applied
|
|
||||||
// through that manager — `systemctl --user --machine=<account>@` — and never as a system unit of
|
|
||||||
// the same name; its record remembers the scope so removal goes the same way.
|
|
||||||
|
|
||||||
// accountManager is a user's service manager whose one unit starts when asked, recording the
|
|
||||||
// commands and refusing any that reach it outside the account's scope.
|
|
||||||
func accountManager(account string, commands *[]string) Runner {
|
|
||||||
active, enabled := false, false
|
|
||||||
return func(_ context.Context, name string, args ...string) (string, error) {
|
|
||||||
line := name + " " + strings.Join(args, " ")
|
|
||||||
*commands = append(*commands, line)
|
|
||||||
if name == "systemctl" && !strings.HasPrefix(line, "systemctl --user --machine="+account+"@ ") {
|
|
||||||
return "", fmt.Errorf("a system-scope command reached the account's manager: %s", line)
|
|
||||||
}
|
|
||||||
switch {
|
|
||||||
case strings.Contains(line, " start "):
|
|
||||||
active = true
|
|
||||||
return "", nil
|
|
||||||
case strings.Contains(line, " stop "):
|
|
||||||
active = false
|
|
||||||
return "", nil
|
|
||||||
case strings.Contains(line, " enable "):
|
|
||||||
enabled = true
|
|
||||||
return "", nil
|
|
||||||
case strings.Contains(line, " disable "):
|
|
||||||
enabled = false
|
|
||||||
return "", nil
|
|
||||||
case strings.Contains(line, "is-enabled"):
|
|
||||||
if enabled {
|
|
||||||
return "enabled", nil
|
|
||||||
}
|
|
||||||
return "disabled", nil
|
|
||||||
case strings.Contains(line, "show") && strings.Contains(line, "ActiveState"):
|
|
||||||
if active {
|
|
||||||
return "LoadState=loaded\nActiveState=active\nSubState=running", nil
|
|
||||||
}
|
|
||||||
return "LoadState=loaded\nActiveState=inactive\nSubState=dead", nil
|
|
||||||
}
|
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAUserScopedUnitIsAppliedThroughTheAccountsManager(t *testing.T) {
|
|
||||||
var commands []string
|
|
||||||
decl := `{"declaration":1,"resources":[
|
|
||||||
{"id":"i3.watcher","type":"service","unit":"i3-reload-watcher.service","state":"running","boot":"enabled","scope":"user","user":"ops"}
|
|
||||||
]}`
|
|
||||||
report, known, err := Apply(context.Background(), archHost(t), parse(t, decl),
|
|
||||||
store.State{}, store.OriginDeclared, accountManager("ops", &commands), nil, nil)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("apply: %v\n%s", err, strings.Join(commands, "\n"))
|
|
||||||
}
|
|
||||||
if !report.Changed() {
|
|
||||||
t.Fatal("a unit that was stopped and is now running changed nothing")
|
|
||||||
}
|
|
||||||
var started, enabled bool
|
|
||||||
for _, c := range commands {
|
|
||||||
if c == "systemctl --user --machine=ops@ start i3-reload-watcher.service" {
|
|
||||||
started = true
|
|
||||||
}
|
|
||||||
if c == "systemctl --user --machine=ops@ enable i3-reload-watcher.service" {
|
|
||||||
enabled = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !started || !enabled {
|
|
||||||
t.Fatalf("the unit was not started and enabled in the account's manager:\n%s", strings.Join(commands, "\n"))
|
|
||||||
}
|
|
||||||
recorded, ok := known.At("service", "i3-reload-watcher.service")
|
|
||||||
if !ok || recorded.Scope != "user" || recorded.User != "ops" {
|
|
||||||
t.Fatalf("the record does not say whose manager the unit is in: %+v", recorded)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestASystemUnitIsUntouchedByTheScope(t *testing.T) {
|
|
||||||
var commands []string
|
|
||||||
decl := `{"declaration":1,"resources":[
|
|
||||||
{"id":"x.daemon","type":"service","unit":"sshd.service","state":"running","boot":"enabled"}
|
|
||||||
]}`
|
|
||||||
if _, _, err := Apply(context.Background(), archHost(t), parse(t, decl),
|
|
||||||
store.State{}, store.OriginDeclared, unitIn(true, &commands), nil, nil); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
for _, c := range commands {
|
|
||||||
if strings.Contains(c, "--user") || strings.Contains(c, "--machine") {
|
|
||||||
t.Fatalf("a system unit was addressed to an account's manager: %s", c)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -684,16 +684,6 @@ type Service struct {
|
|||||||
// declaration that reports success and stops being true at the next power cut.
|
// declaration that reports success and stops being true at the next power cut.
|
||||||
Boot string `json:"boot,omitempty"`
|
Boot string `json:"boot,omitempty"`
|
||||||
|
|
||||||
// Scope is whose service manager the unit belongs to: "system" (absent means system), or
|
|
||||||
// "user" — the operator account's own manager (novox/hq ADR 0177). A workstation's per-user
|
|
||||||
// daemons — a window manager's reload watcher, an audio mask, a memory guard — are units in
|
|
||||||
// that manager, and until this they had no form the mesh could send. A user-scoped unit names
|
|
||||||
// its User; the host talks to that account's manager and never starts a system unit by the
|
|
||||||
// same name.
|
|
||||||
Scope string `json:"scope,omitempty"`
|
|
||||||
// User is the account whose manager a user-scoped unit lives in. Required with scope "user",
|
|
||||||
// refused otherwise; a module names it ${machine:account} and never the person.
|
|
||||||
User string `json:"user,omitempty"`
|
|
||||||
// RestartOn names resources whose change means this service must be restarted.
|
// RestartOn names resources whose change means this service must be restarted.
|
||||||
//
|
//
|
||||||
// Because a running service does not re-read its configuration. Replace the file, find the
|
// Because a running service does not re-read its configuration. Replace the file, find the
|
||||||
@@ -739,33 +729,11 @@ func (s *Service) Identity() string { return s.ID }
|
|||||||
func (s *Service) Kind() Type { return TypeService }
|
func (s *Service) Kind() Type { return TypeService }
|
||||||
func (s *Service) Target() string { return s.Unit }
|
func (s *Service) Target() string { return s.Unit }
|
||||||
|
|
||||||
// ScopeSystem and ScopeUser are the two managers a unit may belong to (novox/hq ADR 0177).
|
|
||||||
const (
|
|
||||||
ScopeSystem = "system"
|
|
||||||
ScopeUser = "user"
|
|
||||||
)
|
|
||||||
|
|
||||||
// UserScoped is whether this unit lives in an account's own service manager.
|
|
||||||
func (s *Service) UserScoped() bool { return s.Scope == ScopeUser }
|
|
||||||
|
|
||||||
func (s *Service) validate(where string, _ bool) []string {
|
func (s *Service) validate(where string, _ bool) []string {
|
||||||
var problems []string
|
var problems []string
|
||||||
if s.Unit == "" {
|
if s.Unit == "" {
|
||||||
problems = append(problems, where+": a service needs a unit")
|
problems = append(problems, where+": a service needs a unit")
|
||||||
}
|
}
|
||||||
switch s.Scope {
|
|
||||||
case "", ScopeSystem:
|
|
||||||
if s.User != "" {
|
|
||||||
problems = append(problems, where+": a system unit names no user; only a user-scoped unit does")
|
|
||||||
}
|
|
||||||
case ScopeUser:
|
|
||||||
if s.User == "" {
|
|
||||||
problems = append(problems, where+": a user-scoped unit names the account whose manager it lives in")
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
problems = append(problems, fmt.Sprintf(
|
|
||||||
"%s: scope %q; a unit is in the \"system\" manager or the operator account's \"user\" one", where, s.Scope))
|
|
||||||
}
|
|
||||||
switch {
|
switch {
|
||||||
case s.State == "running" || s.State == "stopped":
|
case s.State == "running" || s.State == "stopped":
|
||||||
case s.State != "":
|
case s.State != "":
|
||||||
@@ -902,7 +870,7 @@ type Package struct {
|
|||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
Type Type `json:"type"`
|
Type Type `json:"type"`
|
||||||
Package string `json:"package"`
|
Package string `json:"package"`
|
||||||
// Absent declares that the package is NOT installed (novox/hq ADR 0175): the host removes it
|
// Absent declares that the package is NOT installed (novox/hq ADR 0180): the host removes it
|
||||||
// when it is, and leaves a machine that never had it alone. For the one case a module replaces
|
// when it is, and leaves a machine that never had it alone. For the one case a module replaces
|
||||||
// software the machine was found with and the operator has decided it does not come back — the
|
// software the machine was found with and the operator has decided it does not come back — the
|
||||||
// firewall front end a converged machine's filter module retired. Nothing to undo when the
|
// firewall front end a converged machine's filter module retired. Nothing to undo when the
|
||||||
@@ -984,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.
|
||||||
@@ -1089,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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,30 +0,0 @@
|
|||||||
package declaration
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
// novox/hq ADR 0177: a unit is in the system manager or an account's own; a user-scoped one names
|
|
||||||
// the account, a system one may not, and any other word is refused.
|
|
||||||
func TestAUserScopedUnitNamesItsAccountAndASystemOneMayNot(t *testing.T) {
|
|
||||||
cases := []struct{ scope, user, wants string }{
|
|
||||||
{"user", "ops", ""},
|
|
||||||
{"", "", ""},
|
|
||||||
{"system", "", ""},
|
|
||||||
{"user", "", "names the account"},
|
|
||||||
{"", "ops", "names no user"},
|
|
||||||
{"session", "ops", "scope \"session\""},
|
|
||||||
}
|
|
||||||
for _, c := range cases {
|
|
||||||
s := &Service{ID: "m.u", Type: TypeService, Unit: "u.service", State: "running", Scope: c.scope, User: c.user}
|
|
||||||
problems := s.validate("m.u", false)
|
|
||||||
got := strings.Join(problems, "; ")
|
|
||||||
if c.wants == "" && len(problems) != 0 {
|
|
||||||
t.Fatalf("scope %q user %q refused: %s", c.scope, c.user, got)
|
|
||||||
}
|
|
||||||
if c.wants != "" && !strings.Contains(got, c.wants) {
|
|
||||||
t.Fatalf("scope %q user %q: wanted a refusal saying %q, got %q", c.scope, c.user, c.wants, got)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -179,27 +179,18 @@ func legacyFilters(rules, tool string, ufwActive bool) []Filter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var entered func(chain string, seen map[string]bool) bool
|
// A chain of refusals is a ban list when every refusal names the sources it refuses and the
|
||||||
entered = func(chain string, seen map[string]bool) bool {
|
// chain accepts nothing — the same rule the nftables side applies, and no more.
|
||||||
if seen[chain] || accepting[chain] || len(jumpedFrom[chain]) == 0 {
|
//
|
||||||
return false
|
// **The policy of the chains that jump to it says nothing about what it is.** An earlier cut
|
||||||
}
|
// required every path into the chain to come from a built-in whose policy accepts, and the
|
||||||
seen[chain] = true
|
// mesh's own intrusion prevention then read as a foreign rule set on the home server: its ban
|
||||||
for _, from := range jumpedFrom[chain] {
|
// chain hangs off the container runtime's user chain as well as INPUT, and that machine's
|
||||||
if p, builtIn := policy[from]; builtIn {
|
// forward policy is DROP because the runtime set it. The machine reported "NOT the mesh alone"
|
||||||
if p != "ACCEPT" {
|
// about a chain the mesh had just written (novox/hq ADR 0186). The policy is already classified
|
||||||
return false
|
// where it belongs — as the runtime's — so requiring it here counted it twice.
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if !entered(from, seen) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
ban := func(chain, line string) bool {
|
ban := func(chain, line string) bool {
|
||||||
return bansSources(line) && entered(chain, map[string]bool{})
|
return bansSources(line) && !accepting[chain] && len(jumpedFrom[chain]) > 0
|
||||||
}
|
}
|
||||||
type seen struct {
|
type seen struct {
|
||||||
owner string
|
owner string
|
||||||
@@ -320,7 +311,7 @@ func Active(ctx context.Context, run Runner) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Installed says whether ufw is on this machine at all: a command that is not there is a front end
|
// Installed says whether ufw is on this machine at all: a command that is not there is a front end
|
||||||
// that was uninstalled (novox/hq ADR 0175), not one that is silent.
|
// that was uninstalled (novox/hq ADR 0180), not one that is silent.
|
||||||
func Installed(ctx context.Context, run Runner) bool {
|
func Installed(ctx context.Context, run Runner) bool {
|
||||||
_, err := run(ctx, "ufw", "status")
|
_, err := run(ctx, "ufw", "status")
|
||||||
return !missing(err)
|
return !missing(err)
|
||||||
@@ -330,6 +321,6 @@ func Installed(ctx context.Context, run Runner) bool {
|
|||||||
const (
|
const (
|
||||||
RetiredByMesh = "mesh"
|
RetiredByMesh = "mesh"
|
||||||
RetiredFoundSo = "found-inactive"
|
RetiredFoundSo = "found-inactive"
|
||||||
// RetiredRemoved is a front end uninstalled by the module that replaced it (ADR 0175).
|
// RetiredRemoved is a front end uninstalled by the module that replaced it (ADR 0180).
|
||||||
RetiredRemoved = "removed"
|
RetiredRemoved = "removed"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -0,0 +1,60 @@
|
|||||||
|
package firewall
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// The mesh's own ban list is a ban wherever it hangs (novox/hq ADR 0186).
|
||||||
|
//
|
||||||
|
// Captured from the home server after the intrusion prevention had banned four addresses: its ban
|
||||||
|
// chain is jumped to from INPUT, whose policy accepts, and from the container runtime's user chain,
|
||||||
|
// which hangs off a FORWARD the runtime set to DROP. Requiring every path to come from an accepting
|
||||||
|
// built-in made the machine report "NOT the mesh alone" about a chain the mesh had just written.
|
||||||
|
func TestTheMeshsOwnBanChainIsABanBehindADroppingForward(t *testing.T) {
|
||||||
|
legacy, err := os.ReadFile("testdata/home-server-bans-S.txt")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
filters := Filters("", map[string]string{"iptables-legacy": string(legacy)}, false)
|
||||||
|
|
||||||
|
var ban, other []string
|
||||||
|
for _, f := range filters {
|
||||||
|
switch f.Owner {
|
||||||
|
case OwnerBan:
|
||||||
|
ban = append(ban, f.Where)
|
||||||
|
case OwnerOther:
|
||||||
|
other = append(other, f.Where)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(other) > 0 {
|
||||||
|
t.Errorf("the machine reports %v as rule sets the mesh did not write", other)
|
||||||
|
}
|
||||||
|
found := false
|
||||||
|
for _, w := range ban {
|
||||||
|
if w == "chain f2b-route-proxy (iptables-legacy)" {
|
||||||
|
found = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
t.Errorf("the intrusion prevention's own chain was not read as a ban; bans were %v", ban)
|
||||||
|
}
|
||||||
|
if !Alone(filters) {
|
||||||
|
t.Error("a machine filtered by the mesh and its own bans does not read as the mesh alone")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A chain that accepts anything is doing more than banning, and is still not a ban — which is what
|
||||||
|
// keeps a predecessor's allow-and-drop chain classified as something the operator must look at.
|
||||||
|
func TestAChainThatAcceptsIsNotABan(t *testing.T) {
|
||||||
|
rules := "-P INPUT ACCEPT\n" +
|
||||||
|
"-A INPUT -j HAL-MESH-ONLY\n" +
|
||||||
|
"-N HAL-MESH-ONLY\n" +
|
||||||
|
"-A HAL-MESH-ONLY -s 10.0.0.0/8 -j ACCEPT\n" +
|
||||||
|
"-A HAL-MESH-ONLY -s 203.0.113.7/32 -j DROP\n"
|
||||||
|
for _, f := range Filters("", map[string]string{"iptables-legacy": rules}, false) {
|
||||||
|
if f.Where == "chain HAL-MESH-ONLY (iptables-legacy)" && f.Owner != OwnerOther {
|
||||||
|
t.Errorf("a chain that accepts was classified as %s", f.Owner)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+147
@@ -0,0 +1,147 @@
|
|||||||
|
-P INPUT ACCEPT
|
||||||
|
-P FORWARD DROP
|
||||||
|
-P OUTPUT ACCEPT
|
||||||
|
-N DOCKER
|
||||||
|
-N DOCKER-BRIDGE
|
||||||
|
-N DOCKER-CT
|
||||||
|
-N DOCKER-FORWARD
|
||||||
|
-N DOCKER-INTERNAL
|
||||||
|
-N DOCKER-USER
|
||||||
|
-N f2b-route-proxy
|
||||||
|
-N ufw-after-forward
|
||||||
|
-N ufw-after-input
|
||||||
|
-N ufw-after-logging-forward
|
||||||
|
-N ufw-after-logging-input
|
||||||
|
-N ufw-after-logging-output
|
||||||
|
-N ufw-after-output
|
||||||
|
-N ufw-before-forward
|
||||||
|
-N ufw-before-input
|
||||||
|
-N ufw-before-logging-forward
|
||||||
|
-N ufw-before-logging-input
|
||||||
|
-N ufw-before-logging-output
|
||||||
|
-N ufw-before-output
|
||||||
|
-N ufw-reject-forward
|
||||||
|
-N ufw-reject-input
|
||||||
|
-N ufw-reject-output
|
||||||
|
-N ufw-track-forward
|
||||||
|
-N ufw-track-input
|
||||||
|
-N ufw-track-output
|
||||||
|
-A INPUT -p tcp -j f2b-route-proxy
|
||||||
|
-A INPUT -j ufw-before-logging-input
|
||||||
|
-A INPUT -j ufw-before-input
|
||||||
|
-A INPUT -j ufw-after-input
|
||||||
|
-A INPUT -j ufw-after-logging-input
|
||||||
|
-A INPUT -j ufw-reject-input
|
||||||
|
-A INPUT -j ufw-track-input
|
||||||
|
-A FORWARD -j DOCKER-USER
|
||||||
|
-A FORWARD -j DOCKER-FORWARD
|
||||||
|
-A FORWARD -j ufw-before-logging-forward
|
||||||
|
-A FORWARD -j ufw-before-forward
|
||||||
|
-A FORWARD -j ufw-after-forward
|
||||||
|
-A FORWARD -j ufw-after-logging-forward
|
||||||
|
-A FORWARD -j ufw-reject-forward
|
||||||
|
-A FORWARD -j ufw-track-forward
|
||||||
|
-A OUTPUT -j ufw-before-logging-output
|
||||||
|
-A OUTPUT -j ufw-before-output
|
||||||
|
-A OUTPUT -j ufw-after-output
|
||||||
|
-A OUTPUT -j ufw-after-logging-output
|
||||||
|
-A OUTPUT -j ufw-reject-output
|
||||||
|
-A OUTPUT -j ufw-track-output
|
||||||
|
-A DOCKER -d 172.17.0.18/32 ! -i docker0 -o docker0 -p tcp -m tcp --dport 8686 -j ACCEPT
|
||||||
|
-A DOCKER -d 172.17.0.14/32 ! -i docker0 -o docker0 -p tcp -m tcp --dport 8989 -j ACCEPT
|
||||||
|
-A DOCKER -d 172.17.0.15/32 ! -i docker0 -o docker0 -p tcp -m tcp --dport 7878 -j ACCEPT
|
||||||
|
-A DOCKER -d 172.17.0.5/32 ! -i docker0 -o docker0 -p tcp -m tcp --dport 9117 -j ACCEPT
|
||||||
|
-A DOCKER -d 172.17.0.13/32 ! -i docker0 -o docker0 -p tcp -m tcp --dport 6789 -j ACCEPT
|
||||||
|
-A DOCKER -d 172.19.0.2/32 ! -i br-32062158f584 -o br-32062158f584 -p tcp -m tcp --dport 8080 -j ACCEPT
|
||||||
|
-A DOCKER -d 172.17.0.2/32 ! -i docker0 -o docker0 -p tcp -m tcp --dport 5432 -j ACCEPT
|
||||||
|
-A DOCKER -d 172.27.0.2/32 ! -i br-0910a98c6158 -o br-0910a98c6158 -p tcp -m tcp --dport 5678 -j ACCEPT
|
||||||
|
-A DOCKER -d 172.17.0.21/32 ! -i docker0 -o docker0 -p tcp -m tcp --dport 3579 -j ACCEPT
|
||||||
|
-A DOCKER -d 172.17.0.19/32 ! -i docker0 -o docker0 -p tcp -m tcp --dport 8181 -j ACCEPT
|
||||||
|
-A DOCKER -d 172.17.0.17/32 ! -i docker0 -o docker0 -p tcp -m tcp --dport 8787 -j ACCEPT
|
||||||
|
-A DOCKER -d 172.17.0.16/32 ! -i docker0 -o docker0 -p tcp -m tcp --dport 6767 -j ACCEPT
|
||||||
|
-A DOCKER -d 172.17.0.12/32 ! -i docker0 -o docker0 -p tcp -m tcp --dport 3000 -j ACCEPT
|
||||||
|
-A DOCKER -d 172.17.0.11/32 ! -i docker0 -o docker0 -p tcp -m tcp --dport 80 -j ACCEPT
|
||||||
|
-A DOCKER -d 172.17.0.10/32 ! -i docker0 -o docker0 -p tcp -m tcp --dport 9443 -j ACCEPT
|
||||||
|
-A DOCKER -d 172.17.0.10/32 ! -i docker0 -o docker0 -p tcp -m tcp --dport 9000 -j ACCEPT
|
||||||
|
-A DOCKER -d 172.17.0.9/32 ! -i docker0 -o docker0 -p tcp -m tcp --dport 3000 -j ACCEPT
|
||||||
|
-A DOCKER -d 172.17.0.7/32 ! -i docker0 -o docker0 -p tcp -m tcp --dport 1880 -j ACCEPT
|
||||||
|
-A DOCKER -d 172.28.0.2/32 ! -i br-b11461b5b028 -o br-b11461b5b028 -p tcp -m tcp --dport 80 -j ACCEPT
|
||||||
|
-A DOCKER -d 172.23.0.14/32 ! -i br-66ffa5c1cba5 -o br-66ffa5c1cba5 -p tcp -m tcp --dport 6543 -j ACCEPT
|
||||||
|
-A DOCKER -d 172.23.0.14/32 ! -i br-66ffa5c1cba5 -o br-66ffa5c1cba5 -p tcp -m tcp --dport 5432 -j ACCEPT
|
||||||
|
-A DOCKER -d 172.23.0.5/32 ! -i br-66ffa5c1cba5 -o br-66ffa5c1cba5 -p tcp -m tcp --dport 8000 -j ACCEPT
|
||||||
|
-A DOCKER -d 172.26.0.3/32 ! -i br-b0fec361ccaa -o br-b0fec361ccaa -p tcp -m tcp --dport 6167 -j ACCEPT
|
||||||
|
-A DOCKER -d 172.26.0.2/32 ! -i br-b0fec361ccaa -o br-b0fec361ccaa -p tcp -m tcp --dport 80 -j ACCEPT
|
||||||
|
-A DOCKER -d 172.17.0.8/32 ! -i docker0 -o docker0 -p tcp -m tcp --dport 8000 -j ACCEPT
|
||||||
|
-A DOCKER -d 172.17.0.6/32 ! -i docker0 -o docker0 -p udp -m udp --dport 10001 -j ACCEPT
|
||||||
|
-A DOCKER -d 172.17.0.6/32 ! -i docker0 -o docker0 -p tcp -m tcp --dport 8880 -j ACCEPT
|
||||||
|
-A DOCKER -d 172.17.0.6/32 ! -i docker0 -o docker0 -p tcp -m tcp --dport 8843 -j ACCEPT
|
||||||
|
-A DOCKER -d 172.17.0.6/32 ! -i docker0 -o docker0 -p tcp -m tcp --dport 8443 -j ACCEPT
|
||||||
|
-A DOCKER -d 172.17.0.6/32 ! -i docker0 -o docker0 -p tcp -m tcp --dport 8080 -j ACCEPT
|
||||||
|
-A DOCKER -d 172.17.0.6/32 ! -i docker0 -o docker0 -p tcp -m tcp --dport 6789 -j ACCEPT
|
||||||
|
-A DOCKER -d 172.17.0.6/32 ! -i docker0 -o docker0 -p udp -m udp --dport 5514 -j ACCEPT
|
||||||
|
-A DOCKER -d 172.17.0.6/32 ! -i docker0 -o docker0 -p udp -m udp --dport 3478 -j ACCEPT
|
||||||
|
-A DOCKER -d 172.17.0.6/32 ! -i docker0 -o docker0 -p udp -m udp --dport 1900 -j ACCEPT
|
||||||
|
-A DOCKER -d 172.25.0.3/32 ! -i br-b98821f7dc38 -o br-b98821f7dc38 -p tcp -m tcp --dport 8000 -j ACCEPT
|
||||||
|
-A DOCKER -d 172.18.0.3/32 ! -i br-442a0bfc65f8 -o br-442a0bfc65f8 -p tcp -m tcp --dport 1433 -j ACCEPT
|
||||||
|
-A DOCKER -d 172.20.0.3/32 ! -i br-afa37ac8b33d -o br-afa37ac8b33d -p tcp -m tcp --dport 8081 -j ACCEPT
|
||||||
|
-A DOCKER -d 172.20.0.3/32 ! -i br-afa37ac8b33d -o br-afa37ac8b33d -p tcp -m tcp --dport 1883 -j ACCEPT
|
||||||
|
-A DOCKER -d 172.17.0.4/32 ! -i docker0 -o docker0 -p tcp -m tcp --dport 8086 -j ACCEPT
|
||||||
|
-A DOCKER -d 172.21.0.2/32 ! -i br-df15d8e19ec7 -o br-df15d8e19ec7 -p tcp -m tcp --dport 6379 -j ACCEPT
|
||||||
|
-A DOCKER -d 172.30.0.3/32 ! -i br-521eab9a3a5e -o br-521eab9a3a5e -p tcp -m tcp --dport 8283 -j ACCEPT
|
||||||
|
-A DOCKER -d 172.17.0.3/32 ! -i docker0 -o docker0 -p tcp -m tcp --dport 3000 -j ACCEPT
|
||||||
|
-A DOCKER ! -i br-32062158f584 -o br-32062158f584 -j DROP
|
||||||
|
-A DOCKER ! -i docker0 -o docker0 -j DROP
|
||||||
|
-A DOCKER ! -i br-521eab9a3a5e -o br-521eab9a3a5e -j DROP
|
||||||
|
-A DOCKER ! -i br-df15d8e19ec7 -o br-df15d8e19ec7 -j DROP
|
||||||
|
-A DOCKER ! -i br-afa37ac8b33d -o br-afa37ac8b33d -j DROP
|
||||||
|
-A DOCKER ! -i br-442a0bfc65f8 -o br-442a0bfc65f8 -j DROP
|
||||||
|
-A DOCKER ! -i br-b98821f7dc38 -o br-b98821f7dc38 -j DROP
|
||||||
|
-A DOCKER ! -i br-b0fec361ccaa -o br-b0fec361ccaa -j DROP
|
||||||
|
-A DOCKER ! -i br-66ffa5c1cba5 -o br-66ffa5c1cba5 -j DROP
|
||||||
|
-A DOCKER ! -i br-b11461b5b028 -o br-b11461b5b028 -j DROP
|
||||||
|
-A DOCKER ! -i br-2df4e541b877 -o br-2df4e541b877 -j DROP
|
||||||
|
-A DOCKER ! -i br-0910a98c6158 -o br-0910a98c6158 -j DROP
|
||||||
|
-A DOCKER-BRIDGE -o br-32062158f584 -j DOCKER
|
||||||
|
-A DOCKER-BRIDGE -o docker0 -j DOCKER
|
||||||
|
-A DOCKER-BRIDGE -o br-521eab9a3a5e -j DOCKER
|
||||||
|
-A DOCKER-BRIDGE -o br-df15d8e19ec7 -j DOCKER
|
||||||
|
-A DOCKER-BRIDGE -o br-afa37ac8b33d -j DOCKER
|
||||||
|
-A DOCKER-BRIDGE -o br-442a0bfc65f8 -j DOCKER
|
||||||
|
-A DOCKER-BRIDGE -o br-b98821f7dc38 -j DOCKER
|
||||||
|
-A DOCKER-BRIDGE -o br-b0fec361ccaa -j DOCKER
|
||||||
|
-A DOCKER-BRIDGE -o br-66ffa5c1cba5 -j DOCKER
|
||||||
|
-A DOCKER-BRIDGE -o br-b11461b5b028 -j DOCKER
|
||||||
|
-A DOCKER-BRIDGE -o br-2df4e541b877 -j DOCKER
|
||||||
|
-A DOCKER-BRIDGE -o br-0910a98c6158 -j DOCKER
|
||||||
|
-A DOCKER-CT -o br-32062158f584 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
|
||||||
|
-A DOCKER-CT -o docker0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
|
||||||
|
-A DOCKER-CT -o br-521eab9a3a5e -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
|
||||||
|
-A DOCKER-CT -o br-df15d8e19ec7 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
|
||||||
|
-A DOCKER-CT -o br-afa37ac8b33d -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
|
||||||
|
-A DOCKER-CT -o br-442a0bfc65f8 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
|
||||||
|
-A DOCKER-CT -o br-b98821f7dc38 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
|
||||||
|
-A DOCKER-CT -o br-b0fec361ccaa -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
|
||||||
|
-A DOCKER-CT -o br-66ffa5c1cba5 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
|
||||||
|
-A DOCKER-CT -o br-b11461b5b028 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
|
||||||
|
-A DOCKER-CT -o br-2df4e541b877 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
|
||||||
|
-A DOCKER-CT -o br-0910a98c6158 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
|
||||||
|
-A DOCKER-FORWARD -j DOCKER-CT
|
||||||
|
-A DOCKER-FORWARD -j DOCKER-INTERNAL
|
||||||
|
-A DOCKER-FORWARD -j DOCKER-BRIDGE
|
||||||
|
-A DOCKER-FORWARD -i br-32062158f584 -j ACCEPT
|
||||||
|
-A DOCKER-FORWARD -i docker0 -j ACCEPT
|
||||||
|
-A DOCKER-FORWARD -i br-521eab9a3a5e -j ACCEPT
|
||||||
|
-A DOCKER-FORWARD -i br-df15d8e19ec7 -j ACCEPT
|
||||||
|
-A DOCKER-FORWARD -i br-afa37ac8b33d -j ACCEPT
|
||||||
|
-A DOCKER-FORWARD -i br-442a0bfc65f8 -j ACCEPT
|
||||||
|
-A DOCKER-FORWARD -i br-b98821f7dc38 -j ACCEPT
|
||||||
|
-A DOCKER-FORWARD -i br-b0fec361ccaa -j ACCEPT
|
||||||
|
-A DOCKER-FORWARD -i br-66ffa5c1cba5 -j ACCEPT
|
||||||
|
-A DOCKER-FORWARD -i br-b11461b5b028 -j ACCEPT
|
||||||
|
-A DOCKER-FORWARD -i br-2df4e541b877 -j ACCEPT
|
||||||
|
-A DOCKER-FORWARD -i br-0910a98c6158 -j ACCEPT
|
||||||
|
-A DOCKER-USER -p tcp -j f2b-route-proxy
|
||||||
|
-A f2b-route-proxy -s 13.70.107.184/32 -j REJECT --reject-with icmp-port-unreachable
|
||||||
|
-A f2b-route-proxy -s 45.138.12.51/32 -j REJECT --reject-with icmp-port-unreachable
|
||||||
|
-A f2b-route-proxy -s 20.214.191.94/32 -j REJECT --reject-with icmp-port-unreachable
|
||||||
|
-A f2b-route-proxy -j RETURN
|
||||||
@@ -82,10 +82,6 @@ type Applied struct {
|
|||||||
// 0117) — kept here because removal happens once the declaration that said so is gone, and a
|
// 0117) — kept here because removal happens once the declaration that said so is gone, and a
|
||||||
// service removed as if it had a state is stopped: the machine's network manager, for one.
|
// service removed as if it had a state is stopped: the machine's network manager, for one.
|
||||||
Stateless bool `json:"stateless,omitempty"`
|
Stateless bool `json:"stateless,omitempty"`
|
||||||
// Scope and User are, for a service in an account's own manager (novox/hq ADR 0177), which
|
|
||||||
// manager — so removal gives the unit back through the same one it was applied through.
|
|
||||||
Scope string `json:"scope,omitempty"`
|
|
||||||
User string `json:"user,omitempty"`
|
|
||||||
|
|
||||||
// Found is, for a service, the state its unit was in when this host first applied it — before
|
// Found is, for a service, the state its unit was in when this host first applied it — before
|
||||||
// the mesh started, stopped, enabled or disabled anything. Removal gives that back and nothing
|
// the mesh started, stopped, enabled or disabled anything. Removal gives that back and nothing
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ type System interface {
|
|||||||
PackageInstalled(ctx context.Context, run Runner, name string) (bool, error)
|
PackageInstalled(ctx context.Context, run Runner, name string) (bool, error)
|
||||||
InstallPackage(ctx context.Context, run Runner, name string) error
|
InstallPackage(ctx context.Context, run Runner, name string) error
|
||||||
// RemovePackage uninstalls one package, leaving its dependencies and anything the operator
|
// RemovePackage uninstalls one package, leaving its dependencies and anything the operator
|
||||||
// changed in its configuration where the package manager leaves them (novox/hq ADR 0175).
|
// changed in its configuration where the package manager leaves them (novox/hq ADR 0180).
|
||||||
RemovePackage(ctx context.Context, run Runner, name string) error
|
RemovePackage(ctx context.Context, run Runner, name string) error
|
||||||
|
|
||||||
// ServiceState is "running" or "stopped". A unit that does not exist is an error, never
|
// ServiceState is "running" or "stopped". A unit that does not exist is an error, never
|
||||||
|
|||||||
Reference in New Issue
Block a user