Skip to content

Commit 3386996

Browse files
authored
Initial iteration of onboarding automation (sourcetool setup) (#214)
* Go mod tidy Signed-off-by: Adolfo García Veytia (Puerco) <[email protected]> * Implement repository URL on new policies Signed-off-by: Adolfo García Veytia (Puerco) <[email protected]> * Add Pull request automation to sourcetool pkg Signed-off-by: Adolfo García Veytia (Puerco) <[email protected]> * Add setup subcommand family Signed-off-by: Adolfo García Veytia (Puerco) <[email protected]> * Implement add branch rules Signed-off-by: Adolfo García Veytia (Puerco) <[email protected]> * Split fork verification from PR creation Signed-off-by: Adolfo García Veytia (Puerco) <[email protected]> * Pre-check user forks before opening PRs Signed-off-by: Adolfo García Veytia (Puerco) <[email protected]> * Add interactive confirmation of setup changes Signed-off-by: Adolfo Garcia Veytia (puerco) <[email protected]> * Address nits from code review Signed-off-by: Adolfo Garcia Veytia (puerco) <[email protected]> --------- Signed-off-by: Adolfo García Veytia (Puerco) <[email protected]> Signed-off-by: Adolfo Garcia Veytia (puerco) <[email protected]>
1 parent de66272 commit 3386996

File tree

10 files changed

+921
-7
lines changed

10 files changed

+921
-7
lines changed

sourcetool/cmd/root.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,4 +65,5 @@ func init() {
6565

6666
addVerifyCommit(rootCmd)
6767
addStatus(rootCmd)
68+
addSetup(rootCmd)
6869
}

sourcetool/cmd/setup.go

Lines changed: 321 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,321 @@
1+
package cmd
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
"slices"
7+
8+
"github.com/spf13/cobra"
9+
"sigs.k8s.io/release-utils/util"
10+
11+
"github.com/slsa-framework/slsa-source-poc/sourcetool/pkg/policy"
12+
"github.com/slsa-framework/slsa-source-poc/sourcetool/pkg/sourcetool"
13+
)
14+
15+
type setupOpts struct {
16+
branchOptions
17+
policyRepo string
18+
userForkOrg string
19+
enforce bool
20+
interactive bool
21+
}
22+
23+
func (so *setupOpts) AddFlags(cmd *cobra.Command) {
24+
so.branchOptions.AddFlags(cmd)
25+
26+
cmd.PersistentFlags().BoolVar(
27+
&so.enforce, "enforce", false, "create enforcement rules",
28+
)
29+
cmd.PersistentFlags().StringVar(
30+
&so.userForkOrg, "user-fork", "", "GitHub organization to look for forks of repos (for pull requests)",
31+
)
32+
cmd.PersistentFlags().StringVar(
33+
&so.policyRepo, "policy-repo", fmt.Sprintf("%s/%s", policy.SourcePolicyRepoOwner, policy.SourcePolicyRepo), "repository to store the SLSA source policy",
34+
)
35+
36+
cmd.PersistentFlags().BoolVar(
37+
&so.interactive, "interactive", true, "confirm before performing changes",
38+
)
39+
}
40+
41+
// Validate checks the options in context with arguments
42+
func (so *setupOpts) Validate() error {
43+
errs := []error{
44+
so.branchOptions.Validate(),
45+
}
46+
return errors.Join(errs...)
47+
}
48+
49+
func addSetup(parentCmd *cobra.Command) {
50+
setupCmd := &cobra.Command{
51+
Short: "configure SLSA source features in a repository",
52+
Long: fmt.Sprintf(`
53+
%s %s
54+
55+
The setup subcommands can be used to protect a repository with the
56+
SLSA Source tooling by automatically configuring the required security
57+
controls in a repository.
58+
59+
The setup family has two subcommands:
60+
61+
%s
62+
A "one shot" setup process enabling all the security controls required
63+
at once. This is ideal for new repositories or when you are sure the
64+
changes will not disrupt existing repositories.
65+
66+
%s
67+
Enables fine-grained control when configuring security features in a
68+
repository. The setup control subcommand can configure each security
69+
control individually.
70+
71+
`, w("sourcetool setup:"), w2("configure SLSA source controls on a repository"),
72+
w("sourcetool setup repo"), w("sourcetool setup controls")),
73+
Use: "setup",
74+
SilenceUsage: true,
75+
SilenceErrors: true,
76+
}
77+
78+
AddSetupRepo(setupCmd)
79+
AddSetupControls(setupCmd)
80+
parentCmd.AddCommand(setupCmd)
81+
}
82+
83+
func AddSetupRepo(parent *cobra.Command) {
84+
opts := &setupOpts{}
85+
setupRepoCmd := &cobra.Command{
86+
Short: "configure all the SLSA source features in a repository",
87+
Long: `The setup repo subcommand is a "one shot" setup process enabling all
88+
the security controls required to get a repository to a specific SLSA level.
89+
90+
This command is ideal for new repositories or when you are sure the implemented
91+
changes will not disrupt existing workflows.
92+
93+
To use this subcommand you need to export a GitHub token as an environment
94+
variable called GITHUB_TOKEN. The token needs admin permissions on the repo
95+
to configure the branch rules.
96+
97+
`,
98+
Use: "repo owner/repo",
99+
SilenceUsage: false,
100+
SilenceErrors: true,
101+
PreRunE: func(_ *cobra.Command, args []string) error {
102+
if len(args) > 0 {
103+
if err := opts.ParseLocator(args[0]); err != nil {
104+
return err
105+
}
106+
}
107+
108+
// Validate early the repository options to provide a more
109+
// useful message to the user
110+
if err := opts.repoOptions.Validate(); err != nil {
111+
return err
112+
}
113+
114+
if err := opts.EnsureDefaults(); err != nil {
115+
return err
116+
}
117+
118+
return nil
119+
},
120+
RunE: func(cmd *cobra.Command, args []string) (err error) {
121+
if err := opts.Validate(); err != nil {
122+
return err
123+
}
124+
125+
// At this point options are valid, no help needed.
126+
cmd.SilenceUsage = true
127+
128+
// Create a new sourcetool object
129+
srctool, err := sourcetool.New(
130+
sourcetool.WithOwner(opts.owner),
131+
sourcetool.WithRepo(opts.repository),
132+
sourcetool.WithBranch(opts.branch),
133+
)
134+
if err != nil {
135+
return err
136+
}
137+
138+
if opts.interactive {
139+
fmt.Printf(`
140+
sourcetool is about to perform the following actions on your behalf:
141+
142+
- %s.
143+
- %s.
144+
- %s.
145+
146+
`,
147+
srctool.ControlConfigurationDescr(sourcetool.CONFIG_POLICY),
148+
srctool.ControlConfigurationDescr(sourcetool.CONFIG_PROVENANCE_WORKFLOW),
149+
srctool.ControlConfigurationDescr(sourcetool.CONFIG_BRANCH_RULES),
150+
)
151+
152+
_, s, err := util.Ask("Type 'yes' if you want to continue?", "yes|no|no", 3)
153+
if err != nil {
154+
return err
155+
}
156+
157+
if !s {
158+
fmt.Println("Cancelled.")
159+
return nil
160+
}
161+
}
162+
163+
err = srctool.OnboardRepository(
164+
sourcetool.WithEnforce(opts.enforce),
165+
sourcetool.WithUserForkOrg(opts.userForkOrg),
166+
sourcetool.WithPolicyRepo(opts.policyRepo),
167+
)
168+
if err != nil {
169+
return fmt.Errorf("onboarding repo: %w", err)
170+
}
171+
172+
return nil
173+
},
174+
}
175+
opts.AddFlags(setupRepoCmd)
176+
parent.AddCommand(setupRepoCmd)
177+
}
178+
179+
type setupCtlOpts struct {
180+
setupOpts
181+
configs []string
182+
}
183+
184+
func (so *setupCtlOpts) AddFlags(cmd *cobra.Command) {
185+
so.setupOpts.AddFlags(cmd)
186+
187+
cmd.PersistentFlags().StringSliceVar(
188+
&so.configs, "config", []string{}, fmt.Sprintf("control to configure %+v", sourcetool.ControlConfigurations),
189+
)
190+
}
191+
192+
// Validate checks the options in context with arguments
193+
func (so *setupCtlOpts) Validate() error {
194+
errs := []error{
195+
so.setupOpts.Validate(),
196+
}
197+
for _, c := range so.configs {
198+
if !slices.Contains(sourcetool.ControlConfigurations, sourcetool.ControlConfiguration(c)) {
199+
errs = append(errs, fmt.Errorf("unknown configuration: %q", c))
200+
}
201+
}
202+
return errors.Join(errs...)
203+
}
204+
205+
func AddSetupControls(parent *cobra.Command) {
206+
opts := &setupCtlOpts{}
207+
setupControlsCmd := &cobra.Command{
208+
Short: "configure specific SLSA controls in a repository",
209+
Long: fmt.Sprintf(`
210+
%s %s
211+
212+
The setup controls subcommand configures the specified SLSA security
213+
controls in a repository. As opposed to "setup repo", this subcommand lets you
214+
configure each security control individually.
215+
216+
To use this subcommand you need to export a GitHub token as an environment
217+
variable called GITHUB_TOKEN. To configure the branch rules, the token needs
218+
admin permissions on the repo. For the other configuration it is only required
219+
as an identity source.
220+
221+
The values for --config are as follows:
222+
223+
%s
224+
Configures push and delete protection in the repository, required to reach slsa
225+
source level 2+.
226+
227+
%s
228+
Opens a pull request in the repository to add the provenance generation workflow
229+
after every push.
230+
231+
%s
232+
Opens a pull request on the SLSA policy repository to check in a SLSA Source
233+
policy for the repository.
234+
235+
Setting up repository forks
236+
237+
The controls that open pull requests require that you have a fork of the
238+
repositories. Make sure you have a fork of the SLSA source policy repo and
239+
a fork of the repository you want to protect.
240+
241+
`, w("sourcetool setup controls"), w2("configure a repository for SLSA source"),
242+
w2("CONFIG_BRANCH_RULES"), w2("CONFIG_PROVENANCE_WORKFLOW"), w2("CONFIG_POLICY")),
243+
Use: "controls owner/repo --config=CONTROL1 --config=CONTROL2",
244+
SilenceUsage: false,
245+
SilenceErrors: true,
246+
PreRunE: func(_ *cobra.Command, args []string) error {
247+
if len(args) > 0 {
248+
if err := opts.ParseLocator(args[0]); err != nil {
249+
return err
250+
}
251+
}
252+
253+
// Validate early the repository options to provide a more
254+
// useful message to the user
255+
if err := opts.repoOptions.Validate(); err != nil {
256+
return err
257+
}
258+
259+
if err := opts.EnsureDefaults(); err != nil {
260+
return err
261+
}
262+
263+
return nil
264+
},
265+
RunE: func(cmd *cobra.Command, args []string) (err error) {
266+
if err := opts.Validate(); err != nil {
267+
return err
268+
}
269+
270+
// At this point options are valid, no help needed.
271+
cmd.SilenceUsage = true
272+
273+
// Create a new sourcetool object
274+
srctool, err := sourcetool.New(
275+
sourcetool.WithOwner(opts.owner),
276+
sourcetool.WithRepo(opts.repository),
277+
sourcetool.WithBranch(opts.branch),
278+
sourcetool.WithPolicyRepo(opts.policyRepo),
279+
sourcetool.WithUserForkOrg(opts.userForkOrg),
280+
)
281+
if err != nil {
282+
return err
283+
}
284+
cs := []sourcetool.ControlConfiguration{}
285+
if opts.interactive {
286+
fmt.Println("\nsourcetool is about to perform the following actions on your behalf:")
287+
fmt.Println("")
288+
289+
for _, c := range opts.configs {
290+
cs = append(cs, sourcetool.ControlConfiguration(c))
291+
fmt.Printf(" - %s.\n", srctool.ControlConfigurationDescr(sourcetool.ControlConfiguration(c)))
292+
}
293+
fmt.Println("")
294+
295+
_, s, err := util.Ask("Type 'yes' if you want to continue?", "yes|no|no", 3)
296+
if err != nil {
297+
return err
298+
}
299+
300+
if !s {
301+
fmt.Println("Cancelled.")
302+
return nil
303+
}
304+
} else {
305+
for _, c := range opts.configs {
306+
cs = append(cs, sourcetool.ControlConfiguration(c))
307+
}
308+
}
309+
err = srctool.ConfigureControls(
310+
cs, sourcetool.WithEnforce(opts.enforce),
311+
)
312+
if err != nil {
313+
return fmt.Errorf("configuring controls: %w", err)
314+
}
315+
316+
return nil
317+
},
318+
}
319+
opts.AddFlags(setupControlsCmd)
320+
parent.AddCommand(setupControlsCmd)
321+
}

sourcetool/cmd/status.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,10 @@ import (
1919
"github.com/slsa-framework/slsa-source-poc/sourcetool/pkg/sourcetool"
2020
)
2121

22-
var w = color.New(color.FgHiWhite, color.BgBlack).SprintFunc()
22+
var (
23+
w = color.New(color.FgHiWhite, color.BgBlack).SprintFunc()
24+
w2 = color.New(color.Faint, color.FgWhite, color.BgBlack).SprintFunc()
25+
)
2326

2427
// statusOptions
2528
type statusOptions struct {

sourcetool/go.mod

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,20 @@ go 1.24.3
44

55
require (
66
github.com/carabiner-dev/bnd v0.2.0
7+
github.com/carabiner-dev/github v0.2.2
78
github.com/carabiner-dev/vcslocator v0.3.1
89
github.com/fatih/color v1.18.0
910
github.com/go-git/go-git/v5 v5.16.2
1011
github.com/google/go-github/v69 v69.2.0
1112
github.com/in-toto/attestation v1.1.2
1213
github.com/migueleliasweb/go-github-mock v1.3.0
1314
github.com/sigstore/sigstore-go v1.0.0
15+
github.com/sirupsen/logrus v1.9.3
1416
github.com/spf13/cobra v1.9.1
1517
github.com/stretchr/testify v1.10.0
1618
google.golang.org/protobuf v1.36.6
19+
sigs.k8s.io/release-sdk v0.12.2
20+
sigs.k8s.io/release-utils v0.11.1
1721
)
1822

1923
require (
@@ -22,6 +26,7 @@ require (
2226
github.com/ProtonMail/go-crypto v1.1.6 // indirect
2327
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
2428
github.com/blang/semver v3.5.1+incompatible // indirect
29+
github.com/blang/semver/v4 v4.0.0 // indirect
2530
github.com/cenkalti/backoff/v5 v5.0.2 // indirect
2631
github.com/cloudflare/circl v1.6.1 // indirect
2732
github.com/common-nighthawk/go-figure v0.0.0-20210622060536-734e95fb86be // indirect
@@ -53,13 +58,13 @@ require (
5358
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
5459
github.com/google/certificate-transparency-go v1.3.1 // indirect
5560
github.com/google/go-containerregistry v0.20.3 // indirect
61+
github.com/google/go-github/v60 v60.0.0 // indirect
5662
github.com/google/go-github/v71 v71.0.0 // indirect
5763
github.com/google/go-querystring v1.1.0 // indirect
5864
github.com/google/uuid v1.6.0 // indirect
5965
github.com/gorilla/mux v1.8.1 // indirect
6066
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
6167
github.com/hashicorp/go-retryablehttp v0.7.7 // indirect
62-
github.com/hashicorp/hcl v1.0.1-vault-5 // indirect
6368
github.com/in-toto/in-toto-golang v0.9.0 // indirect
6469
github.com/inconshreveable/mousetrap v1.1.0 // indirect
6570
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
@@ -89,7 +94,6 @@ require (
8994
github.com/sigstore/rekor v1.3.10 // indirect
9095
github.com/sigstore/sigstore v1.9.4 // indirect
9196
github.com/sigstore/timestamp-authority v1.2.7 // indirect
92-
github.com/sirupsen/logrus v1.9.3 // indirect
9397
github.com/skeema/knownhosts v1.3.1 // indirect
9498
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 // indirect
9599
github.com/sourcegraph/conc v0.3.0 // indirect
@@ -124,5 +128,5 @@ require (
124128
gopkg.in/warnings.v0 v0.1.2 // indirect
125129
gopkg.in/yaml.v3 v3.0.1 // indirect
126130
k8s.io/klog/v2 v2.130.1 // indirect
127-
sigs.k8s.io/release-utils v0.11.1 // indirect
131+
k8s.io/utils v0.0.0-20241210054802-24370beab758 // indirect
128132
)

0 commit comments

Comments
 (0)