Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

periodically fix missing or modified rules #115

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion cmd/whalewall/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ func mainRetCode() int {
debugLogs := flag.Bool("debug", false, "enable debug logging")
logPath := flag.String("l", "stdout", "path to log to")
timeout := flag.Duration("t", 10*time.Second, "timeout for Docker API requests")
watchInterval := flag.Duration("i", time.Minute, "interval to check created container rules")
displayVersion := flag.Bool("version", false, "print version and build information and exit")
flag.Parse()

Expand All @@ -49,6 +50,11 @@ func mainRetCode() int {
return 0
}

if *watchInterval <= 0 {
log.Println("-i must be greater than 0")
return 1
}

// build logger
logCfg := zap.NewProductionConfig()
logCfg.OutputPaths = []string{*logPath}
Expand Down Expand Up @@ -92,7 +98,7 @@ func mainRetCode() int {
ctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
defer cancel()

r, err := whalewall.NewRuleManager(ctx, logger, sqliteFile, *timeout)
r, err := whalewall.NewRuleManager(ctx, logger, sqliteFile, *timeout, *watchInterval)
if err != nil {
logger.Error("error initializing", zap.Error(err))
}
Expand Down
8 changes: 6 additions & 2 deletions create.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,11 @@ func (r *RuleManager) createContainerRules(ctx context.Context, container types.

contName := stripName(container.Name)
logger := r.logger.With(zap.String("container.id", container.ID[:12]), zap.String("container.name", contName))
logger.Info("creating rules", zap.Bool("container.is_new", isNew))
if isNew {
logger.Info("creating rules", zap.Bool("container.is_new", isNew))
} else {
logger.Debug("watching rules", zap.Bool("container.is_new", isNew))
}

// check that network settings are valid
if container.NetworkSettings == nil {
Expand Down Expand Up @@ -332,7 +336,7 @@ func (r *RuleManager) createContainerRules(ctx context.Context, container types.

logger.Debug("adding to database")

if err := r.addContainer(ctx, tx, container.ID, contName, service, addrs, estContainers); err != nil {
if err := r.addContainerInfo(ctx, tx, container.ID, contName, service, addrs, estContainers); err != nil {
return fmt.Errorf("error adding container information to database: %w", err)
}

Expand Down
2 changes: 1 addition & 1 deletion db.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ func (r *RuleManager) containerExists(ctx context.Context, db database.Querier,
return exists == 1, nil
}

func (r *RuleManager) addContainer(ctx context.Context, tx database.TX, id, name, service string, addrs map[string][]byte, estContainers map[string]struct{}) error {
func (r *RuleManager) addContainerInfo(ctx context.Context, tx database.TX, id, name, service string, addrs map[string][]byte, estContainers map[string]struct{}) error {
for _, addr := range addrs {
err := tx.AddContainerAddr(ctx, addr, id)
if err != nil {
Expand Down
12 changes: 10 additions & 2 deletions manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ type RuleManager struct {

containerTracker *container.Tracker

watchInterval time.Duration

createCh chan containerDetails
deleteCh chan string

Expand All @@ -70,7 +72,7 @@ type containerDetails struct {
isNew bool
}

func NewRuleManager(ctx context.Context, logger *zap.Logger, dbFile string, timeout time.Duration) (*RuleManager, error) {
func NewRuleManager(ctx context.Context, logger *zap.Logger, dbFile string, timeout, watchInterval time.Duration) (*RuleManager, error) {
r := RuleManager{
stopping: make(chan struct{}),
done: make(chan struct{}),
Expand All @@ -89,6 +91,7 @@ func NewRuleManager(ctx context.Context, logger *zap.Logger, dbFile string, time
return nftables.New()
},
containerTracker: container.NewTracker(logger),
watchInterval: watchInterval,
createCh: make(chan containerDetails),
deleteCh: make(chan string),
}
Expand Down Expand Up @@ -126,7 +129,12 @@ func (r *RuleManager) Start(ctx context.Context) error {
r.logger.Error("error syncing containers", zap.Error(err))
}

r.wg.Add(1)
r.wg.Add(2)
go func() {
defer r.wg.Done()

r.watchContainers(ctx)
}()
go func() {
defer r.wg.Done()

Expand Down
35 changes: 35 additions & 0 deletions sync.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"fmt"
"slices"
"time"

"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters"
Expand Down Expand Up @@ -74,6 +75,40 @@ func (r *RuleManager) syncContainers(ctx context.Context) error {
return nil
}

// watchContainers periodically checks that container rules created by
// whalewall haven't been deleted, modified or added to and fixes them
// if necessary.
func (r *RuleManager) watchContainers(ctx context.Context) {
ticker := time.NewTicker(r.watchInterval)
defer ticker.Stop()

for {
select {
case <-ticker.C:
conts, err := r.db.GetContainers(ctx)
if err != nil {
r.logger.Error("error getting containers from database", zap.Error(err))
continue
}
for _, c := range conts {
container, err := r.dockerCli.ContainerInspect(ctx, c.ID)
if err != nil {
r.logger.Error("error inspecting container", zap.String("container.id", c.ID[:12]), zap.Error(err))
continue
}
r.createCh <- containerDetails{
container: container,
isNew: false,
}
}
case <-ctx.Done():
return
case <-r.stopping:
return
}
}
}

func whalewallEnabled(labels map[string]string) (bool, error) {
e, ok := labels[enabledLabel]
if !ok {
Expand Down
17 changes: 9 additions & 8 deletions whalewall_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,15 +48,15 @@
is := is.New(t)

checkFirewallRules := func() {
is.True(runCmd(t, "client", "nslookup google.com") == 0) // udp port 53 is allowed

Check failure on line 51 in whalewall_test.go

View workflow job for this annotation

GitHub Actions / race-test

running [docker compose -f=testdata/docker-compose.yml exec -T client sh -c nslookup google.com]

Check failure on line 51 in whalewall_test.go

View workflow job for this annotation

GitHub Actions / binary-test

running [docker compose -f=testdata/docker-compose.yml exec -T client sh -c nslookup google.com]

Check failure on line 51 in whalewall_test.go

View workflow job for this annotation

GitHub Actions / image-test

running [docker compose -f=testdata/docker-compose.yml exec -T client sh -c nslookup google.com]
is.True(runCmd(t, "client", "curl --connect-timeout 1 http://1.1.1.1") == 0) // tcp port 80 to 1.1.1.1 is allowed

Check failure on line 52 in whalewall_test.go

View workflow job for this annotation

GitHub Actions / race-test

running [docker compose -f=testdata/docker-compose.yml exec -T client sh -c curl --connect-timeout 1 http://1.1.1.1]

Check failure on line 52 in whalewall_test.go

View workflow job for this annotation

GitHub Actions / binary-test

running [docker compose -f=testdata/docker-compose.yml exec -T client sh -c curl --connect-timeout 1 http://1.1.1.1]

Check failure on line 52 in whalewall_test.go

View workflow job for this annotation

GitHub Actions / image-test

running [docker compose -f=testdata/docker-compose.yml exec -T client sh -c curl --connect-timeout 1 http://1.1.1.1]
is.True(runCmd(t, "client", "curl --connect-timeout 1 http://1.0.0.1") != 0) // tcp port 80 to 1.0.0.1 is not allowed

Check failure on line 53 in whalewall_test.go

View workflow job for this annotation

GitHub Actions / race-test

running [docker compose -f=testdata/docker-compose.yml exec -T client sh -c curl --connect-timeout 1 http://1.0.0.1]

Check failure on line 53 in whalewall_test.go

View workflow job for this annotation

GitHub Actions / binary-test

running [docker compose -f=testdata/docker-compose.yml exec -T client sh -c curl --connect-timeout 1 http://1.0.0.1]

Check failure on line 53 in whalewall_test.go

View workflow job for this annotation

GitHub Actions / image-test

running [docker compose -f=testdata/docker-compose.yml exec -T client sh -c curl --connect-timeout 1 http://1.0.0.1]
is.True(runCmd(t, "client", "curl --connect-timeout 1 https://www.google.com") == 0) // DNS and HTTPS is allowed externally

Check failure on line 54 in whalewall_test.go

View workflow job for this annotation

GitHub Actions / race-test

running [docker compose -f=testdata/docker-compose.yml exec -T client sh -c curl --connect-timeout 1 https://www.google.com]

Check failure on line 54 in whalewall_test.go

View workflow job for this annotation

GitHub Actions / binary-test

running [docker compose -f=testdata/docker-compose.yml exec -T client sh -c curl --connect-timeout 1 https://www.google.com]

Check failure on line 54 in whalewall_test.go

View workflow job for this annotation

GitHub Actions / image-test

running [docker compose -f=testdata/docker-compose.yml exec -T client sh -c curl --connect-timeout 1 https://www.google.com]
is.True(portOpen(t, "client", "server", 756, false)) // tcp port 756 is allowed client -> server

Check failure on line 55 in whalewall_test.go

View workflow job for this annotation

GitHub Actions / race-test

running [docker compose -f=testdata/docker-compose.yml exec -T client sh -c nmap -n -p 756 server 2>&1 | egrep "open\s"]

Check failure on line 55 in whalewall_test.go

View workflow job for this annotation

GitHub Actions / binary-test

running [docker compose -f=testdata/docker-compose.yml exec -T client sh -c nmap -n -p 756 server 2>&1 | egrep "open\s"]
is.True(!portOpen(t, "client", "server", 756, true)) // udp port 756 is not allowed client -> server

Check failure on line 56 in whalewall_test.go

View workflow job for this annotation

GitHub Actions / race-test

running [docker compose -f=testdata/docker-compose.yml exec -T client sh -c nmap -n -p 756 -sU server 2>&1 | egrep "open\s"]

Check failure on line 56 in whalewall_test.go

View workflow job for this annotation

GitHub Actions / binary-test

running [docker compose -f=testdata/docker-compose.yml exec -T client sh -c nmap -n -p 756 -sU server 2>&1 | egrep "open\s"]

Check failure on line 56 in whalewall_test.go

View workflow job for this annotation

GitHub Actions / image-test

running [docker compose -f=testdata/docker-compose.yml exec -T client sh -c nmap -n -p 756 -sU server 2>&1 | egrep "open\s"]
is.True(!portOpen(t, "client", "server", 80, false)) // tcp port 80 is not allowed client -> server

Check failure on line 57 in whalewall_test.go

View workflow job for this annotation

GitHub Actions / race-test

running [docker compose -f=testdata/docker-compose.yml exec -T client sh -c nmap -n -p 80 server 2>&1 | egrep "open\s"]

Check failure on line 57 in whalewall_test.go

View workflow job for this annotation

GitHub Actions / binary-test

running [docker compose -f=testdata/docker-compose.yml exec -T client sh -c nmap -n -p 80 server 2>&1 | egrep "open\s"]

Check failure on line 57 in whalewall_test.go

View workflow job for this annotation

GitHub Actions / image-test

running [docker compose -f=testdata/docker-compose.yml exec -T client sh -c nmap -n -p 80 server 2>&1 | egrep "open\s"]
is.True(!portOpen(t, "client", "server", 80, true)) // udp port 80 is not allowed client -> server

Check failure on line 58 in whalewall_test.go

View workflow job for this annotation

GitHub Actions / race-test

running [docker compose -f=testdata/docker-compose.yml exec -T client sh -c nmap -n -p 80 -sU server 2>&1 | egrep "open\s"]

Check failure on line 58 in whalewall_test.go

View workflow job for this annotation

GitHub Actions / binary-test

running [docker compose -f=testdata/docker-compose.yml exec -T client sh -c nmap -n -p 80 -sU server 2>&1 | egrep "open\s"]

Check failure on line 58 in whalewall_test.go

View workflow job for this annotation

GitHub Actions / image-test

running [docker compose -f=testdata/docker-compose.yml exec -T client sh -c nmap -n -p 80 -sU server 2>&1 | egrep "open\s"]
is.True(portOpen(t, "tester", "localhost", 8080, false)) // tcp mapped port 8080:80 of client is allowed from localhost

Check failure on line 59 in whalewall_test.go

View workflow job for this annotation

GitHub Actions / race-test

running [docker compose -f=testdata/docker-compose.yml exec -T tester sh -c nmap -n -p 8080 localhost 2>&1 | egrep "open\s"]

Check failure on line 59 in whalewall_test.go

View workflow job for this annotation

GitHub Actions / binary-test

running [docker compose -f=testdata/docker-compose.yml exec -T tester sh -c nmap -n -p 8080 localhost 2>&1 | egrep "open\s"]

Check failure on line 59 in whalewall_test.go

View workflow job for this annotation

GitHub Actions / image-test

running [docker compose -f=testdata/docker-compose.yml exec -T tester sh -c nmap -n -p 8080 localhost 2>&1 | egrep "open\s"]
is.True(!portOpen(t, "tester", "localhost", 8080, true)) // udp mapped port 8080:80 of client is not allowed from localhost
is.True(!portOpen(t, "tester", "localhost", 8081, false)) // tcp mapped port 8081:80 of server is not allowed from localhost
is.True(!portOpen(t, "tester", "localhost", 8081, true)) // udp mapped port 8081:80 of server is not allowed from localhost
Expand All @@ -68,7 +68,7 @@
stopWhalewall := startWhalewall(t, is, tempDir)
t.Cleanup(stopWhalewall)

is.True(run(t, "docker", "compose", "-f=testdata/docker-compose.yml", "up", "-d") == 0)

Check failure on line 71 in whalewall_test.go

View workflow job for this annotation

GitHub Actions / race-test

running [docker compose -f=testdata/docker-compose.yml up -d]

Check failure on line 71 in whalewall_test.go

View workflow job for this annotation

GitHub Actions / binary-test

running [docker compose -f=testdata/docker-compose.yml up -d]

Check failure on line 71 in whalewall_test.go

View workflow job for this annotation

GitHub Actions / image-test

running [docker compose -f=testdata/docker-compose.yml up -d]
t.Cleanup(func() {
run(t, "docker", "compose", "-f=testdata/docker-compose.yml", "down")
})
Expand Down Expand Up @@ -205,7 +205,7 @@
func startBinary(t *testing.T, is *is.I, tempDir string) func() {
t.Helper()

wwCmd := exec.Command(*whalewallBinary, "-debug", "-d", tempDir)
wwCmd := exec.Command(*whalewallBinary, "-debug", "-d", tempDir, "-i=250ms")
wwCmd.Stdout = os.Stdout
wwCmd.Stderr = os.Stderr
err := wwCmd.Start()
Expand All @@ -229,7 +229,7 @@
func startContainer(t *testing.T, is *is.I, tempDir string) func() {
t.Helper()

dockerCmd := exec.Command(
dockerCmd := exec.Command(

Check failure on line 232 in whalewall_test.go

View workflow job for this annotation

GitHub Actions / lint-go / Run golangci-lint

File is not `gci`-ed with --skip-generated -s standard -s default -s prefix(github.com/capnspacehook/whalewall) (gci)
"docker",
"run",
"--cap-add=NET_ADMIN",
Expand All @@ -240,7 +240,8 @@
*whalewallImage,
"-d=/data",
"-debug",
)
"-i=250ms",

Check failure on line 243 in whalewall_test.go

View workflow job for this annotation

GitHub Actions / lint-go / Run golangci-lint

File is not `gci`-ed with --skip-generated -s standard -s default -s prefix(github.com/capnspacehook/whalewall) (gci)
)
dockerCmd.Stdout = os.Stdout
dockerCmd.Stderr = os.Stderr
err := dockerCmd.Start()
Expand Down Expand Up @@ -270,7 +271,7 @@
logger.Info("starting whalewall")
ctx, cancel := context.WithCancel(context.Background())
dbFile := filepath.Join(tempDir, "db.sqlite")
r, err := NewRuleManager(ctx, logger, dbFile, defaultTimeout)
r, err := NewRuleManager(ctx, logger, dbFile, defaultTimeout, 250*time.Millisecond)
is.NoErr(err)
err = r.Start(ctx)
is.NoErr(err)
Expand Down Expand Up @@ -2335,7 +2336,7 @@
is := is.New(t)

dbFile := filepath.Join(t.TempDir(), "db.sqlite")
r, err := NewRuleManager(context.Background(), logger, dbFile, defaultTimeout)
r, err := NewRuleManager(context.Background(), logger, dbFile, defaultTimeout, defaultTimeout)
is.NoErr(err)

var dockerCli *mockDockerClient
Expand Down Expand Up @@ -2559,7 +2560,7 @@
}

dbFile := filepath.Join(t.TempDir(), "db.sqlite")
r, err := NewRuleManager(context.Background(), logger, dbFile, defaultTimeout)
r, err := NewRuleManager(context.Background(), logger, dbFile, defaultTimeout, time.Minute)
is.NoErr(err)

dockerCli := newMockDockerClient(nil)
Expand Down Expand Up @@ -2701,7 +2702,7 @@
}

dbFile := filepath.Join(t.TempDir(), "db.sqlite")
r, err := NewRuleManager(context.Background(), logger, dbFile, defaultTimeout)
r, err := NewRuleManager(context.Background(), logger, dbFile, defaultTimeout, time.Minute)
is.NoErr(err)

dockerCli := newMockDockerClient(nil)
Expand Down Expand Up @@ -2838,7 +2839,7 @@
is.NoErr(err)

dbFile := filepath.Join(t.TempDir(), "db.sqlite")
r, err := NewRuleManager(context.Background(), logger, dbFile, defaultTimeout)
r, err := NewRuleManager(context.Background(), logger, dbFile, defaultTimeout, time.Minute)
is.NoErr(err)

// configure database to pause before committing so we can cancel
Expand Down
Loading