Skip to content

Commit

Permalink
evaluator accepts sbom
Browse files Browse the repository at this point in the history
  • Loading branch information
chrispatrick committed Mar 8, 2024
1 parent 6e5764a commit 3be5e9c
Show file tree
Hide file tree
Showing 5 changed files with 145 additions and 123 deletions.
13 changes: 5 additions & 8 deletions policy/data/subscription.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package data

import (
"context"

"olympos.io/encoding/edn"
)

Expand All @@ -10,27 +11,23 @@ import (
// possibly allowing earlier data sources to pick up the query first.
type SubscriptionDataSource struct {
queryIndexes map[string]int
subscriptionResults [][]edn.RawMessage
subscriptionResults []map[edn.Keyword]edn.RawMessage
}

func NewSubscriptionDataSource(queryIndexes map[string]int, subscriptionResults [][]edn.RawMessage) SubscriptionDataSource {
func NewSubscriptionDataSource(queryIndexes map[string]int, subscriptionResults []map[edn.Keyword]edn.RawMessage) SubscriptionDataSource {
return SubscriptionDataSource{
queryIndexes: queryIndexes,
subscriptionResults: subscriptionResults,
}
}

func (ds SubscriptionDataSource) Query(_ context.Context, queryName string, _ string, _ map[string]interface{}, output interface{}) (*QueryResponse, error) {
ix, ok := ds.queryIndexes[queryName]
result, ok := ds.subscriptionResults[0][edn.Keyword(queryName)]
if !ok {
return nil, nil
}

if len(ds.subscriptionResults) == 0 || ix >= len(ds.subscriptionResults[0]) {
return nil, nil
}

err := edn.Unmarshal(ds.subscriptionResults[0][ix], output)
err := edn.Unmarshal(result, output)
if err != nil {
return nil, err
}
Expand Down
19 changes: 12 additions & 7 deletions policy/policy_handler/async.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const eventNameAsyncQuery = data.AsyncQueryName // these must match for the even
func WithAsyncMultiQuery() Opt {
return func(h *EventHandler) {
h.subscriptionNames = append(h.subscriptionNames, eventNameAsyncQuery)
h.subscriptionDataProviders = append(h.subscriptionDataProviders, getAsyncSubscriptionData)
h.evalInputProviders = append(h.evalInputProviders, getAsyncInputData)
h.dataSourceProviders = append(h.dataSourceProviders, buildAsyncDataSources(true))
}
}
Expand All @@ -37,28 +37,33 @@ func WithAsync() Opt {
}

h.subscriptionNames = append(h.subscriptionNames, eventNameAsyncQuery)
h.subscriptionDataProviders = append(h.subscriptionDataProviders, getAsyncSubscriptionData)
h.evalInputProviders = append(h.evalInputProviders, getAsyncInputData)
h.dataSourceProviders = append(h.dataSourceProviders, buildAsyncDataSources(false))
}
}

func getAsyncSubscriptionData(ctx context.Context, req skill.RequestContext) (*goals.EvaluationMetadata, skill.Configuration, error) {
func getAsyncInputData(ctx context.Context, req skill.RequestContext) (*goals.EvaluationMetadata, skill.Configuration, *types.SBOM, error) {
if req.Event.Context.AsyncQueryResult.Name != eventNameAsyncQuery {
return nil, skill.Configuration{}, nil
return nil, skill.Configuration{}, nil, nil
}

metaEdn, err := b64.StdEncoding.DecodeString(req.Event.Context.AsyncQueryResult.Metadata)
if err != nil {
return nil, skill.Configuration{}, fmt.Errorf("failed to decode async metadata: %w", err)
return nil, skill.Configuration{}, nil, fmt.Errorf("failed to decode async metadata: %w", err)
}

var metadata data.AsyncResultMetadata
err = edn.Unmarshal(metaEdn, &metadata)
if err != nil {
return nil, skill.Configuration{}, fmt.Errorf("failed to unmarshal async metadata: %w", err)
return nil, skill.Configuration{}, nil, fmt.Errorf("failed to unmarshal async metadata: %w", err)
}

return &metadata.EvaluationMetadata, req.Event.Context.AsyncQueryResult.Configuration, nil
sbom, err := createSbomFromSubscriptionResult(metadata.EvaluationMetadata.SubscriptionResult)
if err != nil {
return nil, skill.Configuration{}, nil, fmt.Errorf("failed to create SBOM from subscription result: %w", err)
}

return &metadata.EvaluationMetadata, req.Event.Context.AsyncQueryResult.Configuration, &sbom, nil
}

func buildAsyncDataSources(multipleQuerySupport bool) dataSourceProvider {
Expand Down
116 changes: 19 additions & 97 deletions policy/policy_handler/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package policy_handler

import (
"context"
"encoding/base64"
"encoding/json"
"fmt"
"net/http"
Expand All @@ -16,29 +15,27 @@ import (
"github.com/atomist-skills/go-skill/policy/storage"
"github.com/atomist-skills/go-skill/policy/types"
"github.com/atomist-skills/go-skill/util"
"github.com/secure-systems-lab/go-securesystemslib/dsse"
"olympos.io/encoding/edn"

v1 "github.com/google/go-containerregistry/pkg/v1"
intoto "github.com/in-toto/in-toto-golang/in_toto"
)

type (
EvaluatorSelector func(ctx context.Context, req skill.RequestContext, goal goals.Goal, dataSource data.DataSource) (goals.GoalEvaluator, error)

subscriptionProvider func(ctx context.Context, req skill.RequestContext) (*goals.EvaluationMetadata, skill.Configuration, error)
dataSourceProvider func(ctx context.Context, req skill.RequestContext, evalMeta goals.EvaluationMetadata) ([]data.DataSource, error)
transactionFilter func(ctx context.Context, req skill.RequestContext) bool
evalInputProvider func(ctx context.Context, req skill.RequestContext) (*goals.EvaluationMetadata, skill.Configuration, *types.SBOM, error)
dataSourceProvider func(ctx context.Context, req skill.RequestContext, evalMeta goals.EvaluationMetadata) ([]data.DataSource, error)
transactionFilter func(ctx context.Context, req skill.RequestContext) bool

EventHandler struct {
// parameters
evalSelector EvaluatorSelector
subscriptionNames []string

// hooks used by opts
subscriptionDataProviders []subscriptionProvider
dataSourceProviders []dataSourceProvider
transactFilters []transactionFilter
evalInputProviders []evalInputProvider
dataSourceProviders []dataSourceProvider
transactFilters []transactionFilter
}

Opt func(handler *EventHandler)
Expand Down Expand Up @@ -107,10 +104,11 @@ func (h EventHandler) handle(ctx context.Context, req skill.RequestContext) skil
var (
evaluationMetadata *goals.EvaluationMetadata
configuration skill.Configuration
sbom *types.SBOM
err error
)
for _, provider := range h.subscriptionDataProviders {
evaluationMetadata, configuration, err = provider(ctx, req)
for _, provider := range h.evalInputProviders {
evaluationMetadata, configuration, sbom, err = provider(ctx, req)
if err != nil {
return skill.NewFailedStatus(fmt.Sprintf("failed to retrieve subscription result [%s]", err.Error()))
}
Expand All @@ -137,10 +135,10 @@ func (h EventHandler) handle(ctx context.Context, req skill.RequestContext) skil

dataSource := data.NewChainDataSource(sources...)

return h.evaluate(ctx, req, dataSource, *evaluationMetadata, configuration)
return h.evaluate(ctx, req, dataSource, *evaluationMetadata, *sbom, configuration)
}

func (h EventHandler) evaluate(ctx context.Context, req skill.RequestContext, dataSource data.DataSource, evaluationMetadata goals.EvaluationMetadata, configuration skill.Configuration) skill.Status {
func (h EventHandler) evaluate(ctx context.Context, req skill.RequestContext, dataSource data.DataSource, evaluationMetadata goals.EvaluationMetadata, sbom types.SBOM, configuration skill.Configuration) skill.Status {
goalName := req.Event.Skill.Name
tx := evaluationMetadata.SubscriptionTx
subscriptionResult := evaluationMetadata.SubscriptionResult
Expand Down Expand Up @@ -173,13 +171,16 @@ func (h EventHandler) evaluate(ctx context.Context, req skill.RequestContext, da
return skill.NewFailedStatus(fmt.Sprintf("Failed to create goal evaluator: %s", err.Error()))
}

commonResults := createSbomFromSubscriptionResult(subscriptionResult)
digest := commonResults.ImageDigest
if err != nil {
req.Log.Errorf(err.Error())
return skill.NewFailedStatus(fmt.Sprintf("Failed to create sbom from subscription: %s", err.Error()))
}
digest := sbom.Source.Image.Digest

req.Log.Infof("Evaluating goal %s for digest %s ", goalName, digest)
evaluationTs := time.Now().UTC()

evaluationResult, err := evaluator.EvaluateGoal(ctx, req, commonResults, subscriptionResult)
evaluationResult, err := evaluator.EvaluateGoal(ctx, req, sbom, subscriptionResult)
if err != nil {
req.Log.Errorf("Failed to evaluate goal %s for digest %s: %s", goal.Definition, digest, err.Error())
return skill.NewFailedStatus("Failed to evaluate goal")
Expand Down Expand Up @@ -222,98 +223,19 @@ type intotoStatement struct {
Predicate json.RawMessage `json:"predicate"`
}

func createSbomFromSubscriptionResult(subscriptionResult []map[edn.Keyword]edn.RawMessage) (types.SBOM, error) {
imageEdn, ok := subscriptionResult[0][edn.Keyword("image")]

if !ok {
return types.SBOM{}, fmt.Errorf("image not found in subscription result")
}

image := util.Decode[goals.ImageSubscriptionQueryResult](imageEdn)

// TODO: probably query for all the intoto data and reconstruct the sbom attestions
attestations := []dsse.Envelope{}

var sourceMap *types.SourceMap

if image.Attestations != nil {
for _, attestation := range *&image.Attestations {
intotoStatement := intotoStatement{
StatementHeader: intoto.StatementHeader{
PredicateType: *attestation.PredicateType,
},
}

payloadBytes, _ := json.Marshal(intotoStatement)

payload := base64.StdEncoding.EncodeToString(payloadBytes)

env := dsse.Envelope{
PayloadType: "application/vnd.in-toto+json",
Payload: payload,
}

for _, predicate := range attestation.Predicates {
if predicate.StartLine != nil {
sourceMap = &types.SourceMap{
Instructions: []types.InstructionSourceMap{
{
Instruction: "FROM_RUNTIME",
StartLine: *predicate.StartLine,
},
},
}
}
}

attestations = append(attestations, env)
}
}

//TODO: handle missing data
sbom := types.SBOM{
Source: types.Source{
Image: &types.ImageSource{
Digest: image.ImageDigest,
Platform: types.Platform{
Architecture: image.ImagePlatforms[0].Architecture,
Os: image.ImagePlatforms[0].Os,
},
Config: &v1.ConfigFile{
Config: v1.Config{
User: image.User,
},
},
},
Provenance: &types.Provenance{
BaseImage: &types.ProvenanceBaseImage{
Digest: image.FromReference.Digest,
Tag: *image.FromTag,
Name: fmt.Sprintf("%s/%s", image.FromRepo.Host, image.FromRepo.Repository),
// distro? - query separately from subscription data
},
SourceMap: sourceMap,
},
},
Attestations: attestations,
}

return sbom, nil
}

func transact(
ctx context.Context,
req skill.RequestContext,
configuration skill.Configuration,
goalName string,
digest string,
goal goals.Goal,
subscriptionResult [][]edn.RawMessage,
subscriptionResult []map[edn.Keyword]edn.RawMessage,
evaluationTs time.Time,
goalResults []goals.GoalEvaluationQueryResult,
tx int64,
) skill.Status {
storageTuple := util.Decode[[]string](subscriptionResult[0][1])
storageTuple := util.Decode[[]string](subscriptionResult[0]["previous"])
previousStorageId := storageTuple[0]
previousConfigHash := storageTuple[1]

Expand Down
18 changes: 11 additions & 7 deletions policy/policy_handler/local.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,20 +32,20 @@ type SyncRequestMetadata struct {
func WithLocal() Opt {
return func(h *EventHandler) {
h.subscriptionNames = append(h.subscriptionNames, eventNameLocalEval)
h.subscriptionDataProviders = append(h.subscriptionDataProviders, getLocalSubscriptionData)
h.evalInputProviders = append(h.evalInputProviders, getLocalSubscriptionData)
h.dataSourceProviders = append([]dataSourceProvider{buildLocalDataSources}, h.dataSourceProviders...)
h.transactFilters = append(h.transactFilters, shouldTransactLocal)
}
}

func getLocalSubscriptionData(_ context.Context, req skill.RequestContext) (*goals.EvaluationMetadata, skill.Configuration, error) {
func getLocalSubscriptionData(_ context.Context, req skill.RequestContext) (*goals.EvaluationMetadata, skill.Configuration, *types.SBOM, error) {
if req.Event.Context.SyncRequest.Name != eventNameLocalEval {
return nil, skill.Configuration{}, nil
return nil, skill.Configuration{}, nil, nil
}

_, sbom, err := parseMetadata(req)
if err != nil {
return nil, skill.Configuration{}, err
return nil, skill.Configuration{}, nil, err
}

var mockCommonSubscriptionData goals.ImageSubscriptionQueryResult
Expand All @@ -65,12 +65,16 @@ func getLocalSubscriptionData(_ context.Context, req skill.RequestContext) (*goa

subscriptionData, err := edn.Marshal(mockCommonSubscriptionData)
if err != nil {
return nil, skill.Configuration{}, err
return nil, skill.Configuration{}, nil, err
}

subscriptionResult := map[edn.Keyword]edn.RawMessage{}
subscriptionResult[edn.Keyword("image")] = subscriptionData

return &goals.EvaluationMetadata{
SubscriptionResult: [][]edn.RawMessage{{subscriptionData}},
}, req.Event.Context.SyncRequest.Configuration, nil
SubscriptionResult: []map[edn.Keyword]edn.RawMessage{
subscriptionResult,
}}, req.Event.Context.SyncRequest.Configuration, sbom, nil
}

func buildLocalDataSources(ctx context.Context, req skill.RequestContext, _ goals.EvaluationMetadata) ([]data.DataSource, error) {
Expand Down
Loading

0 comments on commit 3be5e9c

Please sign in to comment.