Skip to content

Commit b89bfda

Browse files
authored
Add a repo policy feature (#25)
* Introduce repo 'policies' These are meant to be stored centrally. Repos that want to onboard would send a PR adding a policy file for their repo. Signed-off-by: Tom Hennen <[email protected]> * add readme and first policy Signed-off-by: Tom Hennen <[email protected]> --------- Signed-off-by: Tom Hennen <[email protected]>
1 parent 4828f9e commit b89bfda

File tree

4 files changed

+76
-10
lines changed

4 files changed

+76
-10
lines changed

POLICY.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,15 @@ are answered).
1414

1515
Level 1+: git is one of the modern tools, since this tool only works with git this requirement is met.
1616

17-
1817
### Canonical location
1918

2019
Open question: It's unclear how this should be checked by tooling. One thought is that this
2120
requirement may be in the wrong spot. Instead perhaps what we're looking for is that given
2221
packages distributed further downstream should indicate which source repos are canonical.
2322
They could do this using SLSA _build_ provenance.
2423

24+
TODO: Now enabling people to set a canonical location in a policy file. Not yet requried for Level 1.
25+
2526
### Distribute summary attestations
2627

2728
Open question: This tool doesn't yet distribute attestations. We need to figure that out.
@@ -68,7 +69,8 @@ Open question: Is this duplicative of "Distribute summary attestations"
6869

6970
Level 1: N/A
7071

71-
Level 2+: Open question.
72+
Level 2+: For a commit on a branch to qualify as Level 2+ it must be explicitly indicated in the corresponding policy file.
73+
This is taken as an indication that is meant for consumption.
7274

7375
### Continuity
7476

policy/README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# Policy
2+
3+
This folder stores 'policies' for individual repos.
4+
5+
It is a place for organizations to:
6+
7+
* Declare which branches are meant for consumption.
8+
* Indicate when SLSA protections were enabled without allowing
9+
those protections to be disabled without it affecting the
10+
determined SLSA level.
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"canonical_repo": "https://github.com/slsa-framework/slsa-source-repo",
3+
"protected_branches": [
4+
{
5+
"name": "main",
6+
"since": "2025-01-23T16:32:27.845Z",
7+
"target_slsa_source_level": "SLSA_SOURCE_LEVEL_2"
8+
}
9+
]
10+
}

sourcetool/main.go

Lines changed: 52 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package main
22

33
import (
44
"context"
5+
"encoding/json"
56
"errors"
67
"flag"
78
"fmt"
@@ -13,8 +14,10 @@ import (
1314
)
1415

1516
const (
16-
SlsaSourceLevel1 = "SLSA_SOURCE_LEVEL_1"
17-
SlsaSourceLevel2 = "SLSA_SOURCE_LEVEL_2"
17+
SlsaSourceLevel1 = "SLSA_SOURCE_LEVEL_1"
18+
SlsaSourceLevel2 = "SLSA_SOURCE_LEVEL_2"
19+
SourcePolicyRepoOwner = "slsa-framework"
20+
SourcePolicyRepo = "slsa-source-poc"
1821
)
1922

2023
type activity struct {
@@ -26,6 +29,40 @@ type activity struct {
2629
ActivityType string `json:"activity_type"`
2730
}
2831

32+
type protectedBranch struct {
33+
Name string
34+
Since time.Time
35+
TargetSlsaSourceLevel string `json:"target_slsa_source_level"`
36+
}
37+
type repoPolicy struct {
38+
// I'm actually not sure we need this. Consider removing?
39+
CanonicalRepo string `json:"canonical_repo"`
40+
ProtectedBranches []protectedBranch `json:"protected_branches"`
41+
}
42+
43+
func getBranchPolicy(ctx context.Context, gh_client *github.Client, owner string, repo string, branch string) (*protectedBranch, error) {
44+
path := fmt.Sprintf("../policy/github.com/%s/%s/source-policy.json", owner, repo)
45+
46+
policyContents, _, _, err := gh_client.Repositories.GetContents(ctx, SourcePolicyRepoOwner, SourcePolicyRepo, path, nil)
47+
if err != nil {
48+
return nil, err
49+
}
50+
51+
var p repoPolicy
52+
err = json.Unmarshal(policyContents.GetContents(), &p)
53+
if err != nil {
54+
return nil, err
55+
}
56+
57+
for _, pb := range p.ProtectedBranches {
58+
if pb.Name == branch {
59+
return &pb, nil
60+
}
61+
}
62+
63+
return nil, errors.New(fmt.Sprintf("Could not find rule for branch %s", branch))
64+
}
65+
2966
// Checks to see if the rule meets our requirements.
3067
func checkRule(ctx context.Context, gh_client *github.Client, owner string, repo string, rule *github.RepositoryRule, minTime time.Time) (bool, error) {
3168
ruleset, _, err := gh_client.Repositories.GetRuleset(ctx, owner, repo, rule.RulesetID, false)
@@ -104,16 +141,23 @@ func determineSourceLevel(ctx context.Context, gh_client *github.Client, commit
104141
return "", err
105142
}
106143

107-
// We'd like to check that the rules have been enabled for the appropriate amount of time
108-
// (to ensure they weren't disabled). For now we just require something that's not in
109-
// the future.
110-
minTime := pushTime.AddDate(0, 0, -1*minDays)
144+
// We want to check to ensure the repo hasn't enabled/disabled the rules since
145+
// setting the 'since' field in their policy.
146+
branchPolicy, err := getBranchPolicy(ctx, gh_client, owner, repo, branch)
147+
if err != nil {
148+
return "", err
149+
}
150+
151+
if pushTime.Before(branchPolicy.Since) {
152+
// This commit was pushed before they had an explicit policy.
153+
return SlsaSourceLevel1, nil
154+
}
111155

112-
deletionGood, err := checkRule(ctx, gh_client, owner, repo, deletionRule, minTime)
156+
deletionGood, err := checkRule(ctx, gh_client, owner, repo, deletionRule, branchPolicy.Since)
113157
if err != nil {
114158
return "", err
115159
}
116-
nonFFGood, err := checkRule(ctx, gh_client, owner, repo, nonFastFowardRule, minTime)
160+
nonFFGood, err := checkRule(ctx, gh_client, owner, repo, nonFastFowardRule, branchPolicy.Since)
117161
if err != nil {
118162
return "", err
119163
}

0 commit comments

Comments
 (0)