Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,8 @@ change here must keep working with them.
and `_policy_data_digest`.
- **The agent never computes artifact digests.** It passes each evaluation's policy
directory, input and policy data through to the API, which canonicalises and hashes them.
- **Evidence identity.** `policy-manager`'s `newEvidence` seed is every evidence stream's UUID, and plugins in the
field compute it. Never change it. The golden test in `policy-manager/evidence_seed_test.go` pins the UUIDs.
- **Storage failure doesn't drop evidence.** If artifact storage fails, the evidence is still
sent, without digests.
- **OCI policy bundles.** The agent evaluates the extracted `policies/` subdirectory, and that
Expand Down
157 changes: 157 additions & 0 deletions cmd/config_golden_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
package cmd

import (
"os"
"path/filepath"
"strings"
"testing"

"github.com/spf13/viper"
)

// goldenHashFixtures are config files whose agentConfigurationHash was recorded BEFORE the
// declared/runtime config refactor (agent-remote-config G0). The hash feeds the `_agent`
// evidence label fallback and the agent evidence UUID, so it must stay byte-identical.
var goldenHashFixtures = []struct {
name string
yaml string
env map[string]string
hash string
}{
{
name: "minimal",
yaml: `
api:
url: http://localhost:8080
`,
hash: "4f6b1c9d4fc55c1b99e6b9c60ef0b3783d6ca57fdccc4ca4a768a4256a72e842",
},
{
name: "single plugin defaults",
yaml: `
api:
url: http://localhost:8080
plugins:
ssh:
source: ghcr.io/compliance-framework/plugin-ssh:v1
`,
hash: "9513a410fcb588cbf62934306061dbc1c3c2a236b1727dacdfef7f02d110bb2a",
},
{
name: "full plugin",
yaml: `
daemon: true
verbosity: 1
api:
url: http://localhost:8080
auth:
client_id: 123e4567-e89b-12d3-a456-426614174000
client_secret: s3cret
agent_evidence:
enabled: true
emit_on_run_completion: false
interval: 90m
plugins:
ssh:
source: ghcr.io/compliance-framework/plugin-ssh:v1
schedule: "*/5 * * * *"
protocol_version: 2
policies:
- ghcr.io/compliance-framework/plugin-ssh-policies:v1
- ./local-policies
config:
host: 127.0.0.1
port: 22
collect_ip_allow_list: false
account_id: 123456789012
labels:
team: platform
env: prod
policy_data:
max_auth_tries: 3
nested:
allowed: [a, b]
policy_behavior:
deny: [warn]
github:
source: ghcr.io/compliance-framework/plugin-github:v1
config:
token: plain-token
`,
hash: "b3bf4cf694f2aebeeac36762b1a7f4eb89288a9275b7994e99fb2f85183da1c2",
},
{
name: "env sourced plugin config",
yaml: `
api:
url: http://localhost:8080
plugins:
github:
source: ghcr.io/compliance-framework/plugin-github:v1
config:
token: from-file
`,
env: map[string]string{"CCF_PLUGINS_GITHUB_CONFIG_TOKEN": "from-env"},
hash: "402b411f87e7d4ab32e3148317fc24e01e98347451b1e5af5bceaa5a29cc4175",
},
{
name: "agent evidence disabled",
yaml: `
api:
url: http://localhost:8080
agent_evidence:
enabled: false
plugins:
a:
source: ./plugin-a
protocol_version: 1
b:
source: ./plugin-b
schedule: "@hourly"
`,
hash: "40d4e852545aad49f8aad499df08051195b10b7c91cb1013c2bc19f6388ead82",
},
}

// loadGoldenFixture loads a fixture through the agent's file loader. It is the only line that
// changes when the loader is refactored.
func loadGoldenFixture(t *testing.T, yaml string) *agentConfig {
t.Helper()
path := filepath.Join(t.TempDir(), "config.yaml")
if err := os.WriteFile(path, []byte(yaml), 0o600); err != nil {
t.Fatalf("write fixture: %v", err)
}
v := newGoldenViper(t, path)
config, err := loadConfig(AgentCmd(), v)
if err != nil {
t.Fatalf("load fixture: %v", err)
}
return config
}

func TestAgentConfigurationHashGolden(t *testing.T) {
for _, fx := range goldenHashFixtures {
t.Run(fx.name, func(t *testing.T) {
for k, v := range fx.env {
t.Setenv(k, v)
}
config := loadGoldenFixture(t, fx.yaml)
if got := agentConfigurationHash(config); got != fx.hash {
t.Fatalf("agentConfigurationHash changed: got %s want %s", got, fx.hash)
}
})
}
}

func newGoldenViper(t *testing.T, path string) *viper.Viper {
t.Helper()
v := viper.New()
v.SetConfigFile(path)
v.SetEnvPrefix("CCF")
v.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
v.AutomaticEnv()
if err := bindAgentEnv(v); err != nil {
t.Fatalf("bind env: %v", err)
}
return v
}
47 changes: 47 additions & 0 deletions policy-manager/evidence_seed_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package policy_manager

import (
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

// TestEvidenceSeedIsUnchanged pins the evidence UUIDs plugins in the field produce for
// several label and path combinations. Every evidence stream depends on them: they must
// never change.
func TestEvidenceSeedIsUnchanged(t *testing.T) {
const (
// vendorPath is the policy path an OCI bundle is passed as: relative to the agent's
// working directory, with the repository and tag.
vendorPath = ".compliance-framework/policies/compliance-framework/plugin-local-ssh-policies/v0.2.0/policies"
// localPath is a local policy source, passed as configured.
localPath = "/etc/ccf/policies/ssh"
)
sshLabels := func(policyPath string) map[string]string {
return map[string]string{"type": "ssh", "hostname": "web-1", "_policy_path": policyPath}
}
cases := []struct {
name string
labels map[string]string
file, pkg string
want string
}{
{"relative OCI path", sshLabels(vendorPath), vendorPath + "/ssh_deny_password_auth.rego", "data.compliance_framework.ssh_deny_password_auth", "cede5222-a458-4465-8134-c3751575cdd9"},
{"absolute path, nested file", sshLabels(localPath), localPath + "/banner/banner.rego", "data.compliance_framework.banner", "0c0ee58a-50ca-45f1-98d3-0f9602f24101"},
{"no _policy_path label", map[string]string{"_plugin": "test-plugin"}, "test.rego", "data.compliance_framework.no_policy_path", "271009cd-7758-432e-8869-84fa710b0f5a"},
{"no labels", nil, "policies/a.rego", "data.compliance_framework.a", "9eb28e96-4f5a-416a-9623-62430b8e089f"},
{"trailing slash", map[string]string{"type": "k8s", "_policy_path": "policies/", "cluster": "prod"}, "policies/a.rego", "data.compliance_framework.a", "98fc06e7-b1a4-4d83-a272-df4fdd75d06c"},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
p := &PolicyProcessor{labels: tc.labels}
e, err := p.newEvidence(Result{
Policy: Policy{File: tc.file, Package: Package(tc.pkg)},
EvalOutput: &EvalOutput{Title: Pointer("t")},
}, nil)
require.NoError(t, err)
assert.Equal(t, tc.want, e.UUID)
})
}
}
Loading