Documentation
¶
Overview ¶
Package conformance is the sensor protocol conformance suite (RFC-026 WP-S3, RFC-023 D23), starting with results ingest.
It has two halves:
- FakePlatform, a control plane that implements the v2 results contract (RFC-026 §3) strictly and the v2 control plane (RFC-029), records every request and can be told to fail in each §3.8 class. The SDK's own tests run against it: they check the SDK sends the right headers and digest, retries only what it should, splits on 413, never resends a partially accepted report and reuses report_id on retry.
- The live suite (live_test.go), which runs the same contract against a real API: OPENCTEM_CONFORMANCE_URL and OPENCTEM_CONFORMANCE_KEY (a sensor key) select it; see the package README in the test file.
Stability: Stable (docs/STABILITY.md).
Index ¶
- Constants
- func RunSensorSuite(t *testing.T, start StartSensor, opts SuiteOptions)
- func RunToolSuite(t T, manifestPath string, opts ToolSuiteOptions)
- type FakePlatform
- func (f *FakePlatform) AcceptedFindings() int
- func (f *FakePlatform) AcceptedReports() []*ctis.Report
- func (f *FakePlatform) Close()
- func (f *FakePlatform) CommandLogs(id string) []protov2.CommandLogLine
- func (f *FakePlatform) CommandResult(id string) json.RawMessage
- func (f *FakePlatform) CommandState(id string) (string, string)
- func (f *FakePlatform) ConfigReports() []json.RawMessage
- func (f *FakePlatform) ForgetConfigReport()
- func (f *FakePlatform) ForgetManifest()
- func (f *FakePlatform) HeartbeatBuilds() []HeartbeatBuild
- func (f *FakePlatform) HeartbeatOutbox() []json.RawMessage
- func (f *FakePlatform) Heartbeats() []json.RawMessage
- func (f *FakePlatform) LeaseEpoch(id string) int
- func (f *FakePlatform) Manifests() []json.RawMessage
- func (f *FakePlatform) OpenCommand(id string)
- func (f *FakePlatform) QueueCommand(id string)
- func (f *FakePlatform) QueueCommandPayload(id, typ string, payload json.RawMessage)
- func (f *FakePlatform) ReclaimCommand(id string)
- func (f *FakePlatform) Releases() []Release
- func (f *FakePlatform) Reports() map[string]*StoredReport
- func (f *FakePlatform) Requests() []Request
- func (f *FakePlatform) RequestsTo(method, prefix string) []Request
- func (f *FakePlatform) SetAPIKey(key string)
- func (f *FakePlatform) SetConfigReport(on bool)
- func (f *FakePlatform) SetControl(on bool)
- func (f *FakePlatform) SetFault(fl Fault)
- func (f *FakePlatform) SetFutureFields(on bool)
- func (f *FakePlatform) SetLocalPolicy(on bool)
- func (f *FakePlatform) SetLogs(on bool)
- func (f *FakePlatform) SetManifest(on bool)
- func (f *FakePlatform) SetManifestPolicy(allowedTools []string, omitInventory bool)
- func (f *FakePlatform) SetPaused(on bool)
- func (f *FakePlatform) SetV2(on bool)
- func (f *FakePlatform) URL() string
- type Fault
- type FaultAnswer
- type HeartbeatBuild
- type Release
- type Request
- type SensorEndpoint
- type StartSensor
- type StoredReport
- type SuiteOptions
- type T
- type ToolSuiteOptions
Constants ¶
const FutureMember = "x_openctem_future"
FutureMember is the member FutureFields adds to every JSON object answer.
const UnknownCommandType = "x_conformance_unknown"
UnknownCommandType is the command type the suite dispatches to check that a sensor fails a command it does not know instead of hanging or crashing.
Variables ¶
This section is empty.
Functions ¶
func RunSensorSuite ¶ added in v0.16.0
func RunSensorSuite(t *testing.T, start StartSensor, opts SuiteOptions)
RunSensorSuite checks a sensor against the platform contract every sensor must keep, whatever tools it runs, so a third-party sensor can prove it in its own tests:
func TestConformance(t *testing.T) {
httpsec.AllowLoopback = true // the fake listens on 127.0.0.1
conformance.RunSensorSuite(t, func(t *testing.T, p conformance.SensorEndpoint) func() {
kit, err := sensorkit.New(sensorkit.Options{Name: "my-sensor", Version: "1.0.0",
APIURL: p.URL, APIKey: p.APIKey, StateDir: t.TempDir(),
Outbox: sensorkit.OutboxSettings{Dir: t.TempDir()}})
if err != nil {
t.Fatal(err)
}
kit.AddScanner(myScanner)
ctx, cancel := context.WithCancel(context.Background())
done := make(chan error, 1)
go func() { done <- kit.Run(ctx) }()
return func() { cancel(); <-done }
}, conformance.SuiteOptions{ScanPayload: json.RawMessage(`{"scanner":"my-tool","target":"..."}`)})
}
Each check runs against its own FakePlatform (protocol v2, control plane and manifest on):
- Heartbeat: the sensor heartbeats and names the SDK it is built on.
- Manifest: a sensor that announces a manifest digest registers the manifest when the platform asks for it.
- UnknownCommand: a command of a type the sensor does not handle is left pending for a sensor that does (or failed), never claimed and abandoned, and the sensor keeps polling.
- ForwardCompatible: the same with every answer carrying a JSON member no SDK knows (FutureMember), as a newer platform's would: the sensor ignores it and keeps working (and runs its own command, with ScanPayload).
- Command (with ScanPayload): the command completes, and every report sent for it is committed before the command is completed.
func RunToolSuite ¶ added in v0.18.0
func RunToolSuite(t T, manifestPath string, opts ToolSuiteOptions)
RunToolSuite checks a tool that runs as its own program (a tool.yaml with a run section, any language) against adapter protocol v1, the way the sensor's runtime will run it:
- Manifest: the tool.yaml loads strictly and validates, and has a run section.
- Handshake: the program answers the runtime's hello with protocol 1, ignores a message type it does not know, and answers describe with exactly its tool.yaml (the run section aside).
- Validate: a configuration with an unknown key is refused.
- StdoutIsProtocol: everything it writes to standard output is a protocol message (diagnostics belong on standard error).
- ExitsOnEOF: it exits, with status 0, when its input ends.
- Cancel: after run and cancel it sends its result, or exits, within the 10-second grace.
- Fixtures: each self-test fixture (manifest selftest: a task file and the CTIS it must produce) runs through the runtime's own host, with every runtime check, and produces exactly the expected normalized report, with no invalid message.
Use it from go test (conformance.RunToolSuite(t, "tool.yaml", conformance.ToolSuiteOptions{})) or with the command cmd/openctem-conformance.
Types ¶
type FakePlatform ¶
type FakePlatform struct {
Server *httptest.Server
// APIKey is the sensor key it accepts.
APIKey string
// V2 serves the v2 routes and advertises them; false behaves like a
// platform from before RFC-026 (404 on /api/v2).
V2 bool
// Limits are published on hello.
Limits protov2.Limits
// Tools are the sensor's declared tools (empty: any).
Tools []string
// Control serves the api RFC-029 control plane on v2 (heartbeat,
// commands, suppressions, fingerprints, keys) and lists it on hello.
// False (with V2) is a platform from api v0.8: v2 results only.
Control bool
// Paused answers heartbeats as for a disabled sensor (v2: 200 + pause).
Paused bool
// Manifest serves PUT /manifest (api RFC-033) with Control and lists it
// on hello: the fake stores the manifest's digest and asks for it again
// (send_manifest) when a heartbeat names another one.
Manifest bool
// FutureFields adds a member no SDK knows to every JSON object the
// fake answers with, as a newer platform would: a sensor must ignore
// it (forward compatibility, docs/STABILITY.md).
FutureFields bool
// contains filtered or unexported fields
}
FakePlatform is an in-process control plane. Safe for concurrent use.
func NewFakePlatform ¶
func NewFakePlatform(v2 bool) *FakePlatform
NewFakePlatform starts a fake platform. Close it with Close. v2 false is a platform without sensor protocol v2 (every v2 route answers 404), against which the SDK must fail with ErrV2Unsupported; v2 true serves results and the RFC-029 control plane (SetControl(false) leaves only results).
func (*FakePlatform) AcceptedFindings ¶
func (f *FakePlatform) AcceptedFindings() int
AcceptedFindings counts the findings of committed reports.
func (*FakePlatform) AcceptedReports ¶ added in v0.18.0
func (f *FakePlatform) AcceptedReports() []*ctis.Report
AcceptedReports returns the reports the fake accepted: every segment of each committed report.
func (*FakePlatform) CommandLogs ¶ added in v0.18.0
func (f *FakePlatform) CommandLogs(id string) []protov2.CommandLogLine
CommandLogs returns the log lines received for a command, in seq order.
func (*FakePlatform) CommandResult ¶ added in v0.9.0
func (f *FakePlatform) CommandResult(id string) json.RawMessage
CommandResult returns the result a completed command stored.
func (*FakePlatform) CommandState ¶
func (f *FakePlatform) CommandState(id string) (string, string)
CommandState returns a command's state and its error message.
func (*FakePlatform) ConfigReports ¶ added in v0.18.0
func (f *FakePlatform) ConfigReports() []json.RawMessage
ConfigReports returns the config reports received, in order.
func (*FakePlatform) ForgetConfigReport ¶ added in v0.18.0
func (f *FakePlatform) ForgetConfigReport()
ForgetConfigReport drops the stored config report digest, as a platform that lost it: the next heartbeat naming one is asked to send it again.
func (*FakePlatform) ForgetManifest ¶ added in v0.14.0
func (f *FakePlatform) ForgetManifest()
ForgetManifest drops the stored digest, as a platform that lost it: the next heartbeat naming it is asked for the manifest again.
func (*FakePlatform) HeartbeatBuilds ¶ added in v0.10.0
func (f *FakePlatform) HeartbeatBuilds() []HeartbeatBuild
HeartbeatBuilds returns the "sdk" and "sensor" blocks of every heartbeat received, in order.
func (*FakePlatform) HeartbeatOutbox ¶
func (f *FakePlatform) HeartbeatOutbox() []json.RawMessage
HeartbeatOutbox returns the outbox objects heartbeats carried.
func (*FakePlatform) Heartbeats ¶ added in v0.10.0
func (f *FakePlatform) Heartbeats() []json.RawMessage
Heartbeats returns the heartbeat bodies received, in order.
func (*FakePlatform) LeaseEpoch ¶ added in v0.16.0
func (f *FakePlatform) LeaseEpoch(id string) int
LeaseEpoch returns the command's lease epoch (0 before its first claim).
func (*FakePlatform) Manifests ¶ added in v0.14.0
func (f *FakePlatform) Manifests() []json.RawMessage
Manifests returns the manifests registered, in order.
func (*FakePlatform) OpenCommand ¶
func (f *FakePlatform) OpenCommand(id string)
OpenCommand registers a running command assigned to the sensor.
func (*FakePlatform) QueueCommand ¶ added in v0.9.0
func (f *FakePlatform) QueueCommand(id string)
QueueCommand adds a pending command a poll offers.
func (*FakePlatform) QueueCommandPayload ¶ added in v0.16.0
func (f *FakePlatform) QueueCommandPayload(id, typ string, payload json.RawMessage)
QueueCommandPayload adds a pending command of the given type ("" is "scan") and payload, as the platform dispatches it.
func (*FakePlatform) ReclaimCommand ¶ added in v0.16.0
func (f *FakePlatform) ReclaimCommand(id string)
ReclaimCommand simulates a lease loss (api RFC-035 D6): the command was re-queued and claimed again, so its lease epoch moves on while its state stays. A complete or fail that echoes the old epoch is refused with invalid-transition, as the platform answers it.
func (*FakePlatform) Releases ¶ added in v0.10.0
func (f *FakePlatform) Releases() []Release
Releases returns the releases received, in order.
func (*FakePlatform) Reports ¶
func (f *FakePlatform) Reports() map[string]*StoredReport
Reports returns the accepted v2 reports by id.
func (*FakePlatform) Requests ¶
func (f *FakePlatform) Requests() []Request
Requests returns the recorded requests.
func (*FakePlatform) RequestsTo ¶
func (f *FakePlatform) RequestsTo(method, prefix string) []Request
RequestsTo returns the recorded requests whose path has prefix.
func (*FakePlatform) SetAPIKey ¶
func (f *FakePlatform) SetAPIKey(key string)
SetAPIKey replaces the accepted sensor key (a key rotation).
func (*FakePlatform) SetConfigReport ¶ added in v0.18.0
func (f *FakePlatform) SetConfigReport(on bool)
SetConfigReport lists or stops listing the "config_report" feature on hello and serves PUT /config-report (api RFC-033, config report): the fake stores each report and asks for it again (send_config_report) when a heartbeat names another digest.
func (*FakePlatform) SetControl ¶ added in v0.9.0
func (f *FakePlatform) SetControl(on bool)
SetControl switches the RFC-029 control plane on v2 on or off.
func (*FakePlatform) SetFault ¶
func (f *FakePlatform) SetFault(fl Fault)
SetFault installs a fault injector (nil removes it).
func (*FakePlatform) SetFutureFields ¶ added in v0.16.0
func (f *FakePlatform) SetFutureFields(on bool)
SetFutureFields switches FutureFields.
func (*FakePlatform) SetLocalPolicy ¶ added in v0.18.0
func (f *FakePlatform) SetLocalPolicy(on bool)
SetLocalPolicy lists or stops listing the "local_policy" feature on hello (api RFC-040 §5.7): with it, heartbeats and manifests carry the sensor's local policy report.
func (*FakePlatform) SetLogs ¶ added in v0.18.0
func (f *FakePlatform) SetLogs(on bool)
SetLogs makes the fake serve POST /commands/{id}/logs and list "logs" on hello.
func (*FakePlatform) SetManifest ¶ added in v0.14.0
func (f *FakePlatform) SetManifest(on bool)
SetManifest serves or stops serving the sensor manifest (api RFC-033).
func (*FakePlatform) SetManifestPolicy ¶ added in v0.15.0
func (f *FakePlatform) SetManifestPolicy(allowedTools []string, omitInventory bool)
SetManifestPolicy sets what the manifest answers say from now on (api RFC-033 §6.12): the allowed tools (nil: every tool of the manifest) and whether heartbeats may leave the inventory out.
func (*FakePlatform) SetPaused ¶ added in v0.9.0
func (f *FakePlatform) SetPaused(on bool)
SetPaused answers heartbeats as for a disabled sensor.
func (*FakePlatform) SetV2 ¶
func (f *FakePlatform) SetV2(on bool)
SetV2 switches the v2 routes on or off.
type Fault ¶
type Fault func(r *http.Request, seq int) *FaultAnswer
Fault makes the fake answer a request with an error instead of handling it. Return nil to handle it normally.
type FaultAnswer ¶
type FaultAnswer struct {
Status int
Problem protov2.ProblemType // "" for a plain (non-problem) answer
RetryAfter string
// Drop closes the connection without answering (a network error, the
// sender cannot tell whether the request was processed). With Process
// the request is handled first: "the response was lost".
Drop bool
Process bool
}
FaultAnswer is an injected answer.
type HeartbeatBuild ¶ added in v0.10.0
type HeartbeatBuild struct {
SDK *struct {
Name string `json:"name"`
Version string `json:"version"`
} `json:"sdk"`
Sensor *struct {
Name string `json:"name"`
Version string `json:"version"`
Commit string `json:"commit"`
BuildTime string `json:"build_time"`
} `json:"sensor"`
}
HeartbeatBuild is what a heartbeat says about the SDK and the sensor binary ("sdk" and "sensor"); nil where the heartbeat did not carry it.
type Release ¶ added in v0.10.0
type Release struct {
CommandID, Reason string
}
Release is one release a sensor sent (api RFC-030).
type Request ¶
type Request struct {
Method, Path string
Header http.Header
Body []byte // as sent (compressed when Content-Encoding is set)
Status int // what the fake answered
}
Request is one request the fake received.
type SensorEndpoint ¶ added in v0.16.0
SensorEndpoint is the platform RunSensorSuite points the sensor under test at: the fake's base URL and the sensor key it accepts.
type StartSensor ¶ added in v0.16.0
type StartSensor func(t *testing.T, p SensorEndpoint) (stop func())
StartSensor starts the sensor under test against p (in process, or as a child process with API_URL / API_KEY set) and returns a function that stops it and waits until it has exited. The fake listens on 127.0.0.1: an SDK-based sensor needs httpsec.AllowLoopback (or OPENCTEM_SDK_HTTPSEC_ALLOW_LOOPBACK=1 for a child process) to reach it.
type StoredReport ¶
type StoredReport struct {
ReportID string
CommandID string
Segments map[int]segment
Committed bool
Status protov2.Status
}
StoredReport is a report the fake accepted.
type SuiteOptions ¶ added in v0.16.0
type SuiteOptions struct {
// ScanType and ScanPayload are a command the sensor can run to
// completion against the fake ("" ScanType is "scan"). With no
// ScanPayload the command checks are skipped.
ScanType string
ScanPayload json.RawMessage
// Timeout bounds every wait. Zero is 60s.
Timeout time.Duration
}
SuiteOptions configures RunSensorSuite.
type T ¶ added in v0.18.0
type T interface {
Helper()
Logf(format string, args ...any)
Errorf(format string, args ...any)
Fatalf(format string, args ...any)
}
T is what the tool suite reports to: *testing.T, or the conformance command's printer.
type ToolSuiteOptions ¶ added in v0.18.0
type ToolSuiteOptions struct {
// Timeout bounds each check (zero: 30s).
Timeout time.Duration
// Credentials are what an operator would store for the tool; only
// those the manifest declares are delivered.
Credentials map[string]string
// Update rewrites each fixture's expect file with what the tool
// produced (also OPENCTEM_UPDATE_GOLDEN=1).
Update bool
}
ToolSuiteOptions configure RunToolSuite.