Skip to content

Commit e91fedc

Browse files
trumantjmeridth
andauthored
feat: add support for BR-07.01 and BR-07.02 (#127)
* feat: add support for BR-07.01 and BR-07.02 This change adds support for the newly proposed controls for secrets management within the project. BR-07.01 is fully implemented and BR-07.02 is stubbed out This change relates to the work in ossf/security-baseline#373 Signed-off-by: Travis Truman <trumant@gmail.com> * Update evaluation_plans/osps/build_release/evaluations.go Co-authored-by: Jason Meridth <35014+jmeridth@users.noreply.github.com> * Update data/security-posture.go Co-authored-by: Jason Meridth <35014+jmeridth@users.noreply.github.com> --------- Signed-off-by: Travis Truman <trumant@gmail.com> Co-authored-by: Jason Meridth <35014+jmeridth@users.noreply.github.com>
1 parent 25f115e commit e91fedc

File tree

11 files changed

+229
-19
lines changed

11 files changed

+229
-19
lines changed

data/payload.go

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import (
44
"context"
55
"fmt"
66

7-
"github.com/google/go-github/v71/github"
7+
"github.com/google/go-github/v74/github"
88
"github.com/privateerproj/privateer-sdk/config"
99
"github.com/shurcooL/githubv4"
1010
"golang.org/x/oauth2"
@@ -18,8 +18,8 @@ type Payload struct {
1818
RepositoryMetadata RepositoryMetadata
1919
DependencyManifestsCount int
2020
IsCodeRepo bool
21-
22-
client *githubv4.Client
21+
SecurityPosture SecurityPosture
22+
client *githubv4.Client
2323
}
2424

2525
func Loader(config *config.Config) (payload any, err error) {
@@ -32,7 +32,7 @@ func Loader(config *config.Config) (payload any, err error) {
3232
&oauth2.Token{AccessToken: config.GetString("token")},
3333
)))
3434

35-
repositoryMetadata, err := loadRepositoryMetadata(ghClient, config.GetString("owner"), config.GetString("repo"))
35+
repo, repositoryMetadata, err := loadRepositoryMetadata(ghClient, config.GetString("owner"), config.GetString("repo"))
3636
if err != nil {
3737
return nil, err
3838
}
@@ -52,6 +52,11 @@ func Loader(config *config.Config) (payload any, err error) {
5252
return nil, err
5353
}
5454

55+
securityPosture, err := buildSecurityPosture(repo, *rest)
56+
if err != nil {
57+
return nil, err
58+
}
59+
5560
return any(Payload{
5661
GraphqlRepoData: graphql,
5762
RestData: rest,
@@ -60,6 +65,7 @@ func Loader(config *config.Config) (payload any, err error) {
6065
DependencyManifestsCount: dependencyManifestsCount,
6166
IsCodeRepo: isCodeRepo,
6267
client: client,
68+
SecurityPosture: securityPosture,
6369
}), nil
6470
}
6571

data/repository_metadata.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ package data
33
import (
44
"context"
55

6-
"github.com/google/go-github/v71/github"
6+
"github.com/google/go-github/v74/github"
77
)
88

99
type RepositoryMetadata interface {
@@ -42,18 +42,18 @@ func (r *GitHubRepositoryMetadata) IsMFARequiredForAdministrativeActions() *bool
4242
return r.ghOrg.TwoFactorRequirementEnabled
4343
}
4444

45-
func loadRepositoryMetadata(ghClient *github.Client, owner, repo string) (data RepositoryMetadata, err error) {
45+
func loadRepositoryMetadata(ghClient *github.Client, owner, repo string) (ghRepo *github.Repository, data RepositoryMetadata, err error) {
4646
repository, _, err := ghClient.Repositories.Get(context.Background(), owner, repo)
4747
if err != nil {
48-
return &GitHubRepositoryMetadata{}, err
48+
return repository, &GitHubRepositoryMetadata{}, err
4949
}
5050
organization, _, err := ghClient.Organizations.Get(context.Background(), owner)
5151
if err != nil {
52-
return &GitHubRepositoryMetadata{
52+
return repository, &GitHubRepositoryMetadata{
5353
ghRepo: repository,
5454
}, nil
5555
}
56-
return &GitHubRepositoryMetadata{
56+
return repository, &GitHubRepositoryMetadata{
5757
ghRepo: repository,
5858
ghOrg: organization,
5959
}, nil

data/repository_metadata_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ package data
33
import (
44
"testing"
55

6-
"github.com/google/go-github/v71/github"
6+
"github.com/google/go-github/v74/github"
77
"github.com/migueleliasweb/go-github-mock/src/mock"
88
"github.com/stretchr/testify/assert"
99
)
@@ -81,7 +81,7 @@ func TestLoadRepositoryMetadata(t *testing.T) {
8181
testCase.responses...,
8282
)
8383
ghClient := github.NewClient(mockClient)
84-
repoMetadata, err := loadRepositoryMetadata(ghClient, testCase.owner, testCase.repo)
84+
_, repoMetadata, err := loadRepositoryMetadata(ghClient, testCase.owner, testCase.repo)
8585
if testCase.expectedRepoError {
8686
assert.Error(t, err)
8787
} else {

data/rest-data.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import (
1010

1111
"github.com/gomarkdown/markdown"
1212
"github.com/gomarkdown/markdown/ast"
13-
"github.com/google/go-github/v71/github"
13+
"github.com/google/go-github/v74/github"
1414
"github.com/ossf/si-tooling/v2/si"
1515
"github.com/privateerproj/privateer-sdk/config"
1616
)
@@ -347,9 +347,9 @@ func (r *RestData) GetRulesets(branchName string) []Ruleset {
347347
// to distinguish between programming, markup, data, and prose content types for more nuanced
348348
// repository classification.
349349
func (r *RestData) IsCodeRepo() (bool, error) {
350-
languages, _, err := r.ghClient.Repositories.ListLanguages(context.Background(), r.owner, r.repo)
351-
if err != nil {
352-
return false, err
353-
}
354-
return len(languages) > 0, nil
350+
languages, _, err := r.ghClient.Repositories.ListLanguages(context.Background(), r.owner, r.repo)
351+
if err != nil {
352+
return false, err
353+
}
354+
return len(languages) > 0, nil
355355
}

data/rest-data_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import (
44
"net/http"
55
"testing"
66

7-
"github.com/google/go-github/v71/github"
7+
"github.com/google/go-github/v74/github"
88
"github.com/migueleliasweb/go-github-mock/src/mock"
99
"github.com/stretchr/testify/assert"
1010
)

data/security-posture.go

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package data
2+
3+
import (
4+
"github.com/google/go-github/v74/github"
5+
"github.com/ossf/si-tooling/v2/si"
6+
)
7+
8+
// SecurityPosture defines an interface for accessing security-related metadata about a repository.
9+
type SecurityPosture interface {
10+
PreventsPushingSecrets() bool
11+
ScansForSecrets() bool
12+
DefinesPolicyForHandlingSecrets() bool
13+
}
14+
15+
type RepoSecurityPosture struct {
16+
restData RestData
17+
preventsSecretPushing bool
18+
scansForSecrets bool
19+
definesPolicyForHandlingSecrets bool
20+
}
21+
22+
func buildSecurityPosture(repository *github.Repository, rd RestData) (SecurityPosture, error) {
23+
securityConfig := repository.GetSecurityAndAnalysis()
24+
if securityConfig == nil {
25+
return &RepoSecurityPosture{
26+
restData: rd,
27+
}, nil
28+
}
29+
secretsScanningStatus := securityConfig.GetSecretScanning().GetStatus()
30+
insightsClaimsSecretsTooling := insightsClaimsSecretsTooling(rd.Insights)
31+
return &RepoSecurityPosture{
32+
restData: rd,
33+
preventsSecretPushing: secretsScanningStatus == "enabled" || insightsClaimsSecretsTooling,
34+
scansForSecrets: secretsScanningStatus == "enabled" || insightsClaimsSecretsTooling,
35+
// TODO: consider if SecurityInsights should have a policy doc field in ProjectDocumentation to handle this
36+
// definesPolicyForHandlingSecrets: rd.SecurityInsights != nil && ....
37+
}, nil
38+
}
39+
40+
func insightsClaimsSecretsTooling(insights si.SecurityInsights) bool {
41+
if insights.Repository.Security.Tools == nil {
42+
return false
43+
}
44+
for _, tool := range insights.Repository.Security.Tools {
45+
if tool.Type == "secret-scanning" {
46+
return true
47+
}
48+
}
49+
return false
50+
}
51+
52+
func (rsp *RepoSecurityPosture) PreventsPushingSecrets() bool {
53+
return rsp.preventsSecretPushing
54+
}
55+
56+
func (rsp *RepoSecurityPosture) ScansForSecrets() bool {
57+
return rsp.scansForSecrets
58+
}
59+
60+
func (rsp *RepoSecurityPosture) DefinesPolicyForHandlingSecrets() bool {
61+
return rsp.definesPolicyForHandlingSecrets
62+
}

data/security-posture_test.go

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
package data
2+
3+
import (
4+
"testing"
5+
6+
"github.com/google/go-github/v74/github"
7+
"github.com/ossf/si-tooling/v2/si"
8+
"github.com/stretchr/testify/assert"
9+
)
10+
11+
func TestRepoSecurityPostureMethods(t *testing.T) {
12+
rsp := &RepoSecurityPosture{
13+
preventsSecretPushing: true,
14+
scansForSecrets: true,
15+
definesPolicyForHandlingSecrets: false,
16+
}
17+
18+
assert.True(t, rsp.PreventsPushingSecrets())
19+
assert.True(t, rsp.ScansForSecrets())
20+
assert.False(t, rsp.DefinesPolicyForHandlingSecrets())
21+
}
22+
23+
func TestBuildSecurityPosture_NoSecurityConfig(t *testing.T) {
24+
repo := &github.Repository{}
25+
rd := RestData{}
26+
sp, err := buildSecurityPosture(repo, rd)
27+
assert.NoError(t, err)
28+
assert.NotNil(t, sp)
29+
assert.False(t, sp.PreventsPushingSecrets())
30+
assert.False(t, sp.ScansForSecrets())
31+
assert.False(t, sp.DefinesPolicyForHandlingSecrets())
32+
}
33+
34+
func TestBuildSecurityPosture_SecretScanningEnabled(t *testing.T) {
35+
repo := &github.Repository{
36+
SecurityAndAnalysis: &github.SecurityAndAnalysis{
37+
SecretScanning: &github.SecretScanning{
38+
Status: github.Ptr("enabled"),
39+
},
40+
},
41+
}
42+
rd := RestData{}
43+
sp, err := buildSecurityPosture(repo, rd)
44+
assert.NoError(t, err)
45+
assert.True(t, sp.PreventsPushingSecrets())
46+
assert.True(t, sp.ScansForSecrets())
47+
}
48+
49+
func TestBuildSecurityPosture_SecretScanningDisabledButInsightsTooling(t *testing.T) {
50+
repo := &github.Repository{
51+
SecurityAndAnalysis: &github.SecurityAndAnalysis{
52+
SecretScanning: &github.SecretScanning{
53+
Status: github.Ptr("disabled"),
54+
},
55+
},
56+
}
57+
rd := RestData{
58+
Insights: si.SecurityInsights{
59+
Repository: si.Repository{
60+
Security: si.SecurityInfo{
61+
Tools: []si.Tool{
62+
{Type: "secret-scanning"},
63+
},
64+
},
65+
},
66+
},
67+
}
68+
sp, err := buildSecurityPosture(repo, rd)
69+
assert.NoError(t, err)
70+
assert.True(t, sp.PreventsPushingSecrets())
71+
assert.True(t, sp.ScansForSecrets())
72+
}
73+
74+
func TestInsightsClaimsSecretsTooling(t *testing.T) {
75+
insights := si.SecurityInsights{
76+
Repository: si.Repository{
77+
Security: si.SecurityInfo{
78+
Tools: []si.Tool{
79+
{Type: "secret-scanning"},
80+
{Type: "other-tool"},
81+
},
82+
},
83+
},
84+
}
85+
assert.True(t, insightsClaimsSecretsTooling(insights))
86+
87+
insights.Repository.Security.Tools = []si.Tool{
88+
{Type: "other-tool"},
89+
}
90+
assert.False(t, insightsClaimsSecretsTooling(insights))
91+
92+
insights.Repository.Security.Tools = nil
93+
assert.False(t, insightsClaimsSecretsTooling(insights))
94+
}

evaluation_plans/osps/build_release/evaluations.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,3 +174,33 @@ func OSPS_BR_06() (evaluation *layer4.ControlEvaluation) {
174174

175175
return
176176
}
177+
178+
func OSPS_BR_07() (evaluation *layer4.ControlEvaluation) {
179+
evaluation = &layer4.ControlEvaluation{
180+
ControlID: "OSPS-BR-07",
181+
}
182+
183+
evaluation.AddAssessment(
184+
"OSPS-BR-07.01",
185+
"The project MUST prevent the unintentional storage of unencrypted sensitive data, such as secrets and credentials, in the version control system.",
186+
[]string{
187+
"Maturity Level 1",
188+
},
189+
[]layer4.AssessmentStep{
190+
secretScanningInUse,
191+
},
192+
)
193+
194+
evaluation.AddAssessment(
195+
"OSPS-BR-07.02",
196+
"The project MUST define a policy for managing secrets and credentials used by the project. The policy should include guidelines for storing, accessing, and rotating secrets and credentials.",
197+
[]string{
198+
"Maturity Level 3",
199+
},
200+
[]layer4.AssessmentStep{
201+
reusable_steps.NotImplemented,
202+
},
203+
)
204+
205+
return
206+
}

evaluation_plans/osps/build_release/steps.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,3 +307,18 @@ func distributionPointsUseHTTPS(payloadData any, _ map[string]*layer4.Change) (r
307307
}
308308
return layer4.Passed, "All distribution points use HTTPS"
309309
}
310+
311+
func secretScanningInUse(payloadData any, _ map[string]*layer4.Change) (result layer4.Result, message string) {
312+
data, message := reusable_steps.VerifyPayload(payloadData)
313+
if message != "" {
314+
return layer4.Unknown, message
315+
}
316+
317+
if data.SecurityPosture.PreventsPushingSecrets() && data.SecurityPosture.ScansForSecrets() {
318+
return layer4.Passed, "Secret scanning is enabled and prevents pushing secrets"
319+
} else if data.SecurityPosture.PreventsPushingSecrets() || data.SecurityPosture.ScansForSecrets() {
320+
return layer4.Failed, "Secret scanning is only partially enabled"
321+
} else {
322+
return layer4.Failed, "Secret scanning is not enabled"
323+
}
324+
}

go.mod

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ go 1.24.4
44

55
require (
66
github.com/goccy/go-yaml v1.18.0
7-
github.com/google/go-github/v71 v71.0.0
7+
github.com/google/go-github/v74 v74.0.0
88
github.com/migueleliasweb/go-github-mock v1.4.0
99
github.com/ossf/gemara v0.7.1
1010
github.com/ossf/si-tooling/v2 v2.0.5-0.20250508212737-7ddcc8c43db9
@@ -18,6 +18,7 @@ require (
1818
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
1919
github.com/defenseunicorns/go-oscal v0.6.3 // indirect
2020
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
21+
github.com/google/go-github/v71 v71.0.0 // indirect
2122
github.com/google/go-github/v73 v73.0.0 // indirect
2223
github.com/google/go-querystring v1.1.0 // indirect
2324
github.com/google/uuid v1.6.0 // indirect

0 commit comments

Comments
 (0)