Documentation
¶
Overview ¶
Package fides talks to a Fides server — the compliance system Hecate records promotions in and asks for permission.
Only the endpoints a promotion needs are here. Fides has a large API; Hecate uses three parts of it:
- verify a trail's attestation chain, so the tamper-evidence claim is checkable by whoever relies on it rather than asserted in documentation;
- check an environment's policy against a trail;
- check whether an artifact is on an environment's allowlist.
The last two are why a Gate has to name a Fides environment at all.
Index ¶
- Constants
- func IsAuth(err error) bool
- func IsNotFound(err error) bool
- type Anchor
- type Approval
- type Artifact
- type Attestation
- type Chain
- type ChangeVerdict
- type Client
- func (c *Client) Allowlisted(ctx context.Context, environment, sha256 string) (bool, error)
- func (c *Client) Assert(ctx context.Context, sha256, policy string) (*Compliance, error)
- func (c *Client) Attest(ctx context.Context, trail string, a Attestation) error
- func (c *Client) ChangeGate(ctx context.Context, trail string) (*ChangeVerdict, error)
- func (c *Client) PolicyCheck(ctx context.Context, environment, trail string) (*PolicyVerdict, error)
- func (c *Client) RecordApproval(ctx context.Context, trail string, a Approval) error
- func (c *Client) ReportArtifact(ctx context.Context, a Artifact) error
- func (c *Client) TrailForArtifact(ctx context.Context, sha256 string) (string, error)
- func (c *Client) VerifyChain(ctx context.Context, trail string) (*Chain, error)
- type Compliance
- type Config
- type Control
- type Error
- type PolicyResult
- type PolicyVerdict
- type SegregationOfDuties
Constants ¶
const ( // RoleApprover is a reviewer's sign-off. RoleApprover = "approver" // RoleDeployer is the identity that triggers the deployment. Distinct from // the approver on purpose: Fides refuses a trail where they are the same // person, which is the whole point of four-eyes. RoleDeployer = "deployer" )
Approval roles, as Fides names them when it evaluates segregation of duties.
Variables ¶
This section is empty.
Functions ¶
func IsNotFound ¶
IsNotFound reports whether Fides says the thing is not there.
Types ¶
type Anchor ¶
type Anchor struct {
Anchored bool `json:"anchored"`
// HeadMatches is false when the chain has moved on since it was anchored,
// or when it was rewritten under the anchor.
HeadMatches bool `json:"head_matches"`
TSAURL string `json:"tsa_url,omitempty"`
AnchoredAt time.Time `json:"anchored_at,omitempty"`
}
Anchor is the external timestamp over a chain head.
type Approval ¶
type Approval struct {
// By is the human the approval belongs to. Required.
//
// Sent as `on_behalf_of`, because Hecate authenticates to Fides with a
// service token: without it every approval Hecate records would carry the
// service account's identity, every identity would be equal, and
// segregation of duties would evaluate one person having done everything.
// Fides honours the delegation only when it is configured to and the email
// is a known user in the organisation — so this can be refused, and a
// refusal is a real answer rather than a transport failure.
By string
// Role is RoleApprover or RoleDeployer.
Role string
// Reason is free text stored with the approval.
Reason string
}
Approval is one identity signing off on a trail in one role.
type Artifact ¶
type Artifact struct {
// SHA256 identifies it. The `sha256:` prefix is stripped before sending —
// Fides keys artifacts on lowercase hex.
SHA256 string
// Trail links the artifact to the evidence recorded against it.
Trail string
// Name is human-readable, e.g. the image repository.
Name string
// Type is what kind of thing it is, e.g. container-image.
Type string
// Tags are arbitrary labels.
Tags map[string]string
}
Artifact is a thing Fides can hold evidence about.
type Attestation ¶
type Attestation struct {
// Name is what this evidence is, e.g. "promotion".
Name string
// Type is the attestation type an environment policy asks for by name, so
// `policy check` can require it. Getting this wrong means a policy that
// requires "deployment" never sees one.
Type string
// ArtifactSHA256 ties the evidence to what was deployed.
ArtifactSHA256 string
// Payload is the evidence body, stored and hashed into the chain.
Payload any
// SignedBy is who or what produced it.
SignedBy string
}
Attestation is one piece of evidence on a trail.
type Chain ¶
type Chain struct {
// Valid is false when the chain has been tampered with, reordered, or had
// an entry deleted.
Valid bool `json:"valid"`
// Count is how many attestations are on the trail.
Count int `json:"count"`
// BrokenAt is the index of the first bad entry, or -1 when valid.
BrokenAt int `json:"broken_at"`
// Reason says what was wrong, when something was.
Reason string `json:"reason,omitempty"`
// ExternalAnchor reports whether the chain head was timestamped by an
// external authority. A valid chain that only we vouch for is a weaker
// claim than one an RFC3161 authority saw at a point in time.
ExternalAnchor *Anchor `json:"external_anchor,omitempty"`
}
Chain is the verdict on a trail's attestation chain.
Fields mirror Fides' own response rather than being renamed, so an operator comparing `hecate verify` with `fides verify-chain` sees the same words.
type ChangeVerdict ¶
type ChangeVerdict struct {
// Recommendation is "approve" or "hold".
Recommendation string `json:"recommendation"`
// Approved is Fides' own boolean, true only when every control is satisfied
// and a human has signed off.
Approved bool `json:"approved"`
// RiskScore is 0-100, higher being worse.
RiskScore int `json:"risk_score"`
// RiskLevel is "low", "medium" or "high".
RiskLevel string `json:"risk_level"`
// Passed names the controls that were satisfied, by key alone — Fides sends
// strings here and objects for the two below, and that asymmetry is real.
Passed []string `json:"passed,omitempty"`
// Failed and MissingEvidence name the controls that stopped it, so a held
// crossing can say what would unblock it.
Failed []Control `json:"failed,omitempty"`
MissingEvidence []Control `json:"missing_evidence,omitempty"`
// Waived are the controls a human has excused, with who excused them and
// until when. A waiver is a governed exception rather than a pass, so it is
// reported separately: an auditor's first question about a green gate is
// which of it was waived.
Waived []Control `json:"waived,omitempty"`
// Attestations counts the evidence on the trail.
Attestations struct {
Total int `json:"total"`
NonCompliant int `json:"non_compliant"`
} `json:"attestations"`
// Approvals is who signed off, and whether that satisfied four-eyes.
Approvals struct {
Count int `json:"count"`
HumanApprovers int `json:"human_approvers"`
FourEyes bool `json:"four_eyes"`
Approvers []string `json:"approvers,omitempty"`
Deployers []string `json:"deployers,omitempty"`
} `json:"approvals"`
// SoD is Fides' segregation-of-duties finding: committer, approver and
// deployer must be three distinct people.
SoD *SegregationOfDuties `json:"segregation_of_duties,omitempty"`
// Summary is Fides' own sentence about the verdict.
Summary string `json:"summary,omitempty"`
}
ChangeVerdict is the evidence-backed change-approval decision for a trail.
func (ChangeVerdict) Blockers ¶
func (v ChangeVerdict) Blockers() []string
Blockers lists what is standing in the way, for a message a human can act on.
func (ChangeVerdict) Held ¶
func (v ChangeVerdict) Held() bool
Held reports whether the verdict withholds approval.
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
Client is a Fides API client.
func (*Client) Allowlisted ¶
Allowlisted reports whether an artifact digest is approved for an environment. The digest is the bare sha256 hex, as Fides stores it.
func (*Client) Assert ¶
Assert checks an artifact digest against a named policy. The policy may be empty, which asks Fides for every policy that applies.
func (*Client) ChangeGate ¶
ChangeGate reads the change-approval verdict for a trail.
func (*Client) PolicyCheck ¶
func (c *Client) PolicyCheck(ctx context.Context, environment, trail string) (*PolicyVerdict, error)
PolicyCheck evaluates an environment's policy against a trail.
Both identifiers are UUIDs: the environment comes from the Gate, the trail from the crossing being judged.
func (*Client) RecordApproval ¶
RecordApproval records a sign-off on a trail, so Fides can evaluate segregation of duties over the identities involved.
Fides derives its verdict from the trail's committer tag plus the recorded approvals: committer, approver and deployer must be three distinct people. It treats a missing role as non-compliant rather than absent, so recording only one of them leaves the change gate holding for a reason that reads like a policy failure.
func (*Client) ReportArtifact ¶
ReportArtifact records an artifact against a trail.
**It refuses to report without a trail, and that is a safety rule rather than validation.** Fides upserts on the digest with `ON CONFLICT (sha256) DO UPDATE SET trail_id = EXCLUDED.trail_id`, so reporting a digest with an empty trail would overwrite the link CI made when it attached the SBOM and the scans — silently detaching exactly the evidence a change gate exists to read. An artifact Hecate cannot link is one it should leave alone.
func (*Client) TrailForArtifact ¶
TrailForArtifact finds the trail an artifact digest was built on.
The trail that matters already exists: CI opened it when it built the image and recorded the SBOM and scan attestations there. Those attestations are exactly what an environment policy and the change gate judge, so a crossing has to gate on *that* trail. A trail Hecate opened fresh would carry none of them, and both checks would refuse every promotion — a gate that always says no is a gate somebody switches off.
Returns "" when Fides has never seen the digest, which is not an error: it means CI did not register the artifact, and the caller decides whether that is disqualifying.
ponytail: Fides has no by-digest lookup that also returns the trail — its /search/artifacts filters by sha but omits trail_id, and /artifacts returns trail_id but takes no filter — so this reads the org's artifacts and matches here. One call per crossing.
The ceiling is lower than "grows with the artifact count" suggests, which is what this comment used to say: /artifacts also runs a per-row query for each artifact's SBOM and embeds the payload, so the response is the size of every SBOM in the organisation. Hundreds of megabytes is reachable, to learn one 36-byte trail id.
The cheap upgrade is upstream and two lines: /search/artifacts already filters by sha, already has LIMIT 100 and already joins trails, so adding a.trail_id to its SELECT is the whole change. This function then becomes one filtered call with no loop. Tracked in #111.
type Compliance ¶
type Compliance struct {
Compliant bool `json:"compliant"`
Violations []string `json:"violations,omitempty"`
}
Compliance is the answer to `assert`: does this artifact satisfy a policy?
type Config ¶
type Config struct {
// BaseURL is the server root, e.g. https://fides.acme.io. Required: there
// is no public Fides to default to.
BaseURL string
// Token is a Fides API key, sent as a bearer token.
Token string
// Timeout bounds a single call. Zero uses 30s.
Timeout time.Duration
}
Config is what a client needs to reach a Fides server.
type Control ¶
type Control struct {
Key string `json:"control"`
Name string `json:"name"`
// Reasons are Fides' own phrasings, e.g. "missing sbom".
Reasons []string `json:"reasons,omitempty"`
// WaivedReasons is what the waiver excused, present only on Waived.
WaivedReasons []string `json:"waived_reasons,omitempty"`
Reason string `json:"reason,omitempty"`
ApprovedBy string `json:"approved_by,omitempty"`
ExpiresAt string `json:"expires_at,omitempty"`
}
Control is one control the change gate judged.
Sent as an object rather than a bare key because the reasons are the useful part: "CC7.2" is a code to look up, "CC7.2 Vulnerability scanning: failed vuln-scan" is a thing to go and fix.
type Error ¶
Error is a refusal from the server, with the status kept so a caller can tell a rejected token from a trail that is not there.
type PolicyResult ¶
type PolicyResult struct {
Policy string `json:"policy"`
// Applies is false when the policy is conditional on a flow tag the trail
// does not carry. A policy that did not apply is not a policy that passed.
Applies bool `json:"applies"`
// Missing lists the attestation types the policy required and the trail
// does not have a compliant one of.
Missing []string `json:"missing,omitempty"`
}
PolicyResult is one environment policy's verdict on the trail.
type PolicyVerdict ¶
type PolicyVerdict struct {
Compliant bool `json:"compliant"`
TrailID string `json:"trail_id,omitempty"`
Results []PolicyResult `json:"results,omitempty"`
}
PolicyVerdict is the answer to "does this trail satisfy the environment's policy?"
Field names mirror Fides' own response. `compliant` rather than `passed`, and a per-policy breakdown rather than a flat list of violations, because a Gate that refuses a crossing has to be able to say which policy refused it.
func (PolicyVerdict) Unmet ¶
func (v PolicyVerdict) Unmet() []string
Unmet lists the policies that refused, so a failure can name them.
type SegregationOfDuties ¶
type SegregationOfDuties struct {
Committer string `json:"committer,omitempty"`
Approvers []string `json:"approvers,omitempty"`
Deployers []string `json:"deployers,omitempty"`
Compliant bool `json:"compliant"`
Violations []string `json:"violations,omitempty"`
}
SegregationOfDuties is Fides' four-eyes finding for a trail.