mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-09-10 18:45:53 +00:00
139 lines
6.4 KiB
Go
139 lines
6.4 KiB
Go
package agentexec
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/rcourtman/pulse-go-rewrite/internal/operationreceipt"
|
|
)
|
|
|
|
const DockerContainerObservationProtocolVersion = 2
|
|
|
|
const dockerContainerObservationMaxClockSkew = 5 * time.Minute
|
|
|
|
func DecodeDockerContainerObservationPayload(data []byte) (DockerContainerObservationPayload, error) {
|
|
var payload DockerContainerObservationPayload
|
|
if err := decodeStrictDockerLifecycle(data, &payload); err != nil {
|
|
return DockerContainerObservationPayload{}, err
|
|
}
|
|
if err := ValidateDockerContainerObservationPayload(&payload); err != nil {
|
|
return DockerContainerObservationPayload{}, err
|
|
}
|
|
return payload, nil
|
|
}
|
|
|
|
func DecodeDockerContainerObservationResultPayload(data []byte) (DockerContainerObservationResultPayload, error) {
|
|
var result DockerContainerObservationResultPayload
|
|
if err := decodeStrictDockerLifecycle(data, &result); err != nil {
|
|
return DockerContainerObservationResultPayload{}, err
|
|
}
|
|
if err := ValidateDockerContainerObservationResultPayload(&result); err != nil {
|
|
return DockerContainerObservationResultPayload{}, err
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func BindDockerContainerObservationPayload(payload *DockerContainerObservationPayload) error {
|
|
if payload == nil {
|
|
return fmt.Errorf("docker container observation payload is required")
|
|
}
|
|
payload.ProtocolVersion = DockerContainerObservationProtocolVersion
|
|
digest, err := dockerContainerObservationRequestDigest(*payload)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
payload.RequestDigest = digest
|
|
return nil
|
|
}
|
|
|
|
func dockerContainerObservationRequestDigest(payload DockerContainerObservationPayload) (string, error) {
|
|
return operationreceipt.DigestCanonicalJSON(struct {
|
|
ActionID string `json:"action_id"`
|
|
ProtocolVersion int `json:"protocol_version"`
|
|
Runtime string `json:"runtime"`
|
|
ContainerID string `json:"container_id"`
|
|
}{strings.TrimSpace(payload.ActionID), payload.ProtocolVersion, strings.ToLower(strings.TrimSpace(payload.Runtime)), strings.ToLower(strings.TrimSpace(payload.ContainerID))})
|
|
}
|
|
|
|
func ValidateDockerContainerObservationPayload(payload *DockerContainerObservationPayload) error {
|
|
if payload == nil {
|
|
return fmt.Errorf("docker container observation payload is required")
|
|
}
|
|
payload.RequestID = strings.TrimSpace(payload.RequestID)
|
|
payload.ActionID = strings.TrimSpace(payload.ActionID)
|
|
payload.RequestDigest = strings.TrimSpace(payload.RequestDigest)
|
|
payload.Runtime = strings.ToLower(strings.TrimSpace(payload.Runtime))
|
|
payload.ContainerID = strings.ToLower(strings.TrimSpace(payload.ContainerID))
|
|
if payload.RequestID == "" || len(payload.RequestID) > maxRequestIDLength || payload.ActionID == "" || len(payload.ActionID) > maxRequestIDLength {
|
|
return fmt.Errorf("invalid docker observation request identity")
|
|
}
|
|
if payload.ProtocolVersion != DockerContainerObservationProtocolVersion {
|
|
return fmt.Errorf("unsupported docker observation protocol version %d", payload.ProtocolVersion)
|
|
}
|
|
if payload.Runtime != "docker" && payload.Runtime != "podman" {
|
|
return fmt.Errorf("unsupported container runtime %q", payload.Runtime)
|
|
}
|
|
if !dockerContainerIDPattern.MatchString(payload.ContainerID) {
|
|
return fmt.Errorf("container id must be an immutable hexadecimal id")
|
|
}
|
|
digest, err := dockerContainerObservationRequestDigest(*payload)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if payload.RequestDigest != digest {
|
|
return fmt.Errorf("docker observation request digest mismatch")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func ValidateDockerContainerObservationResultPayload(result *DockerContainerObservationResultPayload) error {
|
|
if result == nil {
|
|
return fmt.Errorf("docker container observation result is required")
|
|
}
|
|
result.RequestID = strings.TrimSpace(result.RequestID)
|
|
result.ActionID = strings.TrimSpace(result.ActionID)
|
|
result.RequestDigest = strings.TrimSpace(result.RequestDigest)
|
|
result.ReasonCode = strings.TrimSpace(result.ReasonCode)
|
|
result.Snapshot.ContainerID = strings.ToLower(strings.TrimSpace(result.Snapshot.ContainerID))
|
|
result.Snapshot.State = strings.ToLower(strings.TrimSpace(result.Snapshot.State))
|
|
result.Snapshot.Health = strings.ToLower(strings.TrimSpace(result.Snapshot.Health))
|
|
result.Snapshot.ObservedAt = result.Snapshot.ObservedAt.UTC()
|
|
result.Snapshot.StartedAt = result.Snapshot.StartedAt.UTC()
|
|
if result.RequestID == "" || len(result.RequestID) > maxRequestIDLength || result.ActionID == "" || len(result.ActionID) > maxRequestIDLength {
|
|
return fmt.Errorf("invalid docker observation result identity")
|
|
}
|
|
if result.ProtocolVersion != DockerContainerObservationProtocolVersion || !hostUpdateInventoryHashPattern.MatchString(result.RequestDigest) {
|
|
return fmt.Errorf("invalid docker observation result binding")
|
|
}
|
|
if !result.Observed {
|
|
if !IsActionRefusalReasonCode(result.ReasonCode) || result.Snapshot.ContainerID != "" || result.Snapshot.State != "" || result.Snapshot.Health != "" || result.Snapshot.Running || !result.Snapshot.StartedAt.IsZero() || result.Snapshot.RestartCount != 0 || !result.Snapshot.ObservedAt.IsZero() {
|
|
return fmt.Errorf("inconclusive docker observation requires a bounded reason and no snapshot")
|
|
}
|
|
return nil
|
|
}
|
|
if result.ReasonCode != "" || !dockerContainerIDPattern.MatchString(result.Snapshot.ContainerID) || result.Snapshot.State == "" || len(result.Snapshot.State) > 32 || !IsDockerContainerHealth(result.Snapshot.Health) || result.Snapshot.RestartCount < 0 || result.Snapshot.ObservedAt.IsZero() {
|
|
return fmt.Errorf("invalid docker observation snapshot")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func ValidateDockerContainerObservationResultForRequest(req DockerContainerObservationPayload, result DockerContainerObservationResultPayload, receivedAt time.Time) error {
|
|
if err := ValidateDockerContainerObservationPayload(&req); err != nil {
|
|
return err
|
|
}
|
|
if err := ValidateDockerContainerObservationResultPayload(&result); err != nil {
|
|
return err
|
|
}
|
|
if result.RequestID != req.RequestID || result.ActionID != req.ActionID || result.ProtocolVersion != req.ProtocolVersion || result.RequestDigest != req.RequestDigest || (result.Observed && result.Snapshot.ContainerID != req.ContainerID) {
|
|
return fmt.Errorf("docker observation result identity mismatch")
|
|
}
|
|
if !result.Observed {
|
|
return nil
|
|
}
|
|
if receivedAt.IsZero() || result.Snapshot.ObservedAt.After(receivedAt.UTC().Add(dockerContainerObservationMaxClockSkew)) || result.Snapshot.ObservedAt.Before(receivedAt.UTC().Add(-15*time.Minute)) {
|
|
return fmt.Errorf("docker observation result is stale or clock-skewed")
|
|
}
|
|
return nil
|
|
}
|