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
222 changes: 222 additions & 0 deletions sourcetool/internal/cmd/policy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
package cmd

import (
"context"
"encoding/json"
"fmt"
"os"

"github.com/spf13/cobra"

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

type policyViewOpts struct {
repoOptions
}

type policyCreateOpts struct {
branchOptions
openPullRequest bool
}

func (pco *policyCreateOpts) AddFlags(cmd *cobra.Command) {
pco.branchOptions.AddFlags(cmd)
cmd.PersistentFlags().BoolVar(&pco.openPullRequest, "pr", true, "Open a pull request to check-in the policy")
}

func addPolicy(parentCmd *cobra.Command) {
policyCmd := &cobra.Command{
Short: "tools to work with source policies",
Long: fmt.Sprintf(`
%s %s

The policy subcommands can be used to view, create and update the source
policy for a repository. The policy family has two subcommands:

%s
Shows current repository policy for a repository checjed into the SLSA community
policy repo.

%s
Creates a new policy for a repository and, optionally, check it into the
SLSA community repository.

`, w("sourcetool policy:"), w2("configure SLSA source policies for a repo"),
w("sourcetool policy view"), w("sourcetool policy create")),
Use: "policy",
SilenceUsage: true,
SilenceErrors: true,
}

addPolicyView(policyCmd)
addPolicyCreate(policyCmd)
parentCmd.AddCommand(policyCmd)
}

func addPolicyView(parent *cobra.Command) {
opts := &policyViewOpts{}
policyViewCmd := &cobra.Command{
Short: "view the policy of a repository",
Long: `The view subcommand retrieves the policy stored in the SLSA community
repository for a repository and displays it.
`,
Use: "view owner/repo",
SilenceUsage: false,
SilenceErrors: true,
PreRunE: func(_ *cobra.Command, args []string) error {
if len(args) > 0 {
if err := opts.ParseSlug(args[0]); err != nil {
return err
}
}

// Validate early the repository options to provide a more
// useful message to the user
if err := opts.Validate(); err != nil {
return err
}

return nil
},
RunE: func(cmd *cobra.Command, args []string) (err error) {
if err := opts.Validate(); err != nil {
return err
}

// At this point options are valid, no help needed.
cmd.SilenceUsage = true

authenticator, err := CheckAuth()
if err != nil {
return err
}

// Create a new sourcetool object
srctool, err := sourcetool.New(
sourcetool.WithAuthenticator(authenticator),
// sourcetool.WithPolicyRepo(opts.policyRepo),
)
if err != nil {
return err
}

pcy, err := srctool.GetRepositoryPolicy(context.Background(), opts.GetRepository())
if err != nil {
return err
}

if err := displayPolicy(opts.repoOptions, pcy); err != nil {
return err
}

return nil
},
}
opts.AddFlags(policyViewCmd)
parent.AddCommand(policyViewCmd)
}

// addPolicyCreate adds the create subcreate
func addPolicyCreate(parent *cobra.Command) {
opts := &policyCreateOpts{}
policyViewCmd := &cobra.Command{
Short: "creates a source policy for a repository",
Long: `The create subcommand inspects the controls in place for a repo
and creates a new policy for it.
`,
Use: "create owner/repo@branch",
SilenceUsage: false,
SilenceErrors: true,
PreRunE: func(_ *cobra.Command, args []string) error {
if len(args) > 0 {
if err := opts.ParseLocator(args[0]); err != nil {
return err
}
}

// Validate early the repository options to provide a more
// useful message to the user
if err := opts.repoOptions.Validate(); err != nil {
return err
}

return nil
},
RunE: func(cmd *cobra.Command, args []string) (err error) {
if err := opts.Validate(); err != nil {
return err
}

// At this point options are valid, no help needed.
cmd.SilenceUsage = true

authenticator, err := CheckAuth()
if err != nil {
return err
}

// Create a new sourcetool object
srctool, err := sourcetool.New(
sourcetool.WithAuthenticator(authenticator),
// sourcetool.WithPolicyRepo(opts.policyRepo),
)
if err != nil {
return err
}

epcy, err := srctool.GetRepositoryPolicy(context.Background(), opts.GetRepository())
if err != nil {
return fmt.Errorf("checking for existing policy: %w", err)
}
if epcy != nil {
return fmt.Errorf("repository already has a policy checked into the community repo")
}

// Create the policy, this will open the pull request in the community
// repo if the options say so.
pcy, pr, err := srctool.CreateRepositoryPolicy(
context.Background(), opts.GetRepository(), []*models.Branch{opts.GetBranch()},
)
if err != nil {
return err
}

if err := displayPolicy(opts.repoOptions, pcy); err != nil {
return err
}

if opts.openPullRequest && pr != nil {
fmt.Fprintf(os.Stderr, "\n")
fmt.Fprintf(os.Stderr, "Opened pull request: https://github.com/%s/pulls/%d\n\n", pr.Repo.Path, pr.Number)
}

return nil
},
}
opts.AddFlags(policyViewCmd)
parent.AddCommand(policyViewCmd)
}

func displayPolicy(opts repoOptions, pcy *policy.RepoPolicy) error {
if pcy == nil {
fmt.Println("\n" + w(fmt.Sprintf("✖️ No source policy found for %s/%s", opts.owner, opts.repository)))
fmt.Println("To create and check-in a policy for the repository run:")
fmt.Println()
fmt.Printf(" sourcetool policy create %s/%s\n", opts.owner, opts.repository)
fmt.Println()
return nil
}

data, err := json.MarshalIndent(pcy, "", " ")
if err != nil {
return fmt.Errorf("marshaling policy data: %w", err)
}

fmt.Fprint(os.Stderr, w(fmt.Sprintf("\n🛡️ Source policy for %s/%s:\n\n", opts.owner, opts.repository)))
fmt.Println(string(data))
fmt.Println()
return nil
}
1 change: 1 addition & 0 deletions sourcetool/internal/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ controls and much more.
addCheckTag(rootCmd)
addCreatePolicy(rootCmd)
addAuth(rootCmd)
addPolicy(rootCmd)
return rootCmd
}

Expand Down
7 changes: 7 additions & 0 deletions sourcetool/pkg/sourcetool/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,13 @@ func WithEnforce(enforce bool) ConfigFn {
}
}

func WithCreatePolicyPR(yesno bool) ConfigFn {
return func(t *Tool) error {
t.Options.CreatePolicyPR = yesno
return nil
}
}

func WithUserForkOrg(org string) ConfigFn {
return func(t *Tool) error {
t.Options.UserForkOrg = org
Expand Down
7 changes: 5 additions & 2 deletions sourcetool/pkg/sourcetool/options/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,15 @@ type Options struct {
UseSSH bool
UpdateRepo bool

CreatePolicyPR bool

// PolicyRepo is the repository where the policies are stored
PolicyRepo string
}

// DefaultOptions holds the default options the tool initializes with
var Default = Options{
PolicyRepo: fmt.Sprintf("%s/%s", policy.SourcePolicyRepoOwner, policy.SourcePolicyRepo),
UseSSH: true,
PolicyRepo: fmt.Sprintf("%s/%s", policy.SourcePolicyRepoOwner, policy.SourcePolicyRepo),
UseSSH: true,
CreatePolicyPR: true,
}
32 changes: 31 additions & 1 deletion sourcetool/pkg/sourcetool/tool.go
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ func (t *Tool) CheckPolicyRepoFork(repo *models.Repository) (bool, error) {
// CreateBranchPolicy creates a repository policy
func (t *Tool) CreateBranchPolicy(ctx context.Context, r *models.Repository, branches []*models.Branch) (*policy.RepoPolicy, error) {
if len(branches) > 1 {
// Chanfe this once we support merging policies
// Change this once we support merging policies
return nil, fmt.Errorf("only one branch is supported at a time")
}
if branches == nil {
Expand Down Expand Up @@ -241,3 +241,33 @@ func (t *Tool) createPolicy(r *models.Repository, branch *models.Branch, control
}
return p, nil
}

// GetRepositoryPolicy retrieves the policy of repo from the community
func (t *Tool) GetRepositoryPolicy(ctx context.Context, r *models.Repository) (*policy.RepoPolicy, error) {
pe := policy.NewPolicyEvaluator()
p, _, err := pe.GetPolicy(ctx, r)
if err != nil {
return nil, fmt.Errorf("getting repository policy: %w", err)
}

return p, nil
}

// CreateRepositoryPolicy creates a policy for a repository
func (t *Tool) CreateRepositoryPolicy(ctx context.Context, r *models.Repository, branches []*models.Branch) (*policy.RepoPolicy, *models.PullRequest, error) {
pcy, err := t.CreateBranchPolicy(ctx, r, branches)
if err != nil {
return nil, nil, fmt.Errorf("creating policy for: %w", err)
}

var pr *models.PullRequest

// If the option is set, open the pull request
if t.Options.CreatePolicyPR {
pr, err = t.impl.CreatePolicyPR(t.Authenticator, &t.Options, r, pcy)
if err != nil {
return nil, nil, fmt.Errorf("opening the policy pull request: %w", err)
}
}
return pcy, pr, nil
}
Loading