exposure

package
v0.2.0 Latest Latest
Warning

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

Go to latest
Published: Oct 12, 2026 License: Apache-2.0 Imports: 11 Imported by: 0

Documentation

Overview

Package exposure answers "which published container ports can the internet reach" and manages comment-tagged DOCKER-USER rules that restrict them. Published ports skip the INPUT chain, so ufw alone says nothing about them.

Index

Constants

This section is empty.

Variables

View Source
var ErrLockout = errors.New("exposure: restriction would lock out a required port")

ErrLockout is wrapped when a restriction would cut off a port the platform or the operator's SSH session needs.

Functions

func BindsOf

func BindsOf(ports []PortBinding) []string

BindsOf collects the distinct bind addresses, wildcard spelled explicitly.

func Explain

func Explain(f Finding, outsideLabel string) (explanation, recommendation string)

Explain returns the plain-language explanation and the suggested action.

func ValidCIDR

func ValidCIDR(s string) bool

ValidCIDR reports whether s parses as an IP or CIDR.

Types

type Chain

type Chain struct {
	// Readable is false when the chain could not be read; Reason says why.
	Readable bool
	Reason   string
	Rules    []ChainRule
}

Chain is the DOCKER-USER chain as read from the host.

func ParseChain

func ParseChain(out string) Chain

ParseChain parses `iptables -S DOCKER-USER` output.

type ChainRule

type ChainRule struct {
	Raw           string
	Proto         string
	DPort         int
	OrigDPort     int
	Source        string
	SourceNegated bool
	Target        string
	Comment       string
	Complex       bool
}

ChainRule is one parsed DOCKER-USER rule. Anything the parser does not fully understand sets Complex, so classification never trusts it.

type Class

type Class string

Class is how reachable one published port is.

const (
	ClassLoopback   Class = "loopback"
	ClassPrivate    Class = "private"
	ClassRestricted Class = "restricted"
	ClassExposed    Class = "exposed"
	ClassUnknown    Class = "unknown"
)

Reachability classes.

type Container

type Container struct {
	ID      string
	Name    string
	Image   string
	Owner   Owner
	Ports   []PortBinding
	Running bool
}

Container is one running container's published ports, as the audit reads them.

type Finding

type Finding struct {
	Container     string    `json:"container"`
	Image         string    `json:"image"`
	Owner         Owner     `json:"owner"`
	ImageKind     ImageKind `json:"image_kind"`
	Protocol      string    `json:"protocol"`
	HostPort      int       `json:"host_port"`
	ContainerPort int       `json:"container_port"`
	Binds         []string  `json:"binds"`
	Class         Class     `json:"class"`
	Severity      Severity  `json:"severity"`
	// AllowedSources lists the sources a matching rule lets through, when Class is restricted.
	AllowedSources []string `json:"allowed_sources,omitempty"`
	// Rules are the matching DOCKER-USER rules, verbatim.
	Rules []string `json:"rules,omitempty"`
	// Managed is true when the matching rule is one this platform created.
	Managed        bool   `json:"managed"`
	Explanation    string `json:"explanation"`
	Recommendation string `json:"recommendation,omitempty"`
	// CanRestrict is false when this audit cannot offer the guided fix, with the reason.
	CanRestrict       bool   `json:"can_restrict"`
	CannotRestrictWhy string `json:"cannot_restrict_reason,omitempty"`
}

Finding is the audit result for one published port of one container.

func Audit

func Audit(containers []Container, chain Chain, prefix string) []Finding

Audit turns a container list into findings, one per published (port, protocol, container) and sorted worst first.

func (Finding) NeedsAttention

func (f Finding) NeedsAttention() bool

NeedsAttention reports whether the finding is worth surfacing: published beyond the host's private interfaces and not provably restricted.

type ImageKind

type ImageKind string

ImageKind is a coarse guess at what a container runs, from its image name and published port.

const (
	KindDatastore ImageKind = "datastore"
	KindSearch    ImageKind = "search"
	KindAdmin     ImageKind = "admin"
	KindDockerAPI ImageKind = "docker_api"
	KindWeb       ImageKind = "web"
	KindOther     ImageKind = "other"
)

Image kinds.

func ImageKindOf

func ImageKindOf(image string, containerPort int) ImageKind

ImageKindOf guesses what a container runs from its image repository name, falling back to the container port.

type Manager

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

Manager reads and edits the DOCKER-USER chain, touching only rules that carry its own comment tag.

func NewFakeManager

func NewFakeManager(prefix string, protected []int, run Runner, lookPath func(string) (string, error), goos string) *Manager

NewFakeManager builds a Manager over fake seams for tests.

func NewManager

func NewManager(prefix string, protected []int) *Manager

NewManager builds a Manager over the real iptables binary. protected lists ports that must never be restricted (control plane, ingress, SSH).

func (*Manager) Apply

func (m *Manager) Apply(ctx context.Context, r Restriction) (changed bool, err error)

Apply installs the restriction idempotently and reports whether anything changed.

func (*Manager) Normalize

func (m *Manager) Normalize(r Restriction) (Restriction, error)

Normalize validates a restriction and returns it in canonical form.

func (*Manager) Plan

func (m *Manager) Plan(r Restriction) (Plan, error)

Plan renders the exact commands Apply would run, in execution order.

func (*Manager) Prefix

func (m *Manager) Prefix() string

Prefix returns the rule comment prefix this manager owns.

func (*Manager) ReadChain

func (m *Manager) ReadChain(ctx context.Context) Chain

ReadChain reads DOCKER-USER, reporting why it could not when it cannot.

func (*Manager) Remove

func (m *Manager) Remove(ctx context.Context, port int, proto string) error

Remove deletes the rules this manager tagged for a port, and nothing else.

func (*Manager) Sync

func (m *Manager) Sync(ctx context.Context, want []Restriction) SyncResult

Sync converges the chain to want: it applies every restriction and removes tagged rules whose restriction no longer exists.

type Owner

type Owner struct {
	Kind OwnerKind `json:"kind"`
	Name string    `json:"name,omitempty"`
	// Intentional is true when the operator explicitly asked for public
	// access (a database's PubliclyAccessible, an app's public bind).
	Intentional bool `json:"intentional,omitempty"`
}

Owner is the platform resource behind a container, if any.

type OwnerKind

type OwnerKind string

OwnerKind says who owns the container publishing a port.

const (
	OwnerDatabase  OwnerKind = "database"
	OwnerApp       OwnerKind = "app"
	OwnerUnmanaged OwnerKind = "unmanaged"
)

Owner kinds.

type Plan

type Plan struct {
	Commands []string `json:"commands"`
	Drops    string   `json:"drops"`
	Tag      string   `json:"tag"`
	// Persistence says plainly how long the rules last.
	Persistence string `json:"persistence"`
}

Plan is the exact rule set a restriction produces.

type PortBinding

type PortBinding struct {
	HostIP        string
	HostPort      int
	ContainerPort int
	Protocol      string
}

PortBinding is one published port as Docker reports it.

type Restriction

type Restriction struct {
	Port     int      `json:"port"`
	Protocol string   `json:"protocol"`
	Allow    []string `json:"allow"`
}

Restriction is a desired allow-list for one published port.

type Runner

type Runner func(ctx context.Context, name string, args ...string) ([]byte, error)

Runner executes a command, abstracted so tests never touch a real firewall.

type Severity

type Severity string

Severity ranks a finding for the operator.

const (
	SeverityHigh   Severity = "high"
	SeverityMedium Severity = "medium"
	SeverityLow    Severity = "low"
	SeverityInfo   Severity = "info"
)

Finding severities.

func SeverityFor

func SeverityFor(class Class, kind ImageKind, owner Owner) Severity

SeverityFor ranks a finding by what is exposed, not just that it is.

type SyncResult

type SyncResult struct {
	Applied int
	Removed int
	Errors  []string
}

SyncResult counts what one Sync pass changed.

type Verdict

type Verdict struct {
	Class   Class
	Allowed []string
	Rules   []string
	Managed bool
	Reason  string
}

Verdict is the reachability decision for one published port.

func Classify

func Classify(binds []string, hostPort, containerPort int, proto string, chain Chain, prefix string) Verdict

Classify decides how reachable a binding is given the DOCKER-USER chain. It never reports restricted unless a simple matching DROP or REJECT exists.

Jump to

Keyboard shortcuts

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