96 lines
3.1 KiB
Go
96 lines
3.1 KiB
Go
|
|
package main
|
||
|
|
|
||
|
|
import (
|
||
|
|
"fmt"
|
||
|
|
|
||
|
|
apk "github.com/knqyf263/go-apk-version"
|
||
|
|
)
|
||
|
|
|
||
|
|
type scanInventory struct {
|
||
|
|
Hash string `json:"manifest_hash"`
|
||
|
|
Packages map[string]*scanPackage `json:"packages"`
|
||
|
|
Distributions map[string]*scanDistribution `json:"distributions"`
|
||
|
|
Environments map[string][]*scanEnvironment `json:"environments"`
|
||
|
|
}
|
||
|
|
|
||
|
|
type scanPackage struct {
|
||
|
|
ID string `json:"id"`
|
||
|
|
Name string `json:"name"`
|
||
|
|
Version string `json:"version"`
|
||
|
|
Kind string `json:"kind"`
|
||
|
|
Source *scanPackage `json:"source"`
|
||
|
|
}
|
||
|
|
|
||
|
|
type scanDistribution struct {
|
||
|
|
ID string `json:"id"`
|
||
|
|
DID string `json:"did"`
|
||
|
|
Name string `json:"name"`
|
||
|
|
Version string `json:"version"`
|
||
|
|
PrettyName string `json:"pretty_name"`
|
||
|
|
}
|
||
|
|
|
||
|
|
type scanEnvironment struct {
|
||
|
|
PackageDB string `json:"package_db"`
|
||
|
|
IntroducedIn string `json:"introduced_in"`
|
||
|
|
DistributionID string `json:"distribution_id"`
|
||
|
|
RepositoryIDs []string `json:"repository_ids"`
|
||
|
|
}
|
||
|
|
|
||
|
|
type featureKey struct {
|
||
|
|
Name string
|
||
|
|
Version string
|
||
|
|
Layer string
|
||
|
|
}
|
||
|
|
|
||
|
|
func checkInventory(data *securityData, digest string, layers map[string]bool) error {
|
||
|
|
if data == nil || data.Inventory == nil || data.Layer == nil || data.Layer.Features == nil {
|
||
|
|
return fmt.Errorf("scan inventory is unavailable")
|
||
|
|
}
|
||
|
|
inv := data.Inventory
|
||
|
|
if inv.Hash != digest || data.Layer.Name != digest {
|
||
|
|
return fmt.Errorf("scan identity does not match candidate")
|
||
|
|
}
|
||
|
|
if len(inv.Packages) == 0 || len(inv.Distributions) == 0 || len(inv.Environments) != len(inv.Packages) {
|
||
|
|
return fmt.Errorf("scan inventory is incomplete")
|
||
|
|
}
|
||
|
|
for id, distro := range inv.Distributions {
|
||
|
|
if distro == nil || distro.ID != id || distro.DID != "alpine" || distro.Version != "3.23" || distro.Name != "Alpine Linux" || distro.PrettyName != "Alpine Linux v3.23" {
|
||
|
|
return fmt.Errorf("distribution coverage is not qualified")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
expected := map[featureKey]int{}
|
||
|
|
for id, pkg := range inv.Packages {
|
||
|
|
if pkg == nil || pkg.ID != id || pkg.Name == "" || pkg.Version == "" || pkg.Kind != "binary" {
|
||
|
|
return fmt.Errorf("package identity is incomplete")
|
||
|
|
}
|
||
|
|
if _, err := apk.NewVersion(pkg.Version); err != nil {
|
||
|
|
return fmt.Errorf("package version is not matchable")
|
||
|
|
}
|
||
|
|
if pkg.Source == nil || pkg.Source.Name == "" || pkg.Source.Kind != "source" {
|
||
|
|
return fmt.Errorf("source package identity is missing")
|
||
|
|
}
|
||
|
|
envs := inv.Environments[id]
|
||
|
|
if len(envs) == 0 {
|
||
|
|
return fmt.Errorf("package environment is missing")
|
||
|
|
}
|
||
|
|
for _, env := range envs {
|
||
|
|
if env == nil || env.PackageDB != "lib/apk/db/installed" || !layers[env.IntroducedIn] || inv.Distributions[env.DistributionID] == nil || len(env.RepositoryIDs) != 0 {
|
||
|
|
return fmt.Errorf("package environment is not qualified")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
expected[featureKey{pkg.Name, pkg.Version, envs[0].IntroducedIn}]++
|
||
|
|
}
|
||
|
|
for _, feature := range *data.Layer.Features {
|
||
|
|
key := featureKey{feature.Name, feature.Version, feature.AddedBy}
|
||
|
|
if expected[key] == 0 {
|
||
|
|
return fmt.Errorf("scan feature does not match inventory")
|
||
|
|
}
|
||
|
|
expected[key]--
|
||
|
|
}
|
||
|
|
for _, count := range expected {
|
||
|
|
if count != 0 {
|
||
|
|
return fmt.Errorf("scan features omit indexed packages")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return nil
|
||
|
|
}
|