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
10 changes: 5 additions & 5 deletions DESIGN.md
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ Tag provenance records a tag creation event. It indicates:
"actor": "TomHennen",
"controls": [
{
"name": "IMMUTABLE_TAGS",
"name": "TAG_HYGIENE",
"since": "2025-03-23T18:08:42.375Z"
}
],
Expand All @@ -210,7 +210,7 @@ Tag provenance records a tag creation event. It indicates:
],
"verifiedLevels": [
"SLSA_SOURCE_LEVEL_3",
"IMMUTABLE_TAGS"
"TAG_HYGIENE"
]
}
]
Expand Down Expand Up @@ -282,18 +282,18 @@ This tool can also check to see if the GitHub repo/ref is configured to require
* "Require review from Code Owners"
* "Require approval of the most recent reviewable push"

### IMMUTABLE_TAGS
### TAG_HYGIENE

This tool can also check to see if the GitHub repo is configured to require
immutable tags. To do so it checks that the repo enables the follow rules
tag hygiene. To do so it checks that the repo enables the follow rules
to ~ALL tags:

1. Doesn't allow tag updates
2. Doesn't allow tag deletions
3. Doesn't allow tag fast-forwards

Importing [rulesets/tag_immutability.json](rulesets/tag_immutability.json)
to a repos rulesets will enable the repo controls. The `immutable_tags`
to a repos rulesets will enable the repo controls. The `tag_hygiene`
field in the policy then needs to be enabled too.

TODO: In the future this tool could be updated to allow some subset of tags
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,6 @@
],
"protected_tag": {
"Since": "2025-03-23T18:08:43.25099739Z",
"immutable_tags": true
"tag_hygiene": true
}
}
2 changes: 1 addition & 1 deletion sourcetool/pkg/attest/provenance.go
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,7 @@ func (pa ProvenanceAttestor) CreateSourceProvenance(ctx context.Context, prevAtt
}

func (pa ProvenanceAttestor) CreateTagProvenance(ctx context.Context, commit, ref, actor string) (*spb.Statement, error) {
// 1. Check that the immutable tags control is still enabled and how long it's been enabled, store it in the prov.
// 1. Check that the tag hygiene control is still enabled and how long it's been enabled, store it in the prov.
// 2. Get a VSA associated with this commit, if any.
// 3. Record the levels and branches covered by that VSA in the provenance.

Expand Down
6 changes: 3 additions & 3 deletions sourcetool/pkg/attest/provenance_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ func newNotesContent(content string) *github.RepositoryContent {
}
}

func newImmutableTagsRulesetsResponse(id int64, target github.RulesetTarget, enforcement github.RulesetEnforcement,
func newTagHygieneRulesetsResponse(id int64, target github.RulesetTarget, enforcement github.RulesetEnforcement,
updatedAt time.Time) *github.RepositoryRuleset {
return &github.RepositoryRuleset{
ID: github.Ptr(id),
Expand Down Expand Up @@ -124,7 +124,7 @@ func TestCreateTagProvenance(t *testing.T) {
testVsa := createTestVsa(t, "http://repo", "refs/some/ref", "abc123", slsa_types.SourceVerifiedLevels{"TEST_LEVEL"})

ghc := newTestGhConnection("owner", "repo", "branch",
newImmutableTagsRulesetsResponse(123, github.RulesetTargetTag,
newTagHygieneRulesetsResponse(123, github.RulesetTargetTag,
github.RulesetEnforcementActive, rulesetOldTime),
newNotesContent(testVsa))
verifier := testsupport.NewMockVerifier()
Expand Down Expand Up @@ -160,7 +160,7 @@ func TestCreateTagProvenance(t *testing.T) {
CreatedOn: rulesetOldTime,
Controls: []slsa_types.Control{
{
Name: "IMMUTABLE_TAGS",
Name: "TAG_HYGIENE",
Since: rulesetOldTime,
},
},
Expand Down
20 changes: 10 additions & 10 deletions sourcetool/pkg/gh_control/checklevel.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ func (ghc *GitHubConnection) computeContinuityControl(ctx context.Context, commi
return &slsa_types.Control{Name: slsa_types.ContinuityEnforced, Since: newestRule.UpdatedAt.Time}, nil
}

func enforcesImmutableTags(ruleset *github.RepositoryRuleset) bool {
func enforcesTagHygiene(ruleset *github.RepositoryRuleset) bool {
if ruleset.Rules != nil &&
ruleset.Rules.Update != nil &&
ruleset.Rules.Deletion != nil &&
Expand All @@ -116,7 +116,7 @@ func enforcesImmutableTags(ruleset *github.RepositoryRuleset) bool {
return false
}

func (ghc *GitHubConnection) computeImmutableTagsControl(ctx context.Context, commit string, allRulesets []*github.RepositoryRuleset, activityTime *time.Time) (*slsa_types.Control, error) {
func (ghc *GitHubConnection) computeTagHygieneControl(ctx context.Context, commit string, allRulesets []*github.RepositoryRuleset, activityTime *time.Time) (*slsa_types.Control, error) {
var validRuleset *github.RepositoryRuleset
for _, ruleset := range allRulesets {
if *ruleset.Target != github.RulesetTargetTag {
Expand All @@ -134,7 +134,7 @@ func (ghc *GitHubConnection) computeImmutableTagsControl(ctx context.Context, co
return nil, fmt.Errorf("could not get full ruleset for ruleset id %d: err: %w", ruleset.GetID(), err)
}

if !enforcesImmutableTags(fullRuleset) {
if !enforcesTagHygiene(fullRuleset) {
continue
}
if validRuleset == nil || validRuleset.UpdatedAt.After(ruleset.UpdatedAt.Time) {
Expand All @@ -151,7 +151,7 @@ func (ghc *GitHubConnection) computeImmutableTagsControl(ctx context.Context, co
return nil, nil
}

return &slsa_types.Control{Name: slsa_types.ImmutableTags, Since: validRuleset.UpdatedAt.Time}, nil
return &slsa_types.Control{Name: slsa_types.TagHygiene, Since: validRuleset.UpdatedAt.Time}, nil
}

// Computes the review control returning nil if it's not enabled.
Expand Down Expand Up @@ -235,11 +235,11 @@ func (ghc *GitHubConnection) GetBranchControls(ctx context.Context, commit, ref
if err != nil {
return nil, err
}
ImmutableTagsControl, err := ghc.computeImmutableTagsControl(ctx, commit, allRulesets, &activity.Timestamp)
TagHygieneControl, err := ghc.computeTagHygieneControl(ctx, commit, allRulesets, &activity.Timestamp)
if err != nil {
return nil, fmt.Errorf("could not populate ImmutableTagsControl: %w", err)
return nil, fmt.Errorf("could not populate TagHygieneControl: %w", err)
}
controlStatus.Controls.AddControl(ImmutableTagsControl)
controlStatus.Controls.AddControl(TagHygieneControl)

return &controlStatus, nil
}
Expand All @@ -253,11 +253,11 @@ func (ghc *GitHubConnection) GetTagControls(ctx context.Context, commit, ref str
if err != nil {
return nil, err
}
ImmutableTagsControl, err := ghc.computeImmutableTagsControl(ctx, commit, allRulesets, &controlStatus.CommitPushTime)
TagHygieneControl, err := ghc.computeTagHygieneControl(ctx, commit, allRulesets, &controlStatus.CommitPushTime)
if err != nil {
return nil, fmt.Errorf("could not populate ImmutableTagsControl: %w", err)
return nil, fmt.Errorf("could not populate TagHygieneControl: %w", err)
}
controlStatus.Controls.AddControl(ImmutableTagsControl)
controlStatus.Controls.AddControl(TagHygieneControl)

return &controlStatus, nil
}
30 changes: 15 additions & 15 deletions sourcetool/pkg/policy/policy.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ type ProtectedBranch struct {

// The controls required for protected tags.
type ProtectedTag struct {
Since time.Time
ImmutableTags bool `json:"immutable_tags"`
Since time.Time
TagHygiene bool `json:"tag_hygiene"`
}

type RepoPolicy struct {
Expand Down Expand Up @@ -318,23 +318,23 @@ func computeReviewEnforced(branchPolicy *ProtectedBranch, controls slsa_types.Co
return true, nil
}

func computeImmutableTags(tagPolicy *ProtectedTag, controls slsa_types.Controls) (bool, error) {
func computeTagHygiene(tagPolicy *ProtectedTag, controls slsa_types.Controls) (bool, error) {
if tagPolicy == nil {
// There is no tag policy, so the control isn't met, but it's not an error.
return false, nil
}

if !tagPolicy.ImmutableTags {
if !tagPolicy.TagHygiene {
return false, nil
}

immutableTags := controls.GetControl(slsa_types.ImmutableTags)
if immutableTags == nil {
return false, fmt.Errorf("policy requires immutable tags, but that control is not enabled")
tagHygiene := controls.GetControl(slsa_types.TagHygiene)
if tagHygiene == nil {
return false, fmt.Errorf("policy requires tag hygiene, but that control is not enabled")
}

if tagPolicy.Since.Before(immutableTags.Since) {
return false, fmt.Errorf("policy requires immutable tags since %v, but that control has only been enabled since %v", tagPolicy.Since, immutableTags.Since)
if tagPolicy.Since.Before(tagHygiene.Since) {
return false, fmt.Errorf("policy requires tag hygiene since %v, but that control has only been enabled since %v", tagPolicy.Since, tagHygiene.Since)
}

return true, nil
Expand All @@ -357,12 +357,12 @@ func evaluateBranchControls(branchPolicy *ProtectedBranch, tagPolicy *ProtectedT
verifiedLevels = append(verifiedLevels, slsa_types.ReviewEnforced)
}

immutableTags, err := computeImmutableTags(tagPolicy, controls)
tagHygiene, err := computeTagHygiene(tagPolicy, controls)
if err != nil {
return slsa_types.SourceVerifiedLevels{}, fmt.Errorf("error computing tag immutability enforced: %w", err)
}
if immutableTags {
verifiedLevels = append(verifiedLevels, slsa_types.ImmutableTags)
if tagHygiene {
verifiedLevels = append(verifiedLevels, slsa_types.TagHygiene)
}

return verifiedLevels, nil
Expand All @@ -376,12 +376,12 @@ func evaluateTagProv(tagPolicy *ProtectedTag, tagProvPred *attest.TagProvenanceP
// include the verifiedLevels.

// TODO: handle tag policy?
immutableTags, err := computeImmutableTags(tagPolicy, tagProvPred.Controls)
tagHygiene, err := computeTagHygiene(tagPolicy, tagProvPred.Controls)
if err != nil {
return slsa_types.SourceVerifiedLevels{}, fmt.Errorf("error computing tag immutability enforced: %w", err)
}
if immutableTags {
// TODO: should we include the immutable tag field specifically?
if tagHygiene {
// TODO: should we include the tag hygiene field specifically?
return tagProvPred.VsaSummaries[0].VerifiedLevels, nil
}

Expand Down
Loading