firewall

package
v0.6.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 26, 2026 License: MIT Imports: 31 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrEnvoyUnhealthy   = errors.New("envoy not healthy")
	ErrCoreDNSUnhealthy = errors.New("coredns not healthy")
)

Sentinel errors for health check failures.

Functions

func EnsureCA

func EnsureCA(certDir string) (*x509.Certificate, *ecdsa.PrivateKey, error)

EnsureCA creates a self-signed CA keypair if none exists under certDir, or loads the existing one.

func EnsureDaemon

func EnsureDaemon(cfg config.Config, log *logger.Logger) error

EnsureDaemon checks if the firewall daemon is running and spawns it if not. Returns immediately — does not wait for the daemon to become healthy.

func GenerateCorefile

func GenerateCorefile(rules []config.EgressRule, healthPort int) ([]byte, error)

GenerateCorefile produces a CoreDNS Corefile from the given egress rules. healthPort is the port the CoreDNS health plugin listens on (inside the container).

Only "allow" rules with domain destinations (not IPs/CIDRs) get forward zones. Each allowed domain gets its own zone forwarding to Cloudflare malware-blocking DNS. The catch-all "." zone returns NXDOMAIN for everything else.

func GenerateDomainCert

func GenerateDomainCert(caCert *x509.Certificate, caKey *ecdsa.PrivateKey, domain string) (certPEM, keyPEM []byte, err error)

GenerateDomainCert signs a per-domain certificate for MITM inspection. The certificate is signed by the given CA and has the domain as a SAN. Returns PEM-encoded cert and key bytes.

func GenerateEnvoyConfig

func GenerateEnvoyConfig(rules []config.EgressRule, ports EnvoyPorts) ([]byte, []string, error)

GenerateEnvoyConfig produces an Envoy static bootstrap YAML from egress rules. Returns the YAML bytes and a list of warnings (non-fatal issues).

func GetDaemonPID

func GetDaemonPID(pidFile string) int

GetDaemonPID returns the PID of the running firewall daemon, or 0 if not running.

func IsDaemonRunning

func IsDaemonRunning(pidFile string) bool

IsDaemonRunning checks if the firewall daemon is running via PID file.

func NewRulesStore

func NewRulesStore(cfg config.Config) (*storage.Store[EgressRulesFile], error)

NewRulesStore creates a storage.Store[EgressRulesFile] for egress-rules.yaml. The store uses the firewall data subdirectory for file discovery.

func ProjectRules

func ProjectRules(cfg config.Config) []config.EgressRule

ProjectRules builds the full rule set from project config and required rules. Used by CLI code to sync rules into the store before the daemon starts.

func RegenerateDomainCerts

func RegenerateDomainCerts(rules []config.EgressRule, certDir string, caCert *x509.Certificate, caKey *ecdsa.PrivateKey) error

RegenerateDomainCerts generates certificates for all rules that have PathRules, storing them in certDir/<domain>-cert.pem and <domain>-key.pem. Rules without PathRules are skipped (SNI passthrough, no MITM needed).

func RotateCA

func RotateCA(certDir string, rules []config.EgressRule) error

RotateCA regenerates the CA keypair and all domain certificates. The old CA files are overwritten. Any running containers will need the new CA injected to trust the regenerated domain certs.

func StopDaemon

func StopDaemon(pidFile string) error

StopDaemon sends SIGTERM to the firewall daemon. No-op if not running.

Types

type Daemon

type Daemon struct {
	// contains filtered or unexported fields
}

Daemon manages the firewall stack as a background process. It starts the Envoy+CoreDNS containers, monitors their health, and auto-exits (tearing down the stack) when no clawker containers are running.

func NewDaemon

func NewDaemon(cfg config.Config, log *logger.Logger, opts ...DaemonOption) (*Daemon, error)

NewDaemon creates a new firewall daemon.

func (*Daemon) Run

func (d *Daemon) Run(ctx context.Context) error

Run starts the firewall stack, then blocks until signal, healthcheck failure, or auto-exit.

type DaemonOption

type DaemonOption func(*Daemon)

DaemonOption is a functional option for overriding daemon config values.

func WithDaemonGracePeriod

func WithDaemonGracePeriod(d time.Duration) DaemonOption

WithDaemonGracePeriod overrides the initial grace period before container watching.

func WithDaemonPollInterval

func WithDaemonPollInterval(d time.Duration) DaemonOption

WithDaemonPollInterval overrides the container watcher poll interval.

func WithHealthCheckInterval

func WithHealthCheckInterval(d time.Duration) DaemonOption

WithHealthCheckInterval overrides the healthcheck poll interval.

type EgressRulesFile

type EgressRulesFile struct {
	Rules []config.EgressRule `yaml:"rules" label:"Rules" desc:"Active egress firewall rules"`
}

EgressRulesFile is the top-level document type for storage.Store[T]. It persists the active set of project-level egress rules to disk.

func (EgressRulesFile) Fields added in v0.6.0

func (f EgressRulesFile) Fields() storage.FieldSet

Fields implements storage.Schema for EgressRulesFile.

type EnvoyPorts

type EnvoyPorts struct {
	TLSPort     int // Listener port for TLS egress (SNI passthrough + MITM).
	TCPPortBase int // Starting port for TCP/SSH listeners.
	HTTPPort    int // Listener port for plain HTTP egress (Host header domain detection).
}

EnvoyPorts holds the port configuration for the Envoy proxy, sourced from config.Config.

type FirewallManager

type FirewallManager interface {
	// EnsureRunning starts the firewall stack (Envoy + CoreDNS containers) if not already running.
	EnsureRunning(ctx context.Context) error

	// Stop tears down the firewall stack.
	Stop(ctx context.Context) error

	// IsRunning reports whether the firewall stack is currently running.
	IsRunning(ctx context.Context) bool

	// WaitForHealthy polls until both firewall services pass health probes (TCP+HTTP)
	// or the context expires. Timeout should be set on the context by the caller.
	WaitForHealthy(ctx context.Context) error

	// AddRules adds individual egress rules (CLI "firewall add").
	// Writes to store, regenerates configs, restarts containers if running.
	AddRules(ctx context.Context, rules []config.EgressRule) error

	// RemoveRules deletes egress rules (CLI "firewall remove").
	RemoveRules(ctx context.Context, rules []config.EgressRule) error

	// Reload force-regenerates envoy.yaml and Corefile from current rules
	// and restarts the Envoy container. CoreDNS auto-reloads via reload plugin.
	Reload(ctx context.Context) error

	// List returns all currently active egress rules.
	List(ctx context.Context) ([]config.EgressRule, error)

	// Disable flushes iptables rules in the container, giving unrestricted egress.
	Disable(ctx context.Context, containerID string) error

	// Enable applies iptables rules in the container with current network info.
	Enable(ctx context.Context, containerID string) error

	// Bypass disables firewall and schedules re-enable after timeout.
	// Uses detached docker exec: sleep <timeout> && firewall.sh enable <args>.
	// Returns immediately — timer runs inside the container.
	// To cancel early: call Enable() directly (idempotent, re-applies rules).
	Bypass(ctx context.Context, containerID string, timeout time.Duration) error

	// Status returns a health snapshot of the firewall stack.
	Status(ctx context.Context) (*FirewallStatus, error)

	// EnvoyIP returns the static IP assigned to the Envoy proxy container.
	EnvoyIP() string

	// CoreDNSIP returns the static IP assigned to the CoreDNS container.
	CoreDNSIP() string

	// NetCIDR returns the CIDR block of the isolated Docker firewall network.
	NetCIDR() string
}

FirewallManager is the interface for managing the Envoy+CoreDNS firewall stack. Concrete implementation: DockerFirewallManager. Mock: mocks.FirewallManagerMock.

type FirewallStatus

type FirewallStatus struct {
	Running       bool
	EnvoyHealth   bool
	CoreDNSHealth bool
	RuleCount     int
	EnvoyIP       string
	CoreDNSIP     string
	NetworkID     string
}

FirewallStatus is a health snapshot of the Envoy+CoreDNS firewall stack.

type HealthTimeoutError

type HealthTimeoutError struct {
	Timeout time.Duration
	Err     error // wraps one or both sentinel errors
}

HealthTimeoutError is returned when WaitForHealthy exceeds its deadline.

func (*HealthTimeoutError) Error

func (e *HealthTimeoutError) Error() string

func (*HealthTimeoutError) Unwrap

func (e *HealthTimeoutError) Unwrap() error

type Manager

type Manager struct {
	// contains filtered or unexported fields
}

Manager implements FirewallManager using the Docker API. It manages Envoy and CoreDNS containers on the clawker-net Docker network.

func NewManager

func NewManager(client client.APIClient, cfg config.Config, log *logger.Logger) (*Manager, error)

NewManager creates a new DockerFirewallManager.

func (*Manager) AddRules

func (m *Manager) AddRules(ctx context.Context, rules []config.EgressRule) error

AddRules adds individual egress rules to the running firewall (CLI "firewall add"). Deduplicates against existing rules, persists, regenerates configs, and hot-reloads.

func (*Manager) Bypass

func (m *Manager) Bypass(ctx context.Context, containerID string, timeout time.Duration) error

Bypass disables iptables rules and schedules re-enable after timeout. Uses detached docker exec: sleep <timeout> && firewall.sh enable <args>.

func (*Manager) CoreDNSIP

func (m *Manager) CoreDNSIP() string

CoreDNSIP returns the static IP assigned to the CoreDNS container.

func (*Manager) Disable

func (m *Manager) Disable(ctx context.Context, containerID string) error

Disable disconnects a container from the firewall network, blocking all egress through the firewall stack.

func (*Manager) Enable

func (m *Manager) Enable(ctx context.Context, containerID string) error

Enable re-applies DNAT + firewall DNS in an agent container via docker exec. Reads the current rules from the store to compute TCP port mappings.

func (*Manager) EnsureRunning

func (m *Manager) EnsureRunning(ctx context.Context) error

EnsureRunning starts the firewall stack if not already running. Generates Envoy/CoreDNS configs from whatever rules are in the store, and ensures containers are running. Rule syncing is a CLI responsibility (call SyncRules before EnsureDaemon). Idempotent — safe to call on every container startup.

func (*Manager) EnvoyIP

func (m *Manager) EnvoyIP() string

EnvoyIP returns the static IP assigned to the Envoy proxy container.

func (*Manager) IsRunning

func (m *Manager) IsRunning(ctx context.Context) bool

IsRunning reports whether both firewall containers are running.

func (*Manager) List

func (m *Manager) List(_ context.Context) ([]config.EgressRule, error)

List returns all currently active egress rules.

func (*Manager) NetCIDR

func (m *Manager) NetCIDR() string

NetCIDR returns the CIDR block of the isolated Docker firewall network.

func (*Manager) Reload

func (m *Manager) Reload(ctx context.Context) error

Reload force-regenerates envoy.yaml and Corefile from current rules and restarts the Envoy container.

func (*Manager) RemoveRules

func (m *Manager) RemoveRules(ctx context.Context, rules []config.EgressRule) error

RemoveRules deletes egress rules from the running firewall (CLI "firewall remove"). Persists, regenerates configs, and hot-reloads.

func (*Manager) Status

func (m *Manager) Status(ctx context.Context) (*FirewallStatus, error)

Status returns a health snapshot of the firewall stack.

func (*Manager) Stop

func (m *Manager) Stop(ctx context.Context) error

Stop tears down the firewall stack (containers only, not the network). The network is left in place because monitoring or agent containers may be attached.

func (*Manager) WaitForHealthy

func (m *Manager) WaitForHealthy(ctx context.Context) error

WaitForHealthy polls until both firewall services pass health probes (TCP+HTTP) or the context expires. Probes go through published localhost ports so they work from the host (macOS Docker Desktop doesn't route to container IPs).

  • Envoy: TCP connect to localhost:18901 (published TLS listener).
  • CoreDNS: HTTP GET to localhost:18902/health (published health endpoint).

type NetworkInfo

type NetworkInfo struct {
	NetworkID string
	EnvoyIP   string
	CoreDNSIP string
	CIDR      string
}

NetworkInfo holds discovered state about the firewall Docker network.

type TCPMapping

type TCPMapping struct {
	Dst       string // Destination domain or IP.
	DstPort   int    // Original destination port (e.g. 22, 8080).
	EnvoyPort int    // Envoy listener port (TCPPortBase + index).
}

TCPMapping describes a per-destination iptables DNAT entry for non-TLS traffic. Each TCP/SSH rule gets a dedicated Envoy listener port.

func HTTPMappings

func HTTPMappings(rules []config.EgressRule, httpPort int) []TCPMapping

HTTPMappings computes HTTP port mappings from egress rules. Unlike TCP mappings (one listener per rule), all HTTP rules share a single listener because the Host header provides domain detection. Multiple rules on the same port produce a single DNAT entry.

func TCPMappings

func TCPMappings(rules []config.EgressRule, ports EnvoyPorts) []TCPMapping

TCPMappings computes TCP port mappings from egress rules. The result is deterministic for a given rule set — same rules produce same mappings. Used by both GenerateEnvoyConfig (to build listeners) and Enable (to build iptables args).

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL