Documentation
¶
Overview ¶
Package dockerguard is an in-process Unix socket proxy in front of the Docker Engine API. It forwards only an allowlisted set of endpoints and rejects container configurations that would hand a container the host. See docs/docker-access.md for the surface and the rules.
Index ¶
- Constants
- Variables
- func LogPrivilege(ctx context.Context, logger *slog.Logger, client *docker.Client)
- func RunningInContainer() bool
- func SaveSettings(path string, s Settings) error
- func SocketPath(lookup func(string) (string, bool), dataDir string) string
- func UpstreamSocket(host string) (string, error)
- type BootConfig
- type Booted
- type Config
- type Controller
- type Decision
- type FileSink
- type Grants
- type Guard
- type Mode
- type Policy
- type RuleCount
- type Server
- type Settings
- type Sink
- type Stats
- type Status
- type Tunables
- type Violation
Constants ¶
const ( EnvMode = "APP_DOCKER_GUARD" EnvSocket = "APP_DOCKER_GUARD_SOCKET" EnvMaxBodyBytes = "APP_DOCKER_GUARD_MAX_BODY_BYTES" EnvAuditDedup = "APP_DOCKER_GUARD_AUDIT_DEDUP" EnvSummaryWindow = "APP_DOCKER_GUARD_SUMMARY_WINDOW" EnvAllowHostNetwork = "APP_DOCKER_GUARD_ALLOW_HOST_NETWORK" EnvHeaderTimeout = "APP_DOCKER_GUARD_HEADER_TIMEOUT" EnvRecordQueue = "APP_DOCKER_GUARD_RECORD_QUEUE" )
Environment variables the guard reads.
const ( DefaultMaxBodyBytes int64 = 4 << 20 DefaultAuditDedup = time.Hour DefaultSummaryWindow = 7 * 24 * time.Hour DefaultHeaderTimeout = 30 * time.Second DefaultRecordQueue = 256 // SocketName is the guard socket's file name under the data dir. SocketName = "docker-guard.sock" )
Defaults for the tunables above.
const ( ActionDenied = "docker_guard.denied" ActionWouldDeny = "docker_guard.would_deny" // ActionFamily matches both in an audit search. ActionFamily = "docker_guard" )
Audit actions a Sink writes.
const ( RuleEndpointNotAllowed = "endpoint_not_allowed" RulePathNoncanonical = "path_noncanonical" RuleBodyTooLarge = "body_too_large" RuleBodyInvalid = "body_invalid" RulePrivileged = "privileged" RuleHostPID = "host_pid" RuleHostIPC = "host_ipc" RuleHostUTS = "host_uts" RuleHostUserns = "host_userns" RuleHostCgroupns = "host_cgroupns" RuleContainerNS = "container_namespace_share" RuleNetworkHost = "network_host" RuleCapAdd = "cap_add_disallowed" RuleDevices = "devices_not_granted" RuleDeviceCgroupRules = "device_cgroup_rules" RuleSecurityOpt = "security_opt_weakened" RuleMaskedPaths = "masked_paths_override" RuleBindSensitive = "bind_sensitive_path" RuleBindUndeclared = "bind_undeclared" RuleMountType = "mount_type_disallowed" RuleVolumesFrom = "volumes_from" RuleVolumeBindDriverOpt = "volume_bind_driver_opts" RuleExecPrivileged = "exec_privileged" RuleImageImport = "image_import" )
Rule ids. They appear in audit rows, logs and the 403 body, so they are part of the public contract: never rename one, add a new id instead.
const ( SourceEnv = "env" SourceSettings = "settings" SourceDefault = "default" )
Mode sources, reported so an operator knows where to change it.
const DefaultMode = ModeAudit
DefaultMode is audit so an upgrade cannot break a running deploy; the attention feed offers enforce after a clean week. APP_DOCKER_GUARD sets it explicitly.
const FileSinkName = "docker-guard-audit.jsonl"
FileSinkName is the node-local audit file a node agent appends to.
const SettingsFile = "docker-guard.json"
SettingsFile is the operator's persisted mode choice under the data dir.
Variables ¶
var AllRules = []string{ RuleEndpointNotAllowed, RulePathNoncanonical, RuleBodyTooLarge, RuleBodyInvalid, RulePrivileged, RuleHostPID, RuleHostIPC, RuleHostUTS, RuleHostUserns, RuleHostCgroupns, RuleContainerNS, RuleNetworkHost, RuleCapAdd, RuleDevices, RuleDeviceCgroupRules, RuleSecurityOpt, RuleMaskedPaths, RuleBindSensitive, RuleBindUndeclared, RuleMountType, RuleVolumesFrom, RuleVolumeBindDriverOpt, RuleExecPrivileged, RuleImageImport, }
AllRules lists every rule id, in the order docs/docker-access.md documents them.
var ErrPinnedByEnv = errors.New("dockerguard: mode is pinned by " + EnvMode)
ErrPinnedByEnv means APP_DOCKER_GUARD is set, so the API cannot change it.
var GrantableCaps = []string{"NET_ADMIN", "NET_RAW"}
GrantableCaps are capabilities a container may add only when its own create was declared with them: the egress sidecar's firewall needs these.
Functions ¶
func LogPrivilege ¶
LogPrivilege records how much a compromise of this process would hold.
func RunningInContainer ¶
func RunningInContainer() bool
RunningInContainer reports whether this process sees a container's filesystem rather than the host's, where host path checks cannot resolve.
func SaveSettings ¶
SaveSettings writes path atomically with mode 0600.
func SocketPath ¶
SocketPath is where the guard listens: APP_DOCKER_GUARD_SOCKET, or a private directory under dataDir.
func UpstreamSocket ¶
UpstreamSocket turns a docker host URL into the Unix socket path the guard forwards to. Only unix:// hosts can be guarded.
Types ¶
type BootConfig ¶
type BootConfig struct {
// DataDir holds the socket and settings file and is itself protected.
DataDir string
// ExtraProtected are further host paths no bind may cover.
ExtraProtected []string
// ResolveSymlinks is false when this process does not share the
// daemon's view of the host filesystem.
ResolveSymlinks bool
Sink Sink
Logger *slog.Logger
Lookup func(string) (string, bool)
}
BootConfig is what a binary passes to Boot.
type Booted ¶
type Booted struct {
Server *Server
Controller *Controller
Grants *Grants
Host string
}
Booted is Boot's result. Host is empty when clients should dial Docker directly (mode off, or audit mode whose socket failed to start).
type Config ¶
type Config struct {
Mode Mode
Upstream string // Unix socket path of the real daemon
Policy Policy
Grants *Grants
Tunables Tunables
// Sink receives every decision worth an audit row; may be nil.
Sink Sink
Logger *slog.Logger
}
Config is what New needs.
type Controller ¶
type Controller struct {
// contains filtered or unexported fields
}
Controller owns the guard's configuration for the API: read status, switch mode, persist the choice.
func NewController ¶
func NewController(settingsPath string, lookup func(string) (string, bool), server *Server, upstream string, startErr error) *Controller
NewController wraps server (nil when the guard is off or failed to start).
func (*Controller) NoteAuditStart ¶
func (c *Controller) NoteAuditStart(now time.Time) error
NoteAuditStart stamps AuditSince the first time audit mode runs.
func (*Controller) Status ¶
func (c *Controller) Status() Status
Status reports the current configuration and counters.
type Decision ¶
type Decision struct {
Mode Mode
Denied bool
Method string
Path string
Pattern string
Container string
Violations []Violation
At time.Time
}
Decision is one request a rule flagged. It never carries a body.
type FileSink ¶
type FileSink struct {
// contains filtered or unexported fields
}
FileSink appends one JSON line per decision, for a process with no database (the node agent). The file is created 0600.
func NewFileSink ¶
NewFileSink returns a sink appending to path.
type Grants ¶
type Grants struct {
// contains filtered or unexported fields
}
Grants tracks what each in-flight container create was declared with by internal/docker's Create. A declaration lives only for the duration of that call, so a request that did not come from Create carries no grant.
func (*Grants) DeclareCreate ¶
func (g *Grants) DeclareCreate(d docker.CreateDeclaration) func()
DeclareCreate implements docker.CreateDeclarer.
type Guard ¶
type Guard struct {
// contains filtered or unexported fields
}
Guard is the policy-enforcing reverse proxy. It is an http.Handler; Serve puts it on a Unix socket.
func (*Guard) Close ¶
func (g *Guard) Close()
Close stops the async recorder after draining what it already queued.
type Mode ¶
type Mode string
Mode selects what the guard does with a request a rule would deny.
type Policy ¶
type Policy struct {
// AllowedCaps may appear in CapAdd on any container: the hardening
// profile's minimal set plus operator extras.
AllowedCaps []string
// SensitivePaths are host paths no bind may cover, on top of
// internal/bindmount.ForbiddenPaths.
SensitivePaths []string
// AllowHostNetwork lets a create declared with host networking through.
AllowHostNetwork bool
// ResolveSymlinks checks a bind's resolved host path too. Off when this
// process cannot see the host filesystem (the agent in a container).
ResolveSymlinks bool
}
Policy is the fixed part of what the body validator allows.
func PolicyFromHardening ¶
func PolicyFromHardening(h docker.HardeningConfig, t Tunables, dataDirs ...string) Policy
PolicyFromHardening builds the default policy from the hardening profile and the data directories this process owns.
type RuleCount ¶
type RuleCount struct {
Rule string `json:"rule"`
Denied int64 `json:"denied"`
WouldDeny int64 `json:"would_deny"`
LastSeen time.Time `json:"last_seen"`
// LastPath is the most recent canonical path, never a body.
LastPath string `json:"last_path"`
}
RuleCount is how often one rule fired since boot.
type Server ¶
Server is a running guard socket.
func Listen ¶
Listen creates the guard socket at socket (mode 0600, parent 0700) and serves g on it until ctx ends or Close is called.
type Settings ¶
type Settings struct {
Mode Mode `json:"mode,omitempty"`
// AuditSince is when audit mode was first observed, so the attention
// feed can tell a week of clean observation from a fresh install.
AuditSince time.Time `json:"audit_since,omitempty"`
UpdatedAt time.Time `json:"updated_at,omitempty"`
UpdatedBy string `json:"updated_by,omitempty"`
}
Settings is the persisted part of the guard configuration.
func LoadSettings ¶
LoadSettings reads path; a missing file is the zero Settings.
type Stats ¶
type Stats struct {
// contains filtered or unexported fields
}
Stats counts flagged requests per rule since the guard started.
type Status ¶
type Status struct {
// Mode is the configured mode, Running whether the socket is up, and
// Effective what the running guard does now.
Mode Mode `json:"mode"`
Source string `json:"source"`
Effective Mode `json:"effective"`
Running bool `json:"running"`
Socket string `json:"socket,omitempty"`
Upstream string `json:"upstream,omitempty"`
RestartRequired bool `json:"restart_required"`
ConfigError string `json:"config_error,omitempty"`
StartError string `json:"start_error,omitempty"`
AuditSince time.Time `json:"audit_since,omitempty"`
SinceBoot []RuleCount `json:"since_boot"`
Rules []string `json:"rules"`
}
Status is what the API and doctor report.