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
28 changes: 22 additions & 6 deletions sourcetool/internal/cmd/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,15 +86,21 @@ func AddSetupRepo(parent *cobra.Command) {
setupRepoCmd := &cobra.Command{
Short: "configure all the SLSA source features in a repository",
Long: `The setup repo subcommand is a "one shot" setup process enabling all
the security controls required to get a repository to a specific SLSA level.
the security controls required to get a repository to SLSA Source level 3.

This command is ideal for new repositories or when you are sure the implemented
changes will not disrupt existing workflows.

To use this subcommand you need to export a GitHub token as an environment
To run this command make sure sourcetool is authorized on the repository
(try sourcetool auth whoami ) or export a GitHub token as an environment
variable called GITHUB_TOKEN. The token needs admin permissions on the repo
to configure the branch rules.

If the SLSA controls are already enforce in the repository they will be left
untouched.

Alternatively, to enable each control individually use: sourcetool setup controls.

`,
Use: "repo owner/repo",
SilenceUsage: false,
Expand Down Expand Up @@ -151,7 +157,7 @@ sourcetool is about to perform the following actions on your behalf:
- %s.

`,
srctool.ControlConfigurationDescr(opts.GetBranch(), models.CONFIG_POLICY),
srctool.ControlConfigurationDescr(opts.GetBranch(), models.CONFIG_TAG_RULES),
srctool.ControlConfigurationDescr(opts.GetBranch(), models.CONFIG_GEN_PROVENANCE),
srctool.ControlConfigurationDescr(opts.GetBranch(), models.CONFIG_BRANCH_RULES),
)
Expand Down Expand Up @@ -229,8 +235,12 @@ as an identity source.
The values for --config are as follows:

%s
Configures push and delete protection in the repository, required to reach slsa
source level 2+.
Configures push and delete branch protection in the repository, required to reach
SLSA source level 2+.

%s
Configures udpate, push and delete protection for all tags in the repository,
this is required to reach SLSA source level 2+.

%s
Opens a pull request in the repository to add the provenance generation workflow
Expand All @@ -247,7 +257,8 @@ repositories. Make sure you have a fork of the SLSA source policy repo and
a fork of the repository you want to protect.

`, w("sourcetool setup controls"), w2("configure a repository for SLSA source"),
w2(models.CONFIG_BRANCH_RULES), w2(models.CONFIG_GEN_PROVENANCE), w2(models.CONFIG_POLICY)),
w2(models.CONFIG_BRANCH_RULES), w2(models.CONFIG_TAG_RULES),
w2(models.CONFIG_GEN_PROVENANCE), w2(models.CONFIG_POLICY)),
Use: "controls owner/repo --config=CONTROL1 --config=CONTROL2",
SilenceUsage: false,
SilenceErrors: true,
Expand Down Expand Up @@ -322,6 +333,11 @@ a fork of the repository you want to protect.
opts.GetBranch().Repository, []*models.Branch{opts.GetBranch()}, cs,
)
if err != nil {
// if strings.Contains(err.Error(), models.ErrProtectionAlreadyInPlace.Error()) {
if errors.Is(err, models.ErrProtectionAlreadyInPlace) {
fmt.Printf("\n ℹ️ Controls already enabled on %s\n\n", opts.GetRepository().Path)
return nil
}
return fmt.Errorf("configuring controls: %w", err)
}

Expand Down
15 changes: 12 additions & 3 deletions sourcetool/internal/cmd/status.go
Original file line number Diff line number Diff line change
Expand Up @@ -165,13 +165,22 @@ sourcetool status myorg/myrepo@mybranch

fmt.Println(w("Current SLSA Source level: " + toplevel))
fmt.Println("")

fmt.Println("Recommended actions:")

titled := false
for _, status := range controls.Controls {
if status.RecommendedAction == nil {
continue
}

// Suggest creating the policy but only on the higher levels
if status.Name == slsa.PolicyAvailable && toplevel == slsa.SlsaSourceLevel1 {
continue
}

if !titled {
fmt.Println(w2("✨ Recommended actions:"))
titled = true
}

fmt.Printf(" - %s\n", status.RecommendedAction.Message)
if status.RecommendedAction.Command != "" {
fmt.Printf(" > %s\n", status.RecommendedAction.Command)
Expand Down
50 changes: 47 additions & 3 deletions sourcetool/pkg/ghcontrol/checklevel.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (

"github.com/slsa-framework/slsa-source-poc/sourcetool/pkg/provenance"
"github.com/slsa-framework/slsa-source-poc/sourcetool/pkg/slsa"
"github.com/slsa-framework/slsa-source-poc/sourcetool/pkg/sourcetool/models"
)

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

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

// Create the SLSA ruleset
Expand All @@ -262,7 +262,51 @@ func (ghc *GitHubConnection) EnableBranchRules(ctx context.Context) error {
NonFastForward: &github.EmptyRuleParameters{},
},
}); err != nil {
return fmt.Errorf("creating reposirory ruleset: %w", err)
return fmt.Errorf("creating branch protection ruleset: %w", err)
}

return nil
}

// EnableTagRules adds a ruleset to the repo to enforce delete and push and update
// protection on all branches.
func (ghc *GitHubConnection) EnableTagRules(ctx context.Context) error {
allRules, _, err := ghc.Client().Repositories.GetAllRulesets(
ctx, ghc.Owner(), ghc.Repo(), true,
)
if err != nil {
return fmt.Errorf("fetching tag rules: %w", err)
}
ctl, err := ghc.computeTagHygieneControl(ctx, allRules)
if err != nil {
return fmt.Errorf("checking tag controls: %w", err)
}
if ctl != nil {
// Tag controls are in place, noop
return models.ErrProtectionAlreadyInPlace
}

// Create the SLSA ruleset
if _, _, err := ghc.Client().Repositories.CreateRuleset(ctx, ghc.Owner(), ghc.Repo(), github.RepositoryRuleset{
Name: "SLSA Tag Controls",
Target: github.Ptr(github.RulesetTargetTag),
Enforcement: EnforcementActive,
BypassActors: []*github.BypassActor{},
Conditions: &github.RepositoryRulesetConditions{
RefName: &github.RepositoryRulesetRefConditionParameters{
Exclude: []string{},
Include: []string{"~ALL"},
},
},
Rules: &github.RepositoryRulesetRules{
Deletion: &github.EmptyRuleParameters{},
NonFastForward: &github.EmptyRuleParameters{},
Update: &github.UpdateRuleParameters{
UpdateAllowsFetchAndMerge: false,
},
},
}); err != nil {
return fmt.Errorf("creating tag protection ruleset: %w", err)
}

return nil
Expand Down
13 changes: 13 additions & 0 deletions sourcetool/pkg/sourcetool/backends/vcs/github/github.go
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,11 @@ func (b *Backend) ControlConfigurationDescr(branch *models.Branch, config models
"Open a pull request on the SLSA policy repo to check-in %s SLSA source policy",
repo.Path,
)
case models.CONFIG_TAG_RULES:
return fmt.Sprintf(
"Enable push/update/delete protection for all tags in %s",
repo.Path,
)
default:
return ""
}
Expand Down Expand Up @@ -237,6 +242,14 @@ func (b *Backend) getRecommendedAction(r *models.Repository, _ *models.Branch, c
}
}
return nil
case slsa.TagHygiene:
if state == slsa.StateNotEnabled {
return &slsa.ControlRecommendedAction{
Message: "Enable tag push/update/delete protection",
Command: fmt.Sprintf("sourcetool setup controls --config=%s %s", models.CONFIG_TAG_RULES, r.Path),
}
}
return nil
default:
return nil
}
Expand Down
21 changes: 21 additions & 0 deletions sourcetool/pkg/sourcetool/backends/vcs/github/manage.go
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,23 @@ func (b *Backend) CreateRepoRuleset(r *models.Repository, branches []*models.Bra
return nil
}

func (b *Backend) CreateTagRuleset(r *models.Repository) error {
if r == nil {
return errors.New("unable to create tag ruleset, repository not defined")
}

ghc, err := b.getGitHubConnection(r, "")
if err != nil {
return err
}

if err := ghc.EnableTagRules(context.Background()); err != nil {
return fmt.Errorf("enabling tag protection rules: %w", err)
}

return nil
}

func (b *Backend) ConfigureControls(r *models.Repository, branches []*models.Branch, configs []models.ControlConfiguration) error {
for _, config := range configs {
switch config {
Expand All @@ -188,6 +205,10 @@ func (b *Backend) ConfigureControls(r *models.Repository, branches []*models.Bra
if _, err := b.CreateWorkflowPR(r, branches); err != nil {
return fmt.Errorf("opening SLSA source workflow pull request: %w", err)
}
case models.CONFIG_TAG_RULES:
if err := b.CreateTagRuleset(r); err != nil {
return fmt.Errorf("opening SLSA source workflow pull request: %w", err)
}
case models.CONFIG_POLICY:
// Noop, this is not handled by the VCS handler
default:
Expand Down
4 changes: 4 additions & 0 deletions sourcetool/pkg/sourcetool/models/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package models

import (
"context"
"errors"
"fmt"
"strings"
"time"
Expand All @@ -14,6 +15,8 @@ import (
"github.com/slsa-framework/slsa-source-poc/sourcetool/pkg/slsa"
)

var ErrProtectionAlreadyInPlace = errors.New("controls already in place in the repository")

// AttestationStorageReader abstracts an attestation storage system where
// sourcetool can read VSAs and provenance attestations.
// For now we only have retrieval functions but this may expand to
Expand Down Expand Up @@ -44,6 +47,7 @@ const (
CONFIG_POLICY ControlConfiguration = "CONFIG_POLICY"
CONFIG_GEN_PROVENANCE ControlConfiguration = "CONFIG_GEN_PROVENANCE"
CONFIG_BRANCH_RULES ControlConfiguration = "CONFIG_BRANCH_RULES"
CONFIG_TAG_RULES ControlConfiguration = "CONFIG_TAG_RULES"
)

type Commit struct {
Expand Down
2 changes: 1 addition & 1 deletion sourcetool/pkg/sourcetool/tool.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import (
)

var ControlConfigurations = []models.ControlConfiguration{
models.CONFIG_POLICY, models.CONFIG_GEN_PROVENANCE, models.CONFIG_BRANCH_RULES,
models.CONFIG_POLICY, models.CONFIG_GEN_PROVENANCE, models.CONFIG_BRANCH_RULES, models.CONFIG_TAG_RULES,
}

// New initializes a new source tool instance.
Expand Down
Loading