integrity

package
v0.5.0 Latest Latest
Warning

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

Go to latest
Published: Aug 3, 2026 License: Apache-2.0 Imports: 17 Imported by: 0

Documentation

Overview

Package integrity verifies the plexd binary and hook scripts by SHA-256 checksum, and the SSH host key by its OpenSSH fingerprint.

Package integrity verifies the plexd binary and hook scripts by SHA-256 checksum, and the SSH host key by its OpenSSH fingerprint.

Index

Constants

View Source
const DefaultVerifyInterval = 5 * time.Minute

DefaultVerifyInterval is the default interval between integrity verification runs.

Variables

This section is empty.

Functions

func HashFile

func HashFile(path string) (string, error)

HashFile computes the SHA-256 checksum of the file at path using streaming I/O.

func HostKeyFingerprint added in v0.4.0

func HostKeyFingerprint(path string) (string, error)

HostKeyFingerprint parses the OpenSSH private key at path and renders its public half as the canonical `SHA256:<base64>` fingerprint — the form `ssh-keygen -l` prints, the one the capability manifest already carries, and the one the integrity contract's fingerprint fields accept.

The fingerprint, not a file digest, is what identifies a host key: the same key re-serialised is a different PEM but the same identity, and it is the identity a peer pins.

func SelfChecksum added in v0.2.0

func SelfChecksum() (string, error)

SelfChecksum computes the SHA-256 checksum of the currently running binary.

func WireChecksum added in v0.4.0

func WireChecksum(hexDigest string) (string, error)

WireChecksum re-encodes a hex digest from HashFile into the form the control plane's checksum fields carry: the 32 raw bytes in standard-padded base64.

Hex is this package's own currency — it is what the baseline store holds and what hook comparisons run on — but on the wire those fields are declared `format: byte`, so a hex string is decoded as base64 and yields 48 bytes instead of 32. The capability manifest refuses that outright, and while the heartbeat contract also documents a hex form, the deployed control plane answers it with 400 `binary_checksum_empty`. Base64 is the one encoding both operations accept, so it is the one this agent sends.

Types

type CheckResult

type CheckResult struct {
	// Path is the filesystem path that was verified.
	Path string
	// Expected is the hex-encoded SHA-256 checksum that was expected.
	Expected string
	// Actual is the hex-encoded SHA-256 checksum that was computed.
	Actual string
	// OK is true when Expected matches Actual (or when establishing a new baseline).
	OK bool
}

CheckResult holds the outcome of a file integrity check.

func VerifyFile

func VerifyFile(path, expectedChecksum string, requireChecksum bool) (CheckResult, error)

VerifyFile computes the SHA-256 checksum of the file at path and compares it against expectedChecksum. When requireChecksum is true and expectedChecksum is empty, an error is returned (hooks must have a control-plane-provided checksum). When requireChecksum is false and expectedChecksum is empty, the computed checksum is returned as a new baseline with OK=true.

type Config

type Config struct {
	// Enabled controls whether integrity verification is active.
	// Default: true (set by ApplyDefaults).
	Enabled bool `yaml:"enabled"`

	// BinaryPath is the path to the plexd binary to verify.
	BinaryPath string `yaml:"binary_path"`

	// HooksDir is the directory containing hook scripts to verify.
	HooksDir string `yaml:"hooks_dir"`

	// HostKeyPath is the SSH host key whose fingerprint is verified. It is
	// derived from the agent's data dir rather than configured, so it is kept
	// off the YAML surface.
	HostKeyPath string `yaml:"-"`

	// VerifyInterval is the interval between integrity verification runs.
	// Must be at least 30s when enabled.
	// Default: 5m
	VerifyInterval time.Duration `yaml:"verify_interval"`

	// WatchEnabled controls whether inotify file watching is active.
	// When enabled, file changes in HooksDir trigger immediate checksum
	// recomputation instead of waiting for the next periodic verification.
	// Default: true (set by ApplyDefaults).
	WatchEnabled bool `yaml:"watch_enabled"`
}

Config holds the configuration for integrity verification.

func (*Config) ApplyDefaults

func (c *Config) ApplyDefaults()

ApplyDefaults sets default values for zero-valued fields. On a zero-valued Config, Enabled defaults to true. To disable integrity verification, set Enabled=false before or after calling ApplyDefaults.

func (*Config) Validate

func (c *Config) Validate() error

Validate checks that configuration values are within acceptable ranges.

type Store

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

Store persists known-good checksums as a JSON file in the agent's data directory.

func NewStore

func NewStore(dataDir string) (*Store, error)

NewStore creates a Store backed by dataDir/checksums.json. If the file does not exist, an empty store is created.

func (*Store) Get

func (s *Store) Get(path string) string

Get returns the stored checksum for path, or empty string if not found.

func (*Store) Remove

func (s *Store) Remove(path string) error

Remove deletes the checksum for path and persists to disk.

func (*Store) Set

func (s *Store) Set(path, checksum string) error

Set updates the checksum for path and persists to disk atomically.

type Verifier

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

Verifier orchestrates integrity verification for the plexd binary, the hook scripts, and the SSH host key.

func NewVerifier

func NewVerifier(cfg Config, store *Store, reporter ViolationReporter, logger *slog.Logger) *Verifier

NewVerifier creates a Verifier with the given configuration, store, reporter, and logger.

func (*Verifier) BinaryChecksum

func (v *Verifier) BinaryChecksum() string

BinaryChecksum returns the last computed binary checksum (thread-safe). Returns an empty string before any verification has run.

func (*Verifier) Run

func (v *Verifier) Run(ctx context.Context, nodeID string) error

Run performs periodic integrity verification for the binary, the hooks directory, and the SSH host key. When WatchEnabled is true, it also monitors the hooks directory via inotify for real-time change detection. Run blocks until the context is cancelled.

func (*Verifier) VerifyBinary

func (v *Verifier) VerifyBinary(ctx context.Context, nodeID string) error

VerifyBinary computes the binary checksum, compares against the stored baseline, and reports a violation on mismatch. On first run (no baseline), the checksum is stored as the new baseline.

func (*Verifier) VerifyHook

func (v *Verifier) VerifyHook(ctx context.Context, nodeID, hookPath, expectedChecksum string) (bool, error)

VerifyHook verifies a hook script against the expected checksum from the control plane. Returns true if the hook is safe to execute, false if there is a mismatch. An error is returned if the expected checksum is empty (hooks require a checksum).

func (*Verifier) VerifyHooksDir

func (v *Verifier) VerifyHooksDir(ctx context.Context, nodeID string)

VerifyHooksDir computes checksums for all files in the hooks directory, compares against stored baselines, and reports violations on mismatch. Violations are attributed to the scanning detector; the fsnotify watcher enters the same sweep through verifyHooksDir with the inotify detector.

func (*Verifier) VerifyHostKey added in v0.4.0

func (v *Verifier) VerifyHostKey(ctx context.Context, nodeID string) error

VerifyHostKey computes the SSH host key's fingerprint, compares it against the stored baseline, and reports a violation on mismatch. On first run the fingerprint becomes the baseline, as for the binary.

A key that changes under a running agent is the tamper signal: the SSH server keeps serving the key it loaded at startup, so a divergence on disk means something replaced it. The fingerprint identifies the key, not the file — the same key re-serialised keeps its fingerprint, which is why this is not a checksum comparison. An unset HostKeyPath or an absent file is a no-op: a node that never started the tunnel has no key to watch.

type ViolationReporter

type ViolationReporter interface {
	ReportViolations(ctx context.Context, nodeID string, reports []api.IntegrityViolationReport) error
}

ViolationReporter abstracts control plane violation reporting for testability.

The contract's ingest endpoint takes a batch, so the interface does too: a directory sweep that finds three tampered hooks delivers them as one request rather than three.

Jump to

Keyboard shortcuts

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