Skip to content

Commit 10944f3

Browse files
authored
Automate tag hygiene controls (#245)
* Add TAG_RULES config constant Signed-off-by: Adolfo García Veytia (Puerco) <[email protected]> * Add EnableTagRules to ghcontrol Signed-off-by: Adolfo García Veytia (Puerco) <[email protected]> * Support CONFIG_TAG_RULES in GH backend Signed-off-by: Adolfo García Veytia (Puerco) <[email protected]> * Improve UI handling of recommendtns Signed-off-by: Adolfo García Veytia (Puerco) <[email protected]> * setup: Add tag controls to help Signed-off-by: Adolfo García Veytia (Puerco) <[email protected]> --------- Signed-off-by: Adolfo García Veytia (Puerco) <[email protected]>
1 parent 53486b0 commit 10944f3

File tree

7 files changed

+120
-13
lines changed

7 files changed

+120
-13
lines changed

sourcetool/internal/cmd/setup.go

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -86,15 +86,21 @@ func AddSetupRepo(parent *cobra.Command) {
8686
setupRepoCmd := &cobra.Command{
8787
Short: "configure all the SLSA source features in a repository",
8888
Long: `The setup repo subcommand is a "one shot" setup process enabling all
89-
the security controls required to get a repository to a specific SLSA level.
89+
the security controls required to get a repository to SLSA Source level 3.
9090
9191
This command is ideal for new repositories or when you are sure the implemented
9292
changes will not disrupt existing workflows.
9393
94-
To use this subcommand you need to export a GitHub token as an environment
94+
To run this command make sure sourcetool is authorized on the repository
95+
(try sourcetool auth whoami ) or export a GitHub token as an environment
9596
variable called GITHUB_TOKEN. The token needs admin permissions on the repo
9697
to configure the branch rules.
9798
99+
If the SLSA controls are already enforce in the repository they will be left
100+
untouched.
101+
102+
Alternatively, to enable each control individually use: sourcetool setup controls.
103+
98104
`,
99105
Use: "repo owner/repo",
100106
SilenceUsage: false,
@@ -151,7 +157,7 @@ sourcetool is about to perform the following actions on your behalf:
151157
- %s.
152158
153159
`,
154-
srctool.ControlConfigurationDescr(opts.GetBranch(), models.CONFIG_POLICY),
160+
srctool.ControlConfigurationDescr(opts.GetBranch(), models.CONFIG_TAG_RULES),
155161
srctool.ControlConfigurationDescr(opts.GetBranch(), models.CONFIG_GEN_PROVENANCE),
156162
srctool.ControlConfigurationDescr(opts.GetBranch(), models.CONFIG_BRANCH_RULES),
157163
)
@@ -229,8 +235,12 @@ as an identity source.
229235
The values for --config are as follows:
230236
231237
%s
232-
Configures push and delete protection in the repository, required to reach slsa
233-
source level 2+.
238+
Configures push and delete branch protection in the repository, required to reach
239+
SLSA source level 2+.
240+
241+
%s
242+
Configures udpate, push and delete protection for all tags in the repository,
243+
this is required to reach SLSA source level 2+.
234244
235245
%s
236246
Opens a pull request in the repository to add the provenance generation workflow
@@ -247,7 +257,8 @@ repositories. Make sure you have a fork of the SLSA source policy repo and
247257
a fork of the repository you want to protect.
248258
249259
`, w("sourcetool setup controls"), w2("configure a repository for SLSA source"),
250-
w2(models.CONFIG_BRANCH_RULES), w2(models.CONFIG_GEN_PROVENANCE), w2(models.CONFIG_POLICY)),
260+
w2(models.CONFIG_BRANCH_RULES), w2(models.CONFIG_TAG_RULES),
261+
w2(models.CONFIG_GEN_PROVENANCE), w2(models.CONFIG_POLICY)),
251262
Use: "controls owner/repo --config=CONTROL1 --config=CONTROL2",
252263
SilenceUsage: false,
253264
SilenceErrors: true,
@@ -322,6 +333,11 @@ a fork of the repository you want to protect.
322333
opts.GetBranch().Repository, []*models.Branch{opts.GetBranch()}, cs,
323334
)
324335
if err != nil {
336+
// if strings.Contains(err.Error(), models.ErrProtectionAlreadyInPlace.Error()) {
337+
if errors.Is(err, models.ErrProtectionAlreadyInPlace) {
338+
fmt.Printf("\n ℹ️ Controls already enabled on %s\n\n", opts.GetRepository().Path)
339+
return nil
340+
}
325341
return fmt.Errorf("configuring controls: %w", err)
326342
}
327343

sourcetool/internal/cmd/status.go

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -165,13 +165,22 @@ sourcetool status myorg/myrepo@mybranch
165165

166166
fmt.Println(w("Current SLSA Source level: " + toplevel))
167167
fmt.Println("")
168-
169-
fmt.Println("Recommended actions:")
170-
168+
titled := false
171169
for _, status := range controls.Controls {
172170
if status.RecommendedAction == nil {
173171
continue
174172
}
173+
174+
// Suggest creating the policy but only on the higher levels
175+
if status.Name == slsa.PolicyAvailable && toplevel == slsa.SlsaSourceLevel1 {
176+
continue
177+
}
178+
179+
if !titled {
180+
fmt.Println(w2("✨ Recommended actions:"))
181+
titled = true
182+
}
183+
175184
fmt.Printf(" - %s\n", status.RecommendedAction.Message)
176185
if status.RecommendedAction.Command != "" {
177186
fmt.Printf(" > %s\n", status.RecommendedAction.Command)

sourcetool/pkg/ghcontrol/checklevel.go

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212

1313
"github.com/slsa-framework/slsa-source-poc/sourcetool/pkg/provenance"
1414
"github.com/slsa-framework/slsa-source-poc/sourcetool/pkg/slsa"
15+
"github.com/slsa-framework/slsa-source-poc/sourcetool/pkg/sourcetool/models"
1516
)
1617

1718
const (
@@ -241,8 +242,7 @@ func (ghc *GitHubConnection) EnableBranchRules(ctx context.Context) error {
241242

242243
// Check if they are both enabled and noop if they are
243244
if oldestDeletion != nil && oldestNoFf != nil {
244-
log.Printf("ℹ️ Branch protection already enabled on %s/%s", ghc.Owner(), ghc.Repo())
245-
return nil
245+
return models.ErrProtectionAlreadyInPlace
246246
}
247247

248248
// Create the SLSA ruleset
@@ -262,7 +262,51 @@ func (ghc *GitHubConnection) EnableBranchRules(ctx context.Context) error {
262262
NonFastForward: &github.EmptyRuleParameters{},
263263
},
264264
}); err != nil {
265-
return fmt.Errorf("creating reposirory ruleset: %w", err)
265+
return fmt.Errorf("creating branch protection ruleset: %w", err)
266+
}
267+
268+
return nil
269+
}
270+
271+
// EnableTagRules adds a ruleset to the repo to enforce delete and push and update
272+
// protection on all branches.
273+
func (ghc *GitHubConnection) EnableTagRules(ctx context.Context) error {
274+
allRules, _, err := ghc.Client().Repositories.GetAllRulesets(
275+
ctx, ghc.Owner(), ghc.Repo(), true,
276+
)
277+
if err != nil {
278+
return fmt.Errorf("fetching tag rules: %w", err)
279+
}
280+
ctl, err := ghc.computeTagHygieneControl(ctx, allRules)
281+
if err != nil {
282+
return fmt.Errorf("checking tag controls: %w", err)
283+
}
284+
if ctl != nil {
285+
// Tag controls are in place, noop
286+
return models.ErrProtectionAlreadyInPlace
287+
}
288+
289+
// Create the SLSA ruleset
290+
if _, _, err := ghc.Client().Repositories.CreateRuleset(ctx, ghc.Owner(), ghc.Repo(), github.RepositoryRuleset{
291+
Name: "SLSA Tag Controls",
292+
Target: github.Ptr(github.RulesetTargetTag),
293+
Enforcement: EnforcementActive,
294+
BypassActors: []*github.BypassActor{},
295+
Conditions: &github.RepositoryRulesetConditions{
296+
RefName: &github.RepositoryRulesetRefConditionParameters{
297+
Exclude: []string{},
298+
Include: []string{"~ALL"},
299+
},
300+
},
301+
Rules: &github.RepositoryRulesetRules{
302+
Deletion: &github.EmptyRuleParameters{},
303+
NonFastForward: &github.EmptyRuleParameters{},
304+
Update: &github.UpdateRuleParameters{
305+
UpdateAllowsFetchAndMerge: false,
306+
},
307+
},
308+
}); err != nil {
309+
return fmt.Errorf("creating tag protection ruleset: %w", err)
266310
}
267311

268312
return nil

sourcetool/pkg/sourcetool/backends/vcs/github/github.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,11 @@ func (b *Backend) ControlConfigurationDescr(branch *models.Branch, config models
190190
"Open a pull request on the SLSA policy repo to check-in %s SLSA source policy",
191191
repo.Path,
192192
)
193+
case models.CONFIG_TAG_RULES:
194+
return fmt.Sprintf(
195+
"Enable push/update/delete protection for all tags in %s",
196+
repo.Path,
197+
)
193198
default:
194199
return ""
195200
}
@@ -237,6 +242,14 @@ func (b *Backend) getRecommendedAction(r *models.Repository, _ *models.Branch, c
237242
}
238243
}
239244
return nil
245+
case slsa.TagHygiene:
246+
if state == slsa.StateNotEnabled {
247+
return &slsa.ControlRecommendedAction{
248+
Message: "Enable tag push/update/delete protection",
249+
Command: fmt.Sprintf("sourcetool setup controls --config=%s %s", models.CONFIG_TAG_RULES, r.Path),
250+
}
251+
}
252+
return nil
240253
default:
241254
return nil
242255
}

sourcetool/pkg/sourcetool/backends/vcs/github/manage.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,23 @@ func (b *Backend) CreateRepoRuleset(r *models.Repository, branches []*models.Bra
174174
return nil
175175
}
176176

177+
func (b *Backend) CreateTagRuleset(r *models.Repository) error {
178+
if r == nil {
179+
return errors.New("unable to create tag ruleset, repository not defined")
180+
}
181+
182+
ghc, err := b.getGitHubConnection(r, "")
183+
if err != nil {
184+
return err
185+
}
186+
187+
if err := ghc.EnableTagRules(context.Background()); err != nil {
188+
return fmt.Errorf("enabling tag protection rules: %w", err)
189+
}
190+
191+
return nil
192+
}
193+
177194
func (b *Backend) ConfigureControls(r *models.Repository, branches []*models.Branch, configs []models.ControlConfiguration) error {
178195
for _, config := range configs {
179196
switch config {
@@ -188,6 +205,10 @@ func (b *Backend) ConfigureControls(r *models.Repository, branches []*models.Bra
188205
if _, err := b.CreateWorkflowPR(r, branches); err != nil {
189206
return fmt.Errorf("opening SLSA source workflow pull request: %w", err)
190207
}
208+
case models.CONFIG_TAG_RULES:
209+
if err := b.CreateTagRuleset(r); err != nil {
210+
return fmt.Errorf("opening SLSA source workflow pull request: %w", err)
211+
}
191212
case models.CONFIG_POLICY:
192213
// Noop, this is not handled by the VCS handler
193214
default:

sourcetool/pkg/sourcetool/models/models.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package models
33

44
import (
55
"context"
6+
"errors"
67
"fmt"
78
"strings"
89
"time"
@@ -14,6 +15,8 @@ import (
1415
"github.com/slsa-framework/slsa-source-poc/sourcetool/pkg/slsa"
1516
)
1617

18+
var ErrProtectionAlreadyInPlace = errors.New("controls already in place in the repository")
19+
1720
// AttestationStorageReader abstracts an attestation storage system where
1821
// sourcetool can read VSAs and provenance attestations.
1922
// For now we only have retrieval functions but this may expand to
@@ -44,6 +47,7 @@ const (
4447
CONFIG_POLICY ControlConfiguration = "CONFIG_POLICY"
4548
CONFIG_GEN_PROVENANCE ControlConfiguration = "CONFIG_GEN_PROVENANCE"
4649
CONFIG_BRANCH_RULES ControlConfiguration = "CONFIG_BRANCH_RULES"
50+
CONFIG_TAG_RULES ControlConfiguration = "CONFIG_TAG_RULES"
4751
)
4852

4953
type Commit struct {

sourcetool/pkg/sourcetool/tool.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import (
2020
)
2121

2222
var ControlConfigurations = []models.ControlConfiguration{
23-
models.CONFIG_POLICY, models.CONFIG_GEN_PROVENANCE, models.CONFIG_BRANCH_RULES,
23+
models.CONFIG_POLICY, models.CONFIG_GEN_PROVENANCE, models.CONFIG_BRANCH_RULES, models.CONFIG_TAG_RULES,
2424
}
2525

2626
// New initializes a new source tool instance.

0 commit comments

Comments
 (0)