Skip to content
This repository was archived by the owner on Dec 3, 2024. It is now read-only.

Commit da667de

Browse files
authored
feat: support arbitrary rego input parameters (#196)
* feat: support arbitrary rego input parameters
1 parent 7027d2d commit da667de

File tree

6 files changed

+116
-15
lines changed

6 files changed

+116
-15
lines changed

policy/types.go

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,12 @@ type Options struct {
3838
AttestationStyle mapping.AttestationStyle
3939
Debug bool
4040
AttestationVerifier attestation.Verifier
41+
// extra parameters to pass through to rego as policy inputs
42+
Parameters Parameters
4143
}
4244

45+
type Parameters map[string]string
46+
4347
type Policy struct {
4448
InputFiles []*File
4549
Query string
@@ -50,13 +54,14 @@ type Policy struct {
5054
}
5155

5256
type Input struct {
53-
Digest string `json:"digest"`
54-
PURL string `json:"purl"`
55-
Tag string `json:"tag,omitempty"`
56-
Domain string `json:"domain"`
57-
NormalizedName string `json:"normalized_name"`
58-
FamiliarName string `json:"familiar_name"`
59-
Platform string `json:"platform"`
57+
Digest string `json:"digest"`
58+
PURL string `json:"purl"`
59+
Tag string `json:"tag,omitempty"`
60+
Domain string `json:"domain"`
61+
NormalizedName string `json:"normalized_name"`
62+
FamiliarName string `json:"familiar_name"`
63+
Platform string `json:"platform"`
64+
Parameters Parameters `json:"parameters"`
6065
}
6166

6267
type File struct {
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
config.yaml
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
version: v1
2+
kind: policy-mapping
3+
policies:
4+
- id: test-images
5+
description: Local test images
6+
files:
7+
- path: policy.rego
8+
- path: config.yaml #auto generated
9+
attestations:
10+
style: attached
11+
rules:
12+
- pattern: "^docker[.]io/library/test-image$"
13+
policy-id: test-images
14+
- pattern: "^mirror[.]org/library/(.*)$"
15+
rewrite: docker.io/library/$1
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package attest
2+
3+
import rego.v1
4+
5+
import data.keys
6+
import input.parameters
7+
8+
provs(pred) := p if {
9+
res := attest.fetch(pred)
10+
not res.error
11+
p := res.value
12+
}
13+
14+
atts := union({
15+
provs("https://slsa.dev/provenance/v0.2"),
16+
provs("https://spdx.dev/Document"),
17+
})
18+
19+
opts := {"keys": keys, "skip_tl": true}
20+
21+
statements contains s if {
22+
parameters.foo == "bar"
23+
some att in atts
24+
res := attest.verify(att, opts)
25+
not res.error
26+
s := res.value
27+
}
28+
29+
subjects contains subject if {
30+
some statement in statements
31+
some subject in statement.subject
32+
}
33+
34+
unsafe_statement_from_attestation(att) := statement if {
35+
payload := att.payload
36+
statement := json.unmarshal(base64.decode(payload))
37+
}
38+
39+
violations contains violation if {
40+
some att in atts
41+
statement := unsafe_statement_from_attestation(att)
42+
res := attest.verify(att, opts)
43+
err := res.error
44+
violation := {
45+
"type": "unsigned_statement",
46+
"description": sprintf("Statement is not correctly signed: %v", [err]),
47+
"attestation": statement,
48+
"details": {"error": err},
49+
}
50+
}
51+
52+
result := {
53+
"success": count(statements) > 0,
54+
"violations": violations,
55+
"summary": {
56+
"subjects": subjects,
57+
"slsa_level": "SLSA_BUILD_LEVEL_3",
58+
"verifier": "docker-official-images",
59+
"policy_uri": "https://docker.com/official/policy/v0.1",
60+
},
61+
}

verify.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ func (verifier *ImageVerifier) Verify(ctx context.Context, src *oci.ImageSpec) (
9393
return nil, fmt.Errorf("failed to create attestation resolver: %w", err)
9494
}
9595
evaluator := policy.NewRegoEvaluator(verifier.opts.Debug, verifier.attestationVerifier)
96-
result, err = VerifyAttestations(ctx, resolver, evaluator, resolvedPolicy)
96+
result, err = verifyAttestations(ctx, resolver, evaluator, resolvedPolicy, verifier.opts)
9797
if err != nil {
9898
return nil, fmt.Errorf("failed to evaluate policy: %w", err)
9999
}
@@ -195,7 +195,7 @@ func toVerificationResult(p *policy.Policy, input *policy.Input, result *policy.
195195
}, nil
196196
}
197197

198-
func VerifyAttestations(ctx context.Context, resolver attestation.Resolver, evaluator policy.Evaluator, resolvedPolicy *policy.Policy) (*VerificationResult, error) {
198+
func verifyAttestations(ctx context.Context, resolver attestation.Resolver, evaluator policy.Evaluator, resolvedPolicy *policy.Policy, opts *policy.Options) (*VerificationResult, error) {
199199
desc, err := resolver.ImageDescriptor(ctx)
200200
if err != nil {
201201
return nil, fmt.Errorf("failed to get image descriptor: %w", err)
@@ -247,6 +247,7 @@ func VerifyAttestations(ctx context.Context, resolver attestation.Resolver, eval
247247
Domain: reference.Domain(ref),
248248
NormalizedName: reference.Path(ref),
249249
FamiliarName: reference.FamiliarName(ref),
250+
Parameters: opts.Parameters,
250251
}
251252
// rego has null strings
252253
if tag != "" {

verify_test.go

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ import (
2727
var (
2828
ExampleAttestation = filepath.Join("test", "testdata", "example_attestation.json")
2929
LocalKeysPolicy = filepath.Join("test", "testdata", "local-policy-real")
30+
LocalParamPolicy = filepath.Join("test", "testdata", "local-policy-param")
31+
ExpiresPolicy = filepath.Join("test", "testdata", "expires")
3032
)
3133

3234
const (
@@ -60,7 +62,7 @@ func TestVerifyAttestations(t *testing.T) {
6062
return policy.AllowedResult(), tc.policyEvaluationError
6163
},
6264
}
63-
_, err := VerifyAttestations(ctx, resolver, &mockPE, &policy.Policy{ResolvedName: ""})
65+
_, err := verifyAttestations(ctx, resolver, &mockPE, &policy.Policy{ResolvedName: ""}, &policy.Options{})
6466
if tc.expectedError != nil {
6567
if assert.Error(t, err) {
6668
assert.Equal(t, tc.expectedError.Error(), err.Error())
@@ -204,24 +206,24 @@ func TestSignVerify(t *testing.T) {
204206
keysYaml, err := yaml.Marshal(config)
205207
require.NoError(t, err)
206208

207-
// write keysYaml to config.yaml in LocalKeysPolicy.
208-
err = os.WriteFile(filepath.Join(LocalKeysPolicy, "config.yaml"), keysYaml, 0o600)
209-
require.NoError(t, err)
210-
211209
testCases := []struct {
212210
name string
213211
signTL bool
214212
policyDir string
215213
imageName string
216214
expectedNonSuccess Outcome
215+
spitConfig bool
216+
param string
217217
}{
218218
{name: "happy path", signTL: true, policyDir: PassNoTLPolicyDir},
219219
{name: "sign tl, verify no tl", signTL: true, policyDir: PassPolicyDir},
220220
{name: "no tl", signTL: false, policyDir: PassPolicyDir},
221221
{name: "mirror", signTL: false, policyDir: PassMirrorPolicyDir, imageName: "mirror.org/library/test-image:test"},
222222
{name: "mirror no match", signTL: false, policyDir: PassMirrorPolicyDir, imageName: "incorrect.org/library/test-image:test", expectedNonSuccess: OutcomeNoPolicy},
223223
{name: "verify inputs", signTL: false, policyDir: InputsPolicyDir},
224-
{name: "mirror with verification", signTL: false, policyDir: LocalKeysPolicy, imageName: "mirror.org/library/test-image:test"},
224+
{name: "mirror with verification", signTL: false, policyDir: LocalKeysPolicy, imageName: "mirror.org/library/test-image:test", spitConfig: true},
225+
{name: "policy with input params", spitConfig: true, signTL: false, policyDir: LocalParamPolicy, param: "bar"},
226+
{name: "policy without expected param", spitConfig: true, signTL: false, policyDir: LocalParamPolicy, param: "baz", expectedNonSuccess: OutcomeFailure},
225227
}
226228

227229
attIdx, err := oci.IndexFromPath(test.UnsignedTestIndex())
@@ -232,6 +234,11 @@ func TestSignVerify(t *testing.T) {
232234
if tc.signTL {
233235
opts.TransparencyLog = tlog.GetMockTL()
234236
}
237+
if tc.spitConfig {
238+
// write keysYaml to config.yaml in LocalKeysPolicy.
239+
err = os.WriteFile(filepath.Join(tc.policyDir, "config.yaml"), keysYaml, 0o600)
240+
require.NoError(t, err)
241+
}
235242

236243
signedManifests, err := SignStatements(ctx, attIdx.Index, signer, opts)
237244
require.NoError(t, err)
@@ -254,6 +261,17 @@ func TestSignVerify(t *testing.T) {
254261
DisableTUF: true,
255262
Debug: true,
256263
}
264+
if tc.signTL {
265+
getTL := func(_ context.Context, _ *attestation.VerifyOptions) (tlog.TransparencyLog, error) {
266+
return tlog.GetMockTL(), nil
267+
}
268+
verifier, err := attestation.NewVerfier(attestation.WithLogVerifierFactory(getTL))
269+
require.NoError(t, err)
270+
policyOpts.AttestationVerifier = verifier
271+
}
272+
if tc.param != "" {
273+
policyOpts.Parameters = policy.Parameters{"foo": tc.param}
274+
}
257275
results, err := Verify(ctx, spec, policyOpts)
258276
require.NoError(t, err)
259277
if tc.expectedNonSuccess != "" {

0 commit comments

Comments
 (0)