Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for provenance. #54

Merged
merged 3 commits into from
Nov 21, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
17 changes: 17 additions & 0 deletions cmd/example/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,11 @@ func main() {
fmt.Fprintf(os.Stderr, "error calling endpoint: %s\n", err)
os.Exit(1)
}
case "provenance":
if err := provenance(ctx, client, pname); err != nil {
fmt.Fprintf(os.Stderr, "error calling endpoint: %s\n", err)
os.Exit(1)
}
case "":
fmt.Fprintf(os.Stderr, "endpoint is mandatory\n")
os.Exit(1)
Expand Down Expand Up @@ -99,3 +104,15 @@ func alternatives(ctx context.Context, client v2client.Trusty, pname string) err
fmt.Printf("%+v\n", res)
return nil
}

func provenance(ctx context.Context, client v2client.Trusty, pname string) error {
res, err := client.Provenance(ctx, &v2types.Dependency{
PackageName: pname,
})
if err != nil {
return err
}

fmt.Printf("%+v\n", res)
return nil
}
31 changes: 31 additions & 0 deletions internal/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,7 @@ const (
v2SummaryPath = "v2/summary"
v2PkgPath = "v2/pkg"
v2Alternatives = "v2/alternatives"
v2Provenance = "v2/provenance"
)

// Summary fetches a summary of Security Signal information
Expand Down Expand Up @@ -419,6 +420,36 @@ func (t *Trusty) Alternatives(
return doRequest[v2types.PackageAlternatives](t.Options.HttpClient, u.String())
}

// Provenance fetches detailed provenance information of a given
// package.
func (t *Trusty) Provenance(
_ context.Context,
dep *v2types.Dependency,
) (*v2types.Provenance, error) {
if dep.PackageName == "" {
return nil, fmt.Errorf("dependency has no name defined")
}

u, err := urlFor(t.Options.BaseURL, v2Provenance)
if err != nil {
return nil, fmt.Errorf("failed to parse endpoint: %w", err)
}

// Add query parameters for package_name, package_type, and
// package_version.
q := u.Query()
q.Set("package_name", dep.PackageName)
if dep.PackageType != nil && *dep.PackageType != "" {
puerco marked this conversation as resolved.
Show resolved Hide resolved
q.Set("package_type", strings.ToLower(*dep.PackageType))
}
if dep.PackageVersion != nil && *dep.PackageVersion != "" {
q.Set("package_version", *dep.PackageVersion)
}
u.RawQuery = q.Encode()

return doRequest[v2types.Provenance](t.Options.HttpClient, u.String())
}

// doRequest only wraps (1) an HTTP GET issued to the given URL using
// the given client, and (2) result deserialization.
func doRequest[T any](client netClient, fullurl string) (*T, error) {
Expand Down
1 change: 1 addition & 0 deletions pkg/v2/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ type Trusty interface {
Summary(context.Context, *types.Dependency) (*types.PackageSummaryAnnotation, error)
PackageMetadata(context.Context, *types.Dependency) (*types.TrustyPackageData, error)
Alternatives(context.Context, *types.Dependency) (*types.PackageAlternatives, error)
Provenance(context.Context, *types.Dependency) (*types.Provenance, error)
}

// New returns a new Trusty REST client
Expand Down
49 changes: 38 additions & 11 deletions pkg/v2/types/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,17 +97,17 @@ type SummaryDescription struct {
type ProvenanceType string

var (
// ProvenanceTypeVerifiedProvenance represents a fully
// verified provenance information.
ProvenanceTypeVerifiedProvenance ProvenanceType = "verified_provenance"
// ProvenanceTypeHistoricalProvenance represents a verified
// historical provenance information.
ProvenanceTypeHistoricalProvenance ProvenanceType = "historical_provenance_match"
// ProvenanceTypeVerified represents a fully verified
// provenance information.
ProvenanceTypeVerified ProvenanceType = "verified_provenance_match"
// ProvenanceTypeHistorical represents a verified historical
// provenance information.
ProvenanceTypeHistorical ProvenanceType = "historical_provenance_match"
blkt marked this conversation as resolved.
Show resolved Hide resolved
// ProvenanceTypeUnknown represents no provenance information.
ProvenanceTypeUnknown ProvenanceType = "unknown"
// ProvenanceTypeMismatched represents conflicting provenance
// information.
ProvenanceTypeMismatched ProvenanceType = "mismatched"
ProvenanceTypeMismatched ProvenanceType = "historical_provenance_mismatched"
)

//nolint:revive
Expand All @@ -118,13 +118,13 @@ func (t *ProvenanceType) UnmarshalJSON(data []byte) error {
}

switch tmp {
case "verified_provenance":
*t = ProvenanceTypeVerifiedProvenance
case "verified_provenance_match":
*t = ProvenanceTypeVerified
blkt marked this conversation as resolved.
Show resolved Hide resolved
case "historical_provenance_match":
*t = ProvenanceTypeHistoricalProvenance
*t = ProvenanceTypeHistorical
case "unknown":
*t = ProvenanceTypeUnknown
case "mismatched":
case "historical_provenance_mismatched":
*t = ProvenanceTypeMismatched
default:
return fmt.Errorf("invalid provenance type: %s", tmp)
Expand Down Expand Up @@ -318,3 +318,30 @@ type PackageBasicInfo struct {
Score *float64 `json:"score"`
IsMalicious bool `json:"is_malicious"`
}

// Provenance contains details about historical or cryptographically
// verifiable provenance.
type Provenance struct {
Historical HistoricalProvenance `json:"hp"`
Sigstore SigstoreProvenance `json:"sigstore"`
Sore *float64 `json:"score"`
}

// HistoricalProvenance contains the number of tags in the repo, the
// number of versions of the package, a count of the common tags and
// the ratio of tags to common as overlap.
type HistoricalProvenance struct {
Overlap float64 `json:"overlap"` // 92.23300970873787
Common float64 `json:"common"` // 95.0
Tags float64 `json:"tags"` // 103.0
Versions float64 `json:"versions"` // 152.0
}

// SigstoreProvenance contains details about Sigstore provenance.
type SigstoreProvenance struct {
SourceRepo string `json:"source_repo"`
Workflow string `json:"workflow"`
Issuer string `json:"issuer"`
TokenIssuer string `json:"token_issuer"`
Transparency string `json:"transparency"`
}