Documentation
¶
Overview ¶
Package netctx is a sensor's network self-description: what the host owner declares (site, routes it reaches, DNS suffixes, internet mode), what the SDK detects on the host (interfaces, routes, resolvers, the egress path to the platform) and a fingerprint that changes when the sensor moves. The sensor manifest carries the report (member "network") to a platform that lists the "network" hello feature.
Security. The report is a hint the platform may use only to take work away from this sensor or to rank sensors that are already authorized; it never authorizes anything. It discloses internal topology, so:
- the default mode is summary (private prefixes listed, public ones counted), and the host owner can choose off (only the fingerprint and the internet mode leave the host);
- the report never names a prefix outside the local policy's targets.allow when that is set (entries are narrowed or dropped and counted in Trimmed);
- every list is bounded and sorted, so the report is deterministic;
- detection reads local tables only (interfaces, the routing table, resolv.conf). The only network call is the Echo the caller supplies (the platform's egress echo): no cloud metadata endpoint, no LAN scanning, no DNS query.
Detection never changes local enforcement: the local policy keeps the final say on what this sensor scans.
Stability: Experimental (docs/STABILITY.md).
Index ¶
Constants ¶
const ( // EnvSite is the site label of this host ("hn-dc1"): at most 64 of // [A-Za-z0-9._-]. EnvSite = "SENSOR_NETWORK_SITE" // EnvReach lists the CIDRs this host routes to beyond its own links // (VPN, peering), comma-separated. EnvReach = "SENSOR_NETWORK_REACH" // EnvDNSSuffixes lists the DNS suffixes this host resolves with its // internal view ("corp.example"), comma-separated. EnvDNSSuffixes = "SENSOR_NETWORK_DNS_SUFFIXES" // EnvInternet overrides the detected internet mode: auto (default), // direct, proxy or none. EnvInternet = "SENSOR_INTERNET" // EnvReport is how much the report says: summary (default), full or // off. EnvReport = "SENSOR_NETWORK_REPORT" // EnvDNSResolvers is the resolvers a sensor's tools use instead of the // system's ("10.0.0.2,10.0.0.3:53"); read for the report and the DNS // view check, declared by the sensor that reads it. EnvDNSResolvers = "SENSOR_DNS_RESOLVERS" )
Settings the host owner declares (environment variables).
const ( MaxOnLink = 32 MaxReach = 64 MaxDNSSuffixes = 32 MaxResolvers = 8 MaxSearchDomains = 8 MaxSiteLen = 64 )
Bounds of a report and of a declaration.
const ( ReasonDeclared = "declared" ReasonEchoPublic = "echo_public" ReasonEchoPrivate = "echo_private" ReasonEchoUnsupported = "echo_unsupported" ReasonEchoFailed = "echo_failed" ReasonNoEcho = "no_echo" ReasonNoDefaultRoute = "no_default_route" )
Egress reasons (Egress.Reason): why Internet is what it is.
const DefaultEchoTimeout = 10 * time.Second
DefaultEchoTimeout bounds one egress echo.
const DefaultInterval = 5 * time.Minute
DefaultInterval is how often a Watcher detects again.
const ReportVersion = 1
ReportVersion is the report's schema version.
Variables ¶
var ErrEchoUnsupported = errors.New("the platform does not serve the egress echo")
ErrEchoUnsupported is returned by an Echo when the platform does not serve the egress echo.
var ErrInvalidDeclaration = errors.New("invalid network declaration")
ErrInvalidDeclaration is wrapped by every ParseDeclared error.
Functions ¶
func Fingerprint ¶
Fingerprint is "sha256:" + the hex SHA-256 of the facts that change when the sensor moves: its site, its private on-link prefixes, its default gateways, its resolvers and search domains (each sorted). It is computed before any trimming, so a local policy edit does not read as a move. It is a change detector, not a secret: the facts are low-entropy.
Types ¶
type Declared ¶
type Declared struct {
Site string
Reach []netip.Prefix
DNSSuffixes []string
Internet Internet
Mode Mode
}
Declared is what the host owner states about this host's network.
func ParseDeclared ¶
ParseDeclared reads the declaration from the environment (lookup is os.LookupEnv in a sensor). A value it cannot use is an error naming the setting, never a silent default: the host owner asked for something.
type Detected ¶
type Detected struct {
// OnLink are the prefixes of the host's own interfaces (container and
// virtual bridges left out), sorted.
OnLink []netip.Prefix
// RoutesKnown: the routing table could be read.
RoutesKnown bool
// Routes are the usable routes (rejected and loopback left out).
Routes []Route
// DefaultV4 and DefaultV6: a default route exists (meaningful only
// when RoutesKnown).
DefaultV4, DefaultV6 bool
// Gateways are the default routes' next hops, sorted.
Gateways []netip.Addr
// SystemResolvers are resolv.conf's name servers; Resolvers are the
// ones the sensor's tools use (SENSOR_DNS_RESOLVERS when set).
SystemResolvers, Resolvers []netip.Addr
// ResolversFromEnv: Resolvers come from SENSOR_DNS_RESOLVERS.
ResolversFromEnv bool
// ResolvConfRead: resolv.conf could be read.
ResolvConfRead bool
// SearchDomains are resolv.conf's search (or domain) entries.
SearchDomains []string
}
Detected is what detection found on the host.
type Echo ¶
type Echo func(ctx context.Context) (EchoResult, error)
Echo asks the platform which address this sensor's requests come from. It is the only network call detection makes.
type EchoResult ¶
type EchoResult struct {
Address netip.Addr
// ViaProxy: the request went through a proxy (the control proxy).
ViaProxy bool
}
EchoResult is what the platform's egress echo answered: the address it saw this sensor's request come from.
type Egress ¶
type Egress struct {
Internet Internet
// Declared: the host owner set it (SENSOR_INTERNET).
Declared bool
Reason string
// Address is the address the platform saw (zero: no echo).
Address netip.Addr
ViaProxy bool
}
Egress is the internet mode and how it was decided.
func ResolveEgress ¶
func ResolveEgress(ctx context.Context, d Declared, det Detected, echo Echo, timeout time.Duration) Egress
ResolveEgress decides the internet mode. A declaration wins. Otherwise an echo the platform saw from a public address means the path crosses the internet (direct, or proxy when it went through one). An echo failure is "unknown", never "none": a blocked echo must not take internet work away from a sensor that has internet. "none" is detected only when the routing table is known and has no default route (and no proxy carried the echo).
type Iface ¶
type Iface struct {
Name string
Flags net.Flags
// Addrs are the interface's addresses with their prefix lengths.
Addrs []netip.Prefix
}
Iface is one network interface as detection sees it.
type Options ¶
type Options struct {
Declared Declared
// Sources are where detection reads (zero value: DefaultSources).
Sources *Sources
// Echo is the platform's egress echo (nil: the internet mode is
// declared, "none" without a default route, or "unknown").
Echo Echo
EchoTimeout time.Duration
// Scope returns the local policy's targets.allow (nil: no limit). It
// is called on every refresh, so a reloaded policy applies.
Scope func() Scope
}
Options configure a Watcher.
type Report ¶
type Report struct {
V int `json:"v"`
Mode Mode `json:"mode"`
// Fingerprint changes when the sensor moves (another network, other
// gateways, resolvers, search domains or site); not when its egress
// address changes.
Fingerprint string `json:"fingerprint"`
Internet Internet `json:"internet"`
// InternetDeclared: the host owner set Internet.
InternetDeclared bool `json:"internet_declared,omitempty"`
// EgressAddress is the address the platform's echo saw (summary and
// full only).
EgressAddress string `json:"egress_address,omitempty"`
Declared *ReportDeclared `json:"declared,omitempty"`
Detected *ReportDetected `json:"detected,omitempty"`
// Trimmed counts prefixes narrowed or left out because the local
// policy does not allow them, or because a list was over its bound.
Trimmed int `json:"trimmed,omitempty"`
}
Report is the network member of the sensor manifest.
type ReportDeclared ¶
type ReportDeclared struct {
Site string `json:"site,omitempty"`
Reach []string `json:"reach,omitempty"`
DNSSuffixes []string `json:"dns_suffixes,omitempty"`
}
ReportDeclared is the host owner's declaration as reported.
type ReportDetected ¶
type ReportDetected struct {
// OnLink are the on-link prefixes: private ones (summary), all (full).
OnLink []string `json:"on_link,omitempty"`
// PublicOnLink counts the public on-link prefixes summary leaves out.
PublicOnLink int `json:"public_on_link,omitempty"`
// Routes says whether the routing table was read: "known" or
// "unknown" (DefaultV4/DefaultV6 mean nothing then).
Routes string `json:"routes"`
DefaultV4 bool `json:"default_route_v4,omitempty"`
DefaultV6 bool `json:"default_route_v6,omitempty"`
Resolvers []string `json:"resolvers,omitempty"`
SearchDomains []string `json:"search_domains,omitempty"`
}
ReportDetected is what detection found, as reported.
type Scope ¶
type Scope struct {
// Set: targets.allow is set (or no target is allowed at all); only
// prefixes inside Allow are named.
Set bool
Allow []netip.Prefix
// Deny are targets.deny ranges: a prefix inside one is not named.
Deny []netip.Prefix
// NoPrivate: private targets are refused, so no private prefix is
// named.
NoPrivate bool
}
Scope is what the local policy lets this sensor scan; the report names no prefix outside it.
type Sources ¶
type Sources struct {
// Interfaces lists the host's interfaces.
Interfaces func() ([]Iface, error)
// RouteV4 and RouteV6 return the Linux routing tables
// (/proc/net/route, /proc/net/ipv6_route); nil, or an error, means
// "unknown" (other systems).
RouteV4, RouteV6 func() ([]byte, error)
// ResolvConf returns /etc/resolv.conf.
ResolvConf func() ([]byte, error)
// Lookup reads the environment (SENSOR_DNS_RESOLVERS).
Lookup func(string) (string, bool)
}
Sources are where detection reads from. The zero value reads the host (DefaultSources); tests replace each one.
type Watcher ¶
type Watcher struct {
// contains filtered or unexported fields
}
Watcher detects the host's network facts on demand and on an interval and keeps the latest report. It is safe for concurrent use.
func NewWatcher ¶
NewWatcher returns a watcher; nothing is detected until Refresh or Run.
func (*Watcher) Refresh ¶
Refresh detects now and reports whether the report changed (the first refresh always does).
func (*Watcher) Report ¶
Report is the latest report (nil before the first refresh). The report is never modified once returned.
func (*Watcher) Rescope ¶
Rescope rebuilds the report from the latest detection under the current Scope, without detecting again or calling the echo (the local policy was reloaded: a narrower policy must narrow the report at once). It reports whether the report changed; false before the first refresh.