Skip to content

Commit

Permalink
Link & SLSA attestor (#149)
Browse files Browse the repository at this point in the history
* Initial link attestor

Signed-off-by: John Kjell <john@testifysec.com>

* refactor: move gitoid code to cyrptoutil, use digestvalue everywhere (#139)

When the functionality to calculate gitoids was added, there was a bit
of tech debt incurred since they didn't implement hash.Hash. This
remedies this with an admitedly hacky implementation of hash.Hash that
wraps the gitoid code. This also standardizes our cryptoutil fucntions
around the DigestValue struct that was added around this time to
differentiate between gitoids and regular hash functions.

Signed-off-by: Mikhail Swift <mikhail@testifysec.com>
Signed-off-by: John Kjell <john@testifysec.com>

* chore: bump actions/upload-artifact from 4.2.0 to 4.3.0 (#142)

Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4.2.0 to 4.3.0.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](actions/upload-artifact@694cdab...26f96df)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Signed-off-by: John Kjell <john@testifysec.com>

* chore: bump github/codeql-action from 3.23.1 to 3.23.2 (#143)

Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.23.1 to 3.23.2.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](github/codeql-action@0b21cf2...b7bf0a3)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Tom Meadows <tom@tmlabs.co.uk>
Signed-off-by: John Kjell <john@testifysec.com>

* Adding job to auto cut releases (#141)

adding job to auto cut releases

Signed-off-by: chaosinthecrd <tom@tmlabs.co.uk>
Signed-off-by: John Kjell <john@testifysec.com>

* fixing error in github actions workflow (#147)

fixing error in workflow

Signed-off-by: chaosinthecrd <tom@tmlabs.co.uk>
Signed-off-by: John Kjell <john@testifysec.com>

* RunAttestors refactor (#131)

* improving run attestors

Signed-off-by: chaosinthecrd <tom@tmlabs.co.uk>

* finalising changes.

Signed-off-by: chaosinthecrd <tom@tmlabs.co.uk>

* improving run attestors

Signed-off-by: chaosinthecrd <tom@tmlabs.co.uk>

* finalising changes.

Signed-off-by: chaosinthecrd <tom@tmlabs.co.uk>

* addressing review, restoring run type order

Signed-off-by: chaosinthecrd <tom@tmlabs.co.uk>

* updating error handling logic

Signed-off-by: chaosinthecrd <tom@tmlabs.co.uk>

* updating to go 1.21 for errors.Join

Signed-off-by: chaosinthecrd <tom@tmlabs.co.uk>

---------

Signed-off-by: chaosinthecrd <tom@tmlabs.co.uk>
Signed-off-by: Tom Meadows <tom@tmlabs.co.uk>
Signed-off-by: John Kjell <john@testifysec.com>

* Adding workaround due to failing workflows (#145)

adding workaround due to failing workflows

Signed-off-by: chaosinthecrd <tom@tmlabs.co.uk>
Signed-off-by: John Kjell <john@testifysec.com>

* Checking policy signature against cert constraints (#144)

* adding logic so policy signature can be checked against constraints
* threaded options into policy validation functionary
---------

Signed-off-by: chaosinthecrd <tom@tmlabs.co.uk>
Signed-off-by: John Kjell <john@testifysec.com>
Co-authored-by: John Kjell <john@testifysec.com>
Signed-off-by: John Kjell <john@testifysec.com>

* [StepSecurity] ci: Harden GitHub Actions (#148)

Signed-off-by: StepSecurity Bot <bot@stepsecurity.io>
Signed-off-by: John Kjell <john@testifysec.com>

* Add import for init and export variables

Signed-off-by: John Kjell <john@testifysec.com>

* Add mulitple results to run to allow exporting attestors to indivudal files

Signed-off-by: John Kjell <john@testifysec.com>

* Add collection to result array

Signed-off-by: John Kjell <john@testifysec.com>

* Replace export parameters in run with attestor option

Signed-off-by: John Kjell <john@testifysec.com>

* Fix golang lint isues

Signed-off-by: John Kjell <john@testifysec.com>

* Update link attestor testing

Signed-off-by: John Kjell <john@testifysec.com>

* Add SLSA attestor

Signed-off-by: John Kjell <john@testifysec.com>

* Add interface for product attestor

Signed-off-by: John Kjell <john@testifysec.com>

* Add more attestor interfaces

Signed-off-by: John Kjell <john@testifysec.com>

* Address some review feedback, licenses, and golanglint

Signed-off-by: John Kjell <john@testifysec.com>

* More golangcilint errors

Signed-off-by: John Kjell <john@testifysec.com>

* WIP - Improve testing interfaces for exposing data fields

Signed-off-by: John Kjell <john@testifysec.com>

* added changes

* adding changes to merge into main PR

* Link attestor proposed changes  (#204)

* unmarshal the time in the attestation collection correctly (#203)
* add StepName to AttestorContext
* use CollectionAttestion to properly set start/end times
---------

Signed-off-by: John Kjell <john@testifysec.com>
Co-authored-by: Cole Kennedy <colek42@gmail.com>
Co-authored-by: Cole <cole@testifysec.com>
Co-authored-by: John Kjell <john@testifysec.com>

* Passing SLSA Attest tests for GitHub and GitLab

Signed-off-by: John Kjell <john@testifysec.com>

* Clean up

Signed-off-by: John Kjell <john@testifysec.com>

* Add attestation test for link attestor

Signed-off-by: John Kjell <john@testifysec.com>

* Add data function for git interface and remove unused code

Signed-off-by: John Kjell <john@testifysec.com>

* adding warning mesage for slsa attestor

Signed-off-by: chaosinthecrd <tom@tmlabs.co.uk>

* Try to gracefully handle gitlab jwt

Signed-off-by: John Kjell <john@testifysec.com>

* ran go mod tidy

Signed-off-by: chaosinthecrd <tom@tmlabs.co.uk>

* ensuring link and slsa attestation exporting is optional

Signed-off-by: chaosinthecrd <tom@tmlabs.co.uk>

---------

Signed-off-by: John Kjell <john@testifysec.com>
Signed-off-by: Mikhail Swift <mikhail@testifysec.com>
Signed-off-by: dependabot[bot] <support@github.com>
Signed-off-by: chaosinthecrd <tom@tmlabs.co.uk>
Signed-off-by: Tom Meadows <tom@tmlabs.co.uk>
Signed-off-by: StepSecurity Bot <bot@stepsecurity.io>
Co-authored-by: Mikhail Swift <mikhail@testifysec.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Tom Meadows <tom@tmlabs.co.uk>
Co-authored-by: StepSecurity Bot <bot@stepsecurity.io>
Co-authored-by: Cole Kennedy <colek42@gmail.com>
Co-authored-by: Cole <cole@testifysec.com>
  • Loading branch information
7 people authored May 9, 2024
1 parent 3e62eab commit 87975b4
Show file tree
Hide file tree
Showing 32 changed files with 1,667 additions and 43 deletions.
5 changes: 2 additions & 3 deletions attestation/aws-iid/aws-iid_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,11 +112,10 @@ func TestAttestor_Attest(t *testing.T) {
conf: conf,
}

ctx, err := attestation.NewContext([]attestation.Attestor{a})
ctx, err := attestation.NewContext("test", []attestation.Attestor{a})
require.NoError(t, err)
err = a.Attest(ctx)
require.NoError(t, err)

}

func TestAttestor_getIID(t *testing.T) {
Expand Down Expand Up @@ -154,7 +153,7 @@ func TestAttestor_Subjects(t *testing.T) {
conf: conf,
}

ctx, err := attestation.NewContext([]attestation.Attestor{a})
ctx, err := attestation.NewContext("test", []attestation.Attestor{a})
require.NoError(t, err)
err = a.Attest(ctx)
require.NoError(t, err)
Expand Down
14 changes: 14 additions & 0 deletions attestation/commandrun/commandrun.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,18 @@ const (
// doesn't implement the expected interfaces.
var (
_ attestation.Attestor = &CommandRun{}
_ CommandRunAttestor = &CommandRun{}
)

type CommandRunAttestor interface {
// Attestor
Name() string
Type() string
RunType() attestation.RunType
Attest(ctx *attestation.AttestationContext) error
Data() *CommandRun
}

func init() {
attestation.RegisterAttestation(Name, Type, RunType, func() attestation.Attestor {
return New()
Expand Down Expand Up @@ -129,6 +139,10 @@ func (rc *CommandRun) Attest(ctx *attestation.AttestationContext) error {
return nil
}

func (rc *CommandRun) Data() *CommandRun {
return rc
}

func (rc *CommandRun) Name() string {
return Name
}
Expand Down
8 changes: 7 additions & 1 deletion attestation/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,14 +97,15 @@ type AttestationContext struct {
completedAttestors []CompletedAttestor
products map[string]Product
materials map[string]cryptoutil.DigestSet
stepName string
}

type Product struct {
MimeType string `json:"mime_type"`
Digest cryptoutil.DigestSet `json:"digest"`
}

func NewContext(attestors []Attestor, opts ...AttestationContextOption) (*AttestationContext, error) {
func NewContext(stepName string, attestors []Attestor, opts ...AttestationContextOption) (*AttestationContext, error) {
wd, err := os.Getwd()
if err != nil {
return nil, err
Expand All @@ -117,6 +118,7 @@ func NewContext(attestors []Attestor, opts ...AttestationContextOption) (*Attest
hashes: []cryptoutil.DigestValue{{Hash: crypto.SHA256}, {Hash: crypto.SHA256, GitOID: true}, {Hash: crypto.SHA1, GitOID: true}},
materials: make(map[string]cryptoutil.DigestSet),
products: make(map[string]Product),
stepName: stepName,
}

for _, opt := range opts {
Expand Down Expand Up @@ -219,6 +221,10 @@ func (ctx *AttestationContext) Products() map[string]Product {
return out
}

func (ctx *AttestationContext) StepName() string {
return ctx.stepName
}

func (ctx *AttestationContext) addMaterials(materialer Materialer) {
newMats := materialer.Materials()
for k, v := range newMats {
Expand Down
14 changes: 14 additions & 0 deletions attestation/environment/environment.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,18 @@ const (
// doesn't implement the expected interfaces.
var (
_ attestation.Attestor = &Attestor{}
_ EnvironmentAttestor = &Attestor{}
)

type EnvironmentAttestor interface {
// Attestor
Name() string
Type() string
RunType() attestation.RunType
Attest(ctx *attestation.AttestationContext) error
Data() *Attestor
}

func init() {
attestation.RegisterAttestation(Name, Type, RunType, func() attestation.Attestor {
return New()
Expand Down Expand Up @@ -101,6 +111,10 @@ func (a *Attestor) Attest(ctx *attestation.AttestationContext) error {
return nil
}

func (a *Attestor) Data() *Attestor {
return a
}

// splitVariable splits a string representing an environment variable in the format of
// "KEY=VAL" and returns the key and val separately.
func splitVariable(v string) (key, val string) {
Expand Down
2 changes: 1 addition & 1 deletion attestation/environment/environment_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import (

func TestEnvironment(t *testing.T) {
attestor := New()
ctx, err := attestation.NewContext([]attestation.Attestor{attestor})
ctx, err := attestation.NewContext("test", []attestation.Attestor{attestor})
require.NoError(t, err)

t.Setenv("AWS_ACCESS_KEY_ID", "super secret")
Expand Down
5 changes: 5 additions & 0 deletions attestation/factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,11 @@ type Producer interface {
Products() map[string]Product
}

// Exporter allows attestors to export their attestations for separation from the collection.
type Exporter interface {
Export() bool
}

// BackReffer allows attestors to indicate which of their subjects are good candidates
// to find related attestations. For example the git attestor's commit hash subject
// is a good candidate to find all attestation collections that also refer to a specific
Expand Down
30 changes: 30 additions & 0 deletions attestation/git/git.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,24 @@ var (
_ attestation.Attestor = &Attestor{}
_ attestation.Subjecter = &Attestor{}
_ attestation.BackReffer = &Attestor{}
_ GitAttestor = &Attestor{}
)

type GitAttestor interface {
// Attestor
Name() string
Type() string
RunType() attestation.RunType
Attest(ctx *attestation.AttestationContext) error
Data() *Attestor

// Subjecter
Subjects() map[string]cryptoutil.DigestSet

// Backreffer
BackRefs() map[string]cryptoutil.DigestSet
}

func init() {
attestation.RegisterAttestation(Name, Type, RunType, func() attestation.Attestor {
return New()
Expand Down Expand Up @@ -75,6 +91,7 @@ type Attestor struct {
ParentHashes []string `json:"parenthashes,omitempty"`
TreeHash string `json:"treehash,omitempty"`
Refs []string `json:"refs,omitempty"`
Remotes []string `json:"remotes,omitempty"`
Tags []Tag `json:"tags,omitempty"`
}

Expand Down Expand Up @@ -125,6 +142,15 @@ func (a *Attestor) Attest(ctx *attestation.AttestationContext) error {
}: commit.Hash.String(),
}

remotes, err := repo.Remotes()
if err != nil {
return err
}

for _, remote := range remotes {
a.Remotes = append(a.Remotes, remote.Config().URLs...)
}

//get all the refs for the repo
refs, err := repo.References()
if err != nil {
Expand Down Expand Up @@ -218,6 +244,10 @@ func (a *Attestor) Attest(ctx *attestation.AttestationContext) error {
return nil
}

func (a *Attestor) Data() *Attestor {
return a
}

func (a *Attestor) Subjects() map[string]cryptoutil.DigestSet {
subjects := make(map[string]cryptoutil.DigestSet)
hashes := []cryptoutil.DigestValue{{Hash: crypto.SHA256}}
Expand Down
4 changes: 2 additions & 2 deletions attestation/git/git_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ func TestRunWorksWithCommits(t *testing.T) {
_, dir, cleanup := createTestRepo(t, true)
defer cleanup()

ctx, err := attestation.NewContext([]attestation.Attestor{attestor}, attestation.WithWorkingDir(dir))
ctx, err := attestation.NewContext("test", []attestation.Attestor{attestor}, attestation.WithWorkingDir(dir))
require.NoError(t, err, "Expected no error from NewContext")

err = ctx.RunAttestors()
Expand Down Expand Up @@ -146,7 +146,7 @@ func TestRunWorksWithoutCommits(t *testing.T) {
_, dir, cleanup := createTestRepo(t, false)
defer cleanup()

ctx, err := attestation.NewContext([]attestation.Attestor{attestor}, attestation.WithWorkingDir(dir))
ctx, err := attestation.NewContext("test", []attestation.Attestor{attestor}, attestation.WithWorkingDir(dir))
require.NoError(t, err, "Expected no error from NewContext")

err = ctx.RunAttestors()
Expand Down
30 changes: 25 additions & 5 deletions attestation/github/github.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,20 +48,36 @@ var (
_ attestation.Attestor = &Attestor{}
_ attestation.Subjecter = &Attestor{}
_ attestation.BackReffer = &Attestor{}
_ GitHubAttestor = &Attestor{}
)

type GitHubAttestor interface {
// Attestor
Name() string
Type() string
RunType() attestation.RunType
Attest(ctx *attestation.AttestationContext) error
Data() *Attestor

// Subjecter
Subjects() map[string]cryptoutil.DigestSet

// Backreffer
BackRefs() map[string]cryptoutil.DigestSet
}

// init registers the github attestor.
func init() {
attestation.RegisterAttestation(Name, Type, RunType, func() attestation.Attestor {
return New()
})
}

// ErrNotGitlab is an error type that indicates the environment is not a github ci job.
type ErrNotGitlab struct{}
// ErrNotGitHub is an error type that indicates the environment is not a github ci job.
type ErrNotGitHub struct{}

// Error returns the error message for ErrNotGitlab.
func (e ErrNotGitlab) Error() string {
// Error returns the error message for ErrNotGitHub.
func (e ErrNotGitHub) Error() string {
return "not in a github ci job"
}

Expand Down Expand Up @@ -111,7 +127,7 @@ func (a *Attestor) RunType() attestation.RunType {
// Attest performs the attestation for the github environment.
func (a *Attestor) Attest(ctx *attestation.AttestationContext) error {
if os.Getenv("GITHUB_ACTIONS") != "true" {
return ErrNotGitlab{}
return ErrNotGitHub{}
}

jwtString, err := fetchToken(a.tokenURL, os.Getenv("ACTIONS_ID_TOKEN_REQUEST_TOKEN"), "witness")
Expand Down Expand Up @@ -142,6 +158,10 @@ func (a *Attestor) Attest(ctx *attestation.AttestationContext) error {
return nil
}

func (a *Attestor) Data() *Attestor {
return a
}

// Subjects returns a map of subjects and their corresponding digest sets.
func (a *Attestor) Subjects() map[string]cryptoutil.DigestSet {
subjects := make(map[string]cryptoutil.DigestSet)
Expand Down
26 changes: 24 additions & 2 deletions attestation/gitlab/gitlab.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,24 @@ var (
_ attestation.Attestor = &Attestor{}
_ attestation.Subjecter = &Attestor{}
_ attestation.BackReffer = &Attestor{}
_ GitLabAttestor = &Attestor{}
)

type GitLabAttestor interface {
// Attestor
Name() string
Type() string
RunType() attestation.RunType
Attest(ctx *attestation.AttestationContext) error
Data() *Attestor

// Subjecter
Subjects() map[string]cryptoutil.DigestSet

// Backreffer
BackRefs() map[string]cryptoutil.DigestSet
}

func init() {
attestation.RegisterAttestation(Name, Type, RunType, func() attestation.Attestor {
return New()
Expand Down Expand Up @@ -91,13 +107,15 @@ func (a *Attestor) Attest(ctx *attestation.AttestationContext) error {
}

a.CIServerUrl = os.Getenv("CI_SERVER_URL")
jwksUrl := fmt.Sprintf("%s/-/jwks", a.CIServerUrl)
jwtString := os.Getenv("CI_JOB_JWT")
jwksUrl := fmt.Sprintf("%s/oauth/discovery/keys", a.CIServerUrl)
jwtString := os.Getenv("ID_TOKEN")
if jwtString != "" {
a.JWT = jwt.New(jwt.WithToken(jwtString), jwt.WithJWKSUrl(jwksUrl))
if err := a.JWT.Attest(ctx); err != nil {
return err
}
} else {
log.Warn("(attestation/gitlab) no jwt token found in environment")
}

a.CIConfigPath = os.Getenv("CI_CONFIG_PATH")
Expand All @@ -116,6 +134,10 @@ func (a *Attestor) Attest(ctx *attestation.AttestationContext) error {
return nil
}

func (a *Attestor) Data() *Attestor {
return a
}

func (a *Attestor) Subjects() map[string]cryptoutil.DigestSet {
subjects := make(map[string]cryptoutil.DigestSet)
hashes := []cryptoutil.DigestValue{{Hash: crypto.SHA256}}
Expand Down
Loading

0 comments on commit 87975b4

Please sign in to comment.