diff --git a/README.md b/README.md index 82a9d47c..58355acc 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,7 @@ The **Kubescape Operator** is the central orchestration engine for [Kubescape](h - [Features](#features) - [Continuous Scanning](#continuous-scanning) - [Admission Controller](#admission-controller) + - [SBOM Export to Dependency-Track](#sbom-export-to-dependency-track) - [Development](#development) - [Building from Source](#building-from-source) - [VS Code Configuration](#vs-code-configuration) @@ -239,7 +240,8 @@ The Operator reads configuration from `/etc/config/`. When running locally, set "nodeSbomGeneration": "disable", "seccomp": "disable", "otel": "enable", - "admissionController": "disable" + "admissionController": "disable", + "sbomExport": "disable" }, "components": { "operator": { "enabled": true }, @@ -321,6 +323,12 @@ The Operator reads configuration from `/etc/config/`. When running locally, set | `excludeControls` | `[]` | Control IDs or legacy CIS section numbers omitted from operator posture scans | | `excludeNamespacesRegex` | `""` | Comma-separated string or array of RE2 regex patterns for namespaces to exclude | | `namespaceFilterConfigMapName` | `""` | Optional ConfigMap name in the operator namespace for live namespace filtering | +| `sbomExport.resyncInterval` | `1h` | How often the [SBOM export](docs/features/sbom-export.md) compares every running image with Dependency-Track | +| `sbomExport.maxSBOMSize` | `10485760` | Largest SBOM, in bytes, the SBOM export converts; the operator needs about nine times this in memory, see [SBOM size](docs/features/sbom-export.md#sbom-size) | +| `sbomExport.dependencyTrack.url` | `""` | Dependency-Track API server the SBOM export sends to | +| `sbomExport.dependencyTrack.apiKeyFile` | `/etc/sbom-export/api-key` | File holding the Dependency-Track API key, read on every request | +| `sbomExport.dependencyTrack.rootProject` | `{{ .Cluster }}` | Root project, where created parents go; see [changing the layout](docs/features/sbom-export.md#changing-the-layout) | +| `sbomExport.dependencyTrack.project` | `{{ .Cluster }}/{{ .Namespace }}/{{ .Repository }}` : `{{ .Tag }}`, parent `{{ .Cluster }}/{{ .Namespace }}` | Where each image goes; namespaces can override it with `kubescape.io/dtrack-*` annotations | ### Exclude controls from posture scans @@ -807,6 +815,22 @@ helm upgrade kubescape kubescape/kubescape-operator \ When enabled, the Operator runs an HTTPS webhook server on port `8443` that integrates with Kubernetes admission control. +### SBOM Export to Dependency-Track + +The Operator sends the SBOM of every running image, as node-agent built it, to [Dependency-Track](https://dependencytrack.org): one project per image and namespace, kept current as workloads change. It needs `nodeSbomGeneration`, but neither kubevuln nor registry access. Where projects go is configurable, and namespaces can send their images under projects that already exist with `kubescape.io/dtrack-*` annotations. + +**Enable via Helm**, with the API key in a Secret you create in the Kubescape namespace: +```bash +helm upgrade kubescape kubescape/kubescape-operator \ + -n kubescape \ + --set capabilities.nodeSbomGeneration=enable \ + --set capabilities.sbomExport=enable \ + --set sbomExport.dependencyTrack.url=https://dtrack.example.com \ + --set sbomExport.dependencyTrack.apiKeySecret.name=dependency-track +``` + +See [SBOM export to Dependency-Track](docs/features/sbom-export.md) for the project layout and its annotations, the size limit, the settings and the permissions the API key needs. + --- ## Development diff --git a/config/config.go b/config/config.go index ce11ceb0..a23a9db2 100644 --- a/config/config.go +++ b/config/config.go @@ -34,6 +34,7 @@ type Capabilities struct { VulnerabilityScan string `json:"vulnerabilityScan"` AdmissionController string `json:"admissionController"` RiskAcceptance string `json:"riskAcceptance"` + SBOMExport string `json:"sbomExport"` } type Components struct { @@ -156,6 +157,51 @@ type Config struct { RulesUpdateConfig rulesupdate.RulesUpdaterConfig `mapstructure:"rulesUpdateConfig"` SkipProfilesWithoutInstances bool `mapstructure:"skipProfilesWithoutInstances"` NodeAgentAutoscaler NodeAgentAutoscalerConfig `mapstructure:"nodeAgentAutoscaler"` + SBOMExport SBOMExportConfig `mapstructure:"sbomExport"` +} + +// SBOMExportConfig configures the export of node SBOMs to Dependency-Track, +// turned on by the sbomExport capability. +type SBOMExportConfig struct { + // ResyncInterval is how often the exporter compares what runs in the + // cluster with what Dependency-Track holds. + ResyncInterval time.Duration `mapstructure:"resyncInterval"` + // MaxSBOMSize is the largest SBOM, in bytes, the exporter converts; larger + // ones are skipped, which bounds the operator's memory. + MaxSBOMSize int64 `mapstructure:"maxSBOMSize"` + DependencyTrack DependencyTrackConfig `mapstructure:"dependencyTrack"` +} + +// DependencyTrackConfig is where the exporter sends the SBOMs, and where in +// Dependency-Track's project tree they go. Names and versions are Go +// templates over the cluster, the namespace, its labels, and the image's +// repository and tag; Namespace annotations can override them. +type DependencyTrackConfig struct { + // URL is the base URL of the Dependency-Track API server. + URL string `mapstructure:"url"` + // APIKeyFile is the file the API key is read from, on every request, so a + // rotated Secret needs no restart. + APIKeyFile string `mapstructure:"apiKeyFile"` + // RootProject is where the exporter creates the parents it creates. + RootProject DTCollectionConfig `mapstructure:"rootProject"` + // Project is the project of one image in one namespace. + Project DTProjectConfig `mapstructure:"project"` +} + +// DTCollectionConfig is a collection project: the root, or an image's parent. +type DTCollectionConfig struct { + Name string `mapstructure:"name"` + Version string `mapstructure:"version"` + // Create lets the exporter create the project when it is missing. Without + // it the project must exist, and the exporter never changes it. + Create bool `mapstructure:"create"` +} + +// DTProjectConfig is the project an image is uploaded to, and its parent. +type DTProjectConfig struct { + Name string `mapstructure:"name"` + Version string `mapstructure:"version"` + Parent DTCollectionConfig `mapstructure:"parent"` } // IConfig is an interface for all config types used in the operator @@ -170,6 +216,8 @@ type IConfig interface { ContinuousScanEnabled() bool NodeSbomGenerationEnabled() bool RiskAcceptanceEnabled() bool + SBOMExportEnabled() bool + SBOMExportConfig() SBOMExportConfig CleanUpRoutineInterval() time.Duration MatchingRulesFilename() string TriggerSecurityFramework() bool @@ -250,6 +298,16 @@ func (c *OperatorConfig) NodeSbomGenerationEnabled() bool { return c.components.Capabilities.NodeSbomGeneration == "enable" } +// SBOMExportEnabled reports whether the sbomExport capability is on. +func (c *OperatorConfig) SBOMExportEnabled() bool { + return c.components.Capabilities.SBOMExport == "enable" +} + +// SBOMExportConfig returns where and how often to export SBOMs. +func (c *OperatorConfig) SBOMExportConfig() SBOMExportConfig { + return c.serviceConfig.SBOMExport +} + // RiskAcceptanceEnabled reports whether the riskAcceptance capability is on. The // SecurityException watcher must gate on this because the operator's RBAC for // securityexceptions/clustersecurityexceptions is only granted by the Helm chart @@ -391,6 +449,7 @@ func (c *OperatorConfig) NodeAgentAutoscalerConfig() NodeAgentAutoscalerConfig { return c.serviceConfig.NodeAgentAutoscaler } +// LoadConfig reads config.json from path over the defaults set here. func LoadConfig(path string) (Config, error) { viper.AddConfigPath(path) viper.SetConfigName("config") @@ -432,6 +491,19 @@ func LoadConfig(path string) (Config, error) { viper.SetDefault("nodeAgentAutoscaler.seLinuxType", "spc_t") viper.SetDefault("nodeAgentAutoscaler.bottlerocketAutoDetect", true) + // SBOM export defaults: the project tree is //: + viper.SetDefault("sbomExport.resyncInterval", time.Hour) + viper.SetDefault("sbomExport.maxSBOMSize", 10<<20) + viper.SetDefault("sbomExport.dependencyTrack.apiKeyFile", "/etc/sbom-export/api-key") + viper.SetDefault("sbomExport.dependencyTrack.rootProject.name", "{{ .Cluster }}") + viper.SetDefault("sbomExport.dependencyTrack.rootProject.version", "current") + viper.SetDefault("sbomExport.dependencyTrack.rootProject.create", true) + viper.SetDefault("sbomExport.dependencyTrack.project.name", "{{ .Cluster }}/{{ .Namespace }}/{{ .Repository }}") + viper.SetDefault("sbomExport.dependencyTrack.project.version", "{{ .Tag }}") + viper.SetDefault("sbomExport.dependencyTrack.project.parent.name", "{{ .Cluster }}/{{ .Namespace }}") + viper.SetDefault("sbomExport.dependencyTrack.project.parent.version", "current") + viper.SetDefault("sbomExport.dependencyTrack.project.parent.create", true) + viper.AutomaticEnv() err := viper.ReadInConfig() diff --git a/config/config_test.go b/config/config_test.go index ddd7ee4e..5f73654b 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -120,6 +120,7 @@ func TestLoadCapabilities(t *testing.T) { } } +// TestLoadConfig loads ../configuration/config.json and compares the result. func TestLoadConfig(t *testing.T) { type args struct { path string @@ -179,6 +180,19 @@ func TestLoadConfig(t *testing.T) { SELinuxType: "spc_t", BottlerocketAutoDetect: true, }, + SBOMExport: SBOMExportConfig{ + ResyncInterval: time.Hour, + MaxSBOMSize: 10 << 20, + DependencyTrack: DependencyTrackConfig{ + APIKeyFile: "/etc/sbom-export/api-key", + RootProject: DTCollectionConfig{Name: "{{ .Cluster }}", Version: "current", Create: true}, + Project: DTProjectConfig{ + Name: "{{ .Cluster }}/{{ .Namespace }}/{{ .Repository }}", + Version: "{{ .Tag }}", + Parent: DTCollectionConfig{Name: "{{ .Cluster }}/{{ .Namespace }}", Version: "current", Create: true}, + }, + }, + }, }, }, } @@ -619,3 +633,80 @@ func TestNewOperatorConfig_RegexValidation(t *testing.T) { assert.False(t, opCfg.SkipNamespace("default")) }) } + +// TestSBOMExportEnabled checks that only "enable" turns the export on. +func TestSBOMExportEnabled(t *testing.T) { + tests := []struct { + value string + want bool + }{ + {"enable", true}, + {"disable", false}, + {"", false}, + } + for _, tt := range tests { + t.Run(tt.value, func(t *testing.T) { + cfg, err := NewOperatorConfig( + CapabilitiesConfig{Capabilities: Capabilities{SBOMExport: tt.value}}, + armometadata.ClusterConfig{}, + &utils.Credentials{}, + Config{}, + ) + require.NoError(t, err) + assert.Equal(t, tt.want, cfg.SBOMExportEnabled()) + }) + } +} + +func TestLoadConfigSBOMExportDefaults(t *testing.T) { + viper.Reset() + defer viper.Reset() + dir := t.TempDir() + require.NoError(t, os.WriteFile(filepath.Join(dir, "config.json"), []byte(`{}`), 0600)) + cfg, err := LoadConfig(dir) + require.NoError(t, err) + assert.Equal(t, time.Hour, cfg.SBOMExport.ResyncInterval) + assert.Equal(t, int64(10<<20), cfg.SBOMExport.MaxSBOMSize) + assert.Equal(t, "/etc/sbom-export/api-key", cfg.SBOMExport.DependencyTrack.APIKeyFile) + assert.Empty(t, cfg.SBOMExport.DependencyTrack.URL) + assert.Equal(t, DTCollectionConfig{Name: "{{ .Cluster }}", Version: "current", Create: true}, cfg.SBOMExport.DependencyTrack.RootProject) + assert.Equal(t, DTProjectConfig{ + Name: "{{ .Cluster }}/{{ .Namespace }}/{{ .Repository }}", + Version: "{{ .Tag }}", + Parent: DTCollectionConfig{Name: "{{ .Cluster }}/{{ .Namespace }}", Version: "current", Create: true}, + }, cfg.SBOMExport.DependencyTrack.Project) +} + +func TestLoadConfigSBOMExport(t *testing.T) { + viper.Reset() + defer viper.Reset() + dir := t.TempDir() + require.NoError(t, os.WriteFile(filepath.Join(dir, "config.json"), []byte(`{ + "sbomExport": { + "resyncInterval": "30m", + "maxSBOMSize": 5242880, + "dependencyTrack": { + "url": "https://dtrack.example.com", + "apiKeyFile": "/keys/dtrack", + "rootProject": {"name": "Finance", "version": "current", "create": false}, + "project": {"name": "{{ .Repository }}", "parent": {"name": "Payments Team", "create": false}} + } + } + }`), 0600)) + cfg, err := LoadConfig(dir) + require.NoError(t, err) + assert.Equal(t, SBOMExportConfig{ + ResyncInterval: 30 * time.Minute, + MaxSBOMSize: 5 << 20, + DependencyTrack: DependencyTrackConfig{ + URL: "https://dtrack.example.com", + APIKeyFile: "/keys/dtrack", + RootProject: DTCollectionConfig{Name: "Finance", Version: "current", Create: false}, + Project: DTProjectConfig{ + Name: "{{ .Repository }}", + Version: "{{ .Tag }}", + Parent: DTCollectionConfig{Name: "Payments Team", Version: "current", Create: false}, + }, + }, + }, cfg.SBOMExport, "unset fields keep their defaults") +} diff --git a/docs/features/sbom-export.md b/docs/features/sbom-export.md new file mode 100644 index 00000000..1bacb2a0 --- /dev/null +++ b/docs/features/sbom-export.md @@ -0,0 +1,210 @@ +# SBOM export to Dependency-Track + +## Overview + +With the `sbomExport` capability, the operator sends the SBOMs node-agent +stores (`SBOMSyft` objects, from `nodeSbomGeneration`) to +[Dependency-Track](https://dependencytrack.org), one project per running image +and namespace, and keeps them current as workloads change. It needs neither +kubevuln nor registry access: the SBOMs are the ones node-agent builds from the +image layers on each node. + +## How it works + +The exporter is fed by `SBOMWatch`, the operator's watch of running pods and +`SBOMSyft` objects, which also starts when kubevuln is off but `sbomExport` is +on. An image is exported when its SBOM becomes ready, when a pod starts with an +image whose SBOM is ready, and on every resync (`resyncInterval`, one hour by +default), which compares every running image with Dependency-Track. The resync +catches up after Dependency-Track was unreachable or the operator restarted. + +For each namespace the image runs in, the exporter looks up its project. If the +project's last BOM import is not older than the `SBOMSyft`, and node-agent has +not rebuilt the SBOM since, the image is only marked as the project's latest +version. Otherwise the exporter reads the `SBOMSyft`, converts its syft +document to CycloneDX JSON (1.6) with syft, the way `syft convert` does, and +uploads it. An image that runs in several namespaces is converted once. + +node-agent rebuilds the SBOM of a running image when its own version changes, +and replaces the document in the same `SBOMSyft`. The exporter records the +node-agent version of each upload (`kubescape.io/tool-version`) in the project +property `kubescape/sbom-tool-version`, and sends the SBOM again when it +differs. + +An image stays counted as running in a namespace for one resync interval after +its last pod there ended, so the image of a short Job is still exported once +its SBOM is ready. The operator keeps this in memory: the image of a Job that +ended shortly before an operator restart is not exported, unless it runs again. +Namespaces the operator skips (`excludeNamespaces`, `includeNamespaces`, their +regex forms) are not exported. + +## Projects in Dependency-Track + +By default each cluster gets a tree: + +```text + : current collection, adds up its namespaces +└── / : current collection, adds up the latest version of each image + └── // : +``` + +The collection levels use the fixed version `current`, because +Dependency-Track 5 refuses a project without a version. An image referenced +only by digest gets the first 12 characters of the digest as its version. +Projects of images that stopped running are left as they are. The tree needs +Dependency-Track 4.13 or later (collection projects). + +### Changing the layout + +The tree is built from three projects per image, each a Go template, in the +same style as the node-agent autoscaler's DaemonSet template: + +- the **project** of the image, uploaded with its BOM; +- its **parent**, a collection; +- the **root project**, under which the operator creates parents it creates. + +Templates can use `.Cluster`, `.Namespace`, `.Repository`, `.Tag` and +`.Labels`, the namespace's labels, for example +`{{ .Cluster }}/{{ index .Labels "team" }}`. + +A namespace can override any of them with annotations. Each one replaces only +the setting it names: + +```yaml +apiVersion: v1 +kind: Namespace +metadata: + name: payments + annotations: + kubescape.io/dtrack-root-project: "Finance:current" # name:version, or a project UUID + kubescape.io/dtrack-root-project-create: "false" + kubescape.io/dtrack-parent: "Payments Team:current" + kubescape.io/dtrack-parent-create: "true" + kubescape.io/dtrack-project-name: "{{ .Repository }}" +``` + +With the default settings for every other namespace, this gives: + +```text +prod-cluster : current ← created by the operator (default root) +└── prod-cluster/team-a : current + └── prod-cluster/team-a/nginx : 1.27 +Finance : current ← yours, never modified +└── Payments Team : current ← created by the operator under Finance + ├── payments-api : 1.4 + └── payments-worker : 2.0 +``` + +Rules: + +| Situation | What the operator does | +|-----------|------------------------| +| The parent exists | Uploads the image's project under it. The root is not used. | +| The parent is missing, `create` is true | Creates it as a collection under the root, creating the root too when it is missing and may be created. | +| A project is missing and `create` is false, or it is named by a UUID that does not exist | Logs it once per resync and skips that namespace's images. Nothing is created. | +| A project exists that the operator did not create | Uses it, but never changes it: no collection logic, no BOM, no move. The operator tags what it creates with `managed-by:kubescape`; to let it take over projects created another way, for example by an earlier exporter, add that tag to them. | +| The image's project exists and was not created by the operator | Skips the image and logs a conflict, instead of replacing someone's BOM. | +| Two running images resolve to the same project and version | Uploads the first and skips the second, logging a conflict: one project version holds one BOM. The operator records each project's image digest in the project property `kubescape/image-digest`. When the first image stops running, the second takes the project over. | +| A namespace's labels or `kubescape.io/dtrack-*` annotations change | Moves the projects it created for that namespace's running images to their new place with a PATCH, so their history in Dependency-Track goes with them. When only the root changes, it moves the namespace's parent, also when none of its images can be exported. When the new place names a project that is missing and may not be created, the move waits and is tried again at every resync. A change made while the operator is not running is not seen; those images start new projects. | +| A template or annotation cannot be used (a typo, an unknown field, an empty result, a create flag that is not `true` or `false`) | Logs it once per resync and skips that namespace's images. Invalid default templates in `config.json` turn the export off at startup, with the reason in the log. | + +## SBOM size + +Converting an SBOM takes about nine times its size in memory, one image at a +time. Measured with node-agent's cataloger settings, the SBOM of +`nvcr.io/nvidia/pytorch:26.10-py3` (24.9 MiB) peaked at 176 MiB of heap and the +one of `quay.io/jupyter/all-spark-notebook` (33.9 MiB) at 291 MiB, both without +a `GOMEMLIMIT`. + +SBOMs larger than `sbomExport.maxSBOMSize` (10 MiB by default) are skipped and +logged once per resync. The operator checks the `kubescape.io/resource-size` +annotation before it reads the SBOM, stops reading an `SBOMSyft` that is more +than 1 MiB over the limit, and checks the document's length after. + +node-agent has its own `maxSBOMSize`, 20 MiB by default: it marks a larger SBOM +`too-large` and stores it without its content, so there is nothing to export. +To export bigger SBOMs, raise three settings together: + +1. node-agent's `maxSBOMSize`, so it keeps them; +2. `sbomExport.maxSBOMSize`, so the operator converts them; +3. the operator's memory limit, to at least nine times `sbomExport.maxSBOMSize` + plus 64 MiB for the rest of the operator. The Helm chart sets `GOMEMLIMIT` + from the limit (`operator.gomemlimitPercentage`, 80% by default). + +| `sbomExport.maxSBOMSize` | Memory limit it needs | Operator limit | +|--------------------------|-----------------------|----------------| +| 10 MiB (default) | 154 MiB | 300Mi, the chart's default | +| 20 MiB | 244 MiB | 300Mi, the chart's default | +| 32 MiB | 352 MiB | 512Mi | + +The Helm chart warns in the `ks-capabilities` ConfigMap when the operator's +memory limit is below this rule. + +## Configuration + +`capabilities.json`: + +```json +{ + "capabilities": { + "nodeSbomGeneration": "enable", + "sbomExport": "enable" + } +} +``` + +`config.json`, with the defaults: + +```json +{ + "sbomExport": { + "resyncInterval": "1h", + "maxSBOMSize": 10485760, + "dependencyTrack": { + "url": "https://dtrack.example.com", + "apiKeyFile": "/etc/sbom-export/api-key", + "rootProject": {"name": "{{ .Cluster }}", "version": "current", "create": true}, + "project": { + "name": "{{ .Cluster }}/{{ .Namespace }}/{{ .Repository }}", + "version": "{{ .Tag }}", + "parent": {"name": "{{ .Cluster }}/{{ .Namespace }}", "version": "current", "create": true} + } + } + } +} +``` + +| Setting | Default | Description | +|---------|---------|-------------| +| `sbomExport.resyncInterval` | `1h` | How often every running image is compared with Dependency-Track; also how long an image counts as running after its last pod ended | +| `sbomExport.maxSBOMSize` | `10485760` (10 MiB) | Largest SBOM, in bytes, the operator converts; see [SBOM size](#sbom-size) for the memory it needs | +| `sbomExport.dependencyTrack.url` | *required* | Base URL of the Dependency-Track API server | +| `sbomExport.dependencyTrack.apiKeyFile` | `/etc/sbom-export/api-key` | File holding the API key, read on every request, so a rotated Secret needs no restart | +| `sbomExport.dependencyTrack.rootProject.{name,version,create}` | `{{ .Cluster }}`, `current`, `true` | The root project, where created parents go | +| `sbomExport.dependencyTrack.project.{name,version}` | `{{ .Cluster }}/{{ .Namespace }}/{{ .Repository }}`, `{{ .Tag }}` | The image's project | +| `sbomExport.dependencyTrack.project.parent.{name,version,create}` | `{{ .Cluster }}/{{ .Namespace }}`, `current`, `true` | The image project's parent | + +Without `nodeSbomGeneration`, or with an invalid configuration, the operator +logs why and runs without the export. + +## Permissions in Dependency-Track + +The team that owns the API key needs `VIEW_PORTFOLIO` to look projects up, +`BOM_UPLOAD` to upload, and `PORTFOLIO_MANAGEMENT` to create projects, set the +collection logic, mark the running version latest, record the image digest and +move projects; on Dependency-Track 5, `PORTFOLIO_MANAGEMENT_CREATE` and +`PORTFOLIO_MANAGEMENT_UPDATE` replace it. The operator creates each image's +project before it uploads the BOM, so `PROJECT_CREATION_UPLOAD` is not needed, +also when the layout only names existing root and parent projects. With +portfolio access control on, the team also needs access to the existing +projects the layout names. A refused call is logged with the reason +Dependency-Track gave. + +## Logs + +Every image sent logs `sent SBOM to Dependency-Track` with its project and +version. Each resync logs what happened since the previous one: sent, +unchanged, skipped because no ready SBOM exists yet, skipped as too large, +conflicts, skipped because the layout could not be used, moved, and failed. A +failed export is retried with backoff, up to five times, and then at the next +resync. A refused API key is logged once per resync, with the permissions above. diff --git a/go.mod b/go.mod index f31759b8..552bcb1c 100644 --- a/go.mod +++ b/go.mod @@ -3,19 +3,20 @@ module github.com/kubescape/operator go 1.26.9 require ( + github.com/anchore/syft v1.55.0 github.com/armosec/armoapi-go v0.0.773 github.com/armosec/registryx v0.0.36 github.com/armosec/utils-go v0.0.58 github.com/armosec/utils-k8s-go v0.0.35 - github.com/aws/aws-sdk-go-v2 v1.41.2 - github.com/aws/aws-sdk-go-v2/config v1.32.10 - github.com/aws/aws-sdk-go-v2/credentials v1.19.10 + github.com/aws/aws-sdk-go-v2 v1.47.1 + github.com/aws/aws-sdk-go-v2/config v1.33.6 + github.com/aws/aws-sdk-go-v2/credentials v1.20.6 github.com/aws/aws-sdk-go-v2/service/ecr v1.55.3 github.com/cenkalti/backoff v2.2.1+incompatible github.com/cenkalti/backoff/v4 v4.3.0 github.com/deckarep/golang-set/v2 v2.8.0 github.com/distribution/reference v0.6.0 - github.com/fsnotify/fsnotify v1.9.0 + github.com/fsnotify/fsnotify v1.10.1 github.com/go-logr/stdr v1.2.2 github.com/go-openapi/runtime v0.33.2 github.com/google/cel-go v0.29.0 @@ -41,7 +42,7 @@ require ( go.opentelemetry.io/otel/sdk/metric v1.47.0 go.opentelemetry.io/otel/trace v1.47.0 go.opentelemetry.io/proto/otlp v1.11.1 - golang.org/x/sync v0.23.0 + golang.org/x/sync v0.24.0 google.golang.org/grpc v1.84.0 google.golang.org/protobuf v1.36.12 istio.io/pkg v0.0.0-20231221211216-7635388a563e @@ -54,13 +55,13 @@ require ( ) require ( - cloud.google.com/go/auth v0.20.0 // indirect + cloud.google.com/go/auth v0.23.3 // indirect cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect - cloud.google.com/go/compute/metadata v0.9.0 // indirect - cloud.google.com/go/container v1.46.0 // indirect + cloud.google.com/go/compute/metadata v0.9.1 // indirect + cloud.google.com/go/container v1.51.0 // indirect github.com/IBM/sarama v1.42.1 // indirect github.com/coreos/go-semver v0.3.1 // indirect - github.com/coreos/go-systemd/v22 v22.6.0 // indirect + github.com/coreos/go-systemd/v22 v22.7.0 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/eapache/go-resiliency v1.4.0 // indirect github.com/eapache/go-xerial-snappy v0.0.0-20230731223053-c322873962e3 // indirect @@ -93,10 +94,12 @@ require ( require ( cel.dev/expr v0.25.2 // indirect cloud.google.com/go v0.123.0 // indirect - cloud.google.com/go/artifactregistry v1.20.0 // indirect - cloud.google.com/go/iam v1.5.3 // indirect - cloud.google.com/go/longrunning v0.8.0 // indirect - cyphar.com/go-pathrs v0.2.1 // indirect + cloud.google.com/go/artifactregistry v1.26.0 // indirect + cloud.google.com/go/iam v1.12.0 // indirect + cloud.google.com/go/longrunning v1.2.0 // indirect + cloud.google.com/go/monitoring v1.30.0 // indirect + cloud.google.com/go/storage v1.68.0 // indirect + cyphar.com/go-pathrs v0.2.5 // indirect dario.cat/mergo v1.0.2 // indirect github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 // indirect github.com/AdamKorcz/go-118-fuzz-build v0.0.0-20250520111509-a70c2aa677fa // indirect @@ -109,80 +112,102 @@ require ( github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/containerservice/armcontainerservice/v2 v2.4.0 // indirect github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 // indirect - github.com/Masterminds/semver/v3 v3.4.0 // indirect - github.com/Microsoft/go-winio v0.6.2 // indirect - github.com/Microsoft/hcsshim v0.14.0-rc.1 // indirect + github.com/CycloneDX/cyclonedx-go v0.12.0 // indirect + github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.34.0 // indirect + github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.57.0 // indirect + github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.57.0 // indirect + github.com/Masterminds/semver/v3 v3.5.0 // indirect + github.com/Microsoft/go-winio v0.6.3-0.20251027160822-ad3df93bed29 // indirect + github.com/Microsoft/hcsshim v0.15.0-rc.4 // indirect github.com/NYTimes/gziphandler v1.1.1 // indirect + github.com/STARRY-S/zip v0.2.3 // indirect github.com/acobaugh/osrelease v0.1.0 // indirect + github.com/adrg/xdg v0.5.3 // indirect github.com/agnivade/levenshtein v1.2.1 // indirect - github.com/anchore/go-logger v0.0.0-20250318195838-07ae343dd722 // indirect - github.com/anchore/packageurl-go v0.1.1-0.20250220190351-d62adb6e1115 // indirect - github.com/anchore/stereoscope v0.1.22 // indirect - github.com/anchore/syft v1.42.3 // indirect + github.com/anchore/clio v0.1.1 // indirect + github.com/anchore/fangs v0.1.1 // indirect + github.com/anchore/go-homedir v0.1.1 // indirect + github.com/anchore/go-logger v0.2.0 // indirect + github.com/anchore/go-sync v0.1.2 // indirect + github.com/anchore/packageurl-go v0.2.0 // indirect + github.com/anchore/stereoscope v0.3.4 // indirect + github.com/andybalholm/brotli v1.2.0 // indirect github.com/antlr4-go/antlr/v4 v4.13.1 // indirect github.com/armosec/gojay v1.2.17 // indirect github.com/aws/aws-sdk-go v1.55.7 // indirect - github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.18 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.18 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.18 // indirect - github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 // indirect + github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.20 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.20.1 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.5.4 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.8.4 // indirect + github.com/aws/aws-sdk-go-v2/internal/v4a v1.5.4 // indirect github.com/aws/aws-sdk-go-v2/service/ecs v1.71.0 // indirect github.com/aws/aws-sdk-go-v2/service/eks v1.48.5 // indirect - github.com/aws/aws-sdk-go-v2/service/iam v1.35.3 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.5 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.18 // indirect - github.com/aws/aws-sdk-go-v2/service/signin v1.0.6 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.30.11 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.15 // indirect - github.com/aws/aws-sdk-go-v2/service/sts v1.41.7 // indirect - github.com/aws/smithy-go v1.24.1 // indirect + github.com/aws/aws-sdk-go-v2/service/iam v1.56.1 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.19 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.11.5 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.14.4 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.20.4 // indirect + github.com/aws/aws-sdk-go-v2/service/s3 v1.114.0 // indirect + github.com/aws/aws-sdk-go-v2/service/signin v1.10.1 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.38.1 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.43.1 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.51.1 // indirect + github.com/aws/smithy-go v1.28.1 // indirect github.com/becheran/wildmatch-go v1.0.0 // indirect github.com/beorn7/perks v1.0.1 // indirect + github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect github.com/blang/semver v3.5.1+incompatible // indirect github.com/blang/semver/v4 v4.0.0 // indirect - github.com/bmatcuk/doublestar/v4 v4.10.0 // indirect + github.com/bmatcuk/doublestar/v4 v4.10.2 // indirect + github.com/bodgit/plumbing v1.3.0 // indirect + github.com/bodgit/sevenzip v1.6.1 // indirect + github.com/bodgit/windows v1.0.1 // indirect github.com/briandowns/spinner v1.23.2 // indirect github.com/cenkalti/backoff/v5 v5.0.3 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cilium/cilium v1.17.17 // indirect github.com/cilium/ebpf v0.22.0 // indirect - github.com/containerd/cgroups/v3 v3.1.2 // indirect + github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2 // indirect + github.com/containerd/cgroups/v3 v3.1.3 // indirect github.com/containerd/containerd v1.7.36 // indirect - github.com/containerd/containerd/api v1.10.0 // indirect - github.com/containerd/continuity v0.4.5 // indirect + github.com/containerd/containerd/api v1.12.0 // indirect + github.com/containerd/continuity v0.5.0 // indirect github.com/containerd/errdefs v1.0.0 // indirect github.com/containerd/errdefs/pkg v0.3.0 // indirect github.com/containerd/fifo v1.1.0 // indirect - github.com/containerd/log v0.1.0 // indirect - github.com/containerd/platforms v1.0.0-rc.2 // indirect - github.com/containerd/stargz-snapshotter/estargz v0.18.2 // indirect - github.com/containerd/ttrpc v1.2.7 // indirect - github.com/containerd/typeurl/v2 v2.2.3 // indirect + github.com/containerd/log v0.2.0 // indirect + github.com/containerd/platforms v1.0.0-rc.5 // indirect + github.com/containerd/ttrpc v1.2.9 // indirect + github.com/containerd/typeurl/v2 v2.3.0 // indirect github.com/containers/common v0.64.2 // indirect github.com/coreos/go-oidc/v3 v3.17.0 // indirect github.com/cpuguy83/dockercfg v0.3.2 // indirect - github.com/cyphar/filepath-securejoin v0.6.0 // indirect + github.com/cyphar/filepath-securejoin v0.7.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect - github.com/docker/cli v29.3.0+incompatible // indirect - github.com/docker/distribution v2.8.3+incompatible // indirect + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.1 // indirect + github.com/diskfs/go-diskfs v1.9.4 // indirect + github.com/docker/cli v29.8.2+incompatible // indirect github.com/docker/docker v28.5.2+incompatible // indirect - github.com/docker/docker-credential-helpers v0.9.5 // indirect - github.com/docker/go-connections v0.6.0 // indirect + github.com/docker/docker-credential-helpers v0.9.8 // indirect + github.com/docker/go-connections v0.8.1 // indirect github.com/docker/go-events v0.0.0-20250114142523-c867878c5e32 // indirect github.com/docker/go-units v0.5.0 // indirect + github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 // indirect github.com/ebitengine/purego v0.8.4 // indirect github.com/emicklei/go-restful/v3 v3.12.2 // indirect + github.com/envoyproxy/go-control-plane/envoy v1.37.0 // indirect + github.com/envoyproxy/protoc-gen-validate v1.3.3 // indirect github.com/facebookincubator/nvdtools v0.1.5 // indirect github.com/fatih/color v1.19.0 // indirect + github.com/felixge/fgprof v0.9.5 // indirect github.com/felixge/httpsnoop v1.1.0 // indirect github.com/francoispqt/gojay v1.2.13 // indirect github.com/fxamacker/cbor/v2 v2.9.0 // indirect - github.com/gabriel-vasile/mimetype v1.4.13 // indirect - github.com/github/go-spdx/v2 v2.4.0 // indirect + github.com/gabriel-vasile/mimetype v1.4.15 // indirect + github.com/github/go-spdx/v2 v2.7.1 // indirect github.com/go-errors/errors v1.5.1 // indirect github.com/go-gota/gota v0.12.0 // indirect - github.com/go-ini/ini v1.67.0 // indirect - github.com/go-jose/go-jose/v4 v4.1.4 // indirect + github.com/go-jose/go-jose/v4 v4.1.5 // indirect github.com/go-logr/logr v1.4.4 // indirect github.com/go-ole/go-ole v1.2.6 // indirect github.com/go-openapi/analysis v1.0.0 // indirect @@ -208,43 +233,61 @@ require ( github.com/go-openapi/validate v1.0.0 // indirect github.com/go-viper/mapstructure/v2 v2.5.0 // indirect github.com/gobwas/glob v0.2.3 // indirect + github.com/goccy/go-json v0.10.6 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/gohugoio/hashstructure v0.6.0 // indirect - github.com/golang-jwt/jwt/v5 v5.3.0 // indirect - github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect + github.com/golang-jwt/jwt/v5 v5.3.1 // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/golang/snappy v1.0.0 // indirect github.com/google/btree v1.1.3 // indirect github.com/google/gnostic-models v0.7.0 // indirect github.com/google/go-cmp v0.7.0 // indirect - github.com/google/go-containerregistry v0.21.2 // indirect + github.com/google/go-containerregistry v0.22.1 // indirect github.com/google/licensecheck v0.3.1 // indirect - github.com/google/s2a-go v0.1.9 // indirect - github.com/googleapis/enterprise-certificate-proxy v0.3.15 // indirect - github.com/googleapis/gax-go/v2 v2.22.0 // indirect - github.com/gorilla/mux v1.8.1 // indirect + github.com/google/pprof v0.0.0-20260802141513-ef3492d7dac3 // indirect + github.com/google/s2a-go v0.1.10 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.3.22 // indirect + github.com/googleapis/gax-go/v2 v2.24.1 // indirect + github.com/gookit/color v1.6.2 // indirect github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.31.0 // indirect + github.com/hashicorp/aws-sdk-go-base/v2 v2.0.0-beta.74 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect + github.com/hashicorp/go-cleanhttp v0.5.2 // indirect + github.com/hashicorp/go-getter v1.8.10 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect - github.com/hashicorp/go-version v1.8.0 // indirect + github.com/hashicorp/go-version v1.9.0 // indirect + github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect + github.com/iancoleman/strcase v0.3.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/inspektor-gadget/inspektor-gadget v0.45.1-0.20251020222545-c91c23581ebf // indirect github.com/jinzhu/copier v0.4.0 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/klauspost/compress v1.18.7 // indirect + github.com/klauspost/compress v1.20.1 // indirect + github.com/klauspost/pgzip v1.2.6 // indirect github.com/kubescape/kubescape/v3 v3.0.4 // indirect github.com/kubescape/rbac-utils v0.0.21-0.20230806101615-07e36f555520 // indirect github.com/kubescape/regolibrary v1.0.317-0.20240320124840-1d84ac7186ea // indirect github.com/kylelemons/godebug v1.1.0 // indirect + github.com/lestrrat-go/blackmagic v1.0.4 // indirect + github.com/lestrrat-go/dsig v1.2.1 // indirect + github.com/lestrrat-go/dsig-secp256k1 v1.0.0 // indirect + github.com/lestrrat-go/httpcc v1.0.1 // indirect + github.com/lestrrat-go/httprc/v3 v3.0.5 // indirect + github.com/lestrrat-go/jwx/v3 v3.1.1 // indirect + github.com/lestrrat-go/option/v2 v2.0.0 // indirect github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect github.com/mackerelio/go-osstat v0.2.5 // indirect - github.com/magiconair/properties v1.8.10 // indirect + github.com/magiconair/properties v1.18.12 // indirect github.com/mattn/go-colorable v0.1.16 // indirect github.com/mattn/go-isatty v0.0.24 // indirect + github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect + github.com/mholt/archives v0.1.5 // indirect + github.com/mikelolasagasti/xz v1.0.1 // indirect + github.com/minio/minlz v1.0.1 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect @@ -255,46 +298,53 @@ require ( github.com/moby/sys/sequential v0.7.0 // indirect github.com/moby/sys/signal v0.7.1 // indirect github.com/moby/sys/user v0.4.1 // indirect - github.com/moby/sys/userns v0.1.0 // indirect + github.com/moby/sys/userns v0.2.1 // indirect github.com/moby/term v0.5.2 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect github.com/morikuni/aec v1.0.0 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/nwaples/rardecode/v2 v2.4.1 // indirect github.com/oklog/ulid/v2 v2.1.2 // indirect github.com/olvrng/ujson v1.1.0 // indirect - github.com/open-policy-agent/opa v1.4.0 // indirect + github.com/open-policy-agent/opa v1.19.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.1 // indirect github.com/opencontainers/runtime-spec v1.3.0 // indirect - github.com/opencontainers/selinux v1.13.1 // indirect - github.com/pelletier/go-toml/v2 v2.2.4 // indirect + github.com/opencontainers/selinux v1.15.1 // indirect + github.com/pborman/indent v1.2.1 // indirect + github.com/pelletier/go-toml/v2 v2.4.3 // indirect github.com/peterbourgon/diskv v2.0.1+incompatible // indirect github.com/petermattis/goid v0.0.0-20241211131331-93ee7e083c43 // indirect github.com/picatz/xcel v0.0.0-20250816143731-885b5f678a12 // indirect - github.com/pierrec/lz4/v4 v4.1.22 // indirect + github.com/pierrec/lz4/v4 v4.1.26 // indirect github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect github.com/pkg/errors v0.9.1 // indirect + github.com/pkg/profile v1.7.0 // indirect + github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect - github.com/prometheus/client_golang v1.23.2 // indirect + github.com/prometheus/client_golang v1.24.0 // indirect github.com/prometheus/client_model v0.6.2 // indirect - github.com/prometheus/common v0.67.4 // indirect - github.com/prometheus/procfs v0.19.2 // indirect - github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect + github.com/prometheus/common v0.70.0 // indirect + github.com/prometheus/procfs v0.21.1 // indirect + github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9 // indirect github.com/s3rj1k/go-fanotify/fanotify v0.0.0-20240229202106-bca3154da60a // indirect github.com/sagikazarmark/locafero v0.11.0 // indirect github.com/sasha-s/go-deadlock v0.3.5 // indirect github.com/scylladb/go-set v1.0.3-0.20200225121959-cc7b2070d91e // indirect - github.com/seccomp/libseccomp-golang v0.11.0 // indirect + github.com/seccomp/libseccomp-golang v0.11.1 // indirect + github.com/segmentio/asm v1.2.1 // indirect github.com/shirou/gopsutil/v4 v4.25.6 // indirect - github.com/sirupsen/logrus v1.9.4 // indirect + github.com/sirupsen/logrus v1.10.2 // indirect + github.com/sorairolake/lzip-go v0.3.8 // indirect github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect github.com/spf13/afero v1.15.0 // indirect github.com/spf13/cast v1.10.0 // indirect github.com/spf13/cobra v1.10.2 // indirect github.com/spf13/pflag v1.0.10 // indirect + github.com/spiffe/go-spiffe/v2 v2.8.1 // indirect github.com/stripe/stripe-go/v74 v74.30.0 // indirect github.com/subosito/gotenv v1.6.0 // indirect github.com/sylabs/squashfs v1.0.6 // indirect @@ -303,11 +353,12 @@ require ( github.com/therootcompany/xz v1.0.1 // indirect github.com/tklauser/go-sysconf v0.3.12 // indirect github.com/tklauser/numcpus v0.11.0 // indirect - github.com/ulikunitz/xz v0.5.15 // indirect + github.com/ulikunitz/xz v0.5.17 // indirect github.com/uptrace/opentelemetry-go-extra/otelutil v0.4.0 // indirect github.com/uptrace/opentelemetry-go-extra/otelzap v0.4.0 // indirect github.com/uptrace/uptrace-go v1.47.0 // indirect - github.com/vbatts/tar-split v0.12.2 // indirect + github.com/valyala/fastjson v1.6.10 // indirect + github.com/vektah/gqlparser/v2 v2.5.36 // indirect github.com/vishvananda/netlink v1.3.1 // indirect github.com/vishvananda/netns v0.0.5 // indirect github.com/wagoodman/go-partybus v0.0.0-20230516145632-8ccac152c651 // indirect @@ -316,13 +367,14 @@ require ( github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect github.com/xlab/treeprint v1.2.0 // indirect + github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect github.com/yashtewari/glob-intersection v0.2.0 // indirect github.com/yl2chen/cidranger v1.0.2 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect go.mongodb.org/mongo-driver v1.17.7 // indirect - go.opencensus.io v0.24.0 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/contrib/bridges/otelslog v0.21.0 // indirect + go.opentelemetry.io/contrib/detectors/gcp v1.44.0 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.71.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.71.0 // indirect go.opentelemetry.io/contrib/instrumentation/runtime v0.72.0 // indirect @@ -338,21 +390,22 @@ require ( go.opentelemetry.io/otel/sdk/log v1.47.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.28.0 // indirect - go.yaml.in/yaml/v2 v2.4.3 // indirect + go.yaml.in/yaml/v2 v2.4.4 // indirect go.yaml.in/yaml/v3 v3.0.5 // indirect + go4.org v0.0.0-20230225012048-214862532bf5 // indirect go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect - golang.org/x/crypto v0.57.0 // indirect + golang.org/x/crypto v0.58.0 // indirect golang.org/x/exp v0.0.0-20260908205506-85c1c2202aba // indirect golang.org/x/mod v0.41.0 // indirect - golang.org/x/net v0.59.0 // indirect - golang.org/x/oauth2 v0.36.0 // indirect - golang.org/x/sys v0.48.0 // indirect - golang.org/x/term v0.46.0 // indirect - golang.org/x/text v0.42.0 // indirect - golang.org/x/time v0.15.0 // indirect + golang.org/x/net v0.61.0 // indirect + golang.org/x/oauth2 v0.37.0 // indirect + golang.org/x/sys v0.49.0 // indirect + golang.org/x/term v0.47.0 // indirect + golang.org/x/text v0.43.0 // indirect + golang.org/x/time v0.16.0 // indirect gonum.org/v1/gonum v0.17.0 // indirect - google.golang.org/api v0.278.0 // indirect - google.golang.org/genproto v0.0.0-20260319201613-d00831a3d3e7 // indirect + google.golang.org/api v0.299.0 // indirect + google.golang.org/genproto v0.0.0-20260715232425-e75dac1f907d // indirect google.golang.org/genproto/googleapis/api v0.0.0-20261005182115-fad411399dd8 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20261005182115-fad411399dd8 // indirect gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect @@ -388,3 +441,15 @@ replace github.com/opencontainers/runtime-spec => github.com/opencontainers/runt // Temporary concurrency fix: https://github.com/goradd/maps/pull/22 // Remove this replacement when an upstream release includes the fix. replace github.com/goradd/maps => github.com/matthyx/maps v0.0.0-20260915111345-e9181ad40421 + +// The SBOM export decodes the documents node-agent's syft writes, so it builds +// against the same syft as node-agent and kubevuln. +replace github.com/anchore/syft => github.com/kubescape/syft v1.55.0-ks.3 + +// syft v1.55.0 declares containerd/v2 v2.4.1, whose containerd/api v1.12.0 no +// longer compiles with the containerd v1.7.36 that inspektor-gadget imports. +// syft compiles against v2.2.0. Same pins as node-agent's go.mod, which explains +// them in full. +replace github.com/containerd/containerd/v2 => github.com/containerd/containerd/v2 v2.2.0 + +replace github.com/containerd/containerd/api => github.com/containerd/containerd/api v1.10.0 diff --git a/go.sum b/go.sum index 306100eb..c3c5049a 100644 --- a/go.sum +++ b/go.sum @@ -33,10 +33,10 @@ cloud.google.com/go v0.98.0/go.mod h1:ua6Ush4NALrHk5QXDWnjvZHN93OuF0HfuEPq9I1X0c cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA= cloud.google.com/go v0.123.0 h1:2NAUJwPR47q+E35uaJeYoNhuNEM9kM8SjgRgdeOJUSE= cloud.google.com/go v0.123.0/go.mod h1:xBoMV08QcqUGuPW65Qfm1o9Y4zKZBpGS+7bImXLTAZU= -cloud.google.com/go/artifactregistry v1.20.0 h1:j/XQiQfaeTyQeNj3HNk4iDFREVnY/fxkHIjsxpaDs8A= -cloud.google.com/go/artifactregistry v1.20.0/go.mod h1:0G9wdbGyDFkvrYH+2AlQs9MuTJdbY8Vg45M8VjlI8rc= -cloud.google.com/go/auth v0.20.0 h1:kXTssoVb4azsVDoUiF8KvxAqrsQcQtB53DcSgta74CA= -cloud.google.com/go/auth v0.20.0/go.mod h1:942/yi/itH1SsmpyrbnTMDgGfdy2BUqIKyd0cyYLc5Q= +cloud.google.com/go/artifactregistry v1.26.0 h1:iq5kkdY2FJY8RkkNghUelb3EOcqyy7dVhfDGVk/Xw9g= +cloud.google.com/go/artifactregistry v1.26.0/go.mod h1:c5FPi5GtDBP+OAr5kKhCBNQDT9ZgAyobXQjekx93VWs= +cloud.google.com/go/auth v0.23.3 h1:UMK+oBtuNGMCR/6i6mmySUItqjOazpJrbmZyhGbGBWo= +cloud.google.com/go/auth v0.23.3/go.mod h1:fClbry28fo7XkxhSeT6AQtAVAp6Jy0fW9N99PoPNPFM= cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc= cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= @@ -45,17 +45,21 @@ cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvf cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= -cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs= -cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10= -cloud.google.com/go/container v1.46.0 h1:xX94Lo3xrS5OkdMWKvpEVAbBwjN9uleVv6vOi02fL4s= -cloud.google.com/go/container v1.46.0/go.mod h1:A7gMqdQduTk46+zssWDTKbGS2z46UsJNXfKqvMI1ZO4= +cloud.google.com/go/compute/metadata v0.9.1 h1:CTE1OWBQ0vnF5uHwdFAQJvMQ0Fi/KRcqqKTo9V0F8Ik= +cloud.google.com/go/compute/metadata v0.9.1/go.mod h1:NtnlvB6X3t4R6xSWyVX/ZWk493PCxGQlhI/iqxh4M8I= +cloud.google.com/go/container v1.51.0 h1:KdeWHqwlOvABdhkhSTSb4QFySMOuXnc3UUoQMmq9lic= +cloud.google.com/go/container v1.51.0/go.mod h1:EvqoT2eXfxLweXXUlhAMGR0sOAB00XPzEjoL01esSDs= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= cloud.google.com/go/firestore v1.6.1/go.mod h1:asNXNOzBdyVQmEU+ggO8UPodTkEVFW5Qx+rwHnAz+EY= -cloud.google.com/go/iam v1.5.3 h1:+vMINPiDF2ognBJ97ABAYYwRgsaqxPbQDlMnbHMjolc= -cloud.google.com/go/iam v1.5.3/go.mod h1:MR3v9oLkZCTlaqljW6Eb2d3HGDGK5/bDv93jhfISFvU= -cloud.google.com/go/longrunning v0.8.0 h1:LiKK77J3bx5gDLi4SMViHixjD2ohlkwBi+mKA7EhfW8= -cloud.google.com/go/longrunning v0.8.0/go.mod h1:UmErU2Onzi+fKDg2gR7dusz11Pe26aknR4kHmJJqIfk= +cloud.google.com/go/iam v1.12.0 h1:Aki3bX9aHUDKPHfnRJfDcTdVedvy6quGBQcTqx3DRXk= +cloud.google.com/go/iam v1.12.0/go.mod h1:FEZ4lXpADAC2AIpQY7LANNjjwyQ2jK439CI2VaD+sLY= +cloud.google.com/go/logging v1.19.0 h1:NCqhdVUg3wQ8Cobdf16FDSuTGi3+6+hdSBHrY5TsR6Q= +cloud.google.com/go/logging v1.19.0/go.mod h1:i40NZCHC9Gqvod4yE+yQfDWwlgwW/SrshkkGibCHxcA= +cloud.google.com/go/longrunning v1.2.0 h1:WjYH3YHBGCxGJP9M4dWGHBfXr/cFIjMkNgWcJj7/iMM= +cloud.google.com/go/longrunning v1.2.0/go.mod h1:5KMQALFGOCtFoi2xSOA1u3H7WKlhmckgiyFw7+LGQp0= +cloud.google.com/go/monitoring v1.30.0 h1:r/d+JUbyKmJ8b07iznuKfzVzrIXTWxHQ3lBRm3x2LlY= +cloud.google.com/go/monitoring v1.30.0/go.mod h1:htlUR0QWVMrjFzZmN4LGnMAve9xB/eduwjmINxVZ8RM= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= @@ -65,8 +69,12 @@ cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0Zeo cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= -cyphar.com/go-pathrs v0.2.1 h1:9nx1vOgwVvX1mNBWDu93+vaceedpbsDqo+XuBGL40b8= -cyphar.com/go-pathrs v0.2.1/go.mod h1:y8f1EMG7r+hCuFf/rXsKqMJrJAUoADZGNh5/vZPKcGc= +cloud.google.com/go/storage v1.68.0 h1:gqrAMJ51OZjYgU6AJ2U60um90YQhSjq8HEIQNtJ4C/8= +cloud.google.com/go/storage v1.68.0/go.mod h1:UsS9OgFg/XHOSYakQ8ZtLWWeyGkk1WnmD/GsGfN0BHM= +cloud.google.com/go/trace v1.16.0 h1:GmQovzFc5F0CNfl0VLgL64aoTtu7xsM0YajW2GlG9+E= +cloud.google.com/go/trace v1.16.0/go.mod h1:r+bdAn16dKLSV1G2D5v3e58IlQlizfxWrUfjx7kM7X0= +cyphar.com/go-pathrs v0.2.5 h1:SnX9FBvnoyn3lUs1dkMgZ52bAETpirNu3FTRh5HlRik= +cyphar.com/go-pathrs v0.2.5/go.mod h1:y8f1EMG7r+hCuFf/rXsKqMJrJAUoADZGNh5/vZPKcGc= dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8= dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA= dmitri.shuralyov.com/app/changes v0.0.0-20180602232624-0a106ad413e3/go.mod h1:Yl+fi1br7+Rr3LqpNJf1/uxUdtRUV+Tnj0o93V2B9MU= @@ -107,19 +115,39 @@ github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 h1:XHOnouVk1mx github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk= +github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/CycloneDX/cyclonedx-go v0.12.0 h1:/7Jum36UA6V043tQZ/fE3jf+Nf9gn/qxUFfd7QReMy8= +github.com/CycloneDX/cyclonedx-go v0.12.0/go.mod h1:V2577HhxDDCDLYfkm55WJrz16nHTfyQZwcWUBSG7Z28= github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.34.0 h1:yzIYdwuro811Z27D3T80Wkd3rqZzb0K43nner7Eh1yE= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.34.0/go.mod h1:pJTkW8hEUIIi3Pf65lPZOnn4Y81yCllX6IWk2jNXdkM= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.57.0 h1:jLdiS1vO+XJFyDSWRHBx56r4s/NNtcl5J6KyCcWUX/w= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.57.0/go.mod h1:8lmpHY+1VRoteiOwyrQMDt1YGXOrFKCz+1wJW7n3ODY= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.57.0 h1:cSjUzZ7KU8hicTgzaSv9NmSyM9fTVK3y5lsBUl3wOis= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.57.0/go.mod h1:dzcEjy1WJ0Q4u9twNR3LcLhNoYMRCrMCMafpxa0TjPQ= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.57.0 h1:RoO5+d7uCmDqovLrHCr2/BuViUXvdcrNxyNM1pN9dDQ= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.57.0/go.mod h1:YqwkQPrWSC7+byyc1VlKbWLBF5JsW5IoL6xUkemYSXk= github.com/IBM/sarama v1.42.1 h1:wugyWa15TDEHh2kvq2gAy1IHLjEjuYOYgXz/ruC/OSQ= github.com/IBM/sarama v1.42.1/go.mod h1:Xxho9HkHd4K/MDUo/T/sOqwtX/17D33++E9Wib6hUdQ= -github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0= -github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= -github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= -github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= -github.com/Microsoft/hcsshim v0.14.0-rc.1 h1:qAPXKwGOkVn8LlqgBN8GS0bxZ83hOJpcjxzmlQKxKsQ= -github.com/Microsoft/hcsshim v0.14.0-rc.1/go.mod h1:hTKFGbnDtQb1wHiOWv4v0eN+7boSWAHyK/tNAaYZL0c= +github.com/Masterminds/semver/v3 v3.5.0 h1:kQceYJfbupGfZOKZQg0kou0DgAKhzDg2NZPAwZ/2OOE= +github.com/Masterminds/semver/v3 v3.5.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= +github.com/Microsoft/go-winio v0.6.3-0.20251027160822-ad3df93bed29 h1:0kQAzHq8vLs7Pptv+7TxjdETLf/nIqJpIB4oC6Ba4vY= +github.com/Microsoft/go-winio v0.6.3-0.20251027160822-ad3df93bed29/go.mod h1:ZWa7ssZJT30CCDGJ7fk/2SBTq9BIQrrVjrcss0UW2s0= +github.com/Microsoft/hcsshim v0.15.0-rc.4 h1:aZFX4LH0S20Lgjq0wG61StIClj7im4yzrxIClkaR8Z8= +github.com/Microsoft/hcsshim v0.15.0-rc.4/go.mod h1:BA9CBztgu4h/6Jsvo1O1M4qjWw09PoYpaEYgexPE578= github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I= github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/ProtonMail/go-crypto v1.4.1 h1:9RfcZHqEQUvP8RzecWEUafnZVtEvrBVL9BiF67IQOfM= +github.com/ProtonMail/go-crypto v1.4.1/go.mod h1:e1OaTyu5SYVrO9gKOEhTc+5UcXtTUa+P3uLudwcgPqo= +github.com/STARRY-S/zip v0.2.3 h1:luE4dMvRPDOWQdeDdUxUoZkzUIpTccdKdhHHsQJ1fm4= +github.com/STARRY-S/zip v0.2.3/go.mod h1:lqJ9JdeRipyOQJrYSOtpNAiaesFO6zVDsE8GIGFaoSk= +github.com/VividCortex/ewma v1.2.0 h1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1ow= +github.com/VividCortex/ewma v1.2.0/go.mod h1:nz4BbCtbLyFDeC9SUHbtcT5644juEuWfUAUnGx7j5l4= +github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8= +github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo= github.com/acobaugh/osrelease v0.1.0 h1:Yb59HQDGGNhCj4suHaFQQfBps5wyoKLSSX/J/+UifRE= github.com/acobaugh/osrelease v0.1.0/go.mod h1:4bFEs0MtgHNHBrmHCt67gNisnabCRAlzdVasCEGHTWY= github.com/adrg/xdg v0.5.3 h1:xRnxJXne7+oWDatRhR1JLnvuccuIeCoBu2rtuLqQB78= @@ -131,21 +159,27 @@ github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuy github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/anchore/clio v0.0.0-20250715152405-a0fa658e5084 h1:7DUAXEdAxoANPlDgxYiaSRKnWnTygvdrrWhnmvEjNLg= -github.com/anchore/clio v0.0.0-20250715152405-a0fa658e5084/go.mod h1:42dWox8z4//b898OIELsQnSdYq9q1aCXkwp5fKF+BEU= -github.com/anchore/fangs v0.0.0-20250716230140-94c22408c232 h1:aVC6r9h5wGNh8BYTW3CXxOdPoZzY/bBRWne1NvSTlO8= -github.com/anchore/fangs v0.0.0-20250716230140-94c22408c232/go.mod h1:Zees1AEKNpXIRgdVAMYWITncarLFiPOtEQ7rl45V/h0= -github.com/anchore/go-homedir v0.0.0-20250319154043-c29668562e4d h1:gT69osH9AsdpOfqxbRwtxcNnSZ1zg4aKy2BevO3ZBdc= -github.com/anchore/go-homedir v0.0.0-20250319154043-c29668562e4d/go.mod h1:PhSnuFYknwPZkOWKB1jXBNToChBA+l0FjwOxtViIc50= -github.com/anchore/go-logger v0.0.0-20250318195838-07ae343dd722 h1:2SqmFgE7h+Ql4VyBzhjLkRF/3gDrcpUBj8LjvvO6OOM= -github.com/anchore/go-logger v0.0.0-20250318195838-07ae343dd722/go.mod h1:oFuE8YuTCM+spgMXhePGzk3asS94yO9biUfDzVTFqNw= -github.com/anchore/packageurl-go v0.1.1-0.20250220190351-d62adb6e1115 h1:ZyRCmiEjnoGJZ1+Ah0ZZ/mKKqNhGcUZBl0s7PTTDzvY= -github.com/anchore/packageurl-go v0.1.1-0.20250220190351-d62adb6e1115/go.mod h1:KoYIv7tdP5+CC9VGkeZV4/vGCKsY55VvoG+5dadg4YI= -github.com/anchore/stereoscope v0.1.22 h1:L807G/kk0WZzOCGuRGF7knxMKzwW2PGdbPVRystryd8= -github.com/anchore/stereoscope v0.1.22/go.mod h1:FikPtAb/WnbqwgLHAvQA9O+fWez0K4pbjxzghz++iy4= -github.com/anchore/syft v1.42.3 h1:eIeeGyqfXm/C8wpBWU50xFbOjdL37VbLatMj9nEJ6n4= -github.com/anchore/syft v1.42.3/go.mod h1:i2PZ+276IdPcnd/n32aeIv849iO/QqdjRknbIc39yL0= +github.com/anchore/clio v0.1.1 h1:9bkYnaFbpT0J3G0qSHyQqa/lpK5zV0C9Gtgh82H/jj0= +github.com/anchore/clio v0.1.1/go.mod h1:CFPIopJlakJbBpKtAL/EPpj4W9hgBO7PfFc8N2FAtlE= +github.com/anchore/fangs v0.1.1 h1:NBTHocmWM2/0gNhebpoE/wEB0Z0nxh2lESww2o/Uf5M= +github.com/anchore/fangs v0.1.1/go.mod h1:82oZuk4+RynOvAoxnXKmI1zO5xk/kbOw+5t4fAAPSsk= +github.com/anchore/go-collections v0.1.1 h1:kGlmSehs+ctTBZr+98F2+taHeolHy+XDx1Nczl8GZc8= +github.com/anchore/go-collections v0.1.1/go.mod h1:zgDbPSNDpfU47tVo7aW+2suG7+vXB1Sq1qtDhRJknQ0= +github.com/anchore/go-homedir v0.1.1 h1:KpaO5BPrVT7cVMcEZ9KfFfEi0iuo3DtagPGl0C744X0= +github.com/anchore/go-homedir v0.1.1/go.mod h1:9B0DGhbmMAJVGGEbrlb+PkORM5eDFCEOtZ3xQ22qaLA= +github.com/anchore/go-logger v0.2.0 h1:V8Gazz+F8xwWEWz+whqt8/5163IkO4XrAie+1qbK10k= +github.com/anchore/go-logger v0.2.0/go.mod h1:omqLWBwT/B5/aTwIH6pl5WBylBzeH3BGHUTS6OFeFvY= +github.com/anchore/go-lzo v0.1.1 h1:IwL/fvkdtlIrYIXck6WxZ3nb8WjjHziYYmGxlooyOnM= +github.com/anchore/go-lzo v0.1.1/go.mod h1:3kLx0bve2oN1iDwgM1U5zGku1Tfbdb0No5qp1eL1fIk= +github.com/anchore/go-sync v0.1.2 h1:RDD5RCanrWqhbtBulqPSZpERadUlgcYjpybYzYd3Uk8= +github.com/anchore/go-sync v0.1.2/go.mod h1:grjZH059bCHM6f6jh+Y7VYvX/q8YcjkDJqG6nplHGkE= +github.com/anchore/packageurl-go v0.2.0 h1:CkrM4RMUwrEGAiE1OVlxaZNzWj0TuHRey7o4T/EAErk= +github.com/anchore/packageurl-go v0.2.0/go.mod h1:2JCgOQMIsqZ7TmliXG4PnUthPJAKE3mWQbsW2XHjAOE= +github.com/anchore/stereoscope v0.3.4 h1:dT5AfjCTXUJowMiRIc+VBQVwvwnIEwnxxwA1GwAkMxw= +github.com/anchore/stereoscope v0.3.4/go.mod h1:+HxqHTt7dvH8mea0mf3ulqkf084VvHiT1grpAQf5uEs= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= +github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ= +github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= @@ -169,48 +203,58 @@ github.com/armosec/utils-k8s-go v0.0.35 h1:CliNObhAca5UYl84m5OQecOTm9ZfMFI8648pY github.com/armosec/utils-k8s-go v0.0.35/go.mod h1:iHwR/KhMFtdd8Px1oYexLZYOHqmdknfGTZ8b7sZS0Ms= github.com/aws/aws-sdk-go v1.55.7 h1:UJrkFq7es5CShfBwlWAC8DA077vp8PyVbQd3lqLiztE= github.com/aws/aws-sdk-go v1.55.7/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= -github.com/aws/aws-sdk-go-v2 v1.41.2 h1:LuT2rzqNQsauaGkPK/7813XxcZ3o3yePY0Iy891T2ls= -github.com/aws/aws-sdk-go-v2 v1.41.2/go.mod h1:IvvlAZQXvTXznUPfRVfryiG1fbzE2NGK6m9u39YQ+S4= -github.com/aws/aws-sdk-go-v2/config v1.32.10 h1:9DMthfO6XWZYLfzZglAgW5Fyou2nRI5CuV44sTedKBI= -github.com/aws/aws-sdk-go-v2/config v1.32.10/go.mod h1:2rUIOnA2JaiqYmSKYmRJlcMWy6qTj1vuRFscppSBMcw= -github.com/aws/aws-sdk-go-v2/credentials v1.19.10 h1:EEhmEUFCE1Yhl7vDhNOI5OCL/iKMdkkYFTRpZXNw7m8= -github.com/aws/aws-sdk-go-v2/credentials v1.19.10/go.mod h1:RnnlFCAlxQCkN2Q379B67USkBMu1PipEEiibzYN5UTE= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.18 h1:Ii4s+Sq3yDfaMLpjrJsqD6SmG/Wq/P5L/hw2qa78UAY= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.18/go.mod h1:6x81qnY++ovptLE6nWQeWrpXxbnlIex+4H4eYYGcqfc= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.18 h1:F43zk1vemYIqPAwhjTjYIz0irU2EY7sOb/F5eJ3HuyM= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.18/go.mod h1:w1jdlZXrGKaJcNoL+Nnrj+k5wlpGXqnNrKoP22HvAug= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.18 h1:xCeWVjj0ki0l3nruoyP2slHsGArMxeiiaoPN5QZH6YQ= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.18/go.mod h1:r/eLGuGCBw6l36ZRWiw6PaZwPXb6YOj+i/7MizNl5/k= -github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 h1:WKuaxf++XKWlHWu9ECbMlha8WOEGm0OUEZqm4K/Gcfk= -github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4/go.mod h1:ZWy7j6v1vWGmPReu0iSGvRiise4YI5SkR3OHKTZ6Wuc= +github.com/aws/aws-sdk-go-v2 v1.47.1 h1:uOIZnp4PK3ZhKI0dNrJrhTEsLxbpXHTAJlwoS1pvAtw= +github.com/aws/aws-sdk-go-v2 v1.47.1/go.mod h1:bttEH6JqnUL8LepvDVfdrds/fZ5bCIxzpe3abyUrhDU= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.20 h1:GPRlPwz40I2B2VrBEASOA3Bi77NyeqejNLkifosX0rs= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.20/go.mod h1:g7PNzKcsOKWb4fkSRBA7BZVAS6Y8IcxzN+nRohhQ1Q8= +github.com/aws/aws-sdk-go-v2/config v1.33.6 h1:MBjkSTLczek/UgiK+EYPIoRTqE7gP8vtW3OFbFo7Nug= +github.com/aws/aws-sdk-go-v2/config v1.33.6/go.mod h1:grRAFzdAZJrwcbasJRg2MPvIrVjtlfXllHssN6+E1JE= +github.com/aws/aws-sdk-go-v2/credentials v1.20.6 h1:NpAFXCU7NzXNkdGK3zQTtsRJ+3v9tZQV0xcdRw8uBdw= +github.com/aws/aws-sdk-go-v2/credentials v1.20.6/go.mod h1:mcZCoiPnyMvP8VMNbygNX5lLqSlkYJIMPODylQMurOk= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.20.1 h1:8gALAAmacnIXh+z6VkdDanv4/IkG5APdg4DZLDTmLog= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.20.1/go.mod h1:Z7IJhJU+poOdJjUR2wpyY21ossQ1XS/R3Lk9Msq5kM4= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.5.4 h1:CLq4+8UHCI+ZZYl/EuJxXovaIVN2xeeT8JV+dsApQ5E= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.5.4/go.mod h1:Wv4q5sAM04xAMkoOedxLx2inVf6K5FdxYp+A61L+q/0= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.8.4 h1:dD4MR81I7YkpEBRk6UP9rocC2QnT3qVuXwzlYTtfGEs= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.8.4/go.mod h1:EcXV1kAFd5XwSkDHlj94gnF3q5CkJyYiIJfH8N0VmrE= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.5.4 h1:7Wo47d/xn/7KttCSBd8EGYeZ7ULRFRkUHr6vkZPBzVQ= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.5.4/go.mod h1:tDB2IVC1xC3vX8o+6uRlzhTxP3g1b77CZXFX/oD2FnQ= github.com/aws/aws-sdk-go-v2/service/ecr v1.55.3 h1:RtGctYMmkTerGClvdY6bHXdtly4FeYw9wz/NPz62LF8= github.com/aws/aws-sdk-go-v2/service/ecr v1.55.3/go.mod h1:vBfBu24Ka3/5UZtepbTV0gnc9VPLT8ok+0oDDaYAzn4= github.com/aws/aws-sdk-go-v2/service/ecs v1.71.0 h1:MzP/ElwTpINq+hS80ZQz4epKVnUTlz8Sz+P/AFORCKM= github.com/aws/aws-sdk-go-v2/service/ecs v1.71.0/go.mod h1:pMlGFDpHoLTJOIZHGdJOAWmi+xeIlQXuFTuQxs1epYE= github.com/aws/aws-sdk-go-v2/service/eks v1.48.5 h1:vMwwdzKoUBt7vMHNkF16Hh7+8ndVGOAAEgqcrbz17M4= github.com/aws/aws-sdk-go-v2/service/eks v1.48.5/go.mod h1:9dn8p15siUL80NCTPVNd+YvEpVTmWO+rboGx6qOMBa0= -github.com/aws/aws-sdk-go-v2/service/iam v1.35.3 h1:bWFkGGea2UoD/m229uuRfT0mu+6pKNB0Kq4U6j/Qz3U= -github.com/aws/aws-sdk-go-v2/service/iam v1.35.3/go.mod h1:PpmEOH3ZTQlDAezieBVdFMjPO1jovUMNPA4OpCtnwbY= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.5 h1:CeY9LUdur+Dxoeldqoun6y4WtJ3RQtzk0JMP2gfUay0= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.5/go.mod h1:AZLZf2fMaahW5s/wMRciu1sYbdsikT/UHwbUjOdEVTc= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.18 h1:LTRCYFlnnKFlKsyIQxKhJuDuA3ZkrDQMRYm6rXiHlLY= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.18/go.mod h1:XhwkgGG6bHSd00nO/mexWTcTjgd6PjuvWQMqSn2UaEk= -github.com/aws/aws-sdk-go-v2/service/signin v1.0.6 h1:MzORe+J94I+hYu2a6XmV5yC9huoTv8NRcCrUNedDypQ= -github.com/aws/aws-sdk-go-v2/service/signin v1.0.6/go.mod h1:hXzcHLARD7GeWnifd8j9RWqtfIgxj4/cAtIVIK7hg8g= -github.com/aws/aws-sdk-go-v2/service/sso v1.30.11 h1:7oGD8KPfBOJGXiCoRKrrrQkbvCp8N++u36hrLMPey6o= -github.com/aws/aws-sdk-go-v2/service/sso v1.30.11/go.mod h1:0DO9B5EUJQlIDif+XJRWCljZRKsAFKh3gpFz7UnDtOo= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.15 h1:edCcNp9eGIUDUCrzoCu1jWAXLGFIizeqkdkKgRlJwWc= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.15/go.mod h1:lyRQKED9xWfgkYC/wmmYfv7iVIM68Z5OQ88ZdcV1QbU= -github.com/aws/aws-sdk-go-v2/service/sts v1.41.7 h1:NITQpgo9A5NrDZ57uOWj+abvXSb83BbyggcUBVksN7c= -github.com/aws/aws-sdk-go-v2/service/sts v1.41.7/go.mod h1:sks5UWBhEuWYDPdwlnRFn1w7xWdH29Jcpe+/PJQefEs= -github.com/aws/smithy-go v1.24.1 h1:VbyeNfmYkWoxMVpGUAbQumkODcYmfMRfZ8yQiH30SK0= -github.com/aws/smithy-go v1.24.1/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0= +github.com/aws/aws-sdk-go-v2/service/iam v1.56.1 h1:SLGzTAo/8J81zHUisaiKY6gF804fPsHZBxAZr0uquHM= +github.com/aws/aws-sdk-go-v2/service/iam v1.56.1/go.mod h1:Cc31Ak8Ps/0Vi4QqN3qIuqSHXi/MGLw/Cl6vzUwuqwk= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.19 h1:bAdDl/HkGCcGPoe25ToSHEw23VIxt6CT5fLcg111BKg= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.19/go.mod h1:KaUzbLxv4CeSxh6ZCl9B4m7CuFenS8kUEaDs+f/DQr4= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.11.5 h1:/TYsZXdA8UTa+WCtCYSAJIr1vwl0+eho6TUgJGwFFO8= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.11.5/go.mod h1:qPqp1Uwd/BqdhPufv6oem9j5J7HNsgc2V22dUiDPn+s= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.14.4 h1:29SvnfGhXjTl8ONxFwbj2rs6lbhiFXD2CgFQmbT/bXY= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.14.4/go.mod h1:wm04I5DMuNVvZHFe/dHnUxincvNbbK7AiNBbYsQivek= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.20.4 h1:pPiWfgeNxqluKEph7hvU88kuGKBPOWzO+Dk9t2zqqNs= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.20.4/go.mod h1:YlwGoIUDG/3kBQbdNOVs/xKZ9J01G8e/6D1mRBj9uTk= +github.com/aws/aws-sdk-go-v2/service/s3 v1.114.0 h1:VMAdYqr4Jn/8ATs9BHC5riwrs0d6m1Z2ohFriSwZwm0= +github.com/aws/aws-sdk-go-v2/service/s3 v1.114.0/go.mod h1:9APRWGLFITKD+xzWSIyT9V7QV4bNlEuIieWlzXgGFlI= +github.com/aws/aws-sdk-go-v2/service/signin v1.10.1 h1:DzCCWLzcIRQ77F3DEUljud7bEjTgFOIKXP52NmVRyhU= +github.com/aws/aws-sdk-go-v2/service/signin v1.10.1/go.mod h1:xpo/geVldu8payT375WekctUzopG/hBU7miiqItMUlw= +github.com/aws/aws-sdk-go-v2/service/sso v1.38.1 h1:Umtl/0YZhng4xndfW3lKJrYYP7NLEjI6bGXVomwLcs0= +github.com/aws/aws-sdk-go-v2/service/sso v1.38.1/go.mod h1:rRD/dnm7q0HYE/I5TMaPgkWyyUGLcwuxHLABsLnQ3e0= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.43.1 h1:orIWdNiLgzrhu/11RcPPKO/SBzUUymbUQuZbSPImghg= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.43.1/go.mod h1:skwM/xsbR/1ReUTesv9BhpJp1VjajR7DWQnuVLwiXsQ= +github.com/aws/aws-sdk-go-v2/service/sts v1.51.1 h1:0HOqZXRvMytH6bFHVIc0oJX07sZjfhz0zXtjs6gdE8s= +github.com/aws/aws-sdk-go-v2/service/sts v1.51.1/go.mod h1:26zA0GhDrLo+yiLI2yXWxqB1PdsShfLikoI7GOEgugM= +github.com/aws/smithy-go v1.28.1 h1:R/nXH00c8qcfCzQVELtRw+eLQWtzv+VAIEFJ1/xxXlQ= +github.com/aws/smithy-go v1.28.1/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc= github.com/becheran/wildmatch-go v1.0.0 h1:mE3dGGkTmpKtT4Z+88t8RStG40yN9T+kFEGj2PZFSzA= github.com/becheran/wildmatch-go v1.0.0/go.mod h1:gbMvj0NtVdJ15Mg/mH9uxk2R1QCistMyU7d9KFzroX4= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d h1:xDfNPAt8lFiC1UJrqV3uuy861HCTo708pDMbjHHdCas= +github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d/go.mod h1:6QX/PXZ00z/TKoufEY6K/a0k6AhaJrQKdFe6OfVXsa4= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932 h1:mXoPYz/Ul5HYEDvkta6I8/rnYM5gSdSV2tJ6XbZuEtY= github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932/go.mod h1:NOuUCSz6Q9T7+igc/hlvDOUdtWKryOrtFyIVABv/p7k= @@ -218,17 +262,23 @@ github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdn github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= -github.com/bmatcuk/doublestar/v4 v4.10.0 h1:zU9WiOla1YA122oLM6i4EXvGW62DvKZVxIe6TYWexEs= -github.com/bmatcuk/doublestar/v4 v4.10.0/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= +github.com/bmatcuk/doublestar/v4 v4.10.2 h1:eF7W7HWKg3z9NrWV9pTLnNeoXaqq3Tq9DNKXVMfoCnw= +github.com/bmatcuk/doublestar/v4 v4.10.2/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= +github.com/bodgit/plumbing v1.3.0 h1:pf9Itz1JOQgn7vEOE7v7nlEfBykYqvUYioC61TwWCFU= +github.com/bodgit/plumbing v1.3.0/go.mod h1:JOTb4XiRu5xfnmdnDJo6GmSbSbtSyufrsyZFByMtKEs= +github.com/bodgit/sevenzip v1.6.1 h1:kikg2pUMYC9ljU7W9SaqHXhym5HyKm8/M/jd31fYan4= +github.com/bodgit/sevenzip v1.6.1/go.mod h1:GVoYQbEVbOGT8n2pfqCIMRUaRjQ8F9oSqoBEqZh5fQ8= +github.com/bodgit/windows v1.0.1 h1:tF7K6KOluPYygXa3Z2594zxlkbKPAOvqr97etrGNIz4= +github.com/bodgit/windows v1.0.1/go.mod h1:a6JLwrB4KrTR5hBpp8FI9/9W9jJfeQ2h4XDXU74ZCdM= github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g= +github.com/bradleyjkemp/cupaloy/v2 v2.8.0 h1:any4BmKE+jGIaMpnU8YgH/I2LPiLBufr6oMMlVBbn9M= +github.com/bradleyjkemp/cupaloy/v2 v2.8.0/go.mod h1:bm7JXdkRd4BHJk9HpwqAI8BoAY1lps46Enkdqw6aRX0= github.com/briandowns/spinner v1.23.2 h1:Zc6ecUnI+YzLmJniCfDNaMbW0Wid1d5+qcTq4L2FW8w= github.com/briandowns/spinner v1.23.2/go.mod h1:LaZeM4wm2Ywy6vO571mvhQNRcWfRUnXOs0RcKV0wYKM= github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= -github.com/bytecodealliance/wasmtime-go/v3 v3.0.2 h1:3uZCA/BLTIu+DqCfguByNMJa2HVHpXvjfy0Dy7g6fuA= -github.com/bytecodealliance/wasmtime-go/v3 v3.0.2/go.mod h1:RnUjnIXxEJcL6BgCvNyzCCRzZcxCgsZCi+RNlvYor5Q= github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= @@ -242,9 +292,15 @@ github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chromedp/cdproto v0.0.0-20230802225258-3cf4e6d46a89/go.mod h1:GKljq0VrfU4D5yc+2qA6OVr8pmO/MBbPEWqWQ/oqGEs= +github.com/chromedp/chromedp v0.9.2/go.mod h1:LkSXJKONWTCHAfQasKFUZI+mxqS4tZqhmtGzzhLsnLs= +github.com/chromedp/sysutil v1.0.0/go.mod h1:kgWmDdq8fTzXYcKIBqIYvRRTnYb9aNS9moAV0xufSww= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8= github.com/cilium/cilium v1.17.17 h1:lm2loJwLu9jDjC5Izk71Y5Zkr//rY7UXqvQjNT2KwFE= github.com/cilium/cilium v1.17.17/go.mod h1:h1VXfDHk2lnb02j9QwsTugs2dQw+7eBx03ELeUg7gJE= github.com/cilium/ebpf v0.22.0 h1:v2ktp0roffpMOj2MMf3idtCQZOsAoC4BJbAJN+ke2bY= @@ -252,6 +308,10 @@ github.com/cilium/ebpf v0.22.0/go.mod h1:CDzZbe2hC5JjlDC+CY3KFCzlYwN4gbxppYM+Z10 github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/clipperhouse/uax29/v2 v2.7.0 h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJv2v7Vk= +github.com/clipperhouse/uax29/v2 v2.7.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM= +github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8= +github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= @@ -264,32 +324,40 @@ github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWH github.com/cncf/xds/go v0.0.0-20211130200136-a8f946100490/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2 h1:aBangftG7EVZoUb69Os8IaYg++6uMOdKK83QtkkvJik= github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2/go.mod h1:qwXFYgsP6T7XnJtbKlf1HP8AjxZZyzxMmc+Lq5GjlU4= -github.com/containerd/cgroups/v3 v3.1.2 h1:OSosXMtkhI6Qove637tg1XgK4q+DhR0mX8Wi8EhrHa4= -github.com/containerd/cgroups/v3 v3.1.2/go.mod h1:PKZ2AcWmSBsY/tJUVhtS/rluX0b1uq1GmPO1ElCmbOw= +github.com/containerd/cgroups/v3 v3.1.3 h1:eUNflyMddm18+yrDmZPn3jI7C5hJ9ahABE5q6dyLYXQ= +github.com/containerd/cgroups/v3 v3.1.3/go.mod h1:PKZ2AcWmSBsY/tJUVhtS/rluX0b1uq1GmPO1ElCmbOw= github.com/containerd/containerd v1.7.36 h1:HyMsOG5kmp1LQsGzqwI6Ts06T4VpYZbszfDZYB0bW5w= github.com/containerd/containerd v1.7.36/go.mod h1:ozI//0TomTCLPhQREnx0IXDIQMg+Fk7yTtg9fNvU8EQ= github.com/containerd/containerd/api v1.10.0 h1:5n0oHYVBwN4VhoX9fFykCV9dF1/BvAXeg2F8W6UYq1o= github.com/containerd/containerd/api v1.10.0/go.mod h1:NBm1OAk8ZL+LG8R0ceObGxT5hbUYj7CzTmR3xh0DlMM= -github.com/containerd/continuity v0.4.5 h1:ZRoN1sXq9u7V6QoHMcVWGhOwDFqZ4B9i5H6un1Wh0x4= -github.com/containerd/continuity v0.4.5/go.mod h1:/lNJvtJKUQStBzpVQ1+rasXO1LAWtUQssk28EZvJ3nE= +github.com/containerd/containerd/v2 v2.2.0 h1:K7TqcXy+LnFmZaui2DgHsnp2gAHhVNWYaHlx7HXfys8= +github.com/containerd/containerd/v2 v2.2.0/go.mod h1:YCMjKjA4ZA7egdHNi3/93bJR1+2oniYlnS+c0N62HdE= +github.com/containerd/continuity v0.5.0 h1:7a85HZpCSs+1Zps0Ee3DPSuAWY+0SJM1JNM51nlEVDg= +github.com/containerd/continuity v0.5.0/go.mod h1:/lNJvtJKUQStBzpVQ1+rasXO1LAWtUQssk28EZvJ3nE= github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI= github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE= github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk= github.com/containerd/fifo v1.1.0 h1:4I2mbh5stb1u6ycIABlBw9zgtlK8viPI9QkQNRQEEmY= github.com/containerd/fifo v1.1.0/go.mod h1:bmC4NWMbXlt2EZ0Hc7Fx7QzTFxgPID13eH0Qu+MAb2o= -github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= -github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= -github.com/containerd/platforms v1.0.0-rc.2 h1:0SPgaNZPVWGEi4grZdV8VRYQn78y+nm6acgLGv/QzE4= -github.com/containerd/platforms v1.0.0-rc.2/go.mod h1:J71L7B+aiM5SdIEqmd9wp6THLVRzJGXfNuWCZCllLA4= +github.com/containerd/log v0.2.0 h1:BewD/umNgVnoczglOpX8eRMyEy5t5iPlu5AIpnWDONc= +github.com/containerd/log v0.2.0/go.mod h1:/M7L7CXKcPTfNC74XzaK+5H5KbO5+4lJVpuVI6vRLoM= +github.com/containerd/platforms v1.0.0-rc.5 h1:vXd569rDrz8LeMXzAnBsy6LADV5YtsD8oyaRarxdmSU= +github.com/containerd/platforms v1.0.0-rc.5/go.mod h1:lKlMXyLybmBedS/JJm11uDofzI8L2v0J2ZbYvNsbq1A= +github.com/containerd/plugin v1.1.0 h1:O+7lczNJVMy8rz0YNx3xGB8tTf5qY4i5abF041Ew19U= +github.com/containerd/plugin v1.1.0/go.mod h1:qBTum+A8lJ6lO44A19Eo7y1OlcLj4OWFH1DA/vnHmcc= github.com/containerd/stargz-snapshotter/estargz v0.18.2 h1:yXkZFYIzz3eoLwlTUZKz2iQ4MrckBxJjkmD16ynUTrw= github.com/containerd/stargz-snapshotter/estargz v0.18.2/go.mod h1:XyVU5tcJ3PRpkA9XS2T5us6Eg35yM0214Y+wvrZTBrY= -github.com/containerd/ttrpc v1.2.7 h1:qIrroQvuOL9HQ1X6KHe2ohc7p+HP/0VE6XPU7elJRqQ= -github.com/containerd/ttrpc v1.2.7/go.mod h1:YCXHsb32f+Sq5/72xHubdiJRQY9inL4a4ZQrAbN1q9o= -github.com/containerd/typeurl/v2 v2.2.3 h1:yNA/94zxWdvYACdYO8zofhrTVuQY73fFU1y++dYSw40= -github.com/containerd/typeurl/v2 v2.2.3/go.mod h1:95ljDnPfD3bAbDJRugOiShd/DlAAsxGtUBhJxIn7SCk= +github.com/containerd/ttrpc v1.2.9 h1:ha0ak962T0s3CA/RoZ6S6xiWZQF24GrBaEpiGX1uihg= +github.com/containerd/ttrpc v1.2.9/go.mod h1:jjtQRwXm4DL3KsHKW8vDiUOV6wO0hi6IPhmJhxU7aEs= +github.com/containerd/typeurl/v2 v2.3.0 h1:HZHPhRWo5XMy3QGQoPrUzbW/2ckwjfweHmOwlkIrPAQ= +github.com/containerd/typeurl/v2 v2.3.0/go.mod h1:Qk+PAdUYArVj41TnGi6rJ+48RF0PkcTc4i/taoBcK0w= github.com/containers/common v0.64.2 h1:1xepE7QwQggUXxmyQ1Dbh6Cn0yd7ktk14sN3McSWf5I= github.com/containers/common v0.64.2/go.mod h1:o29GfYy4tefUuShm8mOn2AiL5Mpzdio+viHI7n24KJ4= +github.com/containers/libtrust v0.0.0-20230121012942-c1716e8a8d01 h1:Qzk5C6cYglewc+UyGf6lc8Mj2UaPTHy/iF2De0/77CA= +github.com/containers/libtrust v0.0.0-20230121012942-c1716e8a8d01/go.mod h1:9rfv8iPl1ZP7aqh9YA68wnZv2NUDbXdcdPHVz0pFbPY= +github.com/containers/ocicrypt v1.3.2 h1:MuqHSfiPpGzoAQdgDSX85FgixSgIm9mSDXTLTgugY5E= +github.com/containers/ocicrypt v1.3.2/go.mod h1:ntBZabYG0rlvstB/1rK/ba2laaU5EHE0pD5ioHoPTq4= github.com/coreos/go-oidc/v3 v3.17.0 h1:hWBGaQfbi0iVviX4ibC7bk8OKT5qNr4klBaCHVNvehc= github.com/coreos/go-oidc/v3 v3.17.0/go.mod h1:wqPbKFrVnE90vty060SB40FCJ8fTHTxSwyXJqZH+sI8= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= @@ -297,24 +365,28 @@ github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec= github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= -github.com/coreos/go-systemd/v22 v22.6.0 h1:aGVa/v8B7hpb0TKl0MWoAavPDmHvobFe5R5zn0bCJWo= -github.com/coreos/go-systemd/v22 v22.6.0/go.mod h1:iG+pp635Fo7ZmV/j14KUcmEyWF+0X7Lua8rrTWzYgWU= +github.com/coreos/go-systemd/v22 v22.7.0 h1:LAEzFkke61DFROc7zNLX/WA2i5J8gYqe0rSj9KI28KA= +github.com/coreos/go-systemd/v22 v22.7.0/go.mod h1:xNUYtjHu2EDXbsxz1i41wouACIwT7Ybq9o0BQhMwD0w= github.com/cpuguy83/dockercfg v0.3.2 h1:DlJTyZGBDlXqUZ2Dk2Q3xHs/FtnooJJVaad2S9GKorA= github.com/cpuguy83/dockercfg v0.3.2/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s= github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE= -github.com/cyphar/filepath-securejoin v0.6.0 h1:BtGB77njd6SVO6VztOHfPxKitJvd/VPT+OFBFMOi1Is= -github.com/cyphar/filepath-securejoin v0.6.0/go.mod h1:A8hd4EnAeyujCJRrICiOWqjS1AX0a9kM5XL+NwKoYSc= +github.com/cyberphone/json-canonicalization v0.0.0-20241213102144-19d51d7fe467 h1:uX1JmpONuD549D73r6cgnxyUu18Zb7yHAy5AYU0Pm4Q= +github.com/cyberphone/json-canonicalization v0.0.0-20241213102144-19d51d7fe467/go.mod h1:uzvlm1mxhHkdfqitSA92i7Se+S9ksOn3a3qmv/kyOCw= +github.com/cyphar/filepath-securejoin v0.7.0 h1:s0Y3ITPy6sQn5xt54DuYvTF8hu134ooYLUb58DX/HjE= +github.com/cyphar/filepath-securejoin v0.7.0/go.mod h1:ymLGms/u3BYaviIiuKFnUx8EkQEZeK6cInNoAPJA3o4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/deckarep/golang-set/v2 v2.8.0 h1:swm0rlPCmdWn9mESxKOjWk8hXSqoxOp+ZlfuyaAdFlQ= github.com/deckarep/golang-set/v2 v2.8.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4= -github.com/dgraph-io/badger/v4 v4.7.0 h1:Q+J8HApYAY7UMpL8d9owqiB+odzEc0zn/aqOD9jhc6Y= -github.com/dgraph-io/badger/v4 v4.7.0/go.mod h1:He7TzG3YBy3j4f5baj5B7Zl2XyfNe5bl4Udl0aPemVA= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.1 h1:5RVFMOWjMyRy8cARdy79nAmgYw3hK/4HUq48LQ6Wwqo= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.1/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40= +github.com/dgraph-io/badger/v4 v4.9.4 h1:bcw+waCpzRZ2nmcSPbnPvDVhiEsn98TKmvnAhK7r7LM= +github.com/dgraph-io/badger/v4 v4.9.4/go.mod h1:nJjaJTUOSsQEBhsq209FmwCvMJzEA3e74RjZw6V2pQI= github.com/dgraph-io/ristretto/v2 v2.2.0 h1:bkY3XzJcXoMuELV8F+vS8kzNgicwQFAaGINAEJdWGOM= github.com/dgraph-io/ristretto/v2 v2.2.0/go.mod h1:RZrm63UmcBAaYWC1DotLYBmTvgkrs0+XhBd7Npn7/zI= github.com/dgrijalva/jwt-go/v4 v4.0.0-preview1/go.mod h1:+hnT3ywWDTAFrW5aE+u2Sa/wT555ZqwoCS+pk3p6ry4= @@ -322,25 +394,28 @@ github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/r github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/dgryski/trifles v0.0.0-20230903005119-f50d829f2e54 h1:SG7nF6SRlWhcT7cNTs5R6Hk4V2lcmLz2NsG2VnInyNo= github.com/dgryski/trifles v0.0.0-20230903005119-f50d829f2e54/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA= +github.com/diskfs/go-diskfs v1.9.4 h1:0j2d7eG4IjyxL6+ChWbDPocdBCF6HQ4HBWU2WDYWVnc= +github.com/diskfs/go-diskfs v1.9.4/go.mod h1:TePJORO83Adh5pb2SqsxAwaP0fofFxKLkxctiS/9OQc= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/docker/cli v29.3.0+incompatible h1:z3iWveU7h19Pqx7alZES8j+IeFQZ1lhTwb2F+V9SVvk= -github.com/docker/cli v29.3.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= -github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk= -github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/cli v29.8.2+incompatible h1:2zgdFuoFst2T80oS42vhKGxkd0JRo305eIEKTsxR7pQ= +github.com/docker/cli v29.8.2+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/docker v28.5.2+incompatible h1:DBX0Y0zAjZbSrm1uzOkdr1onVghKaftjlSWt4AFexzM= github.com/docker/docker v28.5.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/docker-credential-helpers v0.9.5 h1:EFNN8DHvaiK8zVqFA2DT6BjXE0GzfLOZ38ggPTKePkY= -github.com/docker/docker-credential-helpers v0.9.5/go.mod h1:v1S+hepowrQXITkEfw6o4+BMbGot02wiKpzWhGUZK6c= -github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94= -github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE= +github.com/docker/docker-credential-helpers v0.9.8 h1:bIREROb7So6PRlq6KTtdS9MPEjC29OQRkFNlvK2OX8Q= +github.com/docker/docker-credential-helpers v0.9.8/go.mod h1:v1S+hepowrQXITkEfw6o4+BMbGot02wiKpzWhGUZK6c= +github.com/docker/go-connections v0.8.1 h1:JibmG5hULs5qXSr/cp/w3Pw5fZuStt4MOHMUExb29/M= +github.com/docker/go-connections v0.8.1/go.mod h1:no1qkHdjq7kLMGUXYAduOhYPSJxxvgWBh7ogVvptn3Q= github.com/docker/go-events v0.0.0-20250114142523-c867878c5e32 h1:EHZfspsnLAz8Hzccd67D5abwLiqoqym2jz/jOS39mCk= github.com/docker/go-events v0.0.0-20250114142523-c867878c5e32/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 h1:2tV76y6Q9BB+NEBasnqvs7e49aEBFI8ejC89PSnWH+4= +github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707/go.mod h1:qssHWj60/X5sZFNxpG4HBPDHVqxNm4DfnCKgrbZOT+s= +github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= -github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= -github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/dustin/go-humanize v1.1.0 h1:dbKTrvD0klcbBV/h4AWJdMuZogJACoMlvWIWZ5b2xWg= +github.com/dustin/go-humanize v1.1.0/go.mod h1:hc1CvRkJMsgxqjmjMQF3QNRAZBwY8AXBAzKYoSX9sFI= github.com/eapache/go-resiliency v1.4.0 h1:3OK9bWpPk5q6pbFAaYSEwD9CLUSHG8bnZuqX2yMt3B0= github.com/eapache/go-resiliency v1.4.0/go.mod h1:5yPzW0MIvSe0JDsv0v+DvcjEv2FyD6iZYSs1ZI+iQho= github.com/eapache/go-xerial-snappy v0.0.0-20230731223053-c322873962e3 h1:Oy0F4ALJ04o5Qqpdz8XLIpNA3WM/iSIXqxtqo7UGVws= @@ -365,8 +440,11 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.m github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= github.com/envoyproxy/go-control-plane v0.10.1/go.mod h1:AY7fTTXNdv/aJ2O5jwpxAPOWUZ7hQAEvzN5Pf27BkQQ= github.com/envoyproxy/go-control-plane v0.14.0 h1:hbG2kr4RuFj222B6+7T83thSPqLjwBIfQawTkC++2HA= +github.com/envoyproxy/go-control-plane v0.14.0/go.mod h1:NcS5X47pLl/hfqxU70yPwL9ZMkUlwlKxtAohpi2wBEU= github.com/envoyproxy/go-control-plane/envoy v1.37.0 h1:u3riX6BoYRfF4Dr7dwSOroNfdSbEPe9Yyl09/B6wBrQ= github.com/envoyproxy/go-control-plane/envoy v1.37.0/go.mod h1:DReE9MMrmecPy+YvQOAOHNYMALuowAnbjjEMkkWOi6A= +github.com/envoyproxy/go-control-plane/ratelimit v0.1.0 h1:/G9QYbddjL25KvtKTv3an9lx6VBE2cnb8wp1vEGNYGI= +github.com/envoyproxy/go-control-plane/ratelimit v0.1.0/go.mod h1:Wk+tMFAFbCXaJPzVVHnPgRKdUdwW/KdbRt94AzgRee4= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v0.6.2/go.mod h1:2t7qjJNvHPx8IjnBOzl9E9/baC+qXE/TeeyBRzgJDws= github.com/envoyproxy/protoc-gen-validate v1.3.3 h1:MVQghNeW+LZcmXe7SY1V36Z+WFMDjpqGAGacLe2T0ds= @@ -381,6 +459,7 @@ github.com/fatih/color v1.19.0 h1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w= github.com/fatih/color v1.19.0/go.mod h1:zNk67I0ZUT1bEGsSGyCZYZNrHuTkJJB+r6Q9VuMi0LE= github.com/fatih/set v0.2.1 h1:nn2CaJyknWE/6txyUDGwysr3G5QC6xWB/PtVjPBbeaA= github.com/fatih/set v0.2.1/go.mod h1:+RKtMCH+favT2+3YecHGxcc0b4KyVWA1QWWJUs4E0CI= +github.com/felixge/fgprof v0.9.3/go.mod h1:RdbpDgzqYVh/T9fPELJyV7EYJuHB55UTEULNun8eiPw= github.com/felixge/fgprof v0.9.5 h1:8+vR6yu2vvSKn08urWyEuxx75NWPEvybbkBirEpsbVY= github.com/felixge/fgprof v0.9.5/go.mod h1:yKl+ERSa++RYOs32d8K6WEXCB4uXdLls4ZaZPpayhMM= github.com/felixge/httpsnoop v1.1.0 h1:3YtUj32ZZkqZtt3sZZsClsymw/QDuVfpNhoA31zeORc= @@ -390,23 +469,23 @@ github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/ github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= -github.com/foxcpp/go-mockdns v1.1.0 h1:jI0rD8M0wuYAxL7r/ynTrCQQq0BVqfB99Vgk7DlmewI= -github.com/foxcpp/go-mockdns v1.1.0/go.mod h1:IhLeSFGed3mJIAXPH2aiRQB+kqz7oqu8ld2qVbOu7Wk= +github.com/foxcpp/go-mockdns v1.2.0 h1:omK3OrHRD1IWJz1FuFBCFquhXslXoF17OvBS6JPzZF0= +github.com/foxcpp/go-mockdns v1.2.0/go.mod h1:IhLeSFGed3mJIAXPH2aiRQB+kqz7oqu8ld2qVbOu7Wk= github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk= github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU= -github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= -github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= +github.com/fsnotify/fsnotify v1.10.1 h1:b0/UzAf9yR5rhf3RPm9gf3ehBPpf0oZKIjtpKrx59Ho= +github.com/fsnotify/fsnotify v1.10.1/go.mod h1:TLheqan6HD6GBK6PrDWyDPBaEV8LspOxvPSjC+bVfgo= github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= -github.com/gabriel-vasile/mimetype v1.4.13 h1:46nXokslUBsAJE/wMsp5gtO500a4F3Nkz9Ufpk2AcUM= -github.com/gabriel-vasile/mimetype v1.4.13/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= +github.com/gabriel-vasile/mimetype v1.4.15 h1:05iP/CYtZ/w455R/KZM6rZ5ieAdh99UPtd+d3YzLmaI= +github.com/gabriel-vasile/mimetype v1.4.15/go.mod h1:azpTcoLcDZRNgFou5j+APrqQx9HqVPWa6ijYQIIVswQ= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/github/go-spdx/v2 v2.4.0 h1:+4IwVwJJbm3rzvrQ6P1nI9BDMcy3la4RchRy5uehV/M= -github.com/github/go-spdx/v2 v2.4.0/go.mod h1:/5rwgS0txhGtRdUZwc02bTglzg6HK3FfuEbECKlK2Sg= +github.com/github/go-spdx/v2 v2.7.1 h1:+siV9wnsncI5pg4otH/sKvqGL8UkcLdPh16TSJYY1V4= +github.com/github/go-spdx/v2 v2.7.1/go.mod h1:Ftc45YYG1WzpzwEPKRVm9Jv8vDqOrN4gWoCkK+bHer0= github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= github.com/go-errors/errors v1.5.1 h1:ZwEMSLRCapFLflTpT7NKaAc7ukJ8ZPEjzlxt8rPN8bk= @@ -420,10 +499,8 @@ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gota/gota v0.12.0 h1:T5BDg1hTf5fZ/CO+T/N0E+DDqUhvoKBl+UVckgcAAQg= github.com/go-gota/gota v0.12.0/go.mod h1:UT+NsWpZC/FhaOyWb9Hui0jXg0Iq8e/YugZHTbyW/34= -github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A= -github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= -github.com/go-jose/go-jose/v4 v4.1.4 h1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA= -github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= +github.com/go-jose/go-jose/v4 v4.1.5 h1:RjgjO2LOtWOJKUC5wpwY9LR3B3vwVAz6JS2YHfYU6eA= +github.com/go-jose/go-jose/v4 v4.1.5/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-latex/latex v0.0.0-20210118124228-b3d85cf34e07/go.mod h1:CO1AlKB2CSIqUrmQPqA0gdRIlnLEY0gK5JGjh37zN5U= @@ -504,6 +581,11 @@ github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPE github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= +github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM= +github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= +github.com/gobwas/ws v1.2.1/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY= +github.com/goccy/go-json v0.10.6 h1:p8HrPJzOakx/mn/bQtjgNjdTcN+/S6FcG2CTtQOrHVU= +github.com/goccy/go-json v0.10.6/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/gocql/gocql v1.6.0 h1:IdFdOTbnpbd0pDhl4REKQDM+Q0SzKXQ1Yh+YZZ8T/qU= github.com/gocql/gocql v1.6.0/go.mod h1:3gM2c4D3AnkISwBxGnMMsS8Oy4y2lhbPRsH4xnJrHG8= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= @@ -512,16 +594,14 @@ github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/gohugoio/hashstructure v0.6.0 h1:7wMB/2CfXoThFYhdWRGv3u3rUM761Cq29CxUW+NltUg= github.com/gohugoio/hashstructure v0.6.0/go.mod h1:lapVLk9XidheHG1IQ4ZSbyYrXcaILU1ZEP/+vno5rBQ= -github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo= -github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= +github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY= +github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= -github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= @@ -578,17 +658,22 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= -github.com/google/go-containerregistry v0.21.2 h1:vYaMU4nU55JJGFC9JR/s8NZcTjbE9DBBbvusTW9NeS0= -github.com/google/go-containerregistry v0.21.2/go.mod h1:ctO5aCaewH4AK1AumSF5DPW+0+R+d2FmylMJdp5G7p0= +github.com/google/go-containerregistry v0.22.1 h1:RZuuSYhTvlDvtsK+NkutoCZ//C0X2ebLK8X8l3ULs84= +github.com/google/go-containerregistry v0.22.1/go.mod h1:bJR35SK8XgisYmhg/FMQ/5RK0S/XrOAqLBV5/LR2XE0= github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= +github.com/google/go-intervals v0.0.2 h1:FGrVEiUnTRKR8yE04qzXYaJMtnIYqobR5QbblK3ixcM= +github.com/google/go-intervals v0.0.2/go.mod h1:MkaR3LNRfeKLPmqgJYs4E66z5InYjmCjbbr4TQlcT6Y= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/licensecheck v0.3.1 h1:QoxgoDkaeC4nFrtGN1jV7IPmDCHFNIVh54e5hSt6sPs= github.com/google/licensecheck v0.3.1/go.mod h1:ORkR35t/JjW+emNKtfJDII0zlciG9JgbT7SmsohlHmY= +github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= +github.com/google/martian/v3 v3.3.3 h1:DIhPTQrbPkgs2yJYdXU/eNACCG5DVQjySNRNlflZ9Fc= +github.com/google/martian/v3 v3.3.3/go.mod h1:iEPrYcgCF7jA9OtScMFQyAlZZ4YXTKEtJ1E6RWzmBA0= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= @@ -603,30 +688,32 @@ github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20250630185457-6e76a2b096b5 h1:xhMrHhTJ6zxu3gA4enFM9MLn9AY7613teCdFnlUVbSQ= -github.com/google/pprof v0.0.0-20250630185457-6e76a2b096b5/go.mod h1:5hDyRhoBCxViHszMt12TnOpEI4VVi+U8Gm9iphldiMA= +github.com/google/pprof v0.0.0-20211214055906-6f57359322fd/go.mod h1:KgnwoLYCZ8IQu3XUZ8Nc/bM9CCZFOyjUNOSygVozoDg= +github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik= +github.com/google/pprof v0.0.0-20260802141513-ef3492d7dac3 h1:LMLX+LgTNWpfvCBdFebv6EsYotImrt/Ppc5cXIriCSo= +github.com/google/pprof v0.0.0-20260802141513-ef3492d7dac3/go.mod h1:jl5iWTm0/hd5PjEYEOuwAJ57L/CibdZfrqZ5XA5GrCk= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0= -github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM= +github.com/google/s2a-go v0.1.10 h1:EMp+aOuXN6l8cE/gjF5Bt+vyZxsUuyCWe9chDWR/+uU= +github.com/google/s2a-go v0.1.10/go.mod h1:pz4tyvwXvJLLbyrkh6FW1eS2zPUXMaTmyNhYtyP2tNw= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/enterprise-certificate-proxy v0.3.15 h1:xolVQTEXusUcAA5UgtyRLjelpFFHWlPQ4XfWGc7MBas= -github.com/googleapis/enterprise-certificate-proxy v0.3.15/go.mod h1:vqVt9yG9480NtzREnTlmGSBmFrA+bzb0yl0TxoBQXOg= +github.com/googleapis/enterprise-certificate-proxy v0.3.22 h1:NU4XpII6jD+Dxcot94fqjE+AfJoE/lQP9q3faYGzC/c= +github.com/googleapis/enterprise-certificate-proxy v0.3.22/go.mod h1:L3D/IQExI6LqEjBdXcZQ1WluSgigQmSwBboFstVPM4w= github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY= github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM= -github.com/googleapis/gax-go/v2 v2.22.0 h1:PjIWBpgGIVKGoCXuiCoP64altEJCj3/Ei+kSU5vlZD4= -github.com/googleapis/gax-go/v2 v2.22.0/go.mod h1:irWBbALSr0Sk3qlqb9SyJ1h68WjgeFuiOzI4Rqw5+aY= +github.com/googleapis/gax-go/v2 v2.24.1 h1:AtqTN21IXMMWo99LiEVAiBfNNQmO40d8xUfZI640mc0= +github.com/googleapis/gax-go/v2 v2.24.1/go.mod h1:bWeBei0NVwaNZKb2y1HUBS7gLXIF3/Tu3pq7j8D2Tb0= +github.com/gookit/assert v0.1.1 h1:lh3GcawXe/p+cU7ESTZ5Ui3Sm/x8JWpIis4/1aF0mY0= +github.com/gookit/assert v0.1.1/go.mod h1:jS5bmIVQZTIwk42uXl4lyj4iaaxx32tqH16CFj0VX2E= github.com/gookit/color v1.2.5/go.mod h1:AhIE+pS6D4Ql0SQWbBeXPHw7gY0/sjHoA4s/n1KB7xg= -github.com/gookit/color v1.6.0 h1:JjJXBTk1ETNyqyilJhkTXJYYigHG24TM9Xa2M1xAhRA= -github.com/gookit/color v1.6.0/go.mod h1:9ACFc7/1IpHGBW8RwuDm/0YEnhg3dwwXpoMsmtyHfjs= +github.com/gookit/color v1.6.2 h1:M6TJRJMuDFPYkrWmjOvMj+E0xIjcHq8lMPy7+vv0z1Y= +github.com/gookit/color v1.6.2/go.mod h1:9ACFc7/1IpHGBW8RwuDm/0YEnhg3dwwXpoMsmtyHfjs= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= -github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= -github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo= @@ -647,6 +734,8 @@ github.com/grpc-ecosystem/grpc-gateway/v2 v2.31.0 h1:Bd7KaOxzULLxtZ/K5s1aLbWhR0+ github.com/grpc-ecosystem/grpc-gateway/v2 v2.31.0/go.mod h1:nN7ts3dFXKtCZWc//yfkpcQNKJABg16/uDVAZpLDalo= github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed h1:5upAirOpQc1Q53c0bnx2ufif5kANL7bfZWcc6VJWJd8= github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4= +github.com/hashicorp/aws-sdk-go-base/v2 v2.0.0-beta.74 h1:mymLUKThnV9wFvogOK8NnsMP9/vlhnjXY98gr2QIGW8= +github.com/hashicorp/aws-sdk-go-base/v2 v2.0.0-beta.74/go.mod h1:Bh9qYL8ehmDxSg14Tk8oxFCP90XHfs6NxV1D84884xA= github.com/hashicorp/consul/api v1.11.0/go.mod h1:XjsvQN+RJGWI2TWy1/kqaE16HrR2J/FWgkYjdZQsX9M= github.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOjagTIwIR1vPms= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -654,7 +743,10 @@ github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= +github.com/hashicorp/go-getter v1.8.10 h1:uufJp+uacA0fSDRxclsHkiE6yUQTKgwv5b25QexgVM8= +github.com/hashicorp/go-getter v1.8.10/go.mod h1:5HyKGCQmT+I3MGAM76vk/fWpktjwxn7xruCUr9CrVuo= github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= github.com/hashicorp/go-hclog v1.0.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= @@ -673,11 +765,13 @@ github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/b github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4= -github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go-version v1.9.0 h1:CeOIz6k+LoN3qX9Z0tyQrPtiB1DFYRPfCIBtaXPSCnA= +github.com/hashicorp/go-version v1.9.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= +github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/mdns v1.0.1/go.mod h1:4gW7WsVCke5TE7EPeYliwHlRUyBtfCwuFwuMg2DmyNY= @@ -691,6 +785,8 @@ github.com/iancoleman/strcase v0.3.0 h1:nTXanmYxhfFAMjZL34Ov6gkzEsSJZ5DbhxWjvSAS github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/ianlancetaylor/demangle v0.0.0-20210905161508-09a460cdf81d/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w= +github.com/ianlancetaylor/demangle v0.0.0-20230524184225-eabc099b10ab/go.mod h1:gx7rwoVhcfuVKG5uya9Hs3Sxj7EIvldVofAWIUtGouw= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= @@ -715,6 +811,7 @@ github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGw github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/jonboulle/clockwork v0.5.0 h1:Hyh9A8u51kptdkR+cqRpT1EebBwTn1oK9YfGYbdFz6I= github.com/jonboulle/clockwork v0.5.0/go.mod h1:3mZlmanh0g2NDKO5TWZVJAfofYk64M7XN3SzBPjZF60= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA= github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= github.com/jsimonetti/rtnetlink/v2 v2.0.1 h1:xda7qaHDSVOsADNouv7ukSuicKZO7GgVUCXxpaIEIlM= @@ -731,8 +828,12 @@ github.com/jung-kurt/gofpdf v1.0.0/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+ github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.18.7 h1:aUyZsS4kH3QTKurYhAOwAHxllVPnOthb3vPfnF1Ehjw= -github.com/klauspost/compress v1.18.7/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ= +github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= +github.com/klauspost/compress v1.20.1 h1:T7kKElXUMXrUJ2E9QhQhxFtcK5rPyLdsGZvdbLMPdiQ= +github.com/klauspost/compress v1.20.1/go.mod h1:LUdAzn7YLVvxLpc7y3V1m40wESHTgc1422pwwBSKYuI= +github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= +github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU= +github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= @@ -767,8 +868,25 @@ github.com/kubescape/storage v0.0.301 h1:SsyS1Xdq8ClIOqCB5gNg7X82lGfrSpWLI+J+Ven github.com/kubescape/storage v0.0.301/go.mod h1:d/1hqWPda2clsjx2wmQgysnB5dThIo3rDKP7RWx+v+M= github.com/kubescape/storage/pkg/apiauth v0.1.0 h1:lkmcuT18mb1hi0R0Bz1MwWpJIpyVo1Ppuwo26fVh9WY= github.com/kubescape/storage/pkg/apiauth v0.1.0/go.mod h1:WiGMS7DCUl6a3SPwuFjxXHosdkNCw8F+JqRtGhBwQF8= +github.com/kubescape/syft v1.55.0-ks.3 h1:GL7gfj3zohpTB23YU6idVP+NuKKG5GfV1r2Fu5FhN8Q= +github.com/kubescape/syft v1.55.0-ks.3/go.mod h1:mBfgoR36a4J2XG7L6EPThwJjUjdQb/fUyxghW3U72Zk= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs= +github.com/lestrrat-go/blackmagic v1.0.4 h1:IwQibdnf8l2KoO+qC3uT4OaTWsW7tuRQXy9TRN9QanA= +github.com/lestrrat-go/blackmagic v1.0.4/go.mod h1:6AWFyKNNj0zEXQYfTMPfZrAXUWUfTIZ5ECEUEJaijtw= +github.com/lestrrat-go/dsig v1.2.1 h1:MwxzZhE4+4fguHi+uDALKVlC3Cn+O1QU1Q/F8D7hVIc= +github.com/lestrrat-go/dsig v1.2.1/go.mod h1:RD2eOaidyPvpc7IJQoO3Qq52RWdy8ZcJs8lrOnoa1Kc= +github.com/lestrrat-go/dsig-secp256k1 v1.0.0 h1:JpDe4Aybfl0soBvoVwjqDbp+9S1Y2OM7gcrVVMFPOzY= +github.com/lestrrat-go/dsig-secp256k1 v1.0.0/go.mod h1:CxUgAhssb8FToqbL8NjSPoGQlnO4w3LG1P0qPWQm/NU= +github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE= +github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E= +github.com/lestrrat-go/httprc/v3 v3.0.5 h1:S+Mb4L2I+bM6JGTibLmxExhyTOqnXjqx+zi9MoXw/TM= +github.com/lestrrat-go/httprc/v3 v3.0.5/go.mod h1:mSMtkZW92Z98M5YoNNztbRGxbXHql7tSitCvaxvo9l0= +github.com/lestrrat-go/jwx/v3 v3.1.1 h1:yd9AdPmZ4INnQ7k42IrzXYpnEG803+SrQ6hdMvzHJzw= +github.com/lestrrat-go/jwx/v3 v3.1.1/go.mod h1:uw/MN2M/Xiu4FhwcIwH11Zsh9JWx9SWzgALl7/uIEkU= +github.com/lestrrat-go/option/v2 v2.0.0 h1:XxrcaJESE1fokHy3FpaQ/cXW8ZsIdWcdFzzLOcID3Ss= +github.com/lestrrat-go/option/v2 v2.0.0/go.mod h1:oSySsmzMoR0iRzCDCaUfsCzxQHUEuhOViQObyy7S6Vg= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0= @@ -780,9 +898,10 @@ github.com/lyft/protoc-gen-star v0.5.3/go.mod h1:V0xaHgaf5oCCqmcxYcWiDfTiKsZsRc8 github.com/mackerelio/go-osstat v0.2.5 h1:+MqTbZUhoIt4m8qzkVoXUJg1EuifwlAJSk4Yl2GXh+o= github.com/mackerelio/go-osstat v0.2.5/go.mod h1:atxwWF+POUZcdtR1wnsUcQxTytoHG4uhl2AKKzrOajY= github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= -github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE= -github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/magiconair/properties v1.18.12 h1:sT9zQpvTB3B4gzrX0tmZNTEaGyg8Zw55MFYRE32Mr9I= +github.com/magiconair/properties v1.18.12/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/matthyx/inspektor-gadget v0.0.0-20260203101533-6ef87216d3dd h1:n8zR1L5t5UWzmQ/DgQ98DF/NrYJL7gUI57GkiDlyu9Y= github.com/matthyx/inspektor-gadget v0.0.0-20260203101533-6ef87216d3dd/go.mod h1:V4TgEmWo37K72pQvC7XuRQssysrxIIkrNX4TtEkgiE0= github.com/matthyx/maps v0.0.0-20260915111345-e9181ad40421 h1:9XVp2iZdJ7AVWvlNb7e9AOgGzXUyZmX79GzNJDNOCLM= @@ -802,6 +921,10 @@ github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Ky github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.24 h1:tGZZoVgT/KiqK1c8ocVLeDS8BSWMRd47J3Lbz7vsReI= github.com/mattn/go-isatty v0.0.24/go.mod h1:nMCL3Zebbrt45jsMDgnfIwz6ydEQApk5oEI3HqDio6A= +github.com/mattn/go-runewidth v0.0.27 h1:Feg/Oou5zI/wnpgDF6omIU0OokC9GxLC/WRknhVlIR0= +github.com/mattn/go-runewidth v0.0.27/go.mod h1:3qAiGCV4Koz/yuveO58qUefmUTRm8r0IGEXZ9jeHp/8= +github.com/mattn/go-sqlite3 v1.14.48 h1:7XHIgl0a8HwOaiK4E47ozLkST78rR9+OtNGx27D/TFs= +github.com/mattn/go-sqlite3 v1.14.48/go.mod h1:6JTjA44L93a0QCyJef5YvlPoKXntQPjzWv5gtm9sB6w= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/g= github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw= @@ -809,12 +932,22 @@ github.com/mdlayher/socket v0.5.1 h1:VZaqt6RkGkt2OE9l3GcC6nZkqD3xKeQLyfleW/uBcos github.com/mdlayher/socket v0.5.1/go.mod h1:TjPLHI1UgwEv5J1B5q0zTZq12A/6H7nKmtTanQE37IQ= github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI= github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= +github.com/mholt/archives v0.1.5 h1:Fh2hl1j7VEhc6DZs2DLMgiBNChUux154a1G+2esNvzQ= +github.com/mholt/archives v0.1.5/go.mod h1:3TPMmBLPsgszL+1As5zECTuKwKvIfj6YcwWPpeTAXF4= github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI= github.com/miekg/dns v1.1.62 h1:cN8OuEF1/x5Rq6Np+h1epln8OiyPWV+lROx9LxcGgIQ= github.com/miekg/dns v1.1.62/go.mod h1:mvDlcItzm+br7MToIKqkglaGhlFMHJ9DTNNWONWXbNQ= +github.com/miekg/pkcs11 v1.1.2 h1:/VxmeAX5qU6Q3EwafypogwWbYryHFmF2RpkJmw3m4MQ= +github.com/miekg/pkcs11 v1.1.2/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= +github.com/mikelolasagasti/xz v1.0.1 h1:Q2F2jX0RYJUG3+WsM+FJknv+6eVjsjXNDV0KJXZzkD0= +github.com/mikelolasagasti/xz v1.0.1/go.mod h1:muAirjiOUxPRXwm9HdDtB3uoRPrGnL85XHtokL9Hcgc= +github.com/minio/minlz v1.0.1 h1:OUZUzXcib8diiX+JYxyRLIdomyZYzHct6EShOKtQY2A= +github.com/minio/minlz v1.0.1/go.mod h1:qT0aEB35q79LLornSzeDH75LBf3aH1MV+jB5w9Wasec= +github.com/mistifyio/go-zfs/v4 v4.0.0 h1:sU0+5dX45tdDK5xNZ3HBi95nxUc48FS92qbIZEvpAg4= +github.com/mistifyio/go-zfs/v4 v4.0.0/go.mod h1:weotFtXTHvBwhr9Mv96KYnDkTPBOHFUbm9cBmQpesL0= github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= @@ -832,6 +965,8 @@ github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg= github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc= github.com/moby/moby v28.5.2+incompatible h1:hIn6qcenb3JY1E3STwqEbBvJ8bha+u1LpqjX4CBvNCk= github.com/moby/moby v28.5.2+incompatible/go.mod h1:fDXVQ6+S340veQPv35CzDahGBmHsiclFwfEygB/TWMc= +github.com/moby/moby/client v0.6.0 h1:AJjEB21QPbXSXjDsZorFBoDZPhMrfbpaPLgSMAW9Bgs= +github.com/moby/moby/client v0.6.0/go.mod h1:OCo00wNRyA3m4lmJ228W3JbyCN4ZNNYjpOXiJydBdcQ= github.com/moby/patternmatcher v0.6.1 h1:qlhtafmr6kgMIJjKJMDmMWq7WLkKIo23hsrpR3x084U= github.com/moby/patternmatcher v0.6.1/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw= @@ -848,8 +983,8 @@ github.com/moby/sys/signal v0.7.1 h1:PrQxdvxcGijdo6UXXo/lU/TvHUWyPhj7UOpSo8tuvk0 github.com/moby/sys/signal v0.7.1/go.mod h1:Se1VGehYokAkrSQwL4tDzHvETwUZlnY7S5XtQ50mQp8= github.com/moby/sys/user v0.4.1 h1:RgjRlaDKi/Xmyrz4t8lyzXT6v2ooFeO/7xtchmhVWE0= github.com/moby/sys/user v0.4.1/go.mod h1:E9QsW5WRe1kUAf7kW8hXKwu1uhsZEAdPLYHYSDudF4Y= -github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g= -github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28= +github.com/moby/sys/userns v0.2.1 h1:4OvdM7BcPkASbuouHsbW3aeMJSFlYDldBRnXVZhaRk8= +github.com/moby/sys/userns v0.2.1/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28= github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ= github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -871,6 +1006,8 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8m github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo= github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM= +github.com/nwaples/rardecode/v2 v2.4.1 h1:F7zNW2LdAuuBThHWXQaiFUGVD/sef299NfWSB1nHAl4= +github.com/nwaples/rardecode/v2 v2.4.1/go.mod h1:7uz379lSxPe6j9nvzxUZ+n7mnJNgjsRNb6IbvGVHRmw= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/oklog/ulid/v2 v2.1.2 h1:IEclFb9JNvzYA6MW2SCxbLzcHTVsfqm3PrqGQJH5zec= @@ -883,8 +1020,8 @@ github.com/onsi/ginkgo/v2 v2.27.2 h1:LzwLj0b89qtIy6SSASkzlNvX6WktqurSHwkk2ipF/Ns github.com/onsi/ginkgo/v2 v2.27.2/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo= github.com/onsi/gomega v1.38.2 h1:eZCjf2xjZAqe+LeWvKb5weQ+NcPwX84kqJ0cZNxok2A= github.com/onsi/gomega v1.38.2/go.mod h1:W2MJcYxRGV63b418Ai34Ud0hEdTVXq9NW9+Sx6uXf3k= -github.com/open-policy-agent/opa v1.4.0 h1:IGO3xt5HhQKQq2axfa9memIFx5lCyaBlG+fXcgHpd3A= -github.com/open-policy-agent/opa v1.4.0/go.mod h1:DNzZPKqKh4U0n0ANxcCVlw8lCSv2c+h5G/3QvSYdWZ8= +github.com/open-policy-agent/opa v1.19.0 h1:+j2OCsjMezZEML2T1lI9giJdGJS/PL1XFKgkHPGIhpo= +github.com/open-policy-agent/opa v1.19.0/go.mod h1:pb6Y6klyf7X7X8uXNDflruA9dQC2gMqWROXI5w/kvv0= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= @@ -893,9 +1030,10 @@ github.com/opencontainers/runtime-spec v1.2.1 h1:S4k4ryNgEpxW1dzyqffOmhI1BHYcjzU github.com/opencontainers/runtime-spec v1.2.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/runtime-tools v0.9.1-0.20250523060157-0ea5ed0382a2 h1:2xZEHOdeQBV6PW8ZtimN863bIOl7OCW/X10K0cnxKeA= github.com/opencontainers/runtime-tools v0.9.1-0.20250523060157-0ea5ed0382a2/go.mod h1:MXdPzqAA8pHC58USHqNCSjyLnRQ6D+NjbpP+02Z1U/0= -github.com/opencontainers/selinux v1.13.1 h1:A8nNeceYngH9Ow++M+VVEwJVpdFmrlxsN22F+ISDCJE= -github.com/opencontainers/selinux v1.13.1/go.mod h1:S10WXZ/osk2kWOYKy1x2f/eXF5ZHJoUs8UU/2caNRbg= +github.com/opencontainers/selinux v1.15.1 h1:ERxeh5caJvCzNAKdI8WQbJmB1LDTn4BuaAg8wihLBpA= +github.com/opencontainers/selinux v1.15.1/go.mod h1:LenyElirjUHszfxrjuFqC85HIeXZKumHcKMQtnaDlQQ= github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8= +github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0= github.com/panjf2000/ants/v2 v2.11.3 h1:AfI0ngBoXJmYOpDh9m516vjqoUu2sLrIVgppI9TZVpg= github.com/panjf2000/ants/v2 v2.11.3/go.mod h1:8u92CYMUc6gyvTIw8Ru7Mt7+/ESnJahz5EVtqfrilek= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= @@ -904,8 +1042,8 @@ github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwp github.com/pborman/indent v1.2.1 h1:lFiviAbISHv3Rf0jcuh489bi06hj98JsVMtIDZQb9yM= github.com/pborman/indent v1.2.1/go.mod h1:FitS+t35kIYtB5xWTZAPhnmrxcciEEOdbyrrpz5K6Vw= github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= -github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= -github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= +github.com/pelletier/go-toml/v2 v2.4.3 h1:GTRvJQutkOSftxIFD5xw9aepkYNuPWmVJpffdDPYVpY= +github.com/pelletier/go-toml/v2 v2.4.3/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/petermattis/goid v0.0.0-20240813172612-4fcff4a6cae7/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4= @@ -915,8 +1053,8 @@ github.com/phpdave11/gofpdf v1.4.2/go.mod h1:zpO6xFn9yxo3YLyMvW8HcKWVdbNqgIfOOp2 github.com/phpdave11/gofpdi v1.0.12/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI= github.com/picatz/xcel v0.0.0-20250816143731-885b5f678a12 h1:RS7RxrC+OtnYpgI0li0NwvpE0cqYewsZGXUb6wAe0oQ= github.com/picatz/xcel v0.0.0-20250816143731-885b5f678a12/go.mod h1:jxNaYyVlWe+WPV3G45KzlMLvplS3PQdHLUsFePIcaEg= -github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU= -github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= +github.com/pierrec/lz4/v4 v4.1.26 h1:GrpZw1gZttORinvzBdXPUXATeqlJjqUG/D87TKMnhjY= +github.com/pierrec/lz4/v4 v4.1.26/go.mod h1:EoQMVJgeeEOMsCqCzqFm2O0cJvljX2nGZjcRIPL34O4= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -926,6 +1064,8 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pkg/profile v1.7.0 h1:hnbDkaNWPCLMO9wGLdBFTIZvzDrDfBM2072E1S9gJkA= github.com/pkg/profile v1.7.0/go.mod h1:8Uer0jas47ZQMJ7VD+OHknK4YDY07LPUC6dEvqDjvNo= github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= +github.com/pkg/xattr v0.4.12 h1:rRTkSyFNTRElv6pkA3zpjHpQ90p/OdHQC1GmGh1aTjM= +github.com/pkg/xattr v0.4.12/go.mod h1:di8WF84zAKk8jzR1UBTEWh9AUlIZZ7M/JNt8e9B6ktU= github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo= github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -935,12 +1075,14 @@ github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndr github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= +github.com/proglottis/gpgme v0.1.6 h1:8WpQ8VWggLdxkuTnW+sZ1r1t92XBNd8GZNDhQ4Rz+98= +github.com/proglottis/gpgme v0.1.6/go.mod h1:5LoXMgpE4bttgwwdv9bLs/vwqv3qV7F4glEEZ7mRKrM= github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= -github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= -github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= +github.com/prometheus/client_golang v1.24.0 h1:5XStIklKuAtJSNpdD3s8XJj/Yv78IQmE1kbNk87JrAI= +github.com/prometheus/client_golang v1.24.0/go.mod h1:QcsNdotprC2nS4BTM2ucbcqxd2CeXTEa9jW7zHO9iDE= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= @@ -950,23 +1092,24 @@ github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvM github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= -github.com/prometheus/common v0.67.4 h1:yR3NqWO1/UyO1w2PhUvXlGQs/PtFmoveVO0KZ4+Lvsc= -github.com/prometheus/common v0.67.4/go.mod h1:gP0fq6YjjNCLssJCQp0yk4M8W6ikLURwkdd/YKtTbyI= +github.com/prometheus/common v0.70.0 h1:bcpru3tWPVnxGnETLgOV5jbp/JRXgYEyv65CuBLAMMI= +github.com/prometheus/common v0.70.0/go.mod h1:S/SFasQmgGiYH6C81LKCtYa8QACgthGg5zxL2udV7SY= github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= -github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws= -github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw= -github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5XpJzTSTfLsJV/mx9Q9g7kxmchpfZyxgzM= -github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/prometheus/procfs v0.21.1 h1:GljZCt+zSTS+NZq88cyQ1LjZ+RCHp3uVuabBWA5+OJI= +github.com/prometheus/procfs v0.21.1/go.mod h1:aB55Cww9pdSJVHk0hUf0inxWyyjPogFIjmHKYgMKmtY= +github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9 h1:bsUq1dX0N8AOIL7EB/X911+m4EHsnWEHeJ0c+3TTBrg= +github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= -github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/rogpeppe/go-internal v1.15.0 h1:D0RCU5rMAp+SpgkiNdrjfJ+LX4J1M32V2NeCY7EJ6hc= +github.com/rogpeppe/go-internal v1.15.0/go.mod h1:DrUVZyrJU+txYW5/1kwtXQSMFio52ZOxX7yM1VHvnxs= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfFZQK844Gfx8o5WFuvpxWRwnSoipWe/p622j1v06w= +github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/s3rj1k/go-fanotify/fanotify v0.0.0-20240229202106-bca3154da60a h1:4VFls9SuqkqeioVevnaeTXrYKQ7JiEsxqKHfxp+/ovA= github.com/s3rj1k/go-fanotify/fanotify v0.0.0-20240229202106-bca3154da60a/go.mod h1:2zG1g57bc+D6FpNc68gsRXJgkidteqTMhWiiUP3m8UE= @@ -978,8 +1121,12 @@ github.com/sasha-s/go-deadlock v0.3.5/go.mod h1:bugP6EGbdGYObIlx7pUZtWqlvo8k9H6v github.com/scylladb/go-set v1.0.3-0.20200225121959-cc7b2070d91e h1:7q6NSFZDeGfvvtIRwBrU/aegEYJYmvev0cHAwo17zZQ= github.com/scylladb/go-set v1.0.3-0.20200225121959-cc7b2070d91e/go.mod h1:DkpGd78rljTxKAnTDPFqXSGxvETQnJyuSOQwsHycqfs= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= -github.com/seccomp/libseccomp-golang v0.11.0 h1:SDkcBRqGLP+sezmMACkxO1EfgbghxIxnRKfd6mHUEis= -github.com/seccomp/libseccomp-golang v0.11.0/go.mod h1:5m1Lk8E9OwgZTTVz4bBOer7JuazaBa+xTkM895tDiWc= +github.com/seccomp/libseccomp-golang v0.11.1 h1:wuk4ZjSx6kyQII4rj6G6fvVzRHQaSiPvccJazDagu4g= +github.com/seccomp/libseccomp-golang v0.11.1/go.mod h1:5m1Lk8E9OwgZTTVz4bBOer7JuazaBa+xTkM895tDiWc= +github.com/secure-systems-lab/go-securesystemslib v0.11.1 h1:ayahDPjfSIqKegyt5YVGEvQ7SAi72GHXTy+b2YjUd5w= +github.com/secure-systems-lab/go-securesystemslib v0.11.1/go.mod h1:UyOjhoZLi76ir63u1ptubEevVe6dlNPmNxwgVvKqFFY= +github.com/segmentio/asm v1.2.1 h1:DTNbBqs57ioxAD4PrArqftgypG4/qNpXoJx8TVXxPR0= +github.com/segmentio/asm v1.2.1/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= @@ -1009,12 +1156,22 @@ github.com/shurcooL/reactions v0.0.0-20181006231557-f2e0b4ca5b82/go.mod h1:TCR1l github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/shurcooL/users v0.0.0-20180125191416-49c67e49c537/go.mod h1:QJTqeLYEDaXHZDBsXlPCDqdhQuJkuw4NOtaxYe3xii4= github.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133/go.mod h1:hKmq5kWdCj2z2KEozexVbfEZIWiTjhE0+UjmZgPqehw= +github.com/sigstore/fulcio v1.8.7 h1:d7QJQxukgXarqPCT9uDNeyzt1Bbt/biw58/VfOnwI/o= +github.com/sigstore/fulcio v1.8.7/go.mod h1:7fX+QyigZxRGKTgHvTfOfxwwniCDxK02PVloHy5/vg4= +github.com/sigstore/protobuf-specs v0.5.2 h1:RSWWUY8QrVTxbYH00jY/jg2e7YnjzrpwP+PeHTMll0E= +github.com/sigstore/protobuf-specs v0.5.2/go.mod h1:DRBzpFuE+LnvQMN10/dU6nBeKwVLGEQ6o2FovN2Rats= +github.com/sigstore/sigstore v1.10.11 h1:fjrwDfR0AWmRONYV/hKGJyqbT0osMtaBz9FTZ6MH9XI= +github.com/sigstore/sigstore v1.10.11/go.mod h1:ecPk+ftLqP8/TmzQFJmn0+QrL2yUQIJvyzVKNmDiw6U= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= -github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w= -github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g= +github.com/sirupsen/logrus v1.10.2 h1:G2SED73/qrAu6YwbdxOD6peLkCBI3z7L+ykJFTXJBBo= +github.com/sirupsen/logrus v1.10.2/go.mod h1:SLEg8TqYulVKKfIGHldVp2K2aYz2DKSVBq4g/H5bR7Q= +github.com/smallstep/pkcs7 v0.2.1 h1:6Kfzr/QizdIuB6LSv8y1LJdZ3aPSfTNhTLqAx9CTLfA= +github.com/smallstep/pkcs7 v0.2.1/go.mod h1:RcXHsMfL+BzH8tRhmrF1NkkpebKpq3JEM66cOFxanf0= github.com/soheilhy/cmux v0.1.5 h1:jjzc5WVemNEDTLwv9tlmemhC73tI08BNOIGwBOo10Js= github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0= +github.com/sorairolake/lzip-go v0.3.8 h1:j5Q2313INdTA80ureWYRhX+1K78mUXfMoPZCw/ivWik= +github.com/sorairolake/lzip-go v0.3.8/go.mod h1:JcBqGMV0frlxwrsE9sMWXDjqn3EeVf0/54YPsw66qkU= github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE= github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw= github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U= @@ -1038,6 +1195,10 @@ github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3A github.com/spf13/viper v1.10.0/go.mod h1:SoyBPwAtKDzypXNDFKN5kzH7ppppbGZtls1UpIy5AsM= github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU= github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY= +github.com/spiffe/go-spiffe/v2 v2.8.1 h1:eXZMLsu+3MLEPJyGJkolqtVrteZfQdUpOWj6LTiDl/E= +github.com/spiffe/go-spiffe/v2 v2.8.1/go.mod h1:47Q0Q9/AqGha8QLHp+kxpH4Wca7X7EnOtlIJy3mxZ3U= +github.com/stefanberger/go-pkcs11uri v0.0.0-20230803200340-78284954bff6 h1:pnnLyeX7o/5aX8qUQ69P/mLojDqwda8hFOCBTmP/6hw= +github.com/stefanberger/go-pkcs11uri v0.0.0-20230803200340-78284954bff6/go.mod h1:39R/xuhNgVhi+K0/zst4TLrJrVmbm6LVgl4A0+ZFS5M= github.com/streadway/amqp v1.1.0 h1:py12iX8XSyI7aN/3dUT8DFIDJazNJsVJdxNVEpnQTZM= github.com/streadway/amqp v1.1.0/go.mod h1:WYSrTEYHOXHd0nwFeUXAe2G2hRnQT+deZJJf88uS9Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -1062,15 +1223,21 @@ github.com/stripe/stripe-go/v74 v74.30.0/go.mod h1:f9L6LvaXa35ja7eyvP6GQswoaIPaB github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= +github.com/sylabs/sif/v2 v2.24.2 h1:rdkFf6z3xLC6yKweU7S9marlKfkS91Ht0ruIri6YWFY= +github.com/sylabs/sif/v2 v2.24.2/go.mod h1:bbrkk0JhTX+xNJcnUtmDkivoO2r4DGWM3LxNR0U2ooA= github.com/sylabs/squashfs v1.0.6 h1:PvJcDzxr+vIm2kH56mEMbaOzvGu79gK7P7IX+R7BDZI= github.com/sylabs/squashfs v1.0.6/go.mod h1:DlDeUawVXLWAsSRa085Eo0ZenGzAB32JdAUFaB0LZfE= github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA= github.com/tchap/go-patricia/v2 v2.3.3 h1:xfNEsODumaEcCcY3gI0hYPZ/PcpVv5ju6RMAhgwZDDc= github.com/tchap/go-patricia/v2 v2.3.3/go.mod h1:VZRHKAb53DLaG+nA9EaYYiaEx6YztwDlLElMsnSHD4k= +github.com/terminalstatic/go-xsd-validate v0.1.8 h1:UVrTCy1j3DhwaYTTUF+QYO/Nan13S0tf+Jwi+p45Bf0= +github.com/terminalstatic/go-xsd-validate v0.1.8/go.mod h1:1kb47fi2c6onlf+B7UrrQ9VYraOhcYwFm3iG+J6F4Zo= github.com/testcontainers/testcontainers-go v0.40.0 h1:pSdJYLOVgLE8YdUY2FHQ1Fxu+aMnb6JfVz1mxk7OeMU= github.com/testcontainers/testcontainers-go v0.40.0/go.mod h1:FSXV5KQtX2HAMlm7U3APNyLkkap35zNLxukw9oBi/MY= github.com/testcontainers/testcontainers-go/modules/k3s v0.40.0 h1:3w6SjtIp/+FdpjWJCyPqaGWknG2iU6MacEWA7hl0IqQ= github.com/testcontainers/testcontainers-go/modules/k3s v0.40.0/go.mod h1:1xJwmfO2g+XKox9LiJXKGCm1vWp7LozX+78UjXVRbF0= +github.com/tetratelabs/wazero v1.12.0 h1:DuWcpNu/FzgEXgGBDp8J1Spc+CWOvvtvVyjKlaZopYU= +github.com/tetratelabs/wazero v1.12.0/go.mod h1:LvKtzl2RqO4gyF27BiXU+nKAjcV8f38U+kP/q2vgxh0= github.com/therootcompany/xz v1.0.1 h1:CmOtsn1CbtmyYiusbfmhmkpAAETj0wBIH6kCYaX+xzw= github.com/therootcompany/xz v1.0.1/go.mod h1:3K3UH1yCKgBneZYhuQUvJ9HPD19UEXEI0BWbMn8qNMY= github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= @@ -1081,16 +1248,25 @@ github.com/tklauser/numcpus v0.11.0/go.mod h1:z+LwcLq54uWZTX0u/bGobaV34u6V7KNlTZ github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75 h1:6fotK7otjonDflCTK0BCfls4SPy3NcCVb5dqqmbRknE= github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75/go.mod h1:KO6IkyS8Y3j8OdNO85qEYBsRPuteD+YciPomcXdrMnk= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= -github.com/ulikunitz/xz v0.5.15 h1:9DNdB5s+SgV3bQ2ApL10xRc35ck0DuIX/isZvIk+ubY= -github.com/ulikunitz/xz v0.5.15/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= +github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= +github.com/ulikunitz/xz v0.5.17 h1:flR0y/x1hgM8EGV1AW3Xll6T413G0glV8UfBwR617V4= +github.com/ulikunitz/xz v0.5.17/go.mod h1:H9Rt/W6/Qj27PGauhQc6nfCDy7vHpzsOThBSaYDoEhw= github.com/uptrace/opentelemetry-go-extra/otelutil v0.4.0 h1:ZC08i0whcTGacAS7iDFxYL5D/bHdtdzxAjMJD8M15Pg= github.com/uptrace/opentelemetry-go-extra/otelutil v0.4.0/go.mod h1:j5Tnr2zA8UTDRzfIAZUaseE5/QPQCRv8JShSuGZek1A= github.com/uptrace/opentelemetry-go-extra/otelzap v0.4.0 h1:nPz2yF9qIJyuYDWhD7+ieSlNTi4OHgoOQ6MpR7jwNa8= github.com/uptrace/opentelemetry-go-extra/otelzap v0.4.0/go.mod h1:ubWgbUJc+h0o4gvsov82+s98XLu6rVQ//tQ47nINT2A= github.com/uptrace/uptrace-go v1.47.0 h1:FQgpAx/DdfDEaJ9xX03RaAc7BXVraBz0qC15oynFDpI= github.com/uptrace/uptrace-go v1.47.0/go.mod h1:xVJgZjczsSa94/pdg5HrtWTY/HyfCXxL7f+MienUh7Y= -github.com/vbatts/tar-split v0.12.2 h1:w/Y6tjxpeiFMR47yzZPlPj/FcPLpXbTUi/9H7d3CPa4= -github.com/vbatts/tar-split v0.12.2/go.mod h1:eF6B6i6ftWQcDqEn3/iGFRFRo8cBIMSJVOpnNdfTMFA= +github.com/valyala/fastjson v1.6.10 h1:/yjJg8jaVQdYR3arGxPE2X5z89xrlhS0eGXdv+ADTh4= +github.com/valyala/fastjson v1.6.10/go.mod h1:e6FubmQouUNP73jtMLmcbxS6ydWIpOfhz34TSfO3JaE= +github.com/vbatts/tar-split v0.12.3 h1:Cd46rkGXI3Td4yrVNwU8ripbxFaQbmesqhjBUUYAJSw= +github.com/vbatts/tar-split v0.12.3/go.mod h1:sQOc6OlqGCr7HkGx/IDBeKiTIvqhmj8KffNhEXG4Nq0= +github.com/vbauerster/cupwriter v0.0.4 h1:9sBPe0uXWLZuWQU5lqVbhyFlxX6c09asST/YfatFAys= +github.com/vbauerster/cupwriter v0.0.4/go.mod h1:IFyzS6Xis5dnBH/rdAhrnuzg3c+KkUqEN6yE8lhJlDw= +github.com/vbauerster/mpb/v8 v8.14.0 h1:55SR80dptMfASxIG/oCEkBXgBhxeSu4GrVsjl16oKmA= +github.com/vbauerster/mpb/v8 v8.14.0/go.mod h1:HgpQPKfcWe3kbuGGPmi+jatHreMase5C3Fp5dpdAy0Q= +github.com/vektah/gqlparser/v2 v2.5.36 h1:CN9mKVHgMkc+XftdOWIhb4HEL8wKSYkFAqhf8booa7s= +github.com/vektah/gqlparser/v2 v2.5.36/go.mod h1:cAJ9qwVgPaUkWv6Gn8vn0mqOE0Ui5Pn56wNy5396XWo= github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU= github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM= github.com/vishvananda/netlink v1.3.1 h1:3AEMt62VKqz90r0tmNhog0r/PpWKmrEShJU0wJW6bV0= @@ -1113,12 +1289,16 @@ github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMc github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= +github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= +github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= github.com/xiang90/probing v0.0.0-20221125231312-a49e3df8f510 h1:S2dVYn90KE98chqDkyE9Z4N61UnQd+KOfgp5Iu53llk= github.com/xiang90/probing v0.0.0-20221125231312-a49e3df8f510/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ= github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= +github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= +github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= github.com/yashtewari/glob-intersection v0.2.0 h1:8iuHdN88yYuCzCdjt0gDe+6bAhUwBeEWqThExu54RFg= github.com/yashtewari/glob-intersection v0.2.0/go.mod h1:LK7pIC3piUjovexikBbJ26Yml7g8xa5bsjfx2v1fwok= github.com/yl2chen/cidranger v1.0.2 h1:lbOWZVCG1tCRX4u24kuM1Tb4nHqWkDxwLdoS+SevawU= @@ -1133,8 +1313,8 @@ github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1 github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= -go.etcd.io/bbolt v1.4.3 h1:dEadXpI6G79deX5prL3QRNP6JB8UxVkqo4UPnHaNXJo= -go.etcd.io/bbolt v1.4.3/go.mod h1:tKQlpPaYCVFctUIgFKFnAlvbmB3tpy1vkTnDWohtc0E= +go.etcd.io/bbolt v1.5.0 h1:S7GAl7Fxv12yohbwFfIbQCGDWbQbtDGPET4P/bD4lxU= +go.etcd.io/bbolt v1.5.0/go.mod h1:mkltfYE5aUHQxUct9N9V+Kp7aSjFqjgrhcXIS70Lrdk= go.etcd.io/etcd/api/v3 v3.5.1/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= go.etcd.io/etcd/api/v3 v3.6.5 h1:pMMc42276sgR1j1raO/Qv3QI9Af/AuyQUW6CBAWuntA= go.etcd.io/etcd/api/v3 v3.6.5/go.mod h1:ob0/oWA/UQQlT1BmaEkWQzI0sJ1M0Et0mMpaABxguOQ= @@ -1160,12 +1340,12 @@ go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= -go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= -go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/contrib/bridges/otelslog v0.21.0 h1:/CdgE52i1OEPHhsGUqrggYNRaZy4v4YgVenGd52dRBI= go.opentelemetry.io/contrib/bridges/otelslog v0.21.0/go.mod h1:9pBdbVbuehIDzxN4accnzzxEUPT8Nq7grbZMo+J1VN4= +go.opentelemetry.io/contrib/detectors/gcp v1.44.0 h1:NmLfL734pJhM0JKaYd2Y28+nY9dPRWYAAbxhRCrKXPw= +go.opentelemetry.io/contrib/detectors/gcp v1.44.0/go.mod h1:tNAsgd8avTGke1+MndXlU5Cru4PQ9Ai/cCNWQv/ZJ/s= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.71.0 h1:B2h3uqicet1CT2N5TOFhS+Gq++9i0/CLmaxvhmhtP5s= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.71.0/go.mod h1:dylvB+ZiiwMvsDij9O84Uy7SijLgHMX4mbkncds+4Sw= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.71.0 h1:3g7B90UzBltIDKq1/5mrTGxTnOFDV0ICOhLoxiZ8jlg= @@ -1190,6 +1370,8 @@ go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.47.0 h1:UFpxO go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.47.0/go.mod h1:YBGjxe3lt0jtXoEXxGrGhv/jM7oUBUDVK7zhAvNyEjc= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.47.0 h1:aXZXsZ012wOgVkqaiQv+Z/d8V0xKjmg70F6m4CE94lc= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.47.0/go.mod h1:ziu1gUIJhAaxM3EV1e3Ralk5AAyL1UOYz3lJJ4VSI38= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.44.0 h1:hqxVTu/GtBF+vJ8d1fzW7fRxZFvgoDjWcxwwCaFDYpU= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.44.0/go.mod h1:z5fVEF4X5v0ESvlJqBrrFlBVoj5EQuefZpzsu7R+x5Q= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.47.0 h1:N3YQCxjxQ/bMjyc3heladfRm9t9RTksGQH8z4w6yU/0= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.47.0/go.mod h1:Mp8HOFqcaUyypCuGv9IhDdTHnJ56lSudSHMd+pVSCEA= go.opentelemetry.io/otel/log v1.47.0 h1:cOTS1CcLbSQeZKanGJ+0JpF/+t4PELi3O3bbl2lqCcI= @@ -1211,6 +1393,10 @@ go.opentelemetry.io/otel/trace v1.47.0/go.mod h1:jNaSLa2PZEYFG6fRjJABAu+bw4FS08u go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.opentelemetry.io/proto/otlp v1.11.1 h1:SCrPqH9NE5CmBrOqpjNCiT0X5mGwMyGERXdogfR7Yjw= go.opentelemetry.io/proto/otlp v1.11.1/go.mod h1:wq8W4aSf6bOy8RZyESOeQFr9Pu7wydWX/TY4sgvrTPg= +go.podman.io/image/v5 v5.41.2 h1:YnVaFrGLPWSBmipKfNvlOsGcBtk5bUxlPxQ+A6CS/uo= +go.podman.io/image/v5 v5.41.2/go.mod h1:T/bzxEFpdpAzyHabPmcq5K+my3wBsht22sFkFjJSKT4= +go.podman.io/storage v1.64.1 h1:DqbG8scW/bD3xHiSRzV7O5fzn46qVC+SwmUN6C6Fr5w= +go.podman.io/storage v1.64.1/go.mod h1:ft0DzCRMZs1yn0rszmLYXEEwgd7IIc66JRwSRPUDwwc= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= @@ -1220,12 +1406,14 @@ go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN8 go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= go.uber.org/zap v1.28.0 h1:IZzaP1Fv73/T/pBMLk4VutPl36uNC+OSUh3JLG3FIjo= go.uber.org/zap v1.28.0/go.mod h1:rDLpOi171uODNm/mxFcuYWxDsqWSAVkFdX4XojSKg/Q= -go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= -go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8= +go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ= +go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE= +go4.org v0.0.0-20230225012048-214862532bf5 h1:nifaUDeh+rPaBCMPMQHZmvJf+QdpLFnuQPwx+LxVmtc= +go4.org v0.0.0-20230225012048-214862532bf5/go.mod h1:F57wTi5Lrj6WLyswp5EYV1ncrEbFGHD4hhz6S1ZYeaU= go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M= go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y= golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw= @@ -1243,8 +1431,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= -golang.org/x/crypto v0.57.0 h1:3ZVCjf8Ggz7zneR/EHRVx68Ctf+2pmIMP2UFhh9cC6M= -golang.org/x/crypto v0.57.0/go.mod h1:Fdz0i5U6CoizGwLda9DttjSk6qlZo25zYNtR+ycvuZA= +golang.org/x/crypto v0.58.0 h1:COkYLr4k7nDI4r6QtMML2AfzeIswWHAJthLS6K/M/54= +golang.org/x/crypto v0.58.0/go.mod h1:Xh+kl5A+M0gMiWDytH2Hhr5Z7WbNCpv/eB/EFgfSk+w= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -1348,8 +1536,8 @@ golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.59.0 h1:5zfYln+w5XCxwrnMMJPufRgNoXEaGxl0wo5GqPXyues= -golang.org/x/net v0.59.0/go.mod h1:2DA/G1UfVbCpQPeWTmMPGY7Cs2PkBkwu743bVX5PIVg= +golang.org/x/net v0.61.0 h1:RR+6j/BTBCrggOLugxa+66R8FvoN6MHlzDlZIbN9YFw= +golang.org/x/net v0.61.0/go.mod h1:WgDCOTH8iwtB66T4/GfI94AHo/hhdb7Hah9NDseQfiI= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -1369,8 +1557,8 @@ golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20211005180243-6b3c2da341f1/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs= -golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q= +golang.org/x/oauth2 v0.37.0 h1:JUlcxA8oAtauLfiH8FX2/FkAWHAdi0QtGCGc+hofE98= +golang.org/x/oauth2 v0.37.0/go.mod h1:IxwZNxUULJmpBFf9K/9NTMSIfZZuvuTy1gGxhigP/58= golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -1384,8 +1572,8 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.23.0 h1:KameEIfc1IkluZyXWLn39Wd4tURc6GbCiISGiZm2bQk= -golang.org/x/sync v0.23.0/go.mod h1:sUUOizhqBxiL6pEWpqNLUiaJn1ShEbZ6BBqskPbjZm0= +golang.org/x/sync v0.24.0 h1:iqiDrGoDgyuvAKrhLQthUEzU/R378N78gXbBjxed7oA= +golang.org/x/sync v0.24.0/go.mod h1:sUUOizhqBxiL6pEWpqNLUiaJn1ShEbZ6BBqskPbjZm0= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1457,21 +1645,23 @@ golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211205182925-97ca703d548d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.48.0 h1:bbX/i/6MgT9BVLM9RT1thmxL04yeTAhbEz4SyadbXoo= -golang.org/x/sys v0.48.0/go.mod h1:hNLxWAXmnKAxqDtdwIYC4bM9oQPEecfsnNMuSxOs3og= +golang.org/x/sys v0.49.0 h1:XbzkgYJHdqh/8m2Uu0W/dQv8nktxx4BFHp1M0gROTXA= +golang.org/x/sys v0.49.0/go.mod h1:hNLxWAXmnKAxqDtdwIYC4bM9oQPEecfsnNMuSxOs3og= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.46.0 h1:3+OXuTbaKDgwk8jTi3aSLHRlmWqHEUDUtxnbFigO4YE= -golang.org/x/term v0.46.0/go.mod h1:+K02xbkittuwc0Am4abfA3Fc+XRGXkvBXNO88NCXPoc= +golang.org/x/term v0.47.0 h1:cpyOwQkr2Nzxe2p6KnrTG7Ei2wtzjsuSjhpXXepZ/BM= +golang.org/x/term v0.47.0/go.mod h1:efAfAuG6RKABvg1ArGebRkvX6LXul14SM67o2e4mUCY= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1483,14 +1673,14 @@ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.42.0 h1:JbOZXgfeCPU9gacVtYliJqOhD+zhrEqK4LfdpmlUZqI= -golang.org/x/text v0.42.0/go.mod h1:ojzP1Z+2QtioaF8DTtO8K5q7JWVVYwZKenzujK0Zd0E= +golang.org/x/text v0.43.0 h1:1QivrlhwAsnMOcqgOdl9my7s9OyZl8O5/UGUTJVXoWc= +golang.org/x/text v0.43.0/go.mod h1:hbSIYA/amXcRXTFSZqh8tWHEoqRvvbKPJ2uz/KU+a+s= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U= -golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno= +golang.org/x/time v0.16.0 h1:vMb6ptszcQMkcwiRTAuNNU50gom6++Q/6gY2hDM6VDE= +golang.org/x/time v0.16.0/go.mod h1:rVKOqvZeKvrDKTQiAHJ7wmwP0RzleSphoEA9RcdLA0s= golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -1551,8 +1741,8 @@ golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.50.0 h1:c2ifzfcuY7L90lZ2aKd8S4K2NpASF08SZx9ZuJkHmSU= -golang.org/x/tools v0.50.0/go.mod h1:7ulVMw3831Mwi5EZD6RomGyffr4VFjuNYXf2BbCEAV0= +golang.org/x/tools v0.51.0 h1:k4Xc/1Om9jwkBJBo4NVLMSARBoWtK10mx+W5BnXCeAI= +golang.org/x/tools v0.51.0/go.mod h1:9eEncMayCV6zRMGhR5eZEC2iBx98qWcF1HZ9Z7wJOoA= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -1600,8 +1790,8 @@ google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdr google.golang.org/api v0.59.0/go.mod h1:sT2boj7M9YJxZzgeZqXogmhfmRWDtPzT31xkieUbuZU= google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I= google.golang.org/api v0.62.0/go.mod h1:dKmwPCydfsad4qCH08MSdgWjfHOyfpd4VtDGgRFdavw= -google.golang.org/api v0.278.0 h1:W7jiRvRi53VYFfZ/HoZjQBtJk7gOFbHD8ot1RzVZU6E= -google.golang.org/api v0.278.0/go.mod h1:B9TqLBwJqVjp1mtt7WeoQwWRwvu/400y5lETOql+giQ= +google.golang.org/api v0.299.0 h1:b3K+ydSMd0kh6TQI6bJyApRQfqQX2MfSOaVkpM59mJw= +google.golang.org/api v0.299.0/go.mod h1:zlR3GVA8b2R5nv5Ij9UWe37StVB3cxDD7DBFi4ZFsHw= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -1678,8 +1868,8 @@ google.golang.org/genproto v0.0.0-20211129164237-f09f9a12af12/go.mod h1:5CzLGKJ6 google.golang.org/genproto v0.0.0-20211203200212-54befc351ae9/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20260319201613-d00831a3d3e7 h1:XzmzkmB14QhVhgnawEVsOn6OFsnpyxNPRY9QV01dNB0= -google.golang.org/genproto v0.0.0-20260319201613-d00831a3d3e7/go.mod h1:L43LFes82YgSonw6iTXTxXUX1OlULt4AQtkik4ULL/I= +google.golang.org/genproto v0.0.0-20260715232425-e75dac1f907d h1:C9v1o0/4quuhOAfmRXA2j+we0PqZIp8traLdeogF3Ms= +google.golang.org/genproto v0.0.0-20260715232425-e75dac1f907d/go.mod h1:Wz2wFJntZFmLGo7pLDXZ3wYk5hyc0Mb+SkHhDDXT+lU= google.golang.org/genproto/googleapis/api v0.0.0-20261005182115-fad411399dd8 h1:cFbkNigDInw+wVTXc+CyhUsoHGJCw0BR6mlNoRVfre0= google.golang.org/genproto/googleapis/api v0.0.0-20261005182115-fad411399dd8/go.mod h1:QK6p7IgD7IZEZJBhhSUCib5X2vMOzlrGCGUw2XyVDh0= google.golang.org/genproto/googleapis/rpc v0.0.0-20261005182115-fad411399dd8 h1:0SXBqli5dnuCKMjI/X3FM226zEaEa0GUcSCJtN1vL2o= diff --git a/mainhandler/handlerequests.go b/mainhandler/handlerequests.go index a11b0c42..b5b3d70a 100644 --- a/mainhandler/handlerequests.go +++ b/mainhandler/handlerequests.go @@ -141,6 +141,9 @@ func (mainHandler *MainHandler) SetupContinuousScanning(ctx context.Context) err return nil } +// HandleWatchers starts the watchers the enabled components need: commands, +// SBOMs or pods for image scans and the SBOM export, container profiles and +// security exceptions. func (mainHandler *MainHandler) HandleWatchers(ctx context.Context) { defer func() { if err := recover(); err != nil { @@ -151,17 +154,28 @@ func (mainHandler *MainHandler) HandleWatchers(ctx context.Context) { commandWatchHandler := watcher.NewCommandWatchHandler(mainHandler.k8sAPI, mainHandler.config) operatorCommandsHandler := watcher.NewOperatorCommandsHandler(ctx, mainHandler.eventWorkerPool, mainHandler.k8sAPI, commandWatchHandler, mainHandler.config) - if mainHandler.config.Components().Kubevuln.Enabled { + kubevulnEnabled := mainHandler.config.Components().Kubevuln.Enabled + sbomExporter := mainHandler.newSBOMExporter() + if kubevulnEnabled || sbomExporter != nil { eventQueue := watcher.NewCooldownQueue() watchHandler := watcher.NewWatchHandler(mainHandler.config, mainHandler.k8sAPI, mainHandler.ksStorageClient, eventQueue) - registryCommandsHandler := watcher.NewRegistryCommandsHandler(ctx, mainHandler.k8sAPI, commandWatchHandler, mainHandler.config) - go registryCommandsHandler.Start() + if sbomExporter != nil { + watchHandler.SetSBOMObserver(sbomExporter) + go sbomExporter.Start(ctx) + } + + if kubevulnEnabled { + registryCommandsHandler := watcher.NewRegistryCommandsHandler(ctx, mainHandler.k8sAPI, commandWatchHandler, mainHandler.config) + go registryCommandsHandler.Start() - // wait for the kubevuln component to be ready - logger.L().Info("Waiting for vuln scan to be ready") - waitFunc := isActionNeedToWait(apis.Command{CommandName: apis.TypeScanImages}) - waitFunc(mainHandler.config) + // wait for the kubevuln component to be ready + logger.L().Info("Waiting for vuln scan to be ready") + waitFunc := isActionNeedToWait(apis.Command{CommandName: apis.TypeScanImages}) + waitFunc(mainHandler.config) + } else { + watchHandler.DisableImageScans() + } // start watching if mainHandler.config.NodeSbomGenerationEnabled() { @@ -169,7 +183,9 @@ func (mainHandler *MainHandler) HandleWatchers(ctx context.Context) { } else { go watchHandler.PodWatch(ctx, mainHandler.eventWorkerPool) } - go watchHandler.ContainerProfileWatch(ctx, mainHandler.eventWorkerPool) + if kubevulnEnabled { + go watchHandler.ContainerProfileWatch(ctx, mainHandler.eventWorkerPool) + } } // Watch SecurityException/ClusterSecurityException CRDs and rescan on diff --git a/mainhandler/sbomexport.go b/mainhandler/sbomexport.go new file mode 100644 index 00000000..fadff038 --- /dev/null +++ b/mainhandler/sbomexport.go @@ -0,0 +1,49 @@ +package mainhandler + +import ( + "github.com/kubescape/go-logger" + "github.com/kubescape/go-logger/helpers" + "github.com/kubescape/operator/config" + "github.com/kubescape/operator/sbomexport" +) + +// newSBOMExporter returns the SBOM exporter when the sbomExport capability is +// on, and nil when it is off or cannot run. It needs nodeSbomGeneration: it +// exports the SBOMs node-agent makes, and SBOMWatch, which feeds it, only runs +// with it. +func (mainHandler *MainHandler) newSBOMExporter() *sbomexport.Exporter { + if !mainHandler.config.SBOMExportEnabled() { + return nil + } + if !mainHandler.config.NodeSbomGenerationEnabled() { + logger.L().Warning("SBOM export is off: the sbomExport capability needs nodeSbomGeneration") + return nil + } + exporter, err := sbomexport.New(sbomExportOptions(mainHandler.config), mainHandler.k8sAPI.KubernetesClient, mainHandler.ksStorageClient) + if err != nil { + logger.L().Error("SBOM export is off: invalid configuration", helpers.Error(err)) + return nil + } + return exporter +} + +// sbomExportOptions reads the exporter's options from the operator +// configuration. +func sbomExportOptions(cfg config.IConfig) sbomexport.Options { + export := cfg.SBOMExportConfig() + dt := export.DependencyTrack + return sbomexport.Options{ + URL: dt.URL, + APIKeyFile: dt.APIKeyFile, + Cluster: cfg.ClusterName(), + Layout: sbomexport.Layout{ + Root: sbomexport.Collection{Name: dt.RootProject.Name, Version: dt.RootProject.Version, Create: dt.RootProject.Create}, + Parent: sbomexport.Collection{Name: dt.Project.Parent.Name, Version: dt.Project.Parent.Version, Create: dt.Project.Parent.Create}, + Name: dt.Project.Name, + Version: dt.Project.Version, + }, + MaxSBOMSize: export.MaxSBOMSize, + ResyncInterval: export.ResyncInterval, + SkipNamespace: cfg.SkipNamespace, + } +} diff --git a/mainhandler/sbomexport_test.go b/mainhandler/sbomexport_test.go new file mode 100644 index 00000000..46f61c3e --- /dev/null +++ b/mainhandler/sbomexport_test.go @@ -0,0 +1,114 @@ +package mainhandler + +import ( + "testing" + "time" + + "github.com/armosec/utils-k8s-go/armometadata" + beUtils "github.com/kubescape/backend/pkg/utils" + "github.com/kubescape/operator/config" + "github.com/kubescape/operator/sbomexport" + "github.com/kubescape/operator/utils" + kssfake "github.com/kubescape/storage/pkg/generated/clientset/versioned/fake" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + k8sfake "k8s.io/client-go/kubernetes/fake" +) + +func sbomExportConfig(t *testing.T, capabilities config.Capabilities, export config.SBOMExportConfig) config.IConfig { + t.Helper() + cfg, err := config.NewOperatorConfig( + config.CapabilitiesConfig{Capabilities: capabilities}, + armometadata.ClusterConfig{ClusterName: "prod-cluster"}, + &beUtils.Credentials{}, + config.Config{SBOMExport: export}, + ) + require.NoError(t, err) + return cfg +} + +// TestNewSBOMExporter checks when the operator starts the export and when it +// runs without it. +func TestNewSBOMExporter(t *testing.T) { + valid := config.SBOMExportConfig{ + ResyncInterval: time.Hour, + MaxSBOMSize: 10 << 20, + DependencyTrack: config.DependencyTrackConfig{ + URL: "https://dtrack.example.com", + APIKeyFile: "/etc/sbom-export/api-key", + RootProject: config.DTCollectionConfig{Name: "{{ .Cluster }}", Version: "current", Create: true}, + Project: config.DTProjectConfig{ + Name: "{{ .Cluster }}/{{ .Namespace }}/{{ .Repository }}", + Version: "{{ .Tag }}", + Parent: config.DTCollectionConfig{Name: "{{ .Cluster }}/{{ .Namespace }}", Version: "current", Create: true}, + }, + }, + } + tt := []struct { + name string + capabilities config.Capabilities + export config.SBOMExportConfig + wantExporter bool + }{ + { + name: "capability off", + capabilities: config.Capabilities{NodeSbomGeneration: "enable"}, + export: valid, + }, + { + name: "without nodeSbomGeneration", + capabilities: config.Capabilities{SBOMExport: "enable"}, + export: valid, + }, + { + name: "without a Dependency-Track URL", + capabilities: config.Capabilities{SBOMExport: "enable", NodeSbomGeneration: "enable"}, + export: config.SBOMExportConfig{ResyncInterval: time.Hour}, + }, + { + name: "capability on and configured", + capabilities: config.Capabilities{SBOMExport: "enable", NodeSbomGeneration: "enable"}, + export: valid, + wantExporter: true, + }, + } + for _, tc := range tt { + t.Run(tc.name, func(t *testing.T) { + mainHandler := &MainHandler{ + config: sbomExportConfig(t, tc.capabilities, tc.export), + k8sAPI: utils.NewK8sInterfaceFake(k8sfake.NewClientset()), + ksStorageClient: kssfake.NewClientset(), + } + assert.Equal(t, tc.wantExporter, mainHandler.newSBOMExporter() != nil) + }) + } +} + +func TestSBOMExportOptionsCarryTheLayoutAndTheClusterName(t *testing.T) { + export := config.SBOMExportConfig{ + ResyncInterval: 30 * time.Minute, + MaxSBOMSize: 5 << 20, + DependencyTrack: config.DependencyTrackConfig{ + URL: "https://dtrack.example.com", + APIKeyFile: "/keys/dtrack", + RootProject: config.DTCollectionConfig{Name: "{{ .Cluster }}", Version: "current", Create: true}, + Project: config.DTProjectConfig{ + Name: "{{ .Repository }}", + Version: "{{ .Tag }}", + Parent: config.DTCollectionConfig{Name: "Payments Team", Version: "current", Create: false}, + }, + }, + } + opts := sbomExportOptions(sbomExportConfig(t, config.Capabilities{}, export)) + assert.Equal(t, "prod-cluster", opts.Cluster) + assert.Equal(t, "https://dtrack.example.com", opts.URL) + assert.Equal(t, "/keys/dtrack", opts.APIKeyFile) + assert.Equal(t, int64(5<<20), opts.MaxSBOMSize) + assert.Equal(t, 30*time.Minute, opts.ResyncInterval) + assert.Equal(t, sbomexport.Layout{ + Root: sbomexport.Collection{Name: "{{ .Cluster }}", Version: "current", Create: true}, + Parent: sbomexport.Collection{Name: "Payments Team", Version: "current", Create: false}, + Name: "{{ .Repository }}", + Version: "{{ .Tag }}", + }, opts.Layout) +} diff --git a/sbomexport/convert.go b/sbomexport/convert.go new file mode 100644 index 00000000..10e08bb6 --- /dev/null +++ b/sbomexport/convert.go @@ -0,0 +1,32 @@ +package sbomexport + +import ( + "bytes" + "fmt" + + "github.com/anchore/syft/syft/format/cyclonedxjson" + "github.com/anchore/syft/syft/format/syftjson" +) + +// cycloneDXVersion is the CycloneDX specification version of the uploaded +// BOMs. +const cycloneDXVersion = "1.6" + +// toCycloneDX converts a syft JSON document, the `.spec.syft` of an SBOMSyft, +// to CycloneDX JSON, the way `syft convert` does: syft's own syft-json +// decoder, then its CycloneDX encoder. +func toCycloneDX(syftJSON []byte) ([]byte, error) { + s, _, _, err := syftjson.NewFormatDecoder().Decode(bytes.NewReader(syftJSON)) + if err != nil { + return nil, fmt.Errorf("decode syft document: %w", err) + } + enc, err := cyclonedxjson.NewFormatEncoderWithConfig(cyclonedxjson.EncoderConfig{Version: cycloneDXVersion}) + if err != nil { + return nil, err + } + var buf bytes.Buffer + if err := enc.Encode(&buf, *s); err != nil { + return nil, fmt.Errorf("encode CycloneDX: %w", err) + } + return buf.Bytes(), nil +} diff --git a/sbomexport/convert_test.go b/sbomexport/convert_test.go new file mode 100644 index 00000000..744017ec --- /dev/null +++ b/sbomexport/convert_test.go @@ -0,0 +1,38 @@ +package sbomexport + +import ( + _ "embed" + "encoding/json" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// curl-8.11.1.syft.json is the `.spec.syft` that node-agent v0.3.310 stored +// for docker.io/curlimages/curl:8.11.1; curl-8.11.1.cdx.json is what +// `syft convert -o cyclonedx-json@1.6` (syft v1.55.0) makes of it. +var ( + //go:embed testdata/curl-8.11.1.syft.json + curlSyftJSON []byte + //go:embed testdata/curl-8.11.1.cdx.json + curlCycloneDX []byte +) + +func TestToCycloneDXMatchesSyftConvert(t *testing.T) { + got, err := toCycloneDX(curlSyftJSON) + require.NoError(t, err) + + var gotBOM, wantBOM map[string]any + require.NoError(t, json.Unmarshal(got, &gotBOM)) + require.NoError(t, json.Unmarshal(curlCycloneDX, &wantBOM)) + assert.Equal(t, cycloneDXVersion, gotBOM["specVersion"]) + assert.Equal(t, wantBOM["components"], gotBOM["components"]) + assert.Equal(t, wantBOM["dependencies"], gotBOM["dependencies"]) + assert.Equal(t, wantBOM["metadata"].(map[string]any)["component"], gotBOM["metadata"].(map[string]any)["component"]) +} + +func TestToCycloneDXRejectsADocumentThatIsNotSyftJSON(t *testing.T) { + _, err := toCycloneDX(curlCycloneDX) + assert.Error(t, err) +} diff --git a/sbomexport/dtrack.go b/sbomexport/dtrack.go new file mode 100644 index 00000000..7c78a328 --- /dev/null +++ b/sbomexport/dtrack.go @@ -0,0 +1,314 @@ +package sbomexport + +import ( + "bytes" + "context" + "encoding/base64" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "net/url" + "os" + "strings" + "time" +) + +const ( + // managedTag marks the projects the exporter created. It changes no other + // project: no collection logic, no BOM, no move. + managedTag = "managed-by:kubescape" + + // propertyGroup holds the project properties the exporter keeps on the + // image projects it creates. + propertyGroup = "kubescape" + // digestProperty records which image digest a project's BOM came from, so + // two images cannot overwrite each other's project. + digestProperty = "image-digest" + // toolVersionProperty records the node-agent version that built the SBOM + // last uploaded, so an SBOM node-agent rebuilt in place is sent again. + toolVersionProperty = "sbom-tool-version" +) + +// project is the part of a Dependency-Track project the exporter reads. +type project struct { + UUID string `json:"uuid"` + Name string `json:"name"` + Version string `json:"version"` + IsLatest bool `json:"isLatest"` + CollectionLogic string `json:"collectionLogic"` + LastBOMImport epochMillis `json:"lastBomImport"` + Tags []tag `json:"tags"` + // Parent is set in a GET by UUID; a lookup by name leaves it out. + Parent *projectParent `json:"parent,omitempty"` +} + +// projectParent is a project's parent, by UUID. +type projectParent struct { + UUID string `json:"uuid"` +} + +type tag struct { + Name string `json:"name"` +} + +// managed reports whether the exporter created p. +func (p *project) managed() bool { + for _, t := range p.Tags { + if strings.EqualFold(t.Name, managedTag) { + return true + } + } + return false +} + +// epochMillis is a Dependency-Track timestamp. It decodes from epoch +// milliseconds or from an RFC 3339 string. +type epochMillis int64 + +// UnmarshalJSON decodes a number of milliseconds, an RFC 3339 string, or null. +func (m *epochMillis) UnmarshalJSON(b []byte) error { + if string(b) == "null" { + *m = 0 + return nil + } + var n int64 + if err := json.Unmarshal(b, &n); err == nil { + *m = epochMillis(n) + return nil + } + var s string + if err := json.Unmarshal(b, &s); err != nil { + return fmt.Errorf("timestamp is neither a number nor a string: %s", b) + } + t, err := time.Parse(time.RFC3339Nano, s) + if err != nil { + return err + } + *m = epochMillis(t.UnixMilli()) + return nil +} + +// Time returns the timestamp as a time.Time; zero when Dependency-Track sent none. +func (m epochMillis) Time() time.Time { + if m == 0 { + return time.Time{} + } + return time.UnixMilli(int64(m)) +} + +// dtClient calls the Dependency-Track API v1. +type dtClient struct { + baseURL string + apiKeyFile string + http *http.Client +} + +// errBody is how much of an error response goes into an error message. +const errBody = 300 + +// do sends one request. The API key is read from apiKeyFile on every call, so +// a rotated Secret takes effect without a restart. A status in ok is a +// success, and a 2xx body is decoded into out when out is not nil; any other +// status is an error. +func (c *dtClient) do(ctx context.Context, method, path string, body any, out any, ok ...int) (int, error) { + key, err := os.ReadFile(c.apiKeyFile) + if err != nil { + return 0, fmt.Errorf("read Dependency-Track API key: %w", err) + } + if len(bytes.TrimSpace(key)) == 0 { + return 0, fmt.Errorf("the Dependency-Track API key file %s is empty", c.apiKeyFile) + } + + var reqBody io.Reader + if body != nil { + b, err := json.Marshal(body) + if err != nil { + return 0, err + } + reqBody = bytes.NewReader(b) + } + req, err := http.NewRequestWithContext(ctx, method, c.baseURL+path, reqBody) + if err != nil { + return 0, err + } + req.Header.Set("X-Api-Key", string(bytes.TrimSpace(key))) + req.Header.Set("Accept", "application/json") + if body != nil { + req.Header.Set("Content-Type", "application/json") + } + + // errors name the path without its query, which can hold a project name + errPath, _, _ := strings.Cut(path, "?") + resp, err := c.http.Do(req) + if err != nil { + return 0, fmt.Errorf("%s %s: %w", method, errPath, err) + } + defer func() { _ = resp.Body.Close() }() + data, err := io.ReadAll(io.LimitReader(resp.Body, 4<<20)) + if err != nil { + return resp.StatusCode, fmt.Errorf("%s %s: read response: %w", method, errPath, err) + } + + for _, s := range ok { + if resp.StatusCode != s { + continue + } + if out != nil && len(data) > 0 && resp.StatusCode < 300 { // a 404 body is plain text + if err := json.Unmarshal(data, out); err != nil { + return resp.StatusCode, fmt.Errorf("%s %s: decode response: %w", method, errPath, err) + } + } + return resp.StatusCode, nil + } + msg := strings.TrimSpace(string(data)) + if len(msg) > errBody { + msg = msg[:errBody] + } + return resp.StatusCode, &httpError{method: method, path: errPath, status: resp.StatusCode, body: msg} +} + +// httpError is a response with an unexpected status. +type httpError struct { + method, path string + status int + body string +} + +// Error names the request and quotes the start of the response body. +func (e *httpError) Error() string { + return fmt.Sprintf("%s %s: HTTP %d: %s", e.method, e.path, e.status, e.body) +} + +// isAuthError reports whether err is Dependency-Track refusing the API key or +// one of its permissions. +func isAuthError(err error) bool { + var h *httpError + return errors.As(err, &h) && (h.status == http.StatusUnauthorized || h.status == http.StatusForbidden) +} + +// lookup returns the project name:version, or nil when it does not exist. +func (c *dtClient) lookup(ctx context.Context, name, version string) (*project, error) { + q := url.Values{"name": {name}, "version": {version}} + var p project + status, err := c.do(ctx, http.MethodGet, "/api/v1/project/lookup?"+q.Encode(), nil, &p, http.StatusOK, http.StatusNotFound) + if err != nil { + return nil, err + } + if status == http.StatusNotFound { + return nil, nil + } + return &p, nil +} + +// getByUUID returns the project with uuid, or nil when it does not exist. +// Dependency-Track answers 400 for some UUIDs it does not know. +func (c *dtClient) getByUUID(ctx context.Context, uuid string) (*project, error) { + var p project + status, err := c.do(ctx, http.MethodGet, "/api/v1/project/"+url.PathEscape(uuid), nil, &p, http.StatusOK, http.StatusNotFound, http.StatusBadRequest) + if err != nil { + return nil, err + } + if status != http.StatusOK { + return nil, nil + } + return &p, nil +} + +// find returns the project ref points at, or nil when it does not exist. +func (c *dtClient) find(ctx context.Context, ref projectRef) (*project, error) { + if ref.uuid != "" { + return c.getByUUID(ctx, ref.uuid) + } + return c.lookup(ctx, ref.name, ref.version) +} + +// createCollection creates the collection project name:version with the given +// collection logic, under parentUUID unless it is empty, tagged as created by +// the exporter. +func (c *dtClient) createCollection(ctx context.Context, name, version, parentUUID, logic string) (*project, error) { + body := map[string]any{"name": name, "version": version, "collectionLogic": logic, "tags": []tag{{Name: managedTag}}} + if parentUUID != "" { + body["parent"] = map[string]string{"uuid": parentUUID} + } + var p project + if _, err := c.do(ctx, http.MethodPut, "/api/v1/project", body, &p, http.StatusCreated, http.StatusOK); err != nil { + return nil, err + } + return &p, nil +} + +// patch changes the given fields of the project with uuid. Dependency-Track +// answers 304 when nothing changed. +func (c *dtClient) patch(ctx context.Context, uuid string, fields map[string]any) error { + _, err := c.do(ctx, http.MethodPatch, "/api/v1/project/"+url.PathEscape(uuid), fields, nil, http.StatusOK, http.StatusNotModified) + return err +} + +// createProject creates the project name:version under parentUUID, tagged as +// created by the exporter. +func (c *dtClient) createProject(ctx context.Context, name, version, parentUUID string) (*project, error) { + body := map[string]any{"name": name, "version": version, "tags": []tag{{Name: managedTag}}} + if parentUUID != "" { + body["parent"] = map[string]string{"uuid": parentUUID} + } + var p project + if _, err := c.do(ctx, http.MethodPut, "/api/v1/project", body, &p, http.StatusCreated, http.StatusOK); err != nil { + return nil, err + } + return &p, nil +} + +// uploadBOM uploads bom as the latest version of the existing project +// name:version. Dependency-Track processes the BOM asynchronously after +// answering. +func (c *dtClient) uploadBOM(ctx context.Context, name, version string, bom []byte) error { + body := map[string]any{ + "projectName": name, + "projectVersion": version, + "autoCreate": false, + "isLatest": true, + "bom": base64.StdEncoding.EncodeToString(bom), + } + _, err := c.do(ctx, http.MethodPut, "/api/v1/bom", body, nil, http.StatusOK) + return err +} + +// projectProperty is a Dependency-Track project property. +type projectProperty struct { + GroupName string `json:"groupName"` + PropertyName string `json:"propertyName"` + PropertyValue string `json:"propertyValue"` + PropertyType string `json:"propertyType"` +} + +// properties returns the exporter's properties of the project with uuid, by +// name. +func (c *dtClient) properties(ctx context.Context, uuid string) (map[string]string, error) { + var props []projectProperty + if _, err := c.do(ctx, http.MethodGet, "/api/v1/project/"+url.PathEscape(uuid)+"/property", nil, &props, http.StatusOK); err != nil { + return nil, err + } + out := map[string]string{} + for _, p := range props { + if p.GroupName == propertyGroup { + out[p.PropertyName] = p.PropertyValue + } + } + return out, nil +} + +// setProperty records the exporter's property name on the project with uuid: +// PUT creates the property, and answers 409 when it exists, which POST then +// updates. +func (c *dtClient) setProperty(ctx context.Context, uuid, name, value string) error { + body := projectProperty{GroupName: propertyGroup, PropertyName: name, PropertyValue: value, PropertyType: "STRING"} + path := "/api/v1/project/" + url.PathEscape(uuid) + "/property" + status, err := c.do(ctx, http.MethodPut, path, body, nil, http.StatusCreated, http.StatusOK, http.StatusConflict) + if err != nil || status != http.StatusConflict { + return err + } + _, err = c.do(ctx, http.MethodPost, path, body, nil, http.StatusOK, http.StatusNotModified) + return err +} diff --git a/sbomexport/dtrack_test.go b/sbomexport/dtrack_test.go new file mode 100644 index 00000000..b2834d30 --- /dev/null +++ b/sbomexport/dtrack_test.go @@ -0,0 +1,117 @@ +package sbomexport + +import ( + "context" + "encoding/json" + "net/http" + "net/http/httptest" + "os" + "strings" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestDTClientLooksUpCreatesAndPatchesProjects(t *testing.T) { + ctx := context.Background() + f, srv, keyFile := newFakeDT(t, time.Now) + c := &dtClient{baseURL: srv.URL, apiKeyFile: keyFile, http: srv.Client()} + + p, err := c.lookup(ctx, "cluster", "current") + require.NoError(t, err) + assert.Nil(t, p, "a missing project is not an error") + + root, err := c.createCollection(ctx, "cluster", "current", "", rootLogic) + require.NoError(t, err) + ns, err := c.createCollection(ctx, "cluster/apps", "current", root.UUID, parentLogic) + require.NoError(t, err) + require.NoError(t, c.patch(ctx, ns.UUID, map[string]any{"collectionLogic": rootLogic})) + + got, parent, ok := f.project("cluster/apps", "current") + require.True(t, ok) + assert.Equal(t, "cluster", parent) + assert.Equal(t, rootLogic, got.CollectionLogic) + + p, err = c.lookup(ctx, "cluster/apps", "current") + require.NoError(t, err) + assert.Equal(t, ns.UUID, p.UUID) +} + +func TestDTClientCreatesAProjectUnderItsParentAndUploadsItsBOM(t *testing.T) { + ctx := context.Background() + now := time.Date(2026, 11, 10, 12, 0, 0, 0, time.UTC) + f, srv, keyFile := newFakeDT(t, func() time.Time { return now }) + c := &dtClient{baseURL: srv.URL, apiKeyFile: keyFile, http: srv.Client()} + ns, err := c.createCollection(ctx, "cluster/apps", "current", "", parentLogic) + require.NoError(t, err) + + _, err = c.createProject(ctx, "cluster/apps/redis", "8", ns.UUID) + require.NoError(t, err) + require.NoError(t, c.uploadBOM(ctx, "cluster/apps/redis", "8", curlCycloneDX)) + + got, parent, ok := f.project("cluster/apps/redis", "8") + require.True(t, ok) + assert.Equal(t, "cluster/apps", parent) + assert.True(t, got.IsLatest) + assert.Equal(t, now, got.LastBOMImport.Time().UTC()) +} + +func TestDTClientReadsTheAPIKeyOnEveryRequest(t *testing.T) { + ctx := context.Background() + _, srv, keyFile := newFakeDT(t, time.Now) + c := &dtClient{baseURL: srv.URL, apiKeyFile: keyFile, http: srv.Client()} + + require.NoError(t, os.WriteFile(keyFile, []byte("revoked-key"), 0600)) + _, err := c.lookup(ctx, "cluster", "current") + require.Error(t, err) + assert.True(t, isAuthError(err)) + + require.NoError(t, os.WriteFile(keyFile, []byte(testAPIKey), 0600)) + _, err = c.lookup(ctx, "cluster", "current") + assert.NoError(t, err, "the rotated key is used without a restart") + + require.NoError(t, os.WriteFile(keyFile, []byte(" \n"), 0600)) + _, err = c.lookup(ctx, "cluster", "current") + assert.ErrorContains(t, err, "is empty") +} + +func TestDTClientErrorCarriesTheStatusAndTheStartOfTheBody(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + http.Error(w, strings.Repeat("x", 2*errBody), http.StatusInternalServerError) + })) + defer srv.Close() + keyFile := t.TempDir() + "/api-key" + require.NoError(t, os.WriteFile(keyFile, []byte(testAPIKey), 0600)) + c := &dtClient{baseURL: srv.URL, apiKeyFile: keyFile, http: srv.Client()} + + err := c.uploadBOM(context.Background(), "cluster/apps/redis", "8", curlCycloneDX) + + require.Error(t, err) + assert.Contains(t, err.Error(), "PUT /api/v1/bom: HTTP 500: "+strings.Repeat("x", errBody)) + assert.NotContains(t, err.Error(), strings.Repeat("x", errBody+1)) + assert.False(t, isAuthError(err)) +} + +// A Dependency-Track timestamp decodes from epoch milliseconds, RFC 3339, null +// or nothing. +func TestEpochMillisDecodesNumbersAndRFC3339(t *testing.T) { + tt := []struct { + name string + json string + want time.Time + }{ + {"epoch milliseconds", `{"lastBomImport":1762776000123}`, time.UnixMilli(1762776000123)}, + {"RFC 3339", `{"lastBomImport":"2025-11-10T12:00:00.123Z"}`, time.UnixMilli(1762776000123)}, + {"null", `{"lastBomImport":null}`, time.Time{}}, + {"missing", `{}`, time.Time{}}, + } + for _, tc := range tt { + t.Run(tc.name, func(t *testing.T) { + var p project + require.NoError(t, json.Unmarshal([]byte(tc.json), &p)) + assert.True(t, tc.want.Equal(p.LastBOMImport.Time()), "got %s", p.LastBOMImport.Time()) + }) + } +} diff --git a/sbomexport/exporter.go b/sbomexport/exporter.go new file mode 100644 index 00000000..9a4013ee --- /dev/null +++ b/sbomexport/exporter.go @@ -0,0 +1,906 @@ +// Package sbomexport sends the SBOMs that node-agent stores (SBOMSyft +// objects) to Dependency-Track, one project per running image and namespace, +// and keeps them current as workloads change. +// +// By default the projects form a tree under the cluster: +// +// : current collection, adds up its namespaces +// └── / : current collection, adds up the latest version of each image +// └── // : +// +// The names, versions and parents are templates (Layout), and a namespace can +// override them with annotations, for example to send its images under a +// project that already exists. The exporter only ever creates or changes +// projects it created itself, which it tags with "managed-by:kubescape". +// +// Two things trigger an export: SBOMWatch's events (an SBOM becomes ready, a +// pod starts with an image whose SBOM is ready), and a resync that compares +// every running image with Dependency-Track, so nothing is lost while +// Dependency-Track is unreachable or the operator restarts. An image is sent +// again only when its SBOMSyft is newer than the project's last BOM import. +package sbomexport + +import ( + "context" + "errors" + "fmt" + "maps" + "net/http" + "net/url" + "slices" + "strings" + "sync" + "sync/atomic" + "time" + + "github.com/kubescape/go-logger" + "github.com/kubescape/go-logger/helpers" + spdxv1beta1 "github.com/kubescape/storage/pkg/apis/softwarecomposition/v1beta1" + kssc "github.com/kubescape/storage/pkg/generated/clientset/versioned" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/watch" + coreinformers "k8s.io/client-go/informers/core/v1" + "k8s.io/client-go/kubernetes" + corelisters "k8s.io/client-go/listers/core/v1" + "k8s.io/client-go/tools/cache" + "k8s.io/client-go/tools/pager" + "k8s.io/client-go/util/workqueue" +) + +const ( + // A root adds up its children; a parent adds up the latest version of each + // of its images, so the running tag is what counts. + rootLogic = "AGGREGATE_DIRECT_CHILDREN" + parentLogic = "AGGREGATE_LATEST_VERSION_CHILDREN" + + // maxRetries bounds the retries of one item between two resyncs. + maxRetries = 5 + + requestTimeout = 2 * time.Minute + + // objectOverhead is room, on top of MaxSBOMSize, for the rest of an + // SBOMSyft object around its syft document when the object is read. + objectOverhead = 1 << 20 + + // moveKeyPrefix marks queue items that move a namespace's projects after + // its layout changed; every other item is an image digest. + moveKeyPrefix = "namespace/" +) + +// Options configures an Exporter. +type Options struct { + // URL is the base URL of the Dependency-Track API server. + URL string + // APIKeyFile holds the API key; it is read on every request. + APIKeyFile string + // Cluster is the cluster name the layout templates see as .Cluster. + Cluster string + // Layout is where images go by default. + Layout Layout + // MaxSBOMSize is the largest SBOM, in bytes, that is converted. Converting + // takes about nine times the SBOM's size in memory, so this bounds it. + MaxSBOMSize int64 + // ResyncInterval is how often every running image is compared with + // Dependency-Track. It is also how long an image counts as running in a + // namespace after its last pod there ended. + ResyncInterval time.Duration + // SkipNamespace reports the namespaces whose images are not exported. + SkipNamespace func(namespace string) bool +} + +// validate checks the options that need no Dependency-Track call. +func (o Options) validate() error { + u, err := url.Parse(o.URL) + if err != nil || (u.Scheme != "https" && u.Scheme != "http") || u.Host == "" { + return fmt.Errorf("sbomExport.dependencyTrack.url %q is not an http(s) URL", o.URL) + } + if o.APIKeyFile == "" { + return errors.New("sbomExport.dependencyTrack.apiKeyFile is empty") + } + if o.MaxSBOMSize <= 0 { + return fmt.Errorf("sbomExport.maxSBOMSize %d must be positive", o.MaxSBOMSize) + } + if o.ResyncInterval <= 0 { + return fmt.Errorf("sbomExport.resyncInterval %s must be positive", o.ResyncInterval) + } + return nil +} + +// Exporter sends SBOMs to Dependency-Track. Create it with New, feed it +// SBOMWatch's events with ObservePod and ObserveSBOM, and run it with Start. +type Exporter struct { + opts Options + pods kubernetes.Interface + sboms sbomStore + dt *dtClient + layout *layout + running *index + queue workqueue.TypedRateLimitingInterface[string] // image digests and move keys + + namespaceInformer cache.SharedIndexInformer + namespaceLister corelisters.NamespaceLister + + mu sync.Mutex + sbomByImage map[string]sbomMeta // digest → its SBOMSyft + projects map[projectRef]string // collection → uuid, emptied on every resync + pendingMoves map[string]*pendingMove // namespace → where its move starts + moveSeq uint64 // counts namespace layout changes + logged map[string]bool // problems logged since the last resync + // While resync lists, these hold the pods and SBOM digests that events + // changed meanwhile. Their state from the events is newer than the + // lists', so resync keeps it. nil when no resync is listing. + touchedPods, touchedSBOMs map[string]bool + + stats stats +} + +// New returns an Exporter that lists pods and watches namespaces with pods, +// and reads SBOMSyft objects with storage. +func New(opts Options, pods kubernetes.Interface, storage kssc.Interface) (*Exporter, error) { + return newExporter(opts, pods, storageSBOMs{client: storage}, &http.Client{Timeout: requestTimeout}, time.Now) +} + +// newExporter is New with the SBOM store, the HTTP client and the clock +// replaceable, for tests. +func newExporter(opts Options, pods kubernetes.Interface, sboms sbomStore, httpClient *http.Client, now func() time.Time) (*Exporter, error) { + if err := opts.validate(); err != nil { + return nil, err + } + l, err := newLayout(opts.Cluster, opts.Layout) + if err != nil { + return nil, fmt.Errorf("sbomExport.dependencyTrack layout: %w", err) + } + if opts.SkipNamespace == nil { + opts.SkipNamespace = func(string) bool { return false } + } + informer := coreinformers.NewNamespaceInformer(pods, 0, cache.Indexers{}) + e := &Exporter{ + opts: opts, + pods: pods, + sboms: sboms, + dt: &dtClient{baseURL: strings.TrimRight(opts.URL, "/"), apiKeyFile: opts.APIKeyFile, http: httpClient}, + layout: l, + running: newIndex(opts.ResyncInterval, now), + queue: workqueue.NewTypedRateLimitingQueueWithConfig( + workqueue.NewTypedItemExponentialFailureRateLimiter[string](5*time.Second, 5*time.Minute), + workqueue.TypedRateLimitingQueueConfig[string]{Name: "sbom-export"}, + ), + namespaceInformer: informer, + namespaceLister: corelisters.NewNamespaceLister(informer.GetIndexer()), + sbomByImage: map[string]sbomMeta{}, + projects: map[projectRef]string{}, + pendingMoves: map[string]*pendingMove{}, + logged: map[string]bool{}, + } + if _, err := informer.AddEventHandler(cache.ResourceEventHandlerFuncs{ + UpdateFunc: func(oldObj, newObj any) { + oldNS, oldOK := oldObj.(*corev1.Namespace) + newNS, newOK := newObj.(*corev1.Namespace) + if oldOK && newOK { + e.namespaceChanged(oldNS, newNS) + } + }, + }); err != nil { + return nil, err + } + return e, nil +} + +// ObservePod records a pod event from SBOMWatch's pod watch, which only sees +// running pods: a pod that stops running arrives as Deleted. +func (e *Exporter) ObservePod(eventType watch.EventType, pod *corev1.Pod) { + if e.opts.SkipNamespace(pod.Namespace) { + return + } + key := pod.Namespace + "/" + pod.Name + var ready []string + e.mu.Lock() + if e.touchedPods != nil { + e.touchedPods[key] = true + } + switch eventType { + case watch.Added, watch.Modified: + for _, d := range e.running.setPod(key, pod.Namespace, runningDigests(pod)) { + if m, ok := e.sbomByImage[d]; ok && m.ready() { + ready = append(ready, d) + } + } + case watch.Deleted: + e.running.removePod(key) + } + e.mu.Unlock() + for _, d := range ready { + e.queue.Add(d) + } +} + +// ObserveSBOM records an SBOMSyft event from SBOMWatch. +func (e *Exporter) ObserveSBOM(eventType watch.EventType, obj *spdxv1beta1.SBOMSyft) { + m, ok := metaOf(obj) + if !ok { + return + } + e.mu.Lock() + if e.touchedSBOMs != nil { + e.touchedSBOMs[m.digest] = true + } + switch eventType { + case watch.Added, watch.Modified: + e.sbomByImage[m.digest] = m + case watch.Deleted: + if cur, ok := e.sbomByImage[m.digest]; ok && cur.name == m.name && cur.namespace == m.namespace { + delete(e.sbomByImage, m.digest) + } + } + e.mu.Unlock() + if eventType != watch.Deleted && m.ready() { + e.queue.Add(m.digest) + } +} + +// pendingMove is a namespace whose layout changed: its projects still sit +// where from put them. seq tells whether another change came in since. +type pendingMove struct { + from namespaceInfo + seq uint64 +} + +// namespaceChanged queues a move of the namespace's projects when a change of +// its labels or of its kubescape.io/dtrack-* annotations can change where its +// images go. The state before the first pending change is the one moved from. +func (e *Exporter) namespaceChanged(oldNS, newNS *corev1.Namespace) { + if maps.Equal(oldNS.Labels, newNS.Labels) && maps.Equal(layoutAnnotations(oldNS), layoutAnnotations(newNS)) { + return + } + e.mu.Lock() + pm, ok := e.pendingMoves[newNS.Name] + if !ok { + pm = &pendingMove{from: infoOf(oldNS)} + e.pendingMoves[newNS.Name] = pm + } + e.moveSeq++ + pm.seq = e.moveSeq + e.mu.Unlock() + e.queue.Add(moveKeyPrefix + newNS.Name) +} + +// layoutAnnotations returns the kubescape.io/dtrack-* annotations of ns. +func layoutAnnotations(ns *corev1.Namespace) map[string]string { + out := map[string]string{} + for k, v := range ns.Annotations { + if strings.HasPrefix(k, "kubescape.io/dtrack-") { + out[k] = v + } + } + return out +} + +// infoOf copies what the layout needs from ns. +func infoOf(ns *corev1.Namespace) namespaceInfo { + return namespaceInfo{name: ns.Name, labels: maps.Clone(ns.Labels), annotations: maps.Clone(ns.Annotations)} +} + +// namespaceInfo returns the namespace's labels and annotations, or none when +// it is not known (yet). +func (e *Exporter) namespaceInfo(name string) namespaceInfo { + if ns, err := e.namespaceLister.Get(name); err == nil { + return infoOf(ns) + } + return namespaceInfo{name: name} +} + +// Start exports until ctx is done: it watches namespaces, one worker drains +// the queue, and a resync runs now and every ResyncInterval. +func (e *Exporter) Start(ctx context.Context) { + go func() { + <-ctx.Done() + e.queue.ShutDown() + }() + go e.namespaceInformer.Run(ctx.Done()) + if !cache.WaitForCacheSync(ctx.Done(), e.namespaceInformer.HasSynced) { + return + } + go e.work(ctx) + + logger.L().Info("starting SBOM export to Dependency-Track", + helpers.String("url", e.opts.URL), + helpers.String("resyncInterval", e.opts.ResyncInterval.String())) + + ticker := time.NewTicker(e.opts.ResyncInterval) + defer ticker.Stop() + for { + if err := e.resync(ctx); err != nil && ctx.Err() == nil { + logger.L().Ctx(ctx).Error("SBOM export resync failed, retrying at the next one", helpers.Error(err)) + } + select { + case <-ctx.Done(): + return + case <-ticker.C: + } + } +} + +// work drains the queue: image digests are exported, namespace keys moved. +// A failed item is retried with backoff, then left to the next resync. +func (e *Exporter) work(ctx context.Context) { + for { + key, shutdown := e.queue.Get() + if shutdown { + return + } + var err error + if ns, ok := strings.CutPrefix(key, moveKeyPrefix); ok { + err = e.move(ctx, ns) + } else { + err = e.process(ctx, key) + } + switch { + case err == nil: + e.queue.Forget(key) + case e.queue.NumRequeues(key) < maxRetries && ctx.Err() == nil: + e.queue.AddRateLimited(key) + default: + e.queue.Forget(key) + logger.L().Ctx(ctx).Warning("giving up on this SBOM export item until the next resync", + helpers.String("item", key), helpers.Error(err)) + } + e.queue.Done(key) + } +} + +// resync rebuilds the running index from one paged list of running pods and +// the SBOM metadata from one list of SBOMSyft objects, keeping what events +// changed while it listed, then queues the pending moves and every running +// image. If either list fails, nothing changes. +func (e *Exporter) resync(ctx context.Context) error { + logger.L().Info("SBOM export since the last resync", e.stats.takeFields()...) + e.mu.Lock() + e.logged = map[string]bool{} + e.touchedPods, e.touchedSBOMs = map[string]bool{}, map[string]bool{} + e.mu.Unlock() + defer func() { + e.mu.Lock() + e.touchedPods, e.touchedSBOMs = nil, nil + e.mu.Unlock() + }() + + pods := map[string]podEntry{} + err := pager.New(func(ctx context.Context, opts metav1.ListOptions) (runtime.Object, error) { + return e.pods.CoreV1().Pods("").List(ctx, opts) + }).EachListItem(ctx, metav1.ListOptions{FieldSelector: "status.phase=Running"}, func(obj runtime.Object) error { + pod := obj.(*corev1.Pod) + if e.opts.SkipNamespace(pod.Namespace) { + return nil + } + if digests := runningDigests(pod); len(digests) > 0 { + pods[pod.Namespace+"/"+pod.Name] = podEntry{namespace: pod.Namespace, digests: digests} + } + return nil + }) + if err != nil { + return fmt.Errorf("list running pods: %w", err) + } + sboms, err := e.sboms.listSBOMs(ctx) + if err != nil { + return fmt.Errorf("list SBOMSyft objects: %w", err) + } + + byImage := make(map[string]sbomMeta, len(sboms)) + for _, m := range sboms { + byImage[m.digest] = m + } + e.mu.Lock() + e.running.replace(pods, e.touchedPods) + for d := range e.touchedSBOMs { + if m, ok := e.sbomByImage[d]; ok { + byImage[d] = m + } else { + delete(byImage, d) + } + } + e.touchedPods, e.touchedSBOMs = nil, nil + e.sbomByImage = byImage + e.projects = map[projectRef]string{} + moves := slices.Sorted(maps.Keys(e.pendingMoves)) + e.mu.Unlock() + + // Moves go first, so an image is moved before it is exported again. + for _, ns := range moves { + e.queue.Add(moveKeyPrefix + ns) + } + for _, d := range e.running.digests() { + e.queue.Add(d) + } + return nil +} + +// sbom returns the metadata of digest's SBOMSyft, if one is known. +func (e *Exporter) sbom(digest string) (sbomMeta, bool) { + e.mu.Lock() + defer e.mu.Unlock() + m, ok := e.sbomByImage[digest] + return m, ok +} + +// result is what exporting one image to one place did. +type result int + +const ( + resultSent result = iota + resultUnchanged + resultConflict // the project belongs to another image or was not created by the exporter + resultSkipped // the layout cannot be resolved, or a project it names is missing + resultTooLarge +) + +// process exports the SBOM of one image to the project of every namespace it +// runs in. One namespace failing does not stop the others; the image is +// retried as a whole, and namespaces already done are then only looked up. +func (e *Exporter) process(ctx context.Context, digest string) error { + namespaces := e.running.namespaces(digest) + if len(namespaces) == 0 { + return nil + } + m, ok := e.sbom(digest) + if !ok || !m.ready() { + e.stats.skipped.Add(1) + return nil + } + if m.size > e.opts.MaxSBOMSize { + e.stats.tooLarge.Add(1) + e.logTooLarge(ctx, m, m.size) + return nil + } + repository, tag := splitRef(m.imageTag) + var bom []byte // converted once, for every namespace + var errs []error + for _, ns := range namespaces { + p, err := e.layout.resolve(e.namespaceInfo(ns), repository, tag) + if err != nil { + e.stats.skippedLayout.Add(1) + e.logOnce(ctx, "layout/"+ns, "cannot resolve the Dependency-Track layout of this namespace, skipping its images", + helpers.String("namespace", ns), helpers.Error(err)) + continue + } + res, err := e.exportTo(ctx, m, ns, p, &bom) + switch { + case err != nil: + e.stats.failed.Add(1) + e.logFailure(ctx, err, m.imageTag, p.name, p.version) + errs = append(errs, err) + case res == resultSent: + e.stats.sent.Add(1) + logger.L().Info("sent SBOM to Dependency-Track", helpers.String("project", p.name), helpers.String("version", p.version)) + case res == resultUnchanged: + e.stats.unchanged.Add(1) + case res == resultConflict: + e.stats.conflicts.Add(1) + case res == resultSkipped: + e.stats.skippedLayout.Add(1) + case res == resultTooLarge: + e.stats.tooLarge.Add(1) + return nil + } + } + return errors.Join(errs...) +} + +// exportTo makes p's project hold m's SBOM as its latest version. An image +// whose project imported a BOM no older than its SBOMSyft, built by the same +// node-agent version, is only marked latest. It never uploads to a project it +// did not create, nor to one that holds the BOM of another image that still +// runs. Before it uploads, it creates a missing project and records m's digest +// on it, so a second image can never take a project whose upload went through +// but whose owner was not recorded. +func (e *Exporter) exportTo(ctx context.Context, m sbomMeta, ns string, p placement, bom *[]byte) (result, error) { + target, err := e.dt.lookup(ctx, p.name, p.version) + if err != nil { + return 0, err + } + owner, sentVersion, parentUUID := "", "", "" + if target != nil { + if !target.managed() { + e.logOnce(ctx, "conflict/"+p.name+":"+p.version, "a Dependency-Track project with this name and version exists and was not created by Kubescape, skipping the image", + helpers.String("project", p.name), helpers.String("version", p.version), helpers.String("image", m.imageTag), helpers.String("namespace", ns)) + return resultConflict, nil + } + // Resolve the parent of an existing project too, so a collection the + // exporter created follows a changed root. + if _, err := e.parent(ctx, p); err != nil { + var missing *missingProjectError + if !errors.As(err, &missing) { + return 0, err + } + e.logMissing(ctx, missing, ns) + } + props, err := e.dt.properties(ctx, target.UUID) + if err != nil { + return 0, err + } + owner, sentVersion = props[digestProperty], props[toolVersionProperty] + if owner != "" && owner != m.digest && len(e.running.namespaces(owner)) > 0 { + e.logOnce(ctx, "conflict/"+p.name+":"+p.version, "two running images map to the same Dependency-Track project and version, skipping the second; include the repository in the project name template", + helpers.String("project", p.name), helpers.String("version", p.version), helpers.String("image", m.imageTag), helpers.String("namespace", ns), + helpers.String("ownerDigest", owner), helpers.String("digest", m.digest)) + return resultConflict, nil + } + // node-agent rebuilds an SBOM in place when its version changes, so + // the creation time alone does not show a new document. + rebuilt := m.toolVersion != "" && m.toolVersion != sentVersion + if owner == m.digest && !rebuilt && !target.LastBOMImport.Time().Before(m.created) { + if !target.IsLatest { + if err := e.dt.patch(ctx, target.UUID, map[string]any{"isLatest": true}); err != nil { + return 0, err + } + } + return resultUnchanged, nil + } + } else { + parentUUID, err = e.parent(ctx, p) + var missing *missingProjectError + if errors.As(err, &missing) { + e.logMissing(ctx, missing, ns) + return resultSkipped, nil + } + if err != nil { + return 0, err + } + } + + if *bom == nil { + doc, err := e.sboms.syftDocument(ctx, m.namespace, m.name, e.opts.MaxSBOMSize+objectOverhead) + var tooLarge *tooLargeError + if errors.As(err, &tooLarge) { + e.logTooLarge(ctx, m, tooLarge.read) + return resultTooLarge, nil + } + if err != nil { + return 0, err + } + if int64(len(doc)) > e.opts.MaxSBOMSize { + e.logTooLarge(ctx, m, int64(len(doc))) + return resultTooLarge, nil + } + if *bom, err = toCycloneDX(doc); err != nil { + return 0, fmt.Errorf("convert SBOMSyft %s/%s: %w", m.namespace, m.name, err) + } + } + if target == nil { + if target, err = e.dt.createProject(ctx, p.name, p.version, parentUUID); err != nil { + return 0, err + } + } + if owner != m.digest { + if err := e.dt.setProperty(ctx, target.UUID, digestProperty, m.digest); err != nil { + return 0, err + } + } + if err := e.dt.uploadBOM(ctx, p.name, p.version, *bom); err != nil { + return 0, err + } + // Recorded after the upload: when this fails, the next pass sends the SBOM + // again rather than skip a document Dependency-Track never got. + if m.toolVersion != "" && m.toolVersion != sentVersion { + if err := e.dt.setProperty(ctx, target.UUID, toolVersionProperty, m.toolVersion); err != nil { + return 0, err + } + } + return resultSent, nil +} + +// logMissing logs, once per resync, a project the layout names that is +// missing and may not be created. +func (e *Exporter) logMissing(ctx context.Context, missing *missingProjectError, ns string) { + e.logOnce(ctx, "missing/"+missing.ref.String(), "a Dependency-Track project the layout names does not exist and may not be created, skipping the namespace's images", + helpers.String("project", missing.ref.String()), helpers.String("reason", missing.reason), helpers.String("namespace", ns)) +} + +// missingProjectError is a project the layout names that does not exist and +// may not be created. +type missingProjectError struct { + ref projectRef + reason string +} + +// Error describes the missing project. +func (e *missingProjectError) Error() string { + return fmt.Sprintf("project %s %s", e.ref, e.reason) +} + +// parent returns the uuid of p's parent, creating it, and the root above it, +// when they are missing and may be created. +func (e *Exporter) parent(ctx context.Context, p placement) (string, error) { + return e.collection(ctx, p.parent, p.parentCreate, parentLogic, func() (string, error) { + return e.collection(ctx, p.root, p.rootCreate, rootLogic, nil) + }) +} + +// collection returns the uuid of the collection project ref names. A missing +// one is created, under the project parentOf returns, only when create allows +// it. A collection the exporter created keeps its collection logic and, when +// parentOf is given, its place under the project parentOf returns, so a +// changed root takes the collection along. Other collections are never +// changed. +func (e *Exporter) collection(ctx context.Context, ref projectRef, create bool, logic string, parentOf func() (string, error)) (string, error) { + e.mu.Lock() + uuid, ok := e.projects[ref] + e.mu.Unlock() + if ok { + return uuid, nil + } + + p, err := e.dt.find(ctx, ref) + if err != nil { + return "", err + } + switch { + case p != nil && p.managed(): + if err := e.reconcileCollection(ctx, p, logic, parentOf); err != nil { + return "", err + } + case p != nil: + case ref.uuid != "": + return "", &missingProjectError{ref: ref, reason: "does not exist; a project named by UUID is never created"} + case !create: + return "", &missingProjectError{ref: ref, reason: "does not exist and create is false"} + default: + parentUUID := "" + if parentOf != nil { + if parentUUID, err = parentOf(); err != nil { + return "", err + } + } + if p, err = e.dt.createCollection(ctx, ref.name, ref.version, parentUUID, logic); err != nil { + return "", err + } + logger.L().Info("created Dependency-Track collection project", helpers.String("project", ref.String()), helpers.String("collectionLogic", logic)) + } + + e.mu.Lock() + e.projects[ref] = p.UUID + e.mu.Unlock() + return p.UUID, nil +} + +// reconcileCollection sets the collection logic of a collection the exporter +// created and, when parentOf is given, moves it under the project parentOf +// returns. When that project is missing and may not be created, the +// collection stays where it is. +func (e *Exporter) reconcileCollection(ctx context.Context, p *project, logic string, parentOf func() (string, error)) error { + fields := map[string]any{} + if p.CollectionLogic != logic { + fields["collectionLogic"] = logic + } + if parentOf != nil { + want, err := parentOf() + var missing *missingProjectError + switch { + case errors.As(err, &missing): + e.logMissing(ctx, missing, "") + case err != nil: + return err + default: + // a lookup by name leaves the parent out; a GET by UUID has it + full, err := e.dt.getByUUID(ctx, p.UUID) + if err != nil { + return err + } + if full != nil && (full.Parent == nil || full.Parent.UUID != want) { + fields["parent"] = map[string]string{"uuid": want} + } + } + } + if len(fields) == 0 { + return nil + } + // Dependency-Track sets the collection logic to NONE on a PATCH that + // leaves it out, so moving a collection sends its logic too. + body := maps.Clone(fields) + body["collectionLogic"] = logic + if err := e.dt.patch(ctx, p.UUID, body); err != nil { + return err + } + logger.L().Info("updated Dependency-Track collection project", helpers.String("project", p.Name+":"+p.Version), helpers.Interface("changes", fields)) + return nil +} + +// move moves the projects of the images running in ns from where the +// namespace's previous labels and annotations put them to where the current +// ones do, so their history in Dependency-Track moves with them. It moves only +// projects the exporter created, and never onto a project that exists; the +// images are then exported as usual. When only the root changes, it moves the +// namespace's parent collection, also when none of its images can be exported. +// A move whose destination is missing and may not be created stays pending and +// is tried again at every resync. A change made while the operator was not +// running is not seen, so those images start new projects. +func (e *Exporter) move(ctx context.Context, ns string) error { + e.mu.Lock() + pm, ok := e.pendingMoves[ns] + var before namespaceInfo + var seq uint64 + if ok { + before, seq = pm.from, pm.seq + } + // The collection cache is dropped, so the move and the exports after it + // find, and reconcile, the collections of the new layout. + e.projects = map[projectRef]string{} + e.mu.Unlock() + if !ok { + return nil + } + now := e.namespaceInfo(ns) + digests := e.running.digestsIn(ns) + parents := map[projectRef]bool{} // parents whose root was handled + waiting := false + var errs []error + for _, d := range digests { + m, ok := e.sbom(d) + if !ok { + continue + } + repository, tag := splitRef(m.imageTag) + from, err1 := e.layout.resolve(before, repository, tag) + to, err2 := e.layout.resolve(now, repository, tag) + if err1 != nil || err2 != nil { + continue + } + var done bool + var err error + if from.name == to.name && from.version == to.version && from.parent == to.parent { + if parents[to.parent] { + continue + } + parents[to.parent] = true + done, err = e.moveParent(ctx, ns, from, to) + } else { + done, err = e.moveProject(ctx, ns, from, to) + } + switch { + case err != nil: + errs = append(errs, err) + case !done: + waiting = true + } + } + if err := errors.Join(errs...); err != nil { + return err + } + // A waiting move keeps the state it started from. Otherwise a change that + // came in meanwhile moves on from where this one ended. + e.mu.Lock() + if cur, ok := e.pendingMoves[ns]; ok && !waiting { + if cur.seq == seq { + delete(e.pendingMoves, ns) + } else { + cur.from = now + } + } + e.mu.Unlock() + for _, d := range digests { + e.queue.Add(d) + } + return nil +} + +// moveProject moves the exporter's project at from to to: its name, version +// and parent. When only the parent changes, the project found at to is the +// same one, and only its parent changes. A different project at to is left +// alone. It reports false when to's parent, or its root, is missing and may +// not be created, so the move waits. +func (e *Exporter) moveProject(ctx context.Context, ns string, from, to placement) (bool, error) { + p, err := e.dt.lookup(ctx, from.name, from.version) + if err != nil || p == nil || !p.managed() { + return true, err + } + target, err := e.dt.lookup(ctx, to.name, to.version) + if err != nil || (target != nil && target.UUID != p.UUID) { + return true, err + } + parentUUID, err := e.parent(ctx, to) + var missing *missingProjectError + if errors.As(err, &missing) { + e.logMissing(ctx, missing, ns) + return false, nil + } + if err != nil { + return false, err + } + if target != nil { + // Only the parent changes. An earlier try of a waiting move may have + // moved this project already; a lookup by name leaves the parent out. + full, err := e.dt.getByUUID(ctx, p.UUID) + if err != nil { + return false, err + } + if full != nil && full.Parent != nil && full.Parent.UUID == parentUUID { + return true, nil + } + } + if err := e.dt.patch(ctx, p.UUID, map[string]any{"name": to.name, "version": to.version, "parent": map[string]string{"uuid": parentUUID}}); err != nil { + return false, err + } + e.stats.moved.Add(1) + logger.L().Info("moved Dependency-Track project after the namespace's layout changed", + helpers.String("namespace", ns), helpers.String("from", from.name+":"+from.version), helpers.String("to", to.name+":"+to.version)) + return true, nil +} + +// moveParent moves to's parent collection under to's root when only the root +// changed. Exports move it too, but only for images they can export, so a +// namespace whose SBOMs are all too large or not ready would keep its parent +// under the old root. A parent the exporter did not create, or none yet, is +// left alone. It reports false when the root is missing and may not be +// created, so the move waits. +func (e *Exporter) moveParent(ctx context.Context, ns string, from, to placement) (bool, error) { + if from.root == to.root && from.rootCreate == to.rootCreate { + return true, nil + } + parent, err := e.dt.find(ctx, to.parent) + if err != nil || parent == nil || !parent.managed() { + return true, err + } + rootUUID, err := e.collection(ctx, to.root, to.rootCreate, rootLogic, nil) + var missing *missingProjectError + if errors.As(err, &missing) { + e.logMissing(ctx, missing, ns) + return false, nil + } + if err != nil { + return false, err + } + return true, e.reconcileCollection(ctx, parent, parentLogic, func() (string, error) { return rootUUID, nil }) +} + +// logTooLarge logs, once per resync, that the SBOM of m is over MaxSBOMSize. +func (e *Exporter) logTooLarge(ctx context.Context, m sbomMeta, size int64) { + e.logOnce(ctx, "too-large/"+m.digest, "SBOM is larger than sbomExport.maxSBOMSize, skipping it", + helpers.String("image", m.imageTag), helpers.Int("size", int(size)), helpers.Int("maxSBOMSize", int(e.opts.MaxSBOMSize))) +} + +// logOnce logs a warning the first time key comes up after a resync, so a +// problem that affects every image is logged once, not once per image. +func (e *Exporter) logOnce(ctx context.Context, key, msg string, fields ...helpers.IDetails) { + e.mu.Lock() + seen := e.logged[key] + e.logged[key] = true + e.mu.Unlock() + if !seen { + logger.L().Ctx(ctx).Warning(msg, fields...) + } +} + +// logFailure logs a failed export of image, at project name:version. A refused +// API key fails every image the same way, so it is logged once per resync. +func (e *Exporter) logFailure(ctx context.Context, err error, image, name, version string) { + fields := []helpers.IDetails{helpers.String("image", image), helpers.String("project", name), helpers.String("version", version), helpers.Error(err)} + if isAuthError(err) { + if e.stats.authLogged.Swap(true) { + return + } + fields = append(fields, helpers.String("hint", "the API key's team needs VIEW_PORTFOLIO, BOM_UPLOAD and PORTFOLIO_MANAGEMENT (on 5.x: PORTFOLIO_MANAGEMENT_CREATE and PORTFOLIO_MANAGEMENT_UPDATE); with portfolio access control on, access to the projects the layout names")) + } + logger.L().Ctx(ctx).Error("failed to export SBOM to Dependency-Track", fields...) +} + +// stats counts what the exporter did between two resyncs. +type stats struct { + sent, unchanged, skipped, tooLarge, conflicts, skippedLayout, moved, failed atomic.Int64 + authLogged atomic.Bool // a refused API key was logged +} + +// takeFields returns the counts as log fields and resets them. +func (s *stats) takeFields() []helpers.IDetails { + s.authLogged.Store(false) + return []helpers.IDetails{ + helpers.Int("sent", int(s.sent.Swap(0))), + helpers.Int("unchanged", int(s.unchanged.Swap(0))), + helpers.Int("skippedNoReadySBOM", int(s.skipped.Swap(0))), + helpers.Int("skippedTooLarge", int(s.tooLarge.Swap(0))), + helpers.Int("conflicts", int(s.conflicts.Swap(0))), + helpers.Int("skippedLayout", int(s.skippedLayout.Swap(0))), + helpers.Int("moved", int(s.moved.Swap(0))), + helpers.Int("failed", int(s.failed.Swap(0))), + } +} diff --git a/sbomexport/exporter_layout_test.go b/sbomexport/exporter_layout_test.go new file mode 100644 index 00000000..07094414 --- /dev/null +++ b/sbomexport/exporter_layout_test.go @@ -0,0 +1,377 @@ +package sbomexport + +import ( + "context" + "strconv" + "testing" + "time" + + helpersv1 "github.com/kubescape/k8s-interface/instanceidhandler/v1/helpers" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/watch" +) + +const nginxImageID = "docker.io/library/nginx@sha256:2222" + +func TestExporterSendsANamespaceUnderAnExistingParentAndLeavesItAlone(t *testing.T) { + te := newTestExporter(t, + namespace("payments", map[string]string{ + AnnotationParent: "Payments Team:current", + AnnotationParentCreate: "false", + AnnotationProjectName: "{{ .Repository }}", + }), + runningPod("payments", "api", redisImageID), + ) + finance := te.dt.addProject("Finance", "current", "") + te.dt.addProject("Payments Team", "current", finance) + te.store.set(sbomObject(redisImageID, redisTag, helpersv1.Learning, te.clock.t.Add(-time.Hour))) + + require.NoError(t, te.resync(context.Background())) + require.Empty(t, te.drain()) + + assert.Equal(t, []string{redisProject + ":8"}, te.dt.takeUploads()) + image, parent, _ := te.dt.project(redisProject, "8") + assert.Equal(t, "Payments Team", parent) + assert.True(t, image.managed()) + team, _, _ := te.dt.project("Payments Team", "current") + assert.Empty(t, team.CollectionLogic, "an existing parent keeps its settings") + assert.Empty(t, team.Tags) + _, _, rootCreated := te.dt.project("cluster", "current") + assert.False(t, rootCreated, "the root is only needed to create a parent") +} + +func TestExporterCreatesTheParentUnderAnExistingRoot(t *testing.T) { + te := newTestExporter(t, + namespace("payments", map[string]string{ + AnnotationRootProject: "Finance:current", + AnnotationRootProjectCreate: "false", + AnnotationParent: "Payments Team:current", + }), + runningPod("payments", "api", redisImageID), + ) + te.dt.addProject("Finance", "current", "") + te.store.set(sbomObject(redisImageID, redisTag, helpersv1.Learning, te.clock.t.Add(-time.Hour))) + + require.NoError(t, te.resync(context.Background())) + require.Empty(t, te.drain()) + + team, parent, ok := te.dt.project("Payments Team", "current") + require.True(t, ok) + assert.Equal(t, "Finance", parent) + assert.Equal(t, parentLogic, team.CollectionLogic) + assert.True(t, team.managed()) + finance, _, _ := te.dt.project("Finance", "current") + assert.Empty(t, finance.CollectionLogic, "an existing root keeps its settings") + _, imageParent, _ := te.dt.project("cluster/payments/"+redisProject, "8") + assert.Equal(t, "Payments Team", imageParent) +} + +// A namespace whose root or parent is missing and may not be created is +// skipped, and nothing is created for it. +func TestExporterSkipsANamespaceWhoseProjectIsMissingAndMayNotBeCreated(t *testing.T) { + tt := []struct { + name string + annotations map[string]string + }{ + {"parent with create false", map[string]string{AnnotationParent: "Payments Team:current", AnnotationParentCreate: "false"}}, + {"parent by an unknown UUID", map[string]string{AnnotationParent: "0a1b2c3d-0000-4000-8000-000000000099"}}, + {"root with create false", map[string]string{AnnotationRootProject: "Finance:current", AnnotationRootProjectCreate: "false"}}, + {"a layout that does not resolve", map[string]string{AnnotationProjectName: "{{ .Digest }}"}}, + } + for _, tc := range tt { + t.Run(tc.name, func(t *testing.T) { + te := newTestExporter(t, namespace("payments", tc.annotations), runningPod("payments", "api", redisImageID)) + te.store.set(sbomObject(redisImageID, redisTag, helpersv1.Learning, te.clock.t.Add(-time.Hour))) + + require.NoError(t, te.resync(context.Background())) + require.Empty(t, te.drain()) + + assert.Empty(t, te.dt.takeUploads()) + assert.Zero(t, te.dt.count(), "nothing is created") + assert.Equal(t, int64(1), te.stats.skippedLayout.Load()) + }) + } +} + +func TestExporterNeverUploadsToAProjectItDidNotCreate(t *testing.T) { + te := newTestExporter(t, runningPod("apps", "web", redisImageID)) + te.dt.addProject("cluster/apps/"+redisProject, "8", "") + te.store.set(sbomObject(redisImageID, redisTag, helpersv1.Learning, te.clock.t.Add(-time.Hour))) + + require.NoError(t, te.resync(context.Background())) + require.Empty(t, te.drain()) + + assert.Empty(t, te.dt.takeUploads()) + assert.Equal(t, int64(1), te.stats.conflicts.Load()) +} + +// Two images whose layout gives the same project and version would overwrite +// each other's BOM. The second is skipped while the first runs, and takes the +// project over once the first stopped. +func TestExporterKeepsTwoRunningImagesFromSharingOneProject(t *testing.T) { + shared := map[string]string{AnnotationProjectName: "{{ .Namespace }}"} + te := newTestExporter(t, namespace("apps", shared), runningPod("apps", "cache", redisImageID), runningPod("apps", "web", nginxImageID)) + te.store.set( + sbomObject(redisImageID, "docker.io/library/redis:1.0", helpersv1.Learning, te.clock.t.Add(-time.Hour)), + sbomObject(nginxImageID, "docker.io/library/nginx:1.0", helpersv1.Learning, te.clock.t.Add(-time.Hour)), + ) + + require.NoError(t, te.resync(context.Background())) + require.Empty(t, te.drain()) + assert.Equal(t, []string{"apps:1.0"}, te.dt.takeUploads()) + assert.Equal(t, int64(1), te.stats.conflicts.Load()) + + p, _, _ := te.dt.project("apps", "1.0") + owner, err := te.dt.digestOwnerForTest(p.UUID) + require.NoError(t, err) + stopped, other := "cache", digestOf(nginxImageID) + if owner == digestOf(nginxImageID) { + stopped, other = "web", digestOf(redisImageID) + } + require.NoError(t, te.client.CoreV1().Pods("apps").Delete(context.Background(), stopped, metav1.DeleteOptions{})) + require.NoError(t, te.resync(context.Background())) + te.clock.t = te.clock.t.Add(2 * time.Hour) + te.stats.takeFields() + + require.NoError(t, te.resync(context.Background())) + require.Empty(t, te.drain()) + assert.Equal(t, []string{"apps:1.0"}, te.dt.takeUploads(), "the image that still runs takes the project over") + owner, err = te.dt.digestOwnerForTest(p.UUID) + require.NoError(t, err) + assert.Equal(t, other, owner) +} + +func TestExporterSkipsAnSBOMLargerThanTheLimit(t *testing.T) { + t.Run("by its size annotation, without reading it", func(t *testing.T) { + te := newTestExporter(t, runningPod("apps", "web", redisImageID)) + big := sbomObject(redisImageID, redisTag, helpersv1.Learning, te.clock.t.Add(-time.Hour)) + big.Annotations[helpersv1.ResourceSizeMetadataKey] = strconv.Itoa(11 << 20) + te.store.set(big) + + require.NoError(t, te.resync(context.Background())) + require.Empty(t, te.drain()) + + assert.Empty(t, te.dt.takeUploads()) + assert.Zero(t, te.store.documentReads()) + assert.Equal(t, int64(1), te.stats.tooLarge.Load()) + }) + t.Run("by the object it reads, when the annotation is missing", func(t *testing.T) { + te := newTestExporter(t, runningPod("apps", "web", redisImageID)) + te.store.set(sbomObject(redisImageID, redisTag, helpersv1.Learning, te.clock.t.Add(-time.Hour))) + te.store.tooLarge = true + + require.NoError(t, te.resync(context.Background())) + require.Empty(t, te.drain()) + + assert.Empty(t, te.dt.takeUploads()) + assert.Equal(t, int64(1), te.stats.tooLarge.Load()) + assert.Equal(t, int64(10<<20+objectOverhead), te.store.lastLimit()) + }) + t.Run("by its document, when the annotation is missing", func(t *testing.T) { + te := newTestExporterWith(t, func(o *Options) { o.MaxSBOMSize = 1000 }, runningPod("apps", "web", redisImageID)) + te.store.set(sbomObject(redisImageID, redisTag, helpersv1.Learning, te.clock.t.Add(-time.Hour))) + + require.NoError(t, te.resync(context.Background())) + require.Empty(t, te.drain()) + + assert.Empty(t, te.dt.takeUploads()) + assert.Equal(t, int64(1), te.stats.tooLarge.Load()) + }) +} + +func TestExporterMovesItsProjectsWhenANamespaceLayoutChanges(t *testing.T) { + te := newTestExporter(t, namespace("payments", nil), runningPod("payments", "api", redisImageID)) + te.store.set(sbomObject(redisImageID, redisTag, helpersv1.Learning, te.clock.t.Add(-time.Hour))) + require.NoError(t, te.resync(context.Background())) + require.Empty(t, te.drain()) + before, _, ok := te.dt.project("cluster/payments/"+redisProject, "8") + require.True(t, ok) + te.dt.takeUploads() + + ns := namespace("payments", map[string]string{AnnotationParent: "Payments Team:current", AnnotationProjectName: "{{ .Repository }}"}) + _, err := te.client.CoreV1().Namespaces().Update(context.Background(), ns, metav1.UpdateOptions{}) + require.NoError(t, err) + require.Eventually(t, func() bool { return te.queue.Len() > 0 }, 5*time.Second, 10*time.Millisecond) + require.Empty(t, te.drain()) + + after, parent, ok := te.dt.project(redisProject, "8") + require.True(t, ok) + assert.Equal(t, before.UUID, after.UUID, "the same project moved, with its history") + assert.Equal(t, "Payments Team", parent) + _, _, stillThere := te.dt.project("cluster/payments/"+redisProject, "8") + assert.False(t, stillThere) + assert.Empty(t, te.dt.takeUploads(), "nothing is uploaded again") + assert.Equal(t, int64(1), te.stats.moved.Load()) +} + +// The namespace a pod event names is resolved when the image is exported, so +// an image seen before its Namespace object still lands in the right place. +func TestExporterUsesTheNamespaceAnnotationsWhenItExports(t *testing.T) { + te := newTestExporter(t, namespace("payments", map[string]string{AnnotationProjectName: "{{ .Repository }}"})) + te.ObserveSBOM(watch.Added, sbomObject(redisImageID, redisTag, helpersv1.Learning, te.clock.t.Add(-time.Hour))) + te.ObservePod(watch.Added, runningPod("payments", "api", redisImageID)) + + require.Empty(t, te.drain()) + + assert.Equal(t, []string{redisProject + ":8"}, te.dt.takeUploads()) +} + +// The image's digest is recorded on its project before the upload, so a +// project never holds a BOM without an owner another image could take. +func TestExporterUploadsOnlyAfterRecordingTheImage(t *testing.T) { + te := newTestExporter(t, runningPod("apps", "web", redisImageID)) + te.store.set(sbomObject(redisImageID, redisTag, helpersv1.Learning, te.clock.t.Add(-time.Hour))) + te.dt.failProperties = 1 + + require.NoError(t, te.resync(context.Background())) + assert.Len(t, te.drain(), 1) + assert.Empty(t, te.dt.takeUploads(), "no upload without a recorded owner") + + te.queue.Add(digestOf(redisImageID)) + require.Empty(t, te.drain()) + assert.Equal(t, []string{"cluster/apps/" + redisProject + ":8"}, te.dt.takeUploads()) + p, _, _ := te.dt.project("cluster/apps/"+redisProject, "8") + owner, err := te.dt.digestOwnerForTest(p.UUID) + require.NoError(t, err) + assert.Equal(t, digestOf(redisImageID), owner) +} + +func TestCollectionKeepsReferencesThatOnlyDifferInWhereTheColonIs(t *testing.T) { + te := newTestExporter(t) + a := te.dt.addProject("team:ops", "current", "") + b := te.dt.addProject("team", "ops:current", "") + + gotA, err := te.collection(context.Background(), projectRef{name: "team:ops", version: "current"}, false, parentLogic, nil) + require.NoError(t, err) + gotB, err := te.collection(context.Background(), projectRef{name: "team", version: "ops:current"}, false, parentLogic, nil) + require.NoError(t, err) + + assert.Equal(t, a, gotA) + assert.Equal(t, b, gotB) +} + +func TestExporterMovesAProjectWhenOnlyItsParentChanges(t *testing.T) { + annotations := map[string]string{AnnotationParent: "Team A:current", AnnotationProjectName: "{{ .Repository }}"} + te := newTestExporter(t, namespace("payments", annotations), runningPod("payments", "api", redisImageID)) + te.store.set(sbomObject(redisImageID, redisTag, helpersv1.Learning, te.clock.t.Add(-time.Hour))) + require.NoError(t, te.resync(context.Background())) + require.Empty(t, te.drain()) + before, parent, _ := te.dt.project(redisProject, "8") + require.Equal(t, "Team A", parent) + te.dt.takeUploads() + + annotations[AnnotationParent] = "Team B:current" + te.updateNamespace(t, namespace("payments", annotations)) + require.Empty(t, te.drain()) + + after, parent, _ := te.dt.project(redisProject, "8") + assert.Equal(t, before.UUID, after.UUID) + assert.Equal(t, "Team B", parent) + assert.Empty(t, te.dt.takeUploads()) +} + +// A parent the exporter created follows its namespace's root. +func TestExporterMovesItsParentWhenOnlyTheRootChanges(t *testing.T) { + te := newTestExporter(t, namespace("payments", nil), runningPod("payments", "api", redisImageID)) + te.dt.addProject("Finance", "current", "") + te.store.set(sbomObject(redisImageID, redisTag, helpersv1.Learning, te.clock.t.Add(-time.Hour))) + require.NoError(t, te.resync(context.Background())) + require.Empty(t, te.drain()) + parent, root, _ := te.dt.project("cluster/payments", "current") + require.Equal(t, "cluster", root) + patches := te.dt.patchCount(parent.UUID) + + te.updateNamespace(t, namespace("payments", map[string]string{AnnotationRootProject: "Finance:current", AnnotationRootProjectCreate: "false"})) + require.Empty(t, te.drain()) + + parent, root, _ = te.dt.project("cluster/payments", "current") + assert.Equal(t, "Finance", root) + assert.Equal(t, parentLogic, parent.CollectionLogic) + assert.Equal(t, 1, te.dt.patchCount(parent.UUID)-patches, "one PATCH moves it and keeps its collection logic") + finance, _, _ := te.dt.project("Finance", "current") + assert.Empty(t, finance.Tags, "the root itself is not changed") +} + +// A layout change that comes in while the previous one is being moved is not +// lost: the projects end up where the latest layout puts them. +func TestExporterKeepsALayoutChangeThatComesInDuringAMove(t *testing.T) { + te := newTestExporter(t, namespace("payments", nil), runningPod("payments", "api", redisImageID)) + te.store.set(sbomObject(redisImageID, redisTag, helpersv1.Learning, te.clock.t.Add(-time.Hour))) + require.NoError(t, te.resync(context.Background())) + require.Empty(t, te.drain()) + before, _, _ := te.dt.project("cluster/payments/"+redisProject, "8") + + toC := namespace("payments", map[string]string{AnnotationParent: "Team C:current", AnnotationProjectName: "{{ .Repository }}"}) + te.dt.onPatch = func() { + // while the move to B patches the project, the namespace changes to C + seq := te.moveSeqForTest() + _, err := te.client.CoreV1().Namespaces().Update(context.Background(), toC, metav1.UpdateOptions{}) + assert.NoError(t, err) + assert.Eventually(t, func() bool { return te.moveSeqForTest() > seq }, 5*time.Second, 10*time.Millisecond) + } + te.updateNamespace(t, namespace("payments", map[string]string{AnnotationParent: "Team B:current", AnnotationProjectName: "{{ .Repository }}"})) + require.Empty(t, te.drain()) + + after, parent, ok := te.dt.project(redisProject, "8") + require.True(t, ok) + assert.Equal(t, before.UUID, after.UUID) + assert.Equal(t, "Team C", parent) +} + +// A move whose parent is missing and may not be created waits, and is done at +// a resync once the parent exists. +func TestExporterKeepsAMoveWhoseParentIsMissingUntilItExists(t *testing.T) { + te := newTestExporter(t, namespace("payments", nil), runningPod("payments", "api", redisImageID)) + te.store.set(sbomObject(redisImageID, redisTag, helpersv1.Learning, te.clock.t.Add(-time.Hour))) + require.NoError(t, te.resync(context.Background())) + require.Empty(t, te.drain()) + before, _, _ := te.dt.project("cluster/payments/"+redisProject, "8") + + te.updateNamespace(t, namespace("payments", map[string]string{AnnotationParent: "Team B:current", AnnotationParentCreate: "false"})) + require.Empty(t, te.drain()) + _, parent, _ := te.dt.project("cluster/payments/"+redisProject, "8") + require.Equal(t, "cluster/payments", parent, "Team B does not exist yet") + + te.dt.addProject("Team B", "current", "") + require.NoError(t, te.resync(context.Background())) + require.Empty(t, te.drain()) + + after, parent, _ := te.dt.project("cluster/payments/"+redisProject, "8") + assert.Equal(t, before.UUID, after.UUID) + assert.Equal(t, "Team B", parent) + assert.Equal(t, int64(1), te.stats.moved.Load()) + + require.NoError(t, te.resync(context.Background())) + require.Empty(t, te.drain()) + assert.Zero(t, te.stats.moved.Load(), "the move is done, not tried again") +} + +// A root-only change moves the parent also when no image of the namespace +// can be exported, and waits while the new root is missing. +func TestExporterMovesTheParentWhenNoImageCanBeExported(t *testing.T) { + te := newTestExporter(t, namespace("payments", nil), runningPod("payments", "api", redisImageID)) + te.store.set(sbomObject(redisImageID, redisTag, helpersv1.Learning, te.clock.t.Add(-time.Hour))) + require.NoError(t, te.resync(context.Background())) + require.Empty(t, te.drain()) + big := sbomObject(redisImageID, redisTag, helpersv1.Learning, te.clock.t) + big.Annotations[helpersv1.ResourceSizeMetadataKey] = strconv.Itoa(11 << 20) + te.store.set(big) + require.NoError(t, te.resync(context.Background())) + require.Empty(t, te.drain()) + + te.updateNamespace(t, namespace("payments", map[string]string{AnnotationRootProject: "Finance:current", AnnotationRootProjectCreate: "false"})) + require.Empty(t, te.drain()) + _, root, _ := te.dt.project("cluster/payments", "current") + require.Equal(t, "cluster", root, "Finance does not exist yet") + + te.dt.addProject("Finance", "current", "") + require.NoError(t, te.resync(context.Background())) + require.Empty(t, te.drain()) + + parent, root, _ := te.dt.project("cluster/payments", "current") + assert.Equal(t, "Finance", root) + assert.Equal(t, parentLogic, parent.CollectionLogic) + assert.Empty(t, te.dt.takeUploads()[1:], "the SBOM is still too large") +} diff --git a/sbomexport/exporter_test.go b/sbomexport/exporter_test.go new file mode 100644 index 00000000..c488bdc7 --- /dev/null +++ b/sbomexport/exporter_test.go @@ -0,0 +1,483 @@ +package sbomexport + +import ( + "context" + "errors" + "os" + "slices" + "strings" + "sync" + "testing" + "time" + + helpersv1 "github.com/kubescape/k8s-interface/instanceidhandler/v1/helpers" + spdxv1beta1 "github.com/kubescape/storage/pkg/apis/softwarecomposition/v1beta1" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/watch" + k8sfake "k8s.io/client-go/kubernetes/fake" + ktesting "k8s.io/client-go/testing" + "k8s.io/client-go/tools/cache" +) + +const ( + redisImageID = "docker.io/library/redis@sha256:1111" + redisTag = "docker.io/library/redis:8" + redisProject = "docker.io/library/redis" +) + +// fakeStore serves SBOMSyft objects from memory; every document is the curl +// fixture. +type fakeStore struct { + mu sync.Mutex + sboms []*spdxv1beta1.SBOMSyft + gets int + tooLarge bool // answer every document read with a *tooLargeError + limit int64 // the limit of the last document read + onList func() // called after listSBOMs took its snapshot, without the lock +} + +// listSBOMs returns the metadata of the objects set, then calls onList. +func (s *fakeStore) listSBOMs(context.Context) ([]sbomMeta, error) { + s.mu.Lock() + var out []sbomMeta + for _, obj := range s.sboms { + if m, ok := metaOf(obj); ok { + out = append(out, m) + } + } + hook := s.onList + s.mu.Unlock() + if hook != nil { + hook() + } + return out, nil +} + +// syftDocument counts the read and returns the curl fixture, or a +// *tooLargeError when tooLarge is set. +func (s *fakeStore) syftDocument(_ context.Context, _, _ string, limit int64) ([]byte, error) { + s.mu.Lock() + defer s.mu.Unlock() + s.gets++ + s.limit = limit + if s.tooLarge { + return nil, &tooLargeError{read: limit + 1} + } + return curlSyftJSON, nil +} + +func (s *fakeStore) set(sboms ...*spdxv1beta1.SBOMSyft) { + s.mu.Lock() + defer s.mu.Unlock() + s.sboms = sboms +} + +// lastLimit returns the limit of the last document read. +func (s *fakeStore) lastLimit() int64 { + s.mu.Lock() + defer s.mu.Unlock() + return s.limit +} + +func (s *fakeStore) documentReads() int { + s.mu.Lock() + defer s.mu.Unlock() + return s.gets +} + +type testExporter struct { + *Exporter + dt *fakeDT + store *fakeStore + clock *fakeClock + client *k8sfake.Clientset +} + +// newTestExporter returns an Exporter for cluster "cluster" with the default +// layout, a one-hour resync and kube-system excluded, over a fake +// Dependency-Track, a fake SBOM store and a fake clientset holding objects +// (pods and namespaces). +func newTestExporter(t *testing.T, objects ...runtime.Object) *testExporter { + t.Helper() + return newTestExporterWith(t, func(*Options) {}, objects...) +} + +// newTestExporterWith is newTestExporter with options changed by change. +func newTestExporterWith(t *testing.T, change func(*Options), objects ...runtime.Object) *testExporter { + t.Helper() + clock := &fakeClock{t: time.Date(2026, 11, 10, 12, 0, 0, 0, time.UTC)} + dt, srv, keyFile := newFakeDT(t, clock.now) + store := &fakeStore{} + client := k8sfake.NewClientset(objects...) + opts := Options{ + URL: srv.URL, + APIKeyFile: keyFile, + Cluster: "cluster", + Layout: defaultLayout, + MaxSBOMSize: 10 << 20, + ResyncInterval: time.Hour, + SkipNamespace: func(ns string) bool { return ns == "kube-system" }, + } + change(&opts) + e, err := newExporter(opts, client, store, srv.Client(), clock.now) + require.NoError(t, err) + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) + t.Cleanup(e.queue.ShutDown) + go e.namespaceInformer.Run(ctx.Done()) + require.True(t, cache.WaitForCacheSync(ctx.Done(), e.namespaceInformer.HasSynced)) + return &testExporter{Exporter: e, dt: dt, store: store, clock: clock, client: client} +} + +// drain processes every queued item once, as Start's worker would, and +// returns the errors. +func (te *testExporter) drain() []error { + var errs []error + for te.queue.Len() > 0 { + key, _ := te.queue.Get() + var err error + if ns, ok := strings.CutPrefix(key, moveKeyPrefix); ok { + err = te.move(context.Background(), ns) + } else { + err = te.process(context.Background(), key) + } + if err != nil { + errs = append(errs, err) + } + te.queue.Forget(key) + te.queue.Done(key) + } + return errs +} + +// updateNamespace replaces ns and waits until the exporter has queued its move. +func (te *testExporter) updateNamespace(t *testing.T, ns *corev1.Namespace) { + t.Helper() + seq := te.moveSeqForTest() + _, err := te.client.CoreV1().Namespaces().Update(context.Background(), ns, metav1.UpdateOptions{}) + require.NoError(t, err) + require.Eventually(t, func() bool { return te.moveSeqForTest() > seq }, 5*time.Second, 10*time.Millisecond) +} + +// moveSeqForTest returns how many namespace layout changes the exporter saw. +func (te *testExporter) moveSeqForTest() uint64 { + te.mu.Lock() + defer te.mu.Unlock() + return te.moveSeq +} + +func namespace(name string, annotations map[string]string) *corev1.Namespace { + return &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: name, Annotations: annotations}} +} + +func runningPod(namespace, name, imageID string) *corev1.Pod { + return &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{Namespace: namespace, Name: name}, + Status: corev1.PodStatus{ + Phase: corev1.PodRunning, + ContainerStatuses: []corev1.ContainerStatus{{Name: "main", ImageID: imageID}}, + }, + } +} + +func sbomObject(imageID, imageTag, status string, created time.Time) *spdxv1beta1.SBOMSyft { + return &spdxv1beta1.SBOMSyft{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "kubescape", + Name: "sbom-" + digestOf(imageID), + CreationTimestamp: metav1.NewTime(created), + Annotations: map[string]string{ + helpersv1.ImageIDMetadataKey: imageID, + helpersv1.ImageTagMetadataKey: imageTag, + helpersv1.StatusMetadataKey: status, + }, + }, + } +} + +// defaultLayout is the operator's default: //:. +var defaultLayout = Layout{ + Root: Collection{Name: "{{ .Cluster }}", Version: "current", Create: true}, + Parent: Collection{Name: "{{ .Cluster }}/{{ .Namespace }}", Version: "current", Create: true}, + Name: "{{ .Cluster }}/{{ .Namespace }}/{{ .Repository }}", + Version: "{{ .Tag }}", +} + +// New refuses options the exporter cannot run with. +func TestNewRejectsAnInvalidConfiguration(t *testing.T) { + valid := Options{URL: "https://dtrack.example.com", APIKeyFile: "/etc/sbom-export/api-key", Cluster: "cluster", Layout: defaultLayout, MaxSBOMSize: 10 << 20, ResyncInterval: time.Hour} + tt := []struct { + name string + change func(*Options) + }{ + {"no URL", func(o *Options) { o.URL = "" }}, + {"a URL without a scheme", func(o *Options) { o.URL = "dtrack.example.com" }}, + {"no API key file", func(o *Options) { o.APIKeyFile = "" }}, + {"a template that does not parse", func(o *Options) { o.Layout.Name = "{{ .Namespace" }}, + {"a template with an unknown field", func(o *Options) { o.Layout.Version = "{{ .Digest }}" }}, + {"no size limit", func(o *Options) { o.MaxSBOMSize = 0 }}, + {"no resync interval", func(o *Options) { o.ResyncInterval = 0 }}, + } + for _, tc := range tt { + t.Run(tc.name, func(t *testing.T) { + opts := valid + tc.change(&opts) + _, err := New(opts, k8sfake.NewClientset(), nil) + assert.Error(t, err) + }) + } + _, err := New(valid, k8sfake.NewClientset(), nil) + assert.NoError(t, err) +} + +func TestExporterSendsAnImageToTheProjectOfEveryNamespaceItRunsIn(t *testing.T) { + te := newTestExporter(t, + runningPod("apps", "web", redisImageID), + runningPod("jobs", "worker", redisImageID), + runningPod("kube-system", "cache", redisImageID), + ) + te.store.set(sbomObject(redisImageID, redisTag, helpersv1.Learning, te.clock.t.Add(-time.Hour))) + + require.NoError(t, te.resync(context.Background())) + require.Empty(t, te.drain()) + + assert.ElementsMatch(t, []string{ + "cluster/apps/" + redisProject + ":8", + "cluster/jobs/" + redisProject + ":8", + }, te.dt.takeUploads(), "nothing for the excluded kube-system") + assert.Equal(t, 1, te.store.documentReads(), "converted once for both namespaces") + + image, parent, _ := te.dt.project("cluster/apps/"+redisProject, "8") + assert.Equal(t, "cluster/apps", parent) + assert.True(t, image.IsLatest) + namespace, parent, _ := te.dt.project("cluster/apps", "current") + assert.Equal(t, "cluster", parent) + assert.Equal(t, parentLogic, namespace.CollectionLogic) + root, parent, _ := te.dt.project("cluster", "current") + assert.Empty(t, parent) + assert.Equal(t, rootLogic, root.CollectionLogic) +} + +func TestExporterSendsAnSBOMAgainOnlyWhenItIsNewerThanTheLastImport(t *testing.T) { + te := newTestExporter(t, runningPod("apps", "web", redisImageID)) + te.store.set(sbomObject(redisImageID, redisTag, helpersv1.Learning, te.clock.t.Add(-time.Hour))) + require.NoError(t, te.resync(context.Background())) + require.Empty(t, te.drain()) + te.dt.takeUploads() + + te.clock.t = te.clock.t.Add(time.Hour) + require.NoError(t, te.resync(context.Background())) + require.Empty(t, te.drain()) + assert.Empty(t, te.dt.takeUploads(), "unchanged") + + te.store.set(sbomObject(redisImageID, redisTag, helpersv1.Learning, te.clock.t.Add(-time.Minute))) + te.clock.t = te.clock.t.Add(time.Hour) + require.NoError(t, te.resync(context.Background())) + require.Empty(t, te.drain()) + assert.Equal(t, []string{"cluster/apps/" + redisProject + ":8"}, te.dt.takeUploads(), "the SBOMSyft is newer than the last import") +} + +func TestExporterMarksTheRunningTagAsLatestWithoutSendingIt(t *testing.T) { + te := newTestExporter(t, runningPod("apps", "web", redisImageID)) + te.store.set(sbomObject(redisImageID, redisTag, helpersv1.Learning, te.clock.t.Add(-time.Hour))) + require.NoError(t, te.resync(context.Background())) + require.Empty(t, te.drain()) + te.dt.takeUploads() + + // another version of the image became the latest one + p, _, _ := te.dt.project("cluster/apps/"+redisProject, "8") + require.NoError(t, te.dt.patchForTest(p.UUID, false)) + + require.NoError(t, te.resync(context.Background())) + require.Empty(t, te.drain()) + assert.Empty(t, te.dt.takeUploads()) + p, _, _ = te.dt.project("cluster/apps/"+redisProject, "8") + assert.True(t, p.IsLatest) +} + +func TestExporterWaitsForTheSBOMToBeReady(t *testing.T) { + te := newTestExporter(t, runningPod("apps", "web", redisImageID)) + te.store.set(sbomObject(redisImageID, redisTag, helpersv1.Initializing, te.clock.t.Add(-time.Hour))) + require.NoError(t, te.resync(context.Background())) + require.Empty(t, te.drain()) + assert.Empty(t, te.dt.takeUploads()) + + te.ObserveSBOM(watch.Modified, sbomObject(redisImageID, redisTag, helpersv1.Learning, te.clock.t.Add(-time.Hour))) + require.Empty(t, te.drain()) + assert.Equal(t, []string{"cluster/apps/" + redisProject + ":8"}, te.dt.takeUploads()) +} + +func TestExporterSendsTheImageOfAJobThatEndedBeforeItsSBOMWasReady(t *testing.T) { + te := newTestExporter(t) + job := runningPod("jobs", "migrate-28937", redisImageID) + + te.ObservePod(watch.Added, job) + te.ObservePod(watch.Deleted, job) + te.clock.t = te.clock.t.Add(10 * time.Minute) + te.ObserveSBOM(watch.Added, sbomObject(redisImageID, redisTag, helpersv1.Learning, te.clock.t)) + require.Empty(t, te.drain()) + assert.Equal(t, []string{"cluster/jobs/" + redisProject + ":8"}, te.dt.takeUploads()) + + te.clock.t = te.clock.t.Add(time.Hour) + require.NoError(t, te.resync(context.Background())) + assert.Zero(t, te.queue.Len(), "the grace period is over") +} + +func TestExporterRetriesWhatDependencyTrackRefused(t *testing.T) { + te := newTestExporter(t, runningPod("apps", "web", redisImageID)) + te.store.set(sbomObject(redisImageID, redisTag, helpersv1.Learning, te.clock.t.Add(-time.Hour))) + require.NoError(t, te.resync(context.Background())) + te.dt.failNext(1) + + assert.Len(t, te.drain(), 1) + assert.Empty(t, te.dt.takeUploads()) + + te.queue.Add(digestOf(redisImageID)) + require.Empty(t, te.drain()) + assert.Equal(t, []string{"cluster/apps/" + redisProject + ":8"}, te.dt.takeUploads()) +} + +func TestExporterResyncKeepsWhatItKnewWhenThePodListFails(t *testing.T) { + te := newTestExporter(t, runningPod("apps", "web", redisImageID)) + require.NoError(t, te.resync(context.Background())) + listErr := errors.New("pods are unavailable") + te.client.PrependReactor("list", "pods", func(ktesting.Action) (bool, runtime.Object, error) { + return true, nil, listErr + }) + + assert.ErrorIs(t, te.resync(context.Background()), listErr) + assert.Equal(t, []string{"apps"}, te.running.namespaces(digestOf(redisImageID))) +} + +// Start exports the running images and returns once its context ends. +func TestStartExportsUntilTheContextEnds(t *testing.T) { + te := newTestExporter(t, runningPod("apps", "web", redisImageID)) + te.store.set(sbomObject(redisImageID, redisTag, helpersv1.Learning, te.clock.t.Add(-time.Hour))) + ctx, cancel := context.WithCancel(context.Background()) + done := make(chan struct{}) + go func() { + defer close(done) + te.Start(ctx) + }() + + var uploads []string + assert.Eventually(t, func() bool { + uploads = append(uploads, te.dt.takeUploads()...) + return slices.Contains(uploads, "cluster/apps/"+redisProject+":8") + }, 10*time.Second, 50*time.Millisecond) + + cancel() + select { + case <-done: + case <-time.After(10 * time.Second): + t.Fatal("Start did not return after the context ended") + } +} + +func TestExporterCountsARefusedAPIKeyAsAFailureAndLogsItOncePerResync(t *testing.T) { + te := newTestExporter(t, runningPod("apps", "web", redisImageID), runningPod("apps", "api", "docker.io/library/nginx@sha256:2222")) + te.store.set( + sbomObject(redisImageID, redisTag, helpersv1.Learning, te.clock.t.Add(-time.Hour)), + sbomObject("docker.io/library/nginx@sha256:2222", "docker.io/library/nginx:1.27", helpersv1.Learning, te.clock.t.Add(-time.Hour)), + ) + require.NoError(t, os.WriteFile(te.dt.keyFile, []byte("revoked-key"), 0600)) + require.NoError(t, te.resync(context.Background())) + + errs := te.drain() + require.Len(t, errs, 2) + assert.True(t, isAuthError(errs[0])) + assert.Equal(t, int64(2), te.stats.failed.Load()) + assert.True(t, te.stats.authLogged.Load()) + + te.stats.takeFields() + assert.False(t, te.stats.authLogged.Load(), "the next resync logs it again") +} + +// node-agent rebuilds the SBOM of a running image in place when its own +// version changes: the creation time stays and the tool version changes. +func TestExporterSendsAnSBOMAgainWhenNodeAgentRebuiltIt(t *testing.T) { + te := newTestExporter(t, runningPod("apps", "web", redisImageID)) + sbom := sbomObject(redisImageID, redisTag, helpersv1.Learning, te.clock.t.Add(-time.Hour)) + sbom.Annotations[helpersv1.ToolVersionMetadataKey] = "v0.3.38" + te.store.set(sbom) + require.NoError(t, te.resync(context.Background())) + require.Empty(t, te.drain()) + require.Len(t, te.dt.takeUploads(), 1) + require.NoError(t, te.resync(context.Background())) + require.Empty(t, te.drain()) + require.Empty(t, te.dt.takeUploads(), "same node-agent version: unchanged") + + rebuilt := sbom.DeepCopy() + rebuilt.Annotations[helpersv1.ToolVersionMetadataKey] = "v0.3.39" + te.store.set(rebuilt) + require.NoError(t, te.resync(context.Background())) + require.Empty(t, te.drain()) + assert.Len(t, te.dt.takeUploads(), 1, "rebuilt by another node-agent version") + + require.NoError(t, te.resync(context.Background())) + require.Empty(t, te.drain()) + assert.Empty(t, te.dt.takeUploads()) + p, _, _ := te.dt.project("cluster/apps/"+redisProject, "8") + assert.Equal(t, "v0.3.39", te.dt.propertyForTest(p.UUID, toolVersionProperty)) +} + +// The tool version is recorded only after the upload, so a failed upload of a +// rebuilt SBOM is tried again instead of being taken as sent. +func TestExporterSendsARebuiltSBOMAgainAfterAFailedUpload(t *testing.T) { + te := newTestExporter(t, runningPod("apps", "web", redisImageID)) + sbom := sbomObject(redisImageID, redisTag, helpersv1.Learning, te.clock.t.Add(-time.Hour)) + sbom.Annotations[helpersv1.ToolVersionMetadataKey] = "v0.3.38" + te.store.set(sbom) + require.NoError(t, te.resync(context.Background())) + require.Empty(t, te.drain()) + require.Len(t, te.dt.takeUploads(), 1) + + rebuilt := sbom.DeepCopy() + rebuilt.Annotations[helpersv1.ToolVersionMetadataKey] = "v0.3.39" + te.store.set(rebuilt) + te.dt.mu.Lock() + te.dt.failUploads = 1 + te.dt.mu.Unlock() + require.NoError(t, te.resync(context.Background())) + require.Len(t, te.drain(), 1) + require.Empty(t, te.dt.takeUploads()) + + require.NoError(t, te.resync(context.Background())) + require.Empty(t, te.drain()) + assert.Len(t, te.dt.takeUploads(), 1) +} + +// An SBOM that becomes ready while resync lists the SBOMSyft objects is not +// replaced by the older state the list returned. +func TestResyncKeepsAnSBOMThatBecameReadyWhileItListed(t *testing.T) { + te := newTestExporter(t, runningPod("apps", "web", redisImageID)) + created := te.clock.t.Add(-time.Hour) + te.store.set(sbomObject(redisImageID, redisTag, helpersv1.Initializing, created)) + te.store.onList = func() { + te.ObserveSBOM(watch.Modified, sbomObject(redisImageID, redisTag, helpersv1.Learning, created)) + } + + require.NoError(t, te.resync(context.Background())) + require.Empty(t, te.drain()) + + assert.Len(t, te.dt.takeUploads(), 1) +} + +// A pod that starts while resync lists the pods stays running instead of +// starting its grace period because the list missed it. +func TestResyncKeepsAPodThatStartedWhileItListed(t *testing.T) { + te := newTestExporter(t) + te.client.PrependReactor("list", "pods", func(ktesting.Action) (bool, runtime.Object, error) { + te.ObservePod(watch.Added, runningPod("apps", "web", redisImageID)) + return false, nil, nil + }) + + require.NoError(t, te.resync(context.Background())) + + te.clock.t = te.clock.t.Add(2 * time.Hour) + assert.Equal(t, []string{"apps"}, te.running.namespaces(digestOf(redisImageID))) +} diff --git a/sbomexport/fakedtrack_test.go b/sbomexport/fakedtrack_test.go new file mode 100644 index 00000000..b3e73ecd --- /dev/null +++ b/sbomexport/fakedtrack_test.go @@ -0,0 +1,330 @@ +package sbomexport + +import ( + "encoding/base64" + "encoding/json" + "fmt" + "net/http" + "net/http/httptest" + "os" + "path/filepath" + "strings" + "sync" + "testing" + "time" +) + +const testAPIKey = "test-api-key" + +// fakeDT is an in-memory Dependency-Track that serves the calls dtClient +// makes: project lookup, get by UUID, create, patch (including a move), BOM +// upload to an existing project, and project properties. +type fakeDT struct { + mu sync.Mutex + now func() time.Time + projects map[string]*project // uuid → project + parents map[string]string // project uuid → parent uuid + properties map[string]map[string]string // project uuid → "group/name" → value + uploads []string // "name:version" of every BOM upload + patches map[string]int // project uuid → PATCH requests + nextID int + fail int // answer the next fail requests with 503 + failProperties int // answer the next property writes with 503 + failUploads int // answer the next BOM uploads with 503 + onPatch func() // called before a PATCH is applied, without the lock + keyFile string // holds testAPIKey +} + +// newFakeDT starts a fakeDT and returns it with an API key file holding +// testAPIKey. +func newFakeDT(t *testing.T, now func() time.Time) (*fakeDT, *httptest.Server, string) { + t.Helper() + keyFile := filepath.Join(t.TempDir(), "api-key") + if err := os.WriteFile(keyFile, []byte(testAPIKey+"\n"), 0600); err != nil { + t.Fatal(err) + } + f := &fakeDT{now: now, projects: map[string]*project{}, parents: map[string]string{}, properties: map[string]map[string]string{}, patches: map[string]int{}, keyFile: keyFile} + srv := httptest.NewServer(f) + t.Cleanup(srv.Close) + return f, srv, keyFile +} + +// ServeHTTP answers the Dependency-Track API calls dtClient makes. +func (f *fakeDT) ServeHTTP(w http.ResponseWriter, r *http.Request) { + if r.Method == http.MethodPatch { + f.mu.Lock() + hook := f.onPatch + f.onPatch = nil + f.mu.Unlock() + if hook != nil { + hook() + } + } + f.mu.Lock() + defer f.mu.Unlock() + if r.Header.Get("X-Api-Key") != testAPIKey { + http.Error(w, "The API key could not be validated", http.StatusUnauthorized) + return + } + if f.fail > 0 { + f.fail-- + http.Error(w, "unavailable", http.StatusServiceUnavailable) + return + } + path := r.URL.Path + switch { + case r.Method == http.MethodGet && path == "/api/v1/project/lookup": + p := f.byNameLocked(r.URL.Query().Get("name"), r.URL.Query().Get("version")) + if p == nil { + http.Error(w, "The project could not be found.", http.StatusNotFound) + return + } + writeJSON(w, http.StatusOK, p) + case r.Method == http.MethodPut && path == "/api/v1/project": + var req struct { + Name, Version, CollectionLogic string + Parent *struct{ UUID string } + Tags []tag + } + _ = json.NewDecoder(r.Body).Decode(&req) + parent := "" + if req.Parent != nil { + parent = req.Parent.UUID + } + p := f.addLocked(req.Name, req.Version, parent, req.Tags) + p.CollectionLogic = req.CollectionLogic + writeJSON(w, http.StatusCreated, p) + case strings.HasSuffix(path, "/property") && strings.HasPrefix(path, "/api/v1/project/"): + f.serveProperty(w, r, strings.TrimSuffix(strings.TrimPrefix(path, "/api/v1/project/"), "/property")) + case r.Method == http.MethodGet && strings.HasPrefix(path, "/api/v1/project/"): + p := f.projects[strings.TrimPrefix(path, "/api/v1/project/")] + if p == nil { + http.Error(w, "The project could not be found.", http.StatusNotFound) + return + } + writeJSON(w, http.StatusOK, p) + case r.Method == http.MethodPatch && strings.HasPrefix(path, "/api/v1/project/"): + uuid := strings.TrimPrefix(path, "/api/v1/project/") + p := f.projects[uuid] + if p == nil { + http.Error(w, "The project could not be found.", http.StatusNotFound) + return + } + var req struct { + IsLatest *bool + CollectionLogic *string + Name, Version *string + Parent *struct{ UUID string } + Tags *[]tag + } + _ = json.NewDecoder(r.Body).Decode(&req) + f.patches[uuid]++ + if req.IsLatest != nil { + p.IsLatest = *req.IsLatest + } + // Like Dependency-Track 4.14, a PATCH without collectionLogic sets it + // to NONE. + p.CollectionLogic = "NONE" + if req.CollectionLogic != nil { + p.CollectionLogic = *req.CollectionLogic + } + if req.Name != nil { + p.Name = *req.Name + } + if req.Version != nil { + p.Version = *req.Version + } + if req.Parent != nil { + f.parents[uuid] = req.Parent.UUID + p.Parent = &projectParent{UUID: req.Parent.UUID} + } + if req.Tags != nil { + p.Tags = *req.Tags + } + writeJSON(w, http.StatusOK, p) + case r.Method == http.MethodPut && path == "/api/v1/bom" && f.failUploads > 0: + f.failUploads-- + http.Error(w, "unavailable", http.StatusServiceUnavailable) + case r.Method == http.MethodPut && path == "/api/v1/bom": + var req struct { + ProjectName, ProjectVersion, BOM string + AutoCreate, IsLatest bool + } + _ = json.NewDecoder(r.Body).Decode(&req) + bom, err := base64.StdEncoding.DecodeString(req.BOM) + if err != nil || !json.Valid(bom) { + http.Error(w, "bad BOM upload", http.StatusBadRequest) + return + } + p := f.byNameLocked(req.ProjectName, req.ProjectVersion) + if p == nil { + http.Error(w, "The project could not be found.", http.StatusNotFound) + return + } + if req.IsLatest { + for _, other := range f.projects { + if other.Name == p.Name { + other.IsLatest = false + } + } + p.IsLatest = true + } + p.LastBOMImport = epochMillis(f.now().UnixMilli()) + f.uploads = append(f.uploads, req.ProjectName+":"+req.ProjectVersion) + writeJSON(w, http.StatusOK, map[string]string{"token": fmt.Sprintf("token-%d", len(f.uploads))}) + default: + http.NotFound(w, r) + } +} + +// serveProperty lists, creates (PUT) and updates (POST) the properties of +// the project with uuid, like Dependency-Track does. +func (f *fakeDT) serveProperty(w http.ResponseWriter, r *http.Request, uuid string) { + if f.projects[uuid] == nil { + http.Error(w, "The project could not be found.", http.StatusNotFound) + return + } + if r.Method == http.MethodGet { + var out []projectProperty + for k, v := range f.properties[uuid] { + group, name, _ := strings.Cut(k, "/") + out = append(out, projectProperty{GroupName: group, PropertyName: name, PropertyValue: v, PropertyType: "STRING"}) + } + writeJSON(w, http.StatusOK, out) + return + } + if f.failProperties > 0 { + f.failProperties-- + http.Error(w, "unavailable", http.StatusServiceUnavailable) + return + } + var p projectProperty + _ = json.NewDecoder(r.Body).Decode(&p) + key := p.GroupName + "/" + p.PropertyName + if f.properties[uuid] == nil { + f.properties[uuid] = map[string]string{} + } + _, exists := f.properties[uuid][key] + switch { + case r.Method == http.MethodPut && exists: + http.Error(w, "A property with the specified project/group/name combination already exists.", http.StatusConflict) + case r.Method == http.MethodPut: + f.properties[uuid][key] = p.PropertyValue + writeJSON(w, http.StatusCreated, p) + case r.Method == http.MethodPost && exists: + f.properties[uuid][key] = p.PropertyValue + writeJSON(w, http.StatusOK, p) + default: + http.Error(w, "The property could not be found.", http.StatusNotFound) + } +} + +// byNameLocked returns the project name:version, or nil. +func (f *fakeDT) byNameLocked(name, version string) *project { + for _, p := range f.projects { + if p.Name == name && p.Version == version { + return p + } + } + return nil +} + +// addLocked creates a project under parentUUID, or at the top when it is +// empty, and returns it. +func (f *fakeDT) addLocked(name, version, parentUUID string, tags []tag) *project { + f.nextID++ + p := &project{UUID: fmt.Sprintf("00000000-0000-0000-0000-%012d", f.nextID), Name: name, Version: version, Tags: tags} + if parentUUID != "" { + p.Parent = &projectParent{UUID: parentUUID} + } + f.projects[p.UUID] = p + f.parents[p.UUID] = parentUUID + return p +} + +// addProject creates a project the way a person would, without the +// exporter's tag, and returns its uuid. +func (f *fakeDT) addProject(name, version, parentUUID string) string { + f.mu.Lock() + defer f.mu.Unlock() + return f.addLocked(name, version, parentUUID, nil).UUID +} + +// patchCount returns how many PATCH requests the project with uuid got. +func (f *fakeDT) patchCount(uuid string) int { + f.mu.Lock() + defer f.mu.Unlock() + return f.patches[uuid] +} + +// project returns a copy of name:version, and the name of its parent. +func (f *fakeDT) project(name, version string) (project, string, bool) { + f.mu.Lock() + defer f.mu.Unlock() + p := f.byNameLocked(name, version) + if p == nil { + return project{}, "", false + } + parent := "" + if pp := f.projects[f.parents[p.UUID]]; pp != nil { + parent = pp.Name + } + return *p, parent, true +} + +// propertyForTest returns the exporter's property name on the project with uuid. +func (f *fakeDT) propertyForTest(uuid, name string) string { + f.mu.Lock() + defer f.mu.Unlock() + return f.properties[uuid][propertyGroup+"/"+name] +} + +// digestOwnerForTest returns the image digest recorded on the project with uuid. +func (f *fakeDT) digestOwnerForTest(uuid string) (string, error) { + f.mu.Lock() + defer f.mu.Unlock() + if f.projects[uuid] == nil { + return "", fmt.Errorf("no project %s", uuid) + } + return f.properties[uuid][propertyGroup+"/"+digestProperty], nil +} + +// count returns how many projects exist. +func (f *fakeDT) count() int { + f.mu.Lock() + defer f.mu.Unlock() + return len(f.projects) +} + +// failNext makes the next n requests fail with 503. +func (f *fakeDT) failNext(n int) { + f.mu.Lock() + defer f.mu.Unlock() + f.fail = n +} + +// patchForTest sets isLatest of the project with uuid, as another upload would. +func (f *fakeDT) patchForTest(uuid string, isLatest bool) error { + f.mu.Lock() + defer f.mu.Unlock() + p := f.projects[uuid] + if p == nil { + return fmt.Errorf("no project %s", uuid) + } + p.IsLatest = isLatest + return nil +} + +func (f *fakeDT) takeUploads() []string { + f.mu.Lock() + defer f.mu.Unlock() + u := f.uploads + f.uploads = nil + return u +} + +func writeJSON(w http.ResponseWriter, status int, v any) { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(status) + _ = json.NewEncoder(w).Encode(v) +} diff --git a/sbomexport/image.go b/sbomexport/image.go new file mode 100644 index 00000000..f4c6a321 --- /dev/null +++ b/sbomexport/image.go @@ -0,0 +1,57 @@ +package sbomexport + +import ( + "strings" + + corev1 "k8s.io/api/core/v1" +) + +// digestOf returns the digest of an image ID, such as "sha256:abc" for +// "docker.io/library/redis@sha256:abc". An ID without "@" is returned as is. +// Pod container statuses and the kubescape.io/image-id annotation of an +// SBOMSyft both go through it, so the two sides compare equal. +func digestOf(imageID string) string { + if i := strings.LastIndex(imageID, "@"); i >= 0 { + return imageID[i+1:] + } + return imageID +} + +// splitRef splits an image reference into the repository and the version its +// Dependency-Track project gets: the tag; without a tag, the first 12 +// characters of the digest; with neither, "latest". +// +// docker.io/library/redis:8-alpine → docker.io/library/redis, 8-alpine +// docker.io/library/redis@sha256:0123… → docker.io/library/redis, 0123456789ab +// localhost:5000/app → localhost:5000/app, latest +func splitRef(ref string) (repository, version string) { + base, digest, hasDigest := strings.Cut(ref, "@") + if i := strings.LastIndex(base, ":"); i > strings.LastIndex(base, "/") { + return base[:i], base[i+1:] + } + if hasDigest { + hex := digest[strings.LastIndex(digest, ":")+1:] + return base, hex[:min(12, len(hex))] + } + return base, "latest" +} + +// runningDigests returns the image digests of the containers that run in pod: +// its containers, and its init containers that are running (sidecars). +func runningDigests(pod *corev1.Pod) []string { + if pod.Status.Phase != corev1.PodRunning { + return nil + } + var digests []string + for _, cs := range pod.Status.ContainerStatuses { + if cs.ImageID != "" { + digests = append(digests, digestOf(cs.ImageID)) + } + } + for _, cs := range pod.Status.InitContainerStatuses { + if cs.ImageID != "" && cs.State.Running != nil { + digests = append(digests, digestOf(cs.ImageID)) + } + } + return digests +} diff --git a/sbomexport/image_test.go b/sbomexport/image_test.go new file mode 100644 index 00000000..7d61216c --- /dev/null +++ b/sbomexport/image_test.go @@ -0,0 +1,76 @@ +package sbomexport + +import ( + "testing" + + "github.com/stretchr/testify/assert" + corev1 "k8s.io/api/core/v1" +) + +// TestSplitRef checks how image references split into repository and version. +func TestSplitRef(t *testing.T) { + tt := []struct { + ref string + repository string + version string + }{ + {"docker.io/library/redis:8-alpine", "docker.io/library/redis", "8-alpine"}, + {"docker.io/library/redis@sha256:0123456789abcdef", "docker.io/library/redis", "0123456789ab"}, + {"docker.io/library/redis:8-alpine@sha256:0123456789abcdef", "docker.io/library/redis", "8-alpine"}, + {"localhost:5000/app", "localhost:5000/app", "latest"}, + {"localhost:5000/app:1.2", "localhost:5000/app", "1.2"}, + {"localhost:5000/app@sha256:abc", "localhost:5000/app", "abc"}, + {"nginx", "nginx", "latest"}, + } + for _, tc := range tt { + t.Run(tc.ref, func(t *testing.T) { + repository, version := splitRef(tc.ref) + assert.Equal(t, tc.repository, repository) + assert.Equal(t, tc.version, version) + }) + } +} + +func TestDigestOf(t *testing.T) { + assert.Equal(t, "sha256:abc", digestOf("docker.io/library/redis@sha256:abc")) + assert.Equal(t, "sha256:abc", digestOf("sha256:abc")) +} + +// TestRunningDigests checks which containers of a pod count as running an image. +func TestRunningDigests(t *testing.T) { + running := corev1.ContainerState{Running: &corev1.ContainerStateRunning{}} + terminated := corev1.ContainerState{Terminated: &corev1.ContainerStateTerminated{}} + tt := []struct { + name string + pod corev1.PodStatus + want []string + }{ + { + name: "containers and running init containers (sidecars) count", + pod: corev1.PodStatus{ + Phase: corev1.PodRunning, + ContainerStatuses: []corev1.ContainerStatus{ + {Name: "app", ImageID: "docker.io/library/app@sha256:app"}, + {Name: "starting"}, // no image ID yet + }, + InitContainerStatuses: []corev1.ContainerStatus{ + {Name: "sidecar", ImageID: "docker.io/library/proxy@sha256:proxy", State: running}, + {Name: "migrate", ImageID: "docker.io/library/migrate@sha256:migrate", State: terminated}, + }, + }, + want: []string{"sha256:app", "sha256:proxy"}, + }, + { + name: "a pod that is not running runs nothing", + pod: corev1.PodStatus{ + Phase: corev1.PodSucceeded, + ContainerStatuses: []corev1.ContainerStatus{{Name: "app", ImageID: "docker.io/library/app@sha256:app"}}, + }, + }, + } + for _, tc := range tt { + t.Run(tc.name, func(t *testing.T) { + assert.Equal(t, tc.want, runningDigests(&corev1.Pod{Status: tc.pod})) + }) + } +} diff --git a/sbomexport/index.go b/sbomexport/index.go new file mode 100644 index 00000000..88829638 --- /dev/null +++ b/sbomexport/index.go @@ -0,0 +1,214 @@ +package sbomexport + +import ( + "slices" + "sync" + "time" +) + +// podEntry is what the index keeps of one running pod. +type podEntry struct { + namespace string + digests []string +} + +// index records, per image digest, the namespaces where a pod runs that +// image. A namespace stays listed for one grace period after its last such pod +// ended, so the image of a short Job is still exported once its SBOM is ready, +// which can take longer than the Job runs. +// +// It is safe for concurrent use. +type index struct { + mu sync.Mutex + grace time.Duration + now func() time.Time + + pods map[string]podEntry // pod key → what it runs + refs map[string]map[string]int // digest → namespace → running pods + ended map[string]map[string]time.Time // digest → namespace → when its last pod ended +} + +// newIndex returns an empty index that keeps ended namespaces for grace. +func newIndex(grace time.Duration, now func() time.Time) *index { + return &index{ + grace: grace, + now: now, + pods: map[string]podEntry{}, + refs: map[string]map[string]int{}, + ended: map[string]map[string]time.Time{}, + } +} + +// setPod records that the pod with key runs digests in namespace, replacing +// what was recorded for it before. It returns the digests that now run in a +// namespace where they did not run before. +func (x *index) setPod(key, namespace string, digests []string) []string { + x.mu.Lock() + defer x.mu.Unlock() + if len(digests) == 0 { + x.removeLocked(key) + return nil + } + digests = slices.Compact(slices.Sorted(slices.Values(digests))) + // Count the new digests before dropping the old entry, so a digest the pod + // keeps never falls to zero and is not reported as started again. + var started []string + for _, d := range digests { + if x.incLocked(d, namespace) { + started = append(started, d) + } + } + x.removeLocked(key) + x.pods[key] = podEntry{namespace: namespace, digests: digests} + return started +} + +// removePod forgets the pod with key. +func (x *index) removePod(key string) { + x.mu.Lock() + defer x.mu.Unlock() + x.removeLocked(key) +} + +// replace rebuilds the index from a full list of running pods, except for the +// pods in keep, whose recorded state stays. An image that no longer runs in a +// namespace starts its grace period there. +func (x *index) replace(pods map[string]podEntry, keep map[string]bool) { + x.mu.Lock() + defer x.mu.Unlock() + for key := range x.pods { + if _, ok := pods[key]; !ok && !keep[key] { + x.removeLocked(key) + } + } + for key, p := range pods { + if keep[key] { + continue + } + x.removeLocked(key) + x.pods[key] = p + for _, d := range p.digests { + x.incLocked(d, p.namespace) + } + } +} + +// namespaces returns, sorted, the namespaces where digest runs or ran less +// than a grace period ago. +func (x *index) namespaces(digest string) []string { + x.mu.Lock() + defer x.mu.Unlock() + x.pruneLocked() + var out []string + for ns := range x.refs[digest] { + out = append(out, ns) + } + for ns := range x.ended[digest] { + out = append(out, ns) + } + slices.Sort(out) + return out +} + +// digestsIn returns, sorted, the digests that run in ns or ran there less than +// a grace period ago. +func (x *index) digestsIn(ns string) []string { + x.mu.Lock() + defer x.mu.Unlock() + x.pruneLocked() + var out []string + for d, r := range x.refs { + if r[ns] > 0 { + out = append(out, d) + } + } + for d, e := range x.ended { + if _, ok := e[ns]; ok { + out = append(out, d) + } + } + slices.Sort(out) + return slices.Compact(out) +} + +// digests returns every digest that namespaces would return a namespace for. +func (x *index) digests() []string { + x.mu.Lock() + defer x.mu.Unlock() + x.pruneLocked() + var out []string + for d := range x.refs { + out = append(out, d) + } + for d := range x.ended { + if _, running := x.refs[d]; !running { + out = append(out, d) + } + } + slices.Sort(out) + return out +} + +// removeLocked forgets the pod with key and uncounts its digests. +func (x *index) removeLocked(key string) { + p, ok := x.pods[key] + if !ok { + return + } + delete(x.pods, key) + for _, d := range p.digests { + x.decLocked(d, p.namespace) + } +} + +// incLocked counts one more pod running digest in ns, and reports whether it +// is the first. +func (x *index) incLocked(digest, ns string) bool { + if x.refs[digest] == nil { + x.refs[digest] = map[string]int{} + } + x.refs[digest][ns]++ + if e := x.ended[digest]; e != nil { + delete(e, ns) + if len(e) == 0 { + delete(x.ended, digest) + } + } + return x.refs[digest][ns] == 1 +} + +// decLocked counts one less pod running digest in ns, and starts the grace +// period of ns when it was the last. +func (x *index) decLocked(digest, ns string) { + r := x.refs[digest] + if r == nil || r[ns] == 0 { + return + } + r[ns]-- + if r[ns] > 0 { + return + } + delete(r, ns) + if len(r) == 0 { + delete(x.refs, digest) + } + if x.ended[digest] == nil { + x.ended[digest] = map[string]time.Time{} + } + x.ended[digest][ns] = x.now() +} + +// pruneLocked drops the namespaces whose grace period is over. +func (x *index) pruneLocked() { + now := x.now() + for d, e := range x.ended { + for ns, at := range e { + if now.Sub(at) >= x.grace { + delete(e, ns) + } + } + if len(e) == 0 { + delete(x.ended, d) + } + } +} diff --git a/sbomexport/index_test.go b/sbomexport/index_test.go new file mode 100644 index 00000000..cd2be303 --- /dev/null +++ b/sbomexport/index_test.go @@ -0,0 +1,94 @@ +package sbomexport + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +// fakeClock is a time source a test moves by hand. +type fakeClock struct{ t time.Time } + +func (c *fakeClock) now() time.Time { return c.t } + +func newTestIndex() (*index, *fakeClock) { + clock := &fakeClock{t: time.Date(2026, 11, 10, 12, 0, 0, 0, time.UTC)} + return newIndex(time.Hour, clock.now), clock +} + +func TestIndexListsEveryNamespaceAnImageRunsIn(t *testing.T) { + x, _ := newTestIndex() + + assert.Equal(t, []string{"sha256:a"}, x.setPod("apps/web", "apps", []string{"sha256:a"})) + assert.Equal(t, []string{"sha256:a", "sha256:b"}, x.setPod("jobs/batch", "jobs", []string{"sha256:b", "sha256:a", "sha256:a"})) + assert.Empty(t, x.setPod("apps/web-2", "apps", []string{"sha256:a"}), "already runs in apps") + + assert.Equal(t, []string{"apps", "jobs"}, x.namespaces("sha256:a")) + assert.Equal(t, []string{"jobs"}, x.namespaces("sha256:b")) + assert.Equal(t, []string{"sha256:a", "sha256:b"}, x.digests()) +} + +// A Modified event for a pod that keeps its images must not queue them again. +func TestIndexDoesNotReportAnUnchangedPodAsStarted(t *testing.T) { + x, _ := newTestIndex() + assert.Equal(t, []string{"sha256:a"}, x.setPod("apps/web", "apps", []string{"sha256:a"})) + + assert.Empty(t, x.setPod("apps/web", "apps", []string{"sha256:a"})) + assert.Equal(t, []string{"sha256:b"}, x.setPod("apps/web", "apps", []string{"sha256:a", "sha256:b"})) + assert.Equal(t, []string{"apps"}, x.namespaces("sha256:a")) +} + +func TestIndexKeepsANamespaceForOneGracePeriodAfterItsLastPod(t *testing.T) { + x, clock := newTestIndex() + x.setPod("apps/web", "apps", []string{"sha256:a"}) + x.setPod("apps/web-2", "apps", []string{"sha256:a"}) + + x.removePod("apps/web") + clock.t = clock.t.Add(2 * time.Hour) + assert.Equal(t, []string{"apps"}, x.namespaces("sha256:a"), "web-2 still runs") + + x.removePod("apps/web-2") + clock.t = clock.t.Add(59 * time.Minute) + assert.Equal(t, []string{"apps"}, x.namespaces("sha256:a"), "inside the grace period") + assert.Equal(t, []string{"sha256:a"}, x.digests()) + + clock.t = clock.t.Add(time.Minute) + assert.Empty(t, x.namespaces("sha256:a")) + assert.Empty(t, x.digests()) +} + +func TestIndexForgetsTheEndOfAnImageThatRunsAgain(t *testing.T) { + x, clock := newTestIndex() + x.setPod("apps/web", "apps", []string{"sha256:a"}) + x.removePod("apps/web") + + assert.Equal(t, []string{"sha256:a"}, x.setPod("apps/web", "apps", []string{"sha256:a"}), "runs again") + clock.t = clock.t.Add(2 * time.Hour) + assert.Equal(t, []string{"apps"}, x.namespaces("sha256:a")) +} + +func TestIndexMovesAPodToItsNewImage(t *testing.T) { + x, _ := newTestIndex() + x.setPod("apps/web", "apps", []string{"sha256:old"}) + + assert.Equal(t, []string{"sha256:new"}, x.setPod("apps/web", "apps", []string{"sha256:new"})) + assert.Equal(t, []string{"apps"}, x.namespaces("sha256:old"), "the old image is in its grace period") + assert.Equal(t, []string{"apps"}, x.namespaces("sha256:new")) +} + +func TestIndexReplaceStartsTheGracePeriodOfWhatStopped(t *testing.T) { + x, clock := newTestIndex() + x.setPod("apps/web", "apps", []string{"sha256:a"}) + x.setPod("jobs/batch", "jobs", []string{"sha256:b"}) + + x.replace(map[string]podEntry{ + "apps/web": {namespace: "apps", digests: []string{"sha256:a"}}, + "apps/api": {namespace: "apps", digests: []string{"sha256:c"}}, + }, nil) + assert.Equal(t, []string{"sha256:a", "sha256:b", "sha256:c"}, x.digests()) + + clock.t = clock.t.Add(time.Hour) + assert.Equal(t, []string{"sha256:a", "sha256:c"}, x.digests(), "batch stopped an hour ago") + assert.Equal(t, []string{"apps"}, x.namespaces("sha256:a")) +} diff --git a/sbomexport/layout.go b/sbomexport/layout.go new file mode 100644 index 00000000..8401d5ac --- /dev/null +++ b/sbomexport/layout.go @@ -0,0 +1,269 @@ +package sbomexport + +import ( + "bytes" + "errors" + "fmt" + "io" + "regexp" + "strconv" + "strings" + "sync" + "text/template" +) + +// Namespace annotations that override the layout for one namespace. Each one +// is optional; what a namespace does not set comes from the defaults. +const ( + // AnnotationRootProject is the root project, as "name:version" or a project UUID. + AnnotationRootProject = "kubescape.io/dtrack-root-project" + // AnnotationRootProjectCreate is "true" or "false". + AnnotationRootProjectCreate = "kubescape.io/dtrack-root-project-create" + // AnnotationParent is the parent project, as "name:version" or a project UUID. + AnnotationParent = "kubescape.io/dtrack-parent" + // AnnotationParentCreate is "true" or "false". + AnnotationParentCreate = "kubescape.io/dtrack-parent-create" + // AnnotationProjectName is a template for the image project's name. + AnnotationProjectName = "kubescape.io/dtrack-project-name" +) + +// TemplateData is what the layout templates can use, e.g. +// "{{ .Cluster }}/{{ .Namespace }}" or `{{ index .Labels "team" }}`. +type TemplateData struct { + Cluster string + Namespace string + Repository string + Tag string + // Labels are the namespace's labels. + Labels map[string]string +} + +// Collection is a collection project in the layout: the root, or the parent of +// an image's project. Name and Version are templates. +type Collection struct { + Name string + Version string + // Create lets the exporter create the project when it is missing. + Create bool +} + +// Layout is where images go in Dependency-Track, by default. Name and Version +// are templates for the image's project. +type Layout struct { + Root Collection + Parent Collection + Name string + Version string +} + +// projectRef points at a Dependency-Track project by UUID, or by name and version. +type projectRef struct { + uuid string + name, version string +} + +// String returns the UUID, or name:version. +func (r projectRef) String() string { + if r.uuid != "" { + return r.uuid + } + return r.name + ":" + r.version +} + +// placement is where one image of one namespace goes. +type placement struct { + root projectRef + rootCreate bool + parent projectRef + parentCreate bool + name, version string // the image's project +} + +// namespaceInfo is what the layout needs to know about a namespace. +type namespaceInfo struct { + name string + labels map[string]string + annotations map[string]string +} + +var uuidPattern = regexp.MustCompile(`^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$`) + +// parseRef reads an annotation's project reference: a UUID, or "name:version", +// split at the last colon. +func parseRef(s string) (projectRef, error) { + s = strings.TrimSpace(s) + if uuidPattern.MatchString(s) { + return projectRef{uuid: strings.ToLower(s)}, nil + } + i := strings.LastIndex(s, ":") + if i <= 0 || i == len(s)-1 { + return projectRef{}, fmt.Errorf("%q is neither a project UUID nor name:version", s) + } + return projectRef{name: s[:i], version: s[i+1:]}, nil +} + +// layout resolves placements from the default Layout and namespace annotations. +type layout struct { + cluster string + root [2]*template.Template // name, version + parent [2]*template.Template + project [2]*template.Template + defs Layout + + mu sync.Mutex + nameByText map[string]*template.Template // parsed AnnotationProjectName values +} + +// parseTemplate parses text as the template for field. A missing map key is an +// error, so {{ .Labels.team }} fails for a namespace without that label instead +// of rendering "". +func parseTemplate(field, text string) (*template.Template, error) { + t, err := template.New(field).Option("missingkey=error").Parse(text) + if err != nil { + return nil, fmt.Errorf("%s %q: %w", field, text, err) + } + return t, nil +} + +// newLayout parses the default templates and checks that they render. +func newLayout(cluster string, l Layout) (*layout, error) { + x := &layout{cluster: cluster, defs: l, nameByText: map[string]*template.Template{}} + var errs []error + for _, f := range []struct { + dst **template.Template + field string + text string + }{ + {&x.root[0], "rootProject.name", l.Root.Name}, + {&x.root[1], "rootProject.version", l.Root.Version}, + {&x.parent[0], "project.parent.name", l.Parent.Name}, + {&x.parent[1], "project.parent.version", l.Parent.Version}, + {&x.project[0], "project.name", l.Name}, + {&x.project[1], "project.version", l.Version}, + } { + t, err := parseTemplate(f.field, f.text) + if err != nil { + errs = append(errs, err) + continue + } + *f.dst = t + } + if err := errors.Join(errs...); err != nil { + return nil, err + } + // Catch unknown fields now. A missing label or an empty result is not an + // error here: a template like {{ .Labels.team }} renders only for + // namespaces that have the label, and resolve rejects it for the others. + sample := TemplateData{Cluster: cluster, Namespace: "default", Repository: "docker.io/library/nginx", Tag: "1.27", Labels: map[string]string{}} + for _, t := range []*template.Template{x.root[0], x.root[1], x.parent[0], x.parent[1], x.project[0], x.project[1]} { + c, err := t.Clone() + if err == nil { + err = c.Option("missingkey=zero").Execute(io.Discard, sample) + } + if err != nil { + errs = append(errs, fmt.Errorf("render %s: %w", t.Name(), err)) + } + } + return x, errors.Join(errs...) +} + +// render executes t and rejects an empty result. +func render(t *template.Template, data TemplateData) (string, error) { + var b bytes.Buffer + if err := t.Execute(&b, data); err != nil { + return "", fmt.Errorf("render %s: %w", t.Name(), err) + } + out := strings.TrimSpace(b.String()) + if out == "" { + return "", fmt.Errorf("render %s: empty result", t.Name()) + } + return out, nil +} + +// projectNameTemplate parses an AnnotationProjectName value once and reuses it. +func (x *layout) projectNameTemplate(text string) (*template.Template, error) { + x.mu.Lock() + defer x.mu.Unlock() + if t, ok := x.nameByText[text]; ok { + return t, nil + } + t, err := parseTemplate(AnnotationProjectName, text) + if err != nil { + return nil, err + } + x.nameByText[text] = t + return t, nil +} + +// boolAnnotation reads a "true"/"false" annotation, or def when it is not set. +func boolAnnotation(ann map[string]string, key string, def bool) (bool, error) { + v, ok := ann[key] + if !ok { + return def, nil + } + b, err := strconv.ParseBool(strings.TrimSpace(v)) + if err != nil { + return false, fmt.Errorf("annotation %s: %q is not true or false", key, v) + } + return b, nil +} + +// resolve returns where repository:tag, running in ns, goes. Each annotation +// replaces only the setting it names. +func (x *layout) resolve(ns namespaceInfo, repository, tag string) (placement, error) { + data := TemplateData{Cluster: x.cluster, Namespace: ns.name, Repository: repository, Tag: tag, Labels: ns.labels} + if data.Labels == nil { + data.Labels = map[string]string{} + } + ann := ns.annotations + var p placement + var err error + + if p.root, err = x.collectionRef(ann, AnnotationRootProject, x.root, data); err != nil { + return placement{}, err + } + if p.rootCreate, err = boolAnnotation(ann, AnnotationRootProjectCreate, x.defs.Root.Create); err != nil { + return placement{}, err + } + if p.parent, err = x.collectionRef(ann, AnnotationParent, x.parent, data); err != nil { + return placement{}, err + } + if p.parentCreate, err = boolAnnotation(ann, AnnotationParentCreate, x.defs.Parent.Create); err != nil { + return placement{}, err + } + + nameTmpl := x.project[0] + if text, ok := ann[AnnotationProjectName]; ok { + if nameTmpl, err = x.projectNameTemplate(text); err != nil { + return placement{}, err + } + } + if p.name, err = render(nameTmpl, data); err != nil { + return placement{}, err + } + if p.version, err = render(x.project[1], data); err != nil { + return placement{}, err + } + return p, nil +} + +// collectionRef reads the reference from the annotation key, or renders the +// default name and version templates. +func (x *layout) collectionRef(ann map[string]string, key string, defs [2]*template.Template, data TemplateData) (projectRef, error) { + if v, ok := ann[key]; ok { + r, err := parseRef(v) + if err != nil { + return projectRef{}, fmt.Errorf("annotation %s: %w", key, err) + } + return r, nil + } + name, err := render(defs[0], data) + if err != nil { + return projectRef{}, err + } + version, err := render(defs[1], data) + if err != nil { + return projectRef{}, err + } + return projectRef{name: name, version: version}, nil +} diff --git a/sbomexport/layout_test.go b/sbomexport/layout_test.go new file mode 100644 index 00000000..c85eb1a9 --- /dev/null +++ b/sbomexport/layout_test.go @@ -0,0 +1,160 @@ +package sbomexport + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestParseRef checks the project references namespace annotations accept. +func TestParseRef(t *testing.T) { + tt := []struct { + in string + want projectRef + wantErr bool + }{ + {in: "Payments Team:current", want: projectRef{name: "Payments Team", version: "current"}}, + {in: "a:b:c", want: projectRef{name: "a:b", version: "c"}}, + {in: "0A1B2C3D-0000-4000-8000-000000000001", want: projectRef{uuid: "0a1b2c3d-0000-4000-8000-000000000001"}}, + {in: "Payments Team", wantErr: true}, + {in: ":current", wantErr: true}, + {in: "Payments Team:", wantErr: true}, + } + for _, tc := range tt { + t.Run(tc.in, func(t *testing.T) { + got, err := parseRef(tc.in) + if tc.wantErr { + assert.Error(t, err) + return + } + require.NoError(t, err) + assert.Equal(t, tc.want, got) + }) + } +} + +func TestLayoutDefaultsBuildTheClusterNamespaceTree(t *testing.T) { + l, err := newLayout("prod", defaultLayout) + require.NoError(t, err) + + p, err := l.resolve(namespaceInfo{name: "apps"}, "docker.io/library/redis", "8") + + require.NoError(t, err) + assert.Equal(t, placement{ + root: projectRef{name: "prod", version: "current"}, + rootCreate: true, + parent: projectRef{name: "prod/apps", version: "current"}, + parentCreate: true, + name: "prod/apps/docker.io/library/redis", + version: "8", + }, p) +} + +// A template that needs a label is valid; a namespace without the label cannot +// be placed, and only that namespace is skipped. +func TestLayoutAcceptsATemplateThatNeedsANamespaceLabel(t *testing.T) { + for _, text := range []string{`{{ index .Labels "team" }}`, `{{ .Labels.team }}`} { + t.Run(text, func(t *testing.T) { + byTeam := defaultLayout + byTeam.Parent.Name = text + l, err := newLayout("prod", byTeam) + require.NoError(t, err) + + _, err = l.resolve(namespaceInfo{name: "apps"}, "docker.io/library/redis", "8") + assert.Error(t, err, "no team label") + p, err := l.resolve(namespaceInfo{name: "apps", labels: map[string]string{"team": "payments"}}, "docker.io/library/redis", "8") + require.NoError(t, err) + assert.Equal(t, projectRef{name: "payments", version: "current"}, p.parent) + }) + } +} + +func TestNewLayoutRejectsATemplateWithAnUnknownField(t *testing.T) { + bad := defaultLayout + bad.Parent.Name = "{{ .Cluster }}/{{ .Team }}" + + _, err := newLayout("prod", bad) + + assert.ErrorContains(t, err, "project.parent.name") +} + +func TestLayoutTemplatesSeeTheNamespaceLabels(t *testing.T) { + byTeam := defaultLayout + byTeam.Parent.Name = `{{ .Cluster }}/{{ index .Labels "team" }}` + l, err := newLayout("prod", byTeam) + require.NoError(t, err) + + p, err := l.resolve(namespaceInfo{name: "apps", labels: map[string]string{"team": "payments"}}, "docker.io/library/redis", "8") + + require.NoError(t, err) + assert.Equal(t, projectRef{name: "prod/payments", version: "current"}, p.parent) +} + +// Each layout annotation replaces only the setting it names. +func TestLayoutAnnotationsOverrideOneSettingEach(t *testing.T) { + l, err := newLayout("prod", defaultLayout) + require.NoError(t, err) + tt := []struct { + name string + annotations map[string]string + check func(t *testing.T, p placement) + }{ + { + name: "root project", + annotations: map[string]string{AnnotationRootProject: "Finance:current", AnnotationRootProjectCreate: "false"}, + check: func(t *testing.T, p placement) { + assert.Equal(t, projectRef{name: "Finance", version: "current"}, p.root) + assert.False(t, p.rootCreate) + assert.Equal(t, projectRef{name: "prod/apps", version: "current"}, p.parent, "the parent keeps its default") + assert.True(t, p.parentCreate) + }, + }, + { + name: "parent by UUID", + annotations: map[string]string{AnnotationParent: "0a1b2c3d-0000-4000-8000-000000000001", AnnotationParentCreate: "false"}, + check: func(t *testing.T, p placement) { + assert.Equal(t, projectRef{uuid: "0a1b2c3d-0000-4000-8000-000000000001"}, p.parent) + assert.False(t, p.parentCreate) + assert.Equal(t, projectRef{name: "prod", version: "current"}, p.root, "the root keeps its default") + }, + }, + { + name: "project name", + annotations: map[string]string{AnnotationProjectName: "{{ .Namespace }}-{{ .Repository }}"}, + check: func(t *testing.T, p placement) { + assert.Equal(t, "apps-docker.io/library/redis", p.name) + assert.Equal(t, "8", p.version, "the version keeps its default") + }, + }, + } + for _, tc := range tt { + t.Run(tc.name, func(t *testing.T) { + p, err := l.resolve(namespaceInfo{name: "apps", annotations: tc.annotations}, "docker.io/library/redis", "8") + require.NoError(t, err) + tc.check(t, p) + }) + } +} + +// An annotation that cannot be used fails resolve, so the namespace is skipped. +func TestLayoutRejectsAnnotationsItCannotUse(t *testing.T) { + l, err := newLayout("prod", defaultLayout) + require.NoError(t, err) + tt := []struct { + name string + annotations map[string]string + }{ + {"a reference without a version", map[string]string{AnnotationParent: "Payments Team"}}, + {"a create flag that is not a boolean", map[string]string{AnnotationParentCreate: "maybe"}}, + {"a template that does not parse", map[string]string{AnnotationProjectName: "{{ .Repository"}}, + {"a template with an unknown field", map[string]string{AnnotationProjectName: "{{ .Digest }}"}}, + {"a template that renders nothing", map[string]string{AnnotationProjectName: `{{ index .Labels "team" }}`}}, + } + for _, tc := range tt { + t.Run(tc.name, func(t *testing.T) { + _, err := l.resolve(namespaceInfo{name: "apps", annotations: tc.annotations}, "docker.io/library/redis", "8") + assert.Error(t, err) + }) + } +} diff --git a/sbomexport/store.go b/sbomexport/store.go new file mode 100644 index 00000000..df1eeeb0 --- /dev/null +++ b/sbomexport/store.go @@ -0,0 +1,137 @@ +package sbomexport + +import ( + "context" + "encoding/json" + "fmt" + "io" + "strconv" + "time" + + helpersv1 "github.com/kubescape/k8s-interface/instanceidhandler/v1/helpers" + spdxv1beta1 "github.com/kubescape/storage/pkg/apis/softwarecomposition/v1beta1" + kssc "github.com/kubescape/storage/pkg/generated/clientset/versioned" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/tools/pager" +) + +// sbomMeta is what the exporter needs to know about an SBOMSyft without its +// document. +type sbomMeta struct { + namespace, name string + digest string + imageTag string + status string + created time.Time + size int64 // bytes, from the kubescape.io/resource-size annotation; 0 when unknown + // toolVersion is the node-agent version that built the SBOM. node-agent + // rebuilds the SBOM of a running image when its version changes and + // replaces the document in place, so the creation time stays the same. + toolVersion string +} + +// ready reports whether the SBOM is complete: the statuses the operator's +// image scans accept too (empty for SBOMs written before the annotation). +func (m sbomMeta) ready() bool { + switch m.status { + case "", helpersv1.Learning, helpersv1.Completed: + return true + } + return false +} + +// metaOf reads sbomMeta from an SBOMSyft's metadata. It reports false when +// the object names no image. +func metaOf(obj *spdxv1beta1.SBOMSyft) (sbomMeta, bool) { + ann := obj.GetAnnotations() + imageID, imageTag := ann[helpersv1.ImageIDMetadataKey], ann[helpersv1.ImageTagMetadataKey] + if imageID == "" || imageTag == "" { + return sbomMeta{}, false + } + size, _ := strconv.ParseInt(ann[helpersv1.ResourceSizeMetadataKey], 10, 64) + return sbomMeta{ + size: size, + namespace: obj.GetNamespace(), + name: obj.GetName(), + digest: digestOf(imageID), + imageTag: imageTag, + status: ann[helpersv1.StatusMetadataKey], + created: obj.GetCreationTimestamp().Time, + toolVersion: ann[helpersv1.ToolVersionMetadataKey], + }, true +} + +// sbomStore reads SBOMSyft objects. +type sbomStore interface { + // listSBOMs returns the metadata of every SBOMSyft. + listSBOMs(ctx context.Context) ([]sbomMeta, error) + // syftDocument returns the `.spec.syft` of one SBOMSyft as JSON. It reads + // at most limit bytes of the object and returns a *tooLargeError past that. + syftDocument(ctx context.Context, namespace, name string, limit int64) ([]byte, error) +} + +// tooLargeError is an SBOMSyft object larger than the limit it was read with. +type tooLargeError struct { + read int64 // bytes read before the limit stopped the read +} + +// Error says how much of the object was read. +func (e *tooLargeError) Error() string { + return fmt.Sprintf("SBOMSyft object is larger than the %d bytes read", e.read) +} + +// storageSBOMs reads SBOMSyft objects from Kubescape's storage. +type storageSBOMs struct { + client kssc.Interface +} + +// listSBOMs pages through every SBOMSyft and keeps the ones metaOf accepts. +func (s storageSBOMs) listSBOMs(ctx context.Context) ([]sbomMeta, error) { + var out []sbomMeta + err := pager.New(func(ctx context.Context, opts metav1.ListOptions) (runtime.Object, error) { + return s.client.SpdxV1beta1().SBOMSyfts("").List(ctx, opts) + }).EachListItem(ctx, metav1.ListOptions{}, func(obj runtime.Object) error { + if m, ok := metaOf(obj.(*spdxv1beta1.SBOMSyft)); ok { + out = append(out, m) + } + return nil + }) + return out, err +} + +// syftDocument GETs the object as raw JSON rather than through the typed +// client: the document goes to syft's own decoder, and the storage types this +// module builds against may lack fields that a newer node-agent writes. The +// operator's storage client asks for protobuf, so this request asks for JSON. +// The response is read up to limit, so an object without a size annotation +// cannot make the operator buffer more than that. +func (s storageSBOMs) syftDocument(ctx context.Context, namespace, name string, limit int64) ([]byte, error) { + body, err := s.client.SpdxV1beta1().RESTClient().Get(). + Namespace(namespace).Resource("sbomsyfts").Name(name). + SetHeader("Accept", "application/json"). + Stream(ctx) + if err != nil { + return nil, fmt.Errorf("get SBOMSyft %s/%s: %w", namespace, name, err) + } + defer func() { _ = body.Close() }() + raw, err := io.ReadAll(io.LimitReader(body, limit+1)) + if err != nil { + return nil, fmt.Errorf("read SBOMSyft %s/%s: %w", namespace, name, err) + } + if int64(len(raw)) > limit { + return nil, &tooLargeError{read: int64(len(raw))} + } + var obj struct { + Spec struct { + Syft json.RawMessage `json:"syft"` + } `json:"spec"` + } + if err := json.Unmarshal(raw, &obj); err != nil { + return nil, fmt.Errorf("decode SBOMSyft %s/%s: %w", namespace, name, err) + } + if len(obj.Spec.Syft) == 0 { + return nil, fmt.Errorf("SBOMSyft %s/%s has no syft document", namespace, name) + } + return obj.Spec.Syft, nil +} diff --git a/sbomexport/store_test.go b/sbomexport/store_test.go new file mode 100644 index 00000000..86602125 --- /dev/null +++ b/sbomexport/store_test.go @@ -0,0 +1,67 @@ +package sbomexport + +import ( + "context" + "net/http" + "net/http/httptest" + "strings" + "testing" + + kssc "github.com/kubescape/storage/pkg/generated/clientset/versioned" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "k8s.io/client-go/rest" +) + +// The operator's storage client asks for protobuf (main.go). The SBOM document +// is read as JSON for syft's decoder, so that one request must ask for JSON. +func TestStorageSBOMsReadsTheSyftDocumentAsJSONFromAProtobufClient(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path != "/apis/spdx.softwarecomposition.kubescape.io/v1beta1/namespaces/kubescape/sbomsyfts/nginx-sbom" { + http.NotFound(w, r) + return + } + if !strings.Contains(r.Header.Get("Accept"), "application/json") { + w.Header().Set("Content-Type", "application/vnd.kubernetes.protobuf") + _, _ = w.Write([]byte("k8s\x00\x0a\x0f")) + return + } + w.Header().Set("Content-Type", "application/json") + _, _ = w.Write([]byte(`{"apiVersion":"spdx.softwarecomposition.kubescape.io/v1beta1","kind":"SBOMSyft","spec":{"syft":` + string(curlSyftJSON) + `}}`)) + })) + defer srv.Close() + client, err := kssc.NewForConfig(&rest.Config{ + Host: srv.URL, + ContentConfig: rest.ContentConfig{ + AcceptContentTypes: "application/vnd.kubernetes.protobuf", + ContentType: "application/vnd.kubernetes.protobuf", + }, + }) + require.NoError(t, err) + + doc, err := storageSBOMs{client: client}.syftDocument(context.Background(), "kubescape", "nginx-sbom", 1<<20) + + require.NoError(t, err) + assert.JSONEq(t, string(curlSyftJSON), string(doc)) + _, err = toCycloneDX(doc) + assert.NoError(t, err) +} + +// An object without a size annotation is read only up to the limit. +func TestStorageSBOMsStopsReadingAnObjectLargerThanTheLimit(t *testing.T) { + big := `{"spec":{"syft":{"artifacts":[` + strings.Repeat(`{"name":"pkg"},`, 200_000) + `{}]}}}` + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.Header().Set("Content-Type", "application/json") + _, _ = w.Write([]byte(big)) + })) + defer srv.Close() + client, err := kssc.NewForConfig(&rest.Config{Host: srv.URL}) + require.NoError(t, err) + + _, err = storageSBOMs{client: client}.syftDocument(context.Background(), "kubescape", "big-sbom", 1<<20) + + var tooLarge *tooLargeError + require.ErrorAs(t, err, &tooLarge) + assert.Equal(t, int64(1<<20+1), tooLarge.read) + assert.Greater(t, len(big), 1<<20) +} diff --git a/sbomexport/testdata/curl-8.11.1.cdx.json b/sbomexport/testdata/curl-8.11.1.cdx.json new file mode 100644 index 00000000..e36e27e5 --- /dev/null +++ b/sbomexport/testdata/curl-8.11.1.cdx.json @@ -0,0 +1 @@ +{"$schema":"http://cyclonedx.org/schema/bom-1.6.schema.json","bomFormat":"CycloneDX","specVersion":"1.6","serialNumber":"urn:uuid:9c3a8bc3-5e1e-4073-8a43-9520535efe88","version":1,"metadata":{"timestamp":"2026-11-10T17:36:49Z","tools":{"components":[{"type":"application","author":"anchore","name":"syft","version":"v1.55.0"}]},"component":{"bom-ref":"d450b75eaf61d760","type":"container","name":"docker.io/curlimages/curl:8.11.1","version":"docker.io/curlimages/curl@sha256:c1fe1679c34d9784c1b0d1e5f62ac0a79fca01fb6377cdd33e90473c6f9f9a69"},"properties":[{"name":"syft:image:labels:docker.cmd","value":"podman run -it quay.io/curl/:8.11.1"},{"name":"syft:image:labels:io.buildah.version","value":"1.23.1"},{"name":"syft:image:labels:maintainer","value":"James Fuller "},{"name":"syft:image:labels:name","value":"curl-linux-amd64:8.11.1"},{"name":"syft:image:labels:org.opencontainers.image.description","value":"minimal image for curl"},{"name":"syft:image:labels:org.opencontainers.image.licenses","value":"MIT"},{"name":"syft:image:labels:org.opencontainers.image.source","value":"https://github.com/curl/curl-container"},{"name":"syft:image:labels:version","value":"8.11.1"}]},"components":[{"bom-ref":"pkg:apk/alpine/alpine-baselayout@3.6.8-r0?arch=x86_64&distro=alpine-3.21.0&package-id=ceeb874c4a135dac","type":"library","name":"alpine-baselayout","version":"3.6.8-r0","licenses":[{"license":{"id":"GPL-2.0-only"}}],"cpe":"cpe:2.3:a:alpine-baselayout:alpine-baselayout:3.6.8-r0:*:*:*:*:*:*:*","purl":"pkg:apk/alpine/alpine-baselayout@3.6.8-r0?arch=x86_64&distro=alpine-3.21.0","properties":[{"name":"syft:package:type","value":"apk"},{"name":"syft:package:metadataType","value":"apk-db-entry"},{"name":"syft:cpe23","value":"cpe:2.3:a:alpine-baselayout:alpine_baselayout:3.6.8-r0:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:alpine_baselayout:alpine-baselayout:3.6.8-r0:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:alpine_baselayout:alpine_baselayout:3.6.8-r0:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:alpine:alpine-baselayout:3.6.8-r0:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:alpine:alpine_baselayout:3.6.8-r0:*:*:*:*:*:*:*"},{"name":"syft:location:0:layerID","value":"sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d"},{"name":"syft:location:0:path","value":"/lib/apk/db/installed"},{"name":"syft:metadata:installedSize","value":"0"},{"name":"syft:metadata:originPackage","value":"alpine-baselayout"},{"name":"syft:metadata:size","value":"0"}]},{"bom-ref":"pkg:apk/alpine/alpine-baselayout-data@3.6.8-r0?arch=x86_64&distro=alpine-3.21.0&package-id=1a41f8b73cec7362&upstream=alpine-baselayout","type":"library","name":"alpine-baselayout-data","version":"3.6.8-r0","licenses":[{"license":{"id":"GPL-2.0-only"}}],"cpe":"cpe:2.3:a:alpine-baselayout-data:alpine-baselayout-data:3.6.8-r0:*:*:*:*:*:*:*","purl":"pkg:apk/alpine/alpine-baselayout-data@3.6.8-r0?arch=x86_64&distro=alpine-3.21.0&upstream=alpine-baselayout","properties":[{"name":"syft:package:type","value":"apk"},{"name":"syft:package:metadataType","value":"apk-db-entry"},{"name":"syft:cpe23","value":"cpe:2.3:a:alpine-baselayout-data:alpine_baselayout_data:3.6.8-r0:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:alpine_baselayout_data:alpine-baselayout-data:3.6.8-r0:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:alpine_baselayout_data:alpine_baselayout_data:3.6.8-r0:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:alpine-baselayout:alpine-baselayout-data:3.6.8-r0:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:alpine-baselayout:alpine_baselayout_data:3.6.8-r0:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:alpine_baselayout:alpine-baselayout-data:3.6.8-r0:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:alpine_baselayout:alpine_baselayout_data:3.6.8-r0:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:alpine:alpine-baselayout-data:3.6.8-r0:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:alpine:alpine_baselayout_data:3.6.8-r0:*:*:*:*:*:*:*"},{"name":"syft:location:0:layerID","value":"sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d"},{"name":"syft:location:0:path","value":"/lib/apk/db/installed"},{"name":"syft:metadata:installedSize","value":"0"},{"name":"syft:metadata:originPackage","value":"alpine-baselayout"},{"name":"syft:metadata:size","value":"0"}]},{"bom-ref":"pkg:apk/alpine/alpine-keys@2.5-r0?arch=x86_64&distro=alpine-3.21.0&package-id=f3faf4bc1f174ce9","type":"library","name":"alpine-keys","version":"2.5-r0","licenses":[{"license":{"id":"MIT"}}],"cpe":"cpe:2.3:a:alpine-keys:alpine-keys:2.5-r0:*:*:*:*:*:*:*","purl":"pkg:apk/alpine/alpine-keys@2.5-r0?arch=x86_64&distro=alpine-3.21.0","properties":[{"name":"syft:package:type","value":"apk"},{"name":"syft:package:metadataType","value":"apk-db-entry"},{"name":"syft:cpe23","value":"cpe:2.3:a:alpine-keys:alpine_keys:2.5-r0:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:alpine_keys:alpine-keys:2.5-r0:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:alpine_keys:alpine_keys:2.5-r0:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:alpine:alpine-keys:2.5-r0:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:alpine:alpine_keys:2.5-r0:*:*:*:*:*:*:*"},{"name":"syft:location:0:layerID","value":"sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d"},{"name":"syft:location:0:path","value":"/lib/apk/db/installed"},{"name":"syft:metadata:installedSize","value":"0"},{"name":"syft:metadata:originPackage","value":"alpine-keys"},{"name":"syft:metadata:size","value":"0"}]},{"bom-ref":"pkg:apk/alpine/alpine-release@3.21.0-r0?arch=x86_64&distro=alpine-3.21.0&package-id=456e920c4331bde6&upstream=alpine-base","type":"library","name":"alpine-release","version":"3.21.0-r0","licenses":[{"license":{"id":"MIT"}}],"cpe":"cpe:2.3:a:alpine-release:alpine-release:3.21.0-r0:*:*:*:*:*:*:*","purl":"pkg:apk/alpine/alpine-release@3.21.0-r0?arch=x86_64&distro=alpine-3.21.0&upstream=alpine-base","properties":[{"name":"syft:package:type","value":"apk"},{"name":"syft:package:metadataType","value":"apk-db-entry"},{"name":"syft:cpe23","value":"cpe:2.3:a:alpine-release:alpine_release:3.21.0-r0:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:alpine_release:alpine-release:3.21.0-r0:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:alpine_release:alpine_release:3.21.0-r0:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:alpine:alpine-release:3.21.0-r0:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:alpine:alpine_release:3.21.0-r0:*:*:*:*:*:*:*"},{"name":"syft:location:0:layerID","value":"sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d"},{"name":"syft:location:0:path","value":"/lib/apk/db/installed"},{"name":"syft:metadata:installedSize","value":"0"},{"name":"syft:metadata:originPackage","value":"alpine-base"},{"name":"syft:metadata:size","value":"0"}]},{"bom-ref":"pkg:apk/alpine/apk-tools@2.14.6-r2?arch=x86_64&distro=alpine-3.21.0&package-id=1095a39f80569c66","type":"library","name":"apk-tools","version":"2.14.6-r2","licenses":[{"license":{"id":"GPL-2.0-only"}}],"cpe":"cpe:2.3:a:apk-tools:apk-tools:2.14.6-r2:*:*:*:*:*:*:*","purl":"pkg:apk/alpine/apk-tools@2.14.6-r2?arch=x86_64&distro=alpine-3.21.0","properties":[{"name":"syft:package:type","value":"apk"},{"name":"syft:package:metadataType","value":"apk-db-entry"},{"name":"syft:cpe23","value":"cpe:2.3:a:apk-tools:apk_tools:2.14.6-r2:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:apk_tools:apk-tools:2.14.6-r2:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:apk_tools:apk_tools:2.14.6-r2:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:apk:apk-tools:2.14.6-r2:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:apk:apk_tools:2.14.6-r2:*:*:*:*:*:*:*"},{"name":"syft:location:0:layerID","value":"sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d"},{"name":"syft:location:0:path","value":"/lib/apk/db/installed"},{"name":"syft:metadata:installedSize","value":"0"},{"name":"syft:metadata:originPackage","value":"apk-tools"},{"name":"syft:metadata:size","value":"0"}]},{"bom-ref":"pkg:apk/alpine/brotli@1.1.0-r2?arch=x86_64&distro=alpine-3.21.0&package-id=e3ed65b6dc9144b9","type":"library","name":"brotli","version":"1.1.0-r2","licenses":[{"license":{"id":"MIT"}}],"cpe":"cpe:2.3:a:brotli:brotli:1.1.0-r2:*:*:*:*:*:*:*","purl":"pkg:apk/alpine/brotli@1.1.0-r2?arch=x86_64&distro=alpine-3.21.0","properties":[{"name":"syft:package:type","value":"apk"},{"name":"syft:package:metadataType","value":"apk-db-entry"},{"name":"syft:cpe23","value":"cpe:2.3:a:google:brotli:1.1.0-r2:*:*:*:*:*:*:*"},{"name":"syft:location:0:layerID","value":"sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d"},{"name":"syft:location:0:path","value":"/lib/apk/db/installed"},{"name":"syft:metadata:installedSize","value":"0"},{"name":"syft:metadata:originPackage","value":"brotli"},{"name":"syft:metadata:size","value":"0"}]},{"bom-ref":"pkg:apk/alpine/brotli-dev@1.1.0-r2?arch=x86_64&distro=alpine-3.21.0&package-id=037ba9595fe77699&upstream=brotli","type":"library","name":"brotli-dev","version":"1.1.0-r2","licenses":[{"license":{"id":"MIT"}}],"cpe":"cpe:2.3:a:brotli-dev:brotli-dev:1.1.0-r2:*:*:*:*:*:*:*","purl":"pkg:apk/alpine/brotli-dev@1.1.0-r2?arch=x86_64&distro=alpine-3.21.0&upstream=brotli","properties":[{"name":"syft:package:type","value":"apk"},{"name":"syft:package:metadataType","value":"apk-db-entry"},{"name":"syft:cpe23","value":"cpe:2.3:a:brotli-dev:brotli_dev:1.1.0-r2:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:brotli_dev:brotli-dev:1.1.0-r2:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:brotli_dev:brotli_dev:1.1.0-r2:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:brotli:brotli-dev:1.1.0-r2:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:brotli:brotli_dev:1.1.0-r2:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:google:brotli-dev:1.1.0-r2:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:google:brotli_dev:1.1.0-r2:*:*:*:*:*:*:*"},{"name":"syft:location:0:layerID","value":"sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d"},{"name":"syft:location:0:path","value":"/lib/apk/db/installed"},{"name":"syft:metadata:installedSize","value":"0"},{"name":"syft:metadata:originPackage","value":"brotli"},{"name":"syft:metadata:size","value":"0"}]},{"bom-ref":"pkg:apk/alpine/brotli-libs@1.1.0-r2?arch=x86_64&distro=alpine-3.21.0&package-id=8f69c879d6b38e38&upstream=brotli","type":"library","name":"brotli-libs","version":"1.1.0-r2","licenses":[{"license":{"id":"MIT"}}],"cpe":"cpe:2.3:a:brotli-libs:brotli-libs:1.1.0-r2:*:*:*:*:*:*:*","purl":"pkg:apk/alpine/brotli-libs@1.1.0-r2?arch=x86_64&distro=alpine-3.21.0&upstream=brotli","properties":[{"name":"syft:package:type","value":"apk"},{"name":"syft:package:metadataType","value":"apk-db-entry"},{"name":"syft:cpe23","value":"cpe:2.3:a:brotli-libs:brotli_libs:1.1.0-r2:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:brotli_libs:brotli-libs:1.1.0-r2:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:brotli_libs:brotli_libs:1.1.0-r2:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:brotli:brotli-libs:1.1.0-r2:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:brotli:brotli_libs:1.1.0-r2:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:google:brotli-libs:1.1.0-r2:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:google:brotli_libs:1.1.0-r2:*:*:*:*:*:*:*"},{"name":"syft:location:0:layerID","value":"sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d"},{"name":"syft:location:0:path","value":"/lib/apk/db/installed"},{"name":"syft:metadata:installedSize","value":"0"},{"name":"syft:metadata:originPackage","value":"brotli"},{"name":"syft:metadata:size","value":"0"}]},{"bom-ref":"pkg:apk/alpine/busybox@1.37.0-r8?arch=x86_64&distro=alpine-3.21.0&package-id=7cbfd25df10d2724","type":"library","name":"busybox","version":"1.37.0-r8","licenses":[{"license":{"id":"GPL-2.0-only"}}],"cpe":"cpe:2.3:a:busybox:busybox:1.37.0-r8:*:*:*:*:*:*:*","purl":"pkg:apk/alpine/busybox@1.37.0-r8?arch=x86_64&distro=alpine-3.21.0","properties":[{"name":"syft:package:type","value":"apk"},{"name":"syft:package:metadataType","value":"apk-db-entry"},{"name":"syft:location:0:layerID","value":"sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d"},{"name":"syft:location:0:path","value":"/lib/apk/db/installed"},{"name":"syft:metadata:installedSize","value":"0"},{"name":"syft:metadata:originPackage","value":"busybox"},{"name":"syft:metadata:size","value":"0"}]},{"bom-ref":"pkg:apk/alpine/busybox-binsh@1.37.0-r8?arch=x86_64&distro=alpine-3.21.0&package-id=3a2e3359d51d0045&upstream=busybox","type":"library","name":"busybox-binsh","version":"1.37.0-r8","licenses":[{"license":{"id":"GPL-2.0-only"}}],"cpe":"cpe:2.3:a:busybox-binsh:busybox-binsh:1.37.0-r8:*:*:*:*:*:*:*","purl":"pkg:apk/alpine/busybox-binsh@1.37.0-r8?arch=x86_64&distro=alpine-3.21.0&upstream=busybox","properties":[{"name":"syft:package:type","value":"apk"},{"name":"syft:package:metadataType","value":"apk-db-entry"},{"name":"syft:cpe23","value":"cpe:2.3:a:busybox-binsh:busybox_binsh:1.37.0-r8:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:busybox_binsh:busybox-binsh:1.37.0-r8:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:busybox_binsh:busybox_binsh:1.37.0-r8:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:busybox:busybox-binsh:1.37.0-r8:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:busybox:busybox_binsh:1.37.0-r8:*:*:*:*:*:*:*"},{"name":"syft:location:0:layerID","value":"sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d"},{"name":"syft:location:0:path","value":"/lib/apk/db/installed"},{"name":"syft:metadata:installedSize","value":"0"},{"name":"syft:metadata:originPackage","value":"busybox"},{"name":"syft:metadata:size","value":"0"}]},{"bom-ref":"pkg:apk/alpine/ca-certificates-bundle@20241010-r0?arch=x86_64&distro=alpine-3.21.0&package-id=a906f1b13dd9bf14&upstream=ca-certificates","type":"library","name":"ca-certificates-bundle","version":"20241010-r0","licenses":[{"expression":"MPL-2.0 AND MIT"}],"cpe":"cpe:2.3:a:ca-certificates-bundle:ca-certificates-bundle:20241010-r0:*:*:*:*:*:*:*","purl":"pkg:apk/alpine/ca-certificates-bundle@20241010-r0?arch=x86_64&distro=alpine-3.21.0&upstream=ca-certificates","properties":[{"name":"syft:package:type","value":"apk"},{"name":"syft:package:metadataType","value":"apk-db-entry"},{"name":"syft:cpe23","value":"cpe:2.3:a:ca-certificates-bundle:ca_certificates_bundle:20241010-r0:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:ca_certificates_bundle:ca-certificates-bundle:20241010-r0:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:ca_certificates_bundle:ca_certificates_bundle:20241010-r0:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:ca-certificates:ca-certificates-bundle:20241010-r0:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:ca-certificates:ca_certificates_bundle:20241010-r0:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:ca_certificates:ca-certificates-bundle:20241010-r0:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:ca_certificates:ca_certificates_bundle:20241010-r0:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:mozilla:ca-certificates-bundle:20241010-r0:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:mozilla:ca_certificates_bundle:20241010-r0:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:ca:ca-certificates-bundle:20241010-r0:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:ca:ca_certificates_bundle:20241010-r0:*:*:*:*:*:*:*"},{"name":"syft:location:0:layerID","value":"sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d"},{"name":"syft:location:0:path","value":"/lib/apk/db/installed"},{"name":"syft:metadata:installedSize","value":"0"},{"name":"syft:metadata:originPackage","value":"ca-certificates"},{"name":"syft:metadata:size","value":"0"}]},{"bom-ref":"pkg:generic/curl@8.11.1?package-id=3e249a6f2317504e","type":"application","name":"curl","version":"8.11.1","cpe":"cpe:2.3:a:haxx:curl:8.11.1:*:*:*:*:*:*:*","purl":"pkg:generic/curl@8.11.1","properties":[{"name":"syft:package:type","value":"binary"},{"name":"syft:location:0:layerID","value":"sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d"},{"name":"syft:location:0:path","value":"/usr/bin/curl"}]},{"bom-ref":"pkg:apk/alpine/e2fsprogs-libs@1.47.1-r1?arch=x86_64&distro=alpine-3.21.0&package-id=412b42ad943bf8cb&upstream=e2fsprogs","type":"library","name":"e2fsprogs-libs","version":"1.47.1-r1","licenses":[{"expression":"GPL-2.0-or-later AND LGPL-2.0-or-later AND BSD-3-Clause AND MIT"}],"cpe":"cpe:2.3:a:e2fsprogs-libs:e2fsprogs-libs:1.47.1-r1:*:*:*:*:*:*:*","purl":"pkg:apk/alpine/e2fsprogs-libs@1.47.1-r1?arch=x86_64&distro=alpine-3.21.0&upstream=e2fsprogs","properties":[{"name":"syft:package:type","value":"apk"},{"name":"syft:package:metadataType","value":"apk-db-entry"},{"name":"syft:cpe23","value":"cpe:2.3:a:e2fsprogs-libs:e2fsprogs_libs:1.47.1-r1:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:e2fsprogs_libs:e2fsprogs-libs:1.47.1-r1:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:e2fsprogs_libs:e2fsprogs_libs:1.47.1-r1:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:e2fsprogs:e2fsprogs-libs:1.47.1-r1:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:e2fsprogs:e2fsprogs_libs:1.47.1-r1:*:*:*:*:*:*:*"},{"name":"syft:location:0:layerID","value":"sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d"},{"name":"syft:location:0:path","value":"/lib/apk/db/installed"},{"name":"syft:metadata:installedSize","value":"0"},{"name":"syft:metadata:originPackage","value":"e2fsprogs"},{"name":"syft:metadata:size","value":"0"}]},{"bom-ref":"pkg:apk/alpine/keyutils-libs@1.6.3-r4?arch=x86_64&distro=alpine-3.21.0&package-id=5c84d53d49f221e1&upstream=keyutils","type":"library","name":"keyutils-libs","version":"1.6.3-r4","licenses":[{"expression":"GPL-2.0-or-later AND LGPL-2.0-or-later"}],"cpe":"cpe:2.3:a:keyutils-libs:keyutils-libs:1.6.3-r4:*:*:*:*:*:*:*","purl":"pkg:apk/alpine/keyutils-libs@1.6.3-r4?arch=x86_64&distro=alpine-3.21.0&upstream=keyutils","properties":[{"name":"syft:package:type","value":"apk"},{"name":"syft:package:metadataType","value":"apk-db-entry"},{"name":"syft:cpe23","value":"cpe:2.3:a:keyutils-libs:keyutils_libs:1.6.3-r4:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:keyutils_libs:keyutils-libs:1.6.3-r4:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:keyutils_libs:keyutils_libs:1.6.3-r4:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:keyutils:keyutils-libs:1.6.3-r4:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:keyutils:keyutils_libs:1.6.3-r4:*:*:*:*:*:*:*"},{"name":"syft:location:0:layerID","value":"sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d"},{"name":"syft:location:0:path","value":"/lib/apk/db/installed"},{"name":"syft:metadata:installedSize","value":"0"},{"name":"syft:metadata:originPackage","value":"keyutils"},{"name":"syft:metadata:size","value":"0"}]},{"bom-ref":"pkg:apk/alpine/krb5@1.21.3-r0?arch=x86_64&distro=alpine-3.21.0&package-id=e13769c76f74134e","type":"library","name":"krb5","version":"1.21.3-r0","licenses":[{"license":{"id":"MIT"}}],"cpe":"cpe:2.3:a:krb5:krb5:1.21.3-r0:*:*:*:*:*:*:*","purl":"pkg:apk/alpine/krb5@1.21.3-r0?arch=x86_64&distro=alpine-3.21.0","properties":[{"name":"syft:package:type","value":"apk"},{"name":"syft:package:metadataType","value":"apk-db-entry"},{"name":"syft:cpe23","value":"cpe:2.3:a:krb5:krb:1.21.3-r0:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:krb:krb5:1.21.3-r0:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:krb:krb:1.21.3-r0:*:*:*:*:*:*:*"},{"name":"syft:location:0:layerID","value":"sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d"},{"name":"syft:location:0:path","value":"/lib/apk/db/installed"},{"name":"syft:metadata:installedSize","value":"0"},{"name":"syft:metadata:originPackage","value":"krb5"},{"name":"syft:metadata:size","value":"0"}]},{"bom-ref":"pkg:apk/alpine/krb5-conf@1.0-r2?arch=x86_64&distro=alpine-3.21.0&package-id=5449192ad1853f9c","type":"library","name":"krb5-conf","version":"1.0-r2","licenses":[{"license":{"id":"MIT"}}],"cpe":"cpe:2.3:a:krb5-conf:krb5-conf:1.0-r2:*:*:*:*:*:*:*","purl":"pkg:apk/alpine/krb5-conf@1.0-r2?arch=x86_64&distro=alpine-3.21.0","properties":[{"name":"syft:package:type","value":"apk"},{"name":"syft:package:metadataType","value":"apk-db-entry"},{"name":"syft:cpe23","value":"cpe:2.3:a:krb5-conf:krb5_conf:1.0-r2:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:krb5_conf:krb5-conf:1.0-r2:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:krb5_conf:krb5_conf:1.0-r2:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:krb-conf:krb5-conf:1.0-r2:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:krb-conf:krb5_conf:1.0-r2:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:krb5-conf:krb-conf:1.0-r2:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:krb5-conf:krb_conf:1.0-r2:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:krb5_conf:krb-conf:1.0-r2:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:krb5_conf:krb_conf:1.0-r2:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:krb_conf:krb5-conf:1.0-r2:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:krb_conf:krb5_conf:1.0-r2:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:krb-conf:krb-conf:1.0-r2:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:krb-conf:krb_conf:1.0-r2:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:krb_conf:krb-conf:1.0-r2:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:krb_conf:krb_conf:1.0-r2:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:krb5:krb5-conf:1.0-r2:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:krb5:krb5_conf:1.0-r2:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:krb5:krb-conf:1.0-r2:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:krb5:krb_conf:1.0-r2:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:krb:krb5-conf:1.0-r2:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:krb:krb5_conf:1.0-r2:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:krb:krb-conf:1.0-r2:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:krb:krb_conf:1.0-r2:*:*:*:*:*:*:*"},{"name":"syft:location:0:layerID","value":"sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d"},{"name":"syft:location:0:path","value":"/lib/apk/db/installed"},{"name":"syft:metadata:installedSize","value":"0"},{"name":"syft:metadata:originPackage","value":"krb5-conf"},{"name":"syft:metadata:size","value":"0"}]},{"bom-ref":"pkg:apk/alpine/krb5-libs@1.21.3-r0?arch=x86_64&distro=alpine-3.21.0&package-id=67d8ec97d517d9c2&upstream=krb5","type":"library","name":"krb5-libs","version":"1.21.3-r0","licenses":[{"license":{"id":"MIT"}}],"cpe":"cpe:2.3:a:krb5-libs:krb5-libs:1.21.3-r0:*:*:*:*:*:*:*","purl":"pkg:apk/alpine/krb5-libs@1.21.3-r0?arch=x86_64&distro=alpine-3.21.0&upstream=krb5","properties":[{"name":"syft:package:type","value":"apk"},{"name":"syft:package:metadataType","value":"apk-db-entry"},{"name":"syft:cpe23","value":"cpe:2.3:a:krb5-libs:krb5_libs:1.21.3-r0:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:krb5_libs:krb5-libs:1.21.3-r0:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:krb5_libs:krb5_libs:1.21.3-r0:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:krb-libs:krb5-libs:1.21.3-r0:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:krb-libs:krb5_libs:1.21.3-r0:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:krb5-libs:krb-libs:1.21.3-r0:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:krb5-libs:krb_libs:1.21.3-r0:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:krb5_libs:krb-libs:1.21.3-r0:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:krb5_libs:krb_libs:1.21.3-r0:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:krb_libs:krb5-libs:1.21.3-r0:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:krb_libs:krb5_libs:1.21.3-r0:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:krb-libs:krb-libs:1.21.3-r0:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:krb-libs:krb_libs:1.21.3-r0:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:krb_libs:krb-libs:1.21.3-r0:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:krb_libs:krb_libs:1.21.3-r0:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:krb5:krb5-libs:1.21.3-r0:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:krb5:krb5_libs:1.21.3-r0:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:krb5:krb-libs:1.21.3-r0:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:krb5:krb_libs:1.21.3-r0:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:krb:krb5-libs:1.21.3-r0:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:krb:krb5_libs:1.21.3-r0:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:krb:krb-libs:1.21.3-r0:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:krb:krb_libs:1.21.3-r0:*:*:*:*:*:*:*"},{"name":"syft:location:0:layerID","value":"sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d"},{"name":"syft:location:0:path","value":"/lib/apk/db/installed"},{"name":"syft:metadata:installedSize","value":"0"},{"name":"syft:metadata:originPackage","value":"krb5"},{"name":"syft:metadata:size","value":"0"}]},{"bom-ref":"pkg:apk/alpine/libcom_err@1.47.1-r1?arch=x86_64&distro=alpine-3.21.0&package-id=dbfb4d1dbecb422f&upstream=e2fsprogs","type":"library","name":"libcom_err","version":"1.47.1-r1","licenses":[{"expression":"GPL-2.0-or-later AND LGPL-2.0-or-later AND BSD-3-Clause AND MIT"}],"cpe":"cpe:2.3:a:libcom-err:libcom-err:1.47.1-r1:*:*:*:*:*:*:*","purl":"pkg:apk/alpine/libcom_err@1.47.1-r1?arch=x86_64&distro=alpine-3.21.0&upstream=e2fsprogs","properties":[{"name":"syft:package:type","value":"apk"},{"name":"syft:package:metadataType","value":"apk-db-entry"},{"name":"syft:cpe23","value":"cpe:2.3:a:libcom-err:libcom_err:1.47.1-r1:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:libcom_err:libcom-err:1.47.1-r1:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:libcom_err:libcom_err:1.47.1-r1:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:libcom:libcom-err:1.47.1-r1:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:libcom:libcom_err:1.47.1-r1:*:*:*:*:*:*:*"},{"name":"syft:location:0:layerID","value":"sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d"},{"name":"syft:location:0:path","value":"/lib/apk/db/installed"},{"name":"syft:metadata:installedSize","value":"0"},{"name":"syft:metadata:originPackage","value":"e2fsprogs"},{"name":"syft:metadata:size","value":"0"}]},{"bom-ref":"pkg:apk/alpine/libcrypto3@3.3.2-r4?arch=x86_64&distro=alpine-3.21.0&package-id=88109714e4e7213c&upstream=openssl","type":"library","name":"libcrypto3","version":"3.3.2-r4","licenses":[{"license":{"id":"Apache-2.0"}}],"cpe":"cpe:2.3:a:libcrypto3:libcrypto3:3.3.2-r4:*:*:*:*:*:*:*","purl":"pkg:apk/alpine/libcrypto3@3.3.2-r4?arch=x86_64&distro=alpine-3.21.0&upstream=openssl","properties":[{"name":"syft:package:type","value":"apk"},{"name":"syft:package:metadataType","value":"apk-db-entry"},{"name":"syft:cpe23","value":"cpe:2.3:a:libcrypto3:libcrypto:3.3.2-r4:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:libcrypto:libcrypto3:3.3.2-r4:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:libcrypto:libcrypto:3.3.2-r4:*:*:*:*:*:*:*"},{"name":"syft:location:0:layerID","value":"sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d"},{"name":"syft:location:0:path","value":"/lib/apk/db/installed"},{"name":"syft:metadata:installedSize","value":"0"},{"name":"syft:metadata:originPackage","value":"openssl"},{"name":"syft:metadata:size","value":"0"}]},{"bom-ref":"pkg:apk/alpine/libgcc@14.2.0-r4?arch=x86_64&distro=alpine-3.21.0&package-id=3d4d4f53df422ab4&upstream=gcc","type":"library","name":"libgcc","version":"14.2.0-r4","licenses":[{"expression":"GPL-2.0-or-later AND LGPL-2.1-or-later"}],"cpe":"cpe:2.3:a:libgcc:libgcc:14.2.0-r4:*:*:*:*:*:*:*","purl":"pkg:apk/alpine/libgcc@14.2.0-r4?arch=x86_64&distro=alpine-3.21.0&upstream=gcc","properties":[{"name":"syft:package:type","value":"apk"},{"name":"syft:package:metadataType","value":"apk-db-entry"},{"name":"syft:location:0:layerID","value":"sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d"},{"name":"syft:location:0:path","value":"/lib/apk/db/installed"},{"name":"syft:metadata:installedSize","value":"0"},{"name":"syft:metadata:originPackage","value":"gcc"},{"name":"syft:metadata:size","value":"0"}]},{"bom-ref":"pkg:apk/alpine/libidn2@2.3.7-r0?arch=x86_64&distro=alpine-3.21.0&package-id=97c06e6e1903e776","type":"library","name":"libidn2","version":"2.3.7-r0","licenses":[{"expression":"GPL-2.0-or-later OR LGPL-3.0-or-later"}],"cpe":"cpe:2.3:a:libidn2:libidn2:2.3.7-r0:*:*:*:*:*:*:*","purl":"pkg:apk/alpine/libidn2@2.3.7-r0?arch=x86_64&distro=alpine-3.21.0","properties":[{"name":"syft:package:type","value":"apk"},{"name":"syft:package:metadataType","value":"apk-db-entry"},{"name":"syft:cpe23","value":"cpe:2.3:a:libidn2:libidn:2.3.7-r0:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:libidn:libidn2:2.3.7-r0:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:libidn:libidn:2.3.7-r0:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:gnu:libidn2:2.3.7-r0:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:gnu:libidn:2.3.7-r0:*:*:*:*:*:*:*"},{"name":"syft:location:0:layerID","value":"sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d"},{"name":"syft:location:0:path","value":"/lib/apk/db/installed"},{"name":"syft:metadata:installedSize","value":"0"},{"name":"syft:metadata:originPackage","value":"libidn2"},{"name":"syft:metadata:size","value":"0"}]},{"bom-ref":"pkg:apk/alpine/libpsl@0.21.5-r3?arch=x86_64&distro=alpine-3.21.0&package-id=fc2164ebd15bcb82","type":"library","name":"libpsl","version":"0.21.5-r3","licenses":[{"license":{"id":"MIT"}}],"cpe":"cpe:2.3:a:libpsl:libpsl:0.21.5-r3:*:*:*:*:*:*:*","purl":"pkg:apk/alpine/libpsl@0.21.5-r3?arch=x86_64&distro=alpine-3.21.0","properties":[{"name":"syft:package:type","value":"apk"},{"name":"syft:package:metadataType","value":"apk-db-entry"},{"name":"syft:location:0:layerID","value":"sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d"},{"name":"syft:location:0:path","value":"/lib/apk/db/installed"},{"name":"syft:metadata:installedSize","value":"0"},{"name":"syft:metadata:originPackage","value":"libpsl"},{"name":"syft:metadata:size","value":"0"}]},{"bom-ref":"pkg:apk/alpine/libssh2@1.11.1-r0?arch=x86_64&distro=alpine-3.21.0&package-id=ba1753b9ff7d9303","type":"library","name":"libssh2","version":"1.11.1-r0","licenses":[{"license":{"id":"BSD-3-Clause"}}],"cpe":"cpe:2.3:a:libssh2:libssh2:1.11.1-r0:*:*:*:*:*:*:*","purl":"pkg:apk/alpine/libssh2@1.11.1-r0?arch=x86_64&distro=alpine-3.21.0","properties":[{"name":"syft:package:type","value":"apk"},{"name":"syft:package:metadataType","value":"apk-db-entry"},{"name":"syft:cpe23","value":"cpe:2.3:a:libssh2:libssh:1.11.1-r0:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:libssh:libssh2:1.11.1-r0:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:libssh:libssh:1.11.1-r0:*:*:*:*:*:*:*"},{"name":"syft:location:0:layerID","value":"sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d"},{"name":"syft:location:0:path","value":"/lib/apk/db/installed"},{"name":"syft:metadata:installedSize","value":"0"},{"name":"syft:metadata:originPackage","value":"libssh2"},{"name":"syft:metadata:size","value":"0"}]},{"bom-ref":"pkg:apk/alpine/libssl3@3.3.2-r4?arch=x86_64&distro=alpine-3.21.0&package-id=0f1bae216faefb4b&upstream=openssl","type":"library","name":"libssl3","version":"3.3.2-r4","licenses":[{"license":{"id":"Apache-2.0"}}],"cpe":"cpe:2.3:a:libssl3:libssl3:3.3.2-r4:*:*:*:*:*:*:*","purl":"pkg:apk/alpine/libssl3@3.3.2-r4?arch=x86_64&distro=alpine-3.21.0&upstream=openssl","properties":[{"name":"syft:package:type","value":"apk"},{"name":"syft:package:metadataType","value":"apk-db-entry"},{"name":"syft:cpe23","value":"cpe:2.3:a:libssl3:libssl:3.3.2-r4:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:libssl:libssl3:3.3.2-r4:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:libssl:libssl:3.3.2-r4:*:*:*:*:*:*:*"},{"name":"syft:location:0:layerID","value":"sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d"},{"name":"syft:location:0:path","value":"/lib/apk/db/installed"},{"name":"syft:metadata:installedSize","value":"0"},{"name":"syft:metadata:originPackage","value":"openssl"},{"name":"syft:metadata:size","value":"0"}]},{"bom-ref":"pkg:apk/alpine/libstdc%2B%2B@14.2.0-r4?arch=x86_64&distro=alpine-3.21.0&package-id=f579a97329d24094&upstream=gcc","type":"library","name":"libstdc++","version":"14.2.0-r4","licenses":[{"expression":"GPL-2.0-or-later AND LGPL-2.1-or-later"}],"cpe":"cpe:2.3:a:libstdc\\+\\+:libstdc\\+\\+:14.2.0-r4:*:*:*:*:*:*:*","purl":"pkg:apk/alpine/libstdc%2B%2B@14.2.0-r4?arch=x86_64&distro=alpine-3.21.0&upstream=gcc","properties":[{"name":"syft:package:type","value":"apk"},{"name":"syft:package:metadataType","value":"apk-db-entry"},{"name":"syft:location:0:layerID","value":"sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d"},{"name":"syft:location:0:path","value":"/lib/apk/db/installed"},{"name":"syft:metadata:installedSize","value":"0"},{"name":"syft:metadata:originPackage","value":"gcc"},{"name":"syft:metadata:size","value":"0"}]},{"bom-ref":"pkg:apk/alpine/libunistring@1.2-r0?arch=x86_64&distro=alpine-3.21.0&package-id=07c4841b11e2910b","type":"library","name":"libunistring","version":"1.2-r0","licenses":[{"expression":"GPL-2.0-or-later OR LGPL-3.0-or-later"}],"cpe":"cpe:2.3:a:libunistring:libunistring:1.2-r0:*:*:*:*:*:*:*","purl":"pkg:apk/alpine/libunistring@1.2-r0?arch=x86_64&distro=alpine-3.21.0","properties":[{"name":"syft:package:type","value":"apk"},{"name":"syft:package:metadataType","value":"apk-db-entry"},{"name":"syft:cpe23","value":"cpe:2.3:a:gnu:libunistring:1.2-r0:*:*:*:*:*:*:*"},{"name":"syft:location:0:layerID","value":"sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d"},{"name":"syft:location:0:path","value":"/lib/apk/db/installed"},{"name":"syft:metadata:installedSize","value":"0"},{"name":"syft:metadata:originPackage","value":"libunistring"},{"name":"syft:metadata:size","value":"0"}]},{"bom-ref":"pkg:apk/alpine/libverto@0.3.2-r2?arch=x86_64&distro=alpine-3.21.0&package-id=00fb4f5e90a57319","type":"library","name":"libverto","version":"0.3.2-r2","licenses":[{"license":{"id":"MIT"}}],"cpe":"cpe:2.3:a:latchset:libverto:0.3.2-r2:*:*:*:*:*:*:*","purl":"pkg:apk/alpine/libverto@0.3.2-r2?arch=x86_64&distro=alpine-3.21.0","properties":[{"name":"syft:package:type","value":"apk"},{"name":"syft:package:metadataType","value":"apk-db-entry"},{"name":"syft:cpe23","value":"cpe:2.3:a:libverto:libverto:0.3.2-r2:*:*:*:*:*:*:*"},{"name":"syft:location:0:layerID","value":"sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d"},{"name":"syft:location:0:path","value":"/lib/apk/db/installed"},{"name":"syft:metadata:installedSize","value":"0"},{"name":"syft:metadata:originPackage","value":"libverto"},{"name":"syft:metadata:size","value":"0"}]},{"bom-ref":"pkg:apk/alpine/musl@1.2.5-r8?arch=x86_64&distro=alpine-3.21.0&package-id=25a411dcbf4c73ba","type":"library","name":"musl","version":"1.2.5-r8","licenses":[{"license":{"id":"MIT"}}],"cpe":"cpe:2.3:a:musl-libc:musl:1.2.5-r8:*:*:*:*:*:*:*","purl":"pkg:apk/alpine/musl@1.2.5-r8?arch=x86_64&distro=alpine-3.21.0","properties":[{"name":"syft:package:type","value":"apk"},{"name":"syft:package:metadataType","value":"apk-db-entry"},{"name":"syft:cpe23","value":"cpe:2.3:a:musl_libc:musl:1.2.5-r8:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:musl:musl:1.2.5-r8:*:*:*:*:*:*:*"},{"name":"syft:location:0:layerID","value":"sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d"},{"name":"syft:location:0:path","value":"/lib/apk/db/installed"},{"name":"syft:metadata:installedSize","value":"0"},{"name":"syft:metadata:originPackage","value":"musl"},{"name":"syft:metadata:size","value":"0"}]},{"bom-ref":"pkg:apk/alpine/musl-utils@1.2.5-r8?arch=x86_64&distro=alpine-3.21.0&package-id=9ed24d5185052508&upstream=musl","type":"library","name":"musl-utils","version":"1.2.5-r8","licenses":[{"expression":"MIT AND BSD-2-Clause AND GPL-2.0-or-later"}],"cpe":"cpe:2.3:a:musl-utils:musl-utils:1.2.5-r8:*:*:*:*:*:*:*","purl":"pkg:apk/alpine/musl-utils@1.2.5-r8?arch=x86_64&distro=alpine-3.21.0&upstream=musl","properties":[{"name":"syft:package:type","value":"apk"},{"name":"syft:package:metadataType","value":"apk-db-entry"},{"name":"syft:cpe23","value":"cpe:2.3:a:musl-utils:musl_utils:1.2.5-r8:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:musl_utils:musl-utils:1.2.5-r8:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:musl_utils:musl_utils:1.2.5-r8:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:musl-libc:musl-utils:1.2.5-r8:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:musl-libc:musl_utils:1.2.5-r8:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:musl:musl-utils:1.2.5-r8:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:musl:musl_utils:1.2.5-r8:*:*:*:*:*:*:*"},{"name":"syft:location:0:layerID","value":"sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d"},{"name":"syft:location:0:path","value":"/lib/apk/db/installed"},{"name":"syft:metadata:installedSize","value":"0"},{"name":"syft:metadata:originPackage","value":"musl"},{"name":"syft:metadata:size","value":"0"}]},{"bom-ref":"pkg:apk/alpine/nghttp2-dev@1.64.0-r0?arch=x86_64&distro=alpine-3.21.0&package-id=c9d137d8d21db486&upstream=nghttp2","type":"library","name":"nghttp2-dev","version":"1.64.0-r0","licenses":[{"license":{"id":"MIT"}}],"cpe":"cpe:2.3:a:nghttp2-dev:nghttp2-dev:1.64.0-r0:*:*:*:*:*:*:*","purl":"pkg:apk/alpine/nghttp2-dev@1.64.0-r0?arch=x86_64&distro=alpine-3.21.0&upstream=nghttp2","properties":[{"name":"syft:package:type","value":"apk"},{"name":"syft:package:metadataType","value":"apk-db-entry"},{"name":"syft:cpe23","value":"cpe:2.3:a:nghttp2-dev:nghttp2_dev:1.64.0-r0:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:nghttp2_dev:nghttp2-dev:1.64.0-r0:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:nghttp2_dev:nghttp2_dev:1.64.0-r0:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:nghttp-dev:nghttp2-dev:1.64.0-r0:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:nghttp-dev:nghttp2_dev:1.64.0-r0:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:nghttp2-dev:nghttp-dev:1.64.0-r0:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:nghttp2-dev:nghttp_dev:1.64.0-r0:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:nghttp2_dev:nghttp-dev:1.64.0-r0:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:nghttp2_dev:nghttp_dev:1.64.0-r0:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:nghttp_dev:nghttp2-dev:1.64.0-r0:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:nghttp_dev:nghttp2_dev:1.64.0-r0:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:nghttp-dev:nghttp-dev:1.64.0-r0:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:nghttp-dev:nghttp_dev:1.64.0-r0:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:nghttp_dev:nghttp-dev:1.64.0-r0:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:nghttp_dev:nghttp_dev:1.64.0-r0:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:nghttp2:nghttp2-dev:1.64.0-r0:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:nghttp2:nghttp2_dev:1.64.0-r0:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:nghttp2:nghttp-dev:1.64.0-r0:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:nghttp2:nghttp_dev:1.64.0-r0:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:nghttp:nghttp2-dev:1.64.0-r0:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:nghttp:nghttp2_dev:1.64.0-r0:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:nghttp:nghttp-dev:1.64.0-r0:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:nghttp:nghttp_dev:1.64.0-r0:*:*:*:*:*:*:*"},{"name":"syft:location:0:layerID","value":"sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d"},{"name":"syft:location:0:path","value":"/lib/apk/db/installed"},{"name":"syft:metadata:installedSize","value":"0"},{"name":"syft:metadata:originPackage","value":"nghttp2"},{"name":"syft:metadata:size","value":"0"}]},{"bom-ref":"pkg:apk/alpine/nghttp2-libs@1.64.0-r0?arch=x86_64&distro=alpine-3.21.0&package-id=c16da0382f03c90a&upstream=nghttp2","type":"library","name":"nghttp2-libs","version":"1.64.0-r0","licenses":[{"license":{"id":"MIT"}}],"cpe":"cpe:2.3:a:nghttp2-libs:nghttp2-libs:1.64.0-r0:*:*:*:*:*:*:*","purl":"pkg:apk/alpine/nghttp2-libs@1.64.0-r0?arch=x86_64&distro=alpine-3.21.0&upstream=nghttp2","properties":[{"name":"syft:package:type","value":"apk"},{"name":"syft:package:metadataType","value":"apk-db-entry"},{"name":"syft:cpe23","value":"cpe:2.3:a:nghttp2-libs:nghttp2_libs:1.64.0-r0:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:nghttp2_libs:nghttp2-libs:1.64.0-r0:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:nghttp2_libs:nghttp2_libs:1.64.0-r0:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:nghttp-libs:nghttp2-libs:1.64.0-r0:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:nghttp-libs:nghttp2_libs:1.64.0-r0:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:nghttp2-libs:nghttp-libs:1.64.0-r0:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:nghttp2-libs:nghttp_libs:1.64.0-r0:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:nghttp2_libs:nghttp-libs:1.64.0-r0:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:nghttp2_libs:nghttp_libs:1.64.0-r0:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:nghttp_libs:nghttp2-libs:1.64.0-r0:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:nghttp_libs:nghttp2_libs:1.64.0-r0:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:nghttp-libs:nghttp-libs:1.64.0-r0:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:nghttp-libs:nghttp_libs:1.64.0-r0:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:nghttp_libs:nghttp-libs:1.64.0-r0:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:nghttp_libs:nghttp_libs:1.64.0-r0:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:nghttp2:nghttp2-libs:1.64.0-r0:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:nghttp2:nghttp2_libs:1.64.0-r0:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:nghttp2:nghttp-libs:1.64.0-r0:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:nghttp2:nghttp_libs:1.64.0-r0:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:nghttp:nghttp2-libs:1.64.0-r0:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:nghttp:nghttp2_libs:1.64.0-r0:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:nghttp:nghttp-libs:1.64.0-r0:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:nghttp:nghttp_libs:1.64.0-r0:*:*:*:*:*:*:*"},{"name":"syft:location:0:layerID","value":"sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d"},{"name":"syft:location:0:path","value":"/lib/apk/db/installed"},{"name":"syft:metadata:installedSize","value":"0"},{"name":"syft:metadata:originPackage","value":"nghttp2"},{"name":"syft:metadata:size","value":"0"}]},{"bom-ref":"pkg:apk/alpine/pkgconf@2.3.0-r0?arch=x86_64&distro=alpine-3.21.0&package-id=c86c3c8f976d31b1","type":"library","name":"pkgconf","version":"2.3.0-r0","licenses":[{"license":{"id":"ISC"}}],"cpe":"cpe:2.3:a:pkgconf:pkgconf:2.3.0-r0:*:*:*:*:*:*:*","purl":"pkg:apk/alpine/pkgconf@2.3.0-r0?arch=x86_64&distro=alpine-3.21.0","properties":[{"name":"syft:package:type","value":"apk"},{"name":"syft:package:metadataType","value":"apk-db-entry"},{"name":"syft:location:0:layerID","value":"sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d"},{"name":"syft:location:0:path","value":"/lib/apk/db/installed"},{"name":"syft:metadata:installedSize","value":"0"},{"name":"syft:metadata:originPackage","value":"pkgconf"},{"name":"syft:metadata:size","value":"0"}]},{"bom-ref":"pkg:apk/alpine/scanelf@1.3.8-r1?arch=x86_64&distro=alpine-3.21.0&package-id=e70542a40cdca454&upstream=pax-utils","type":"library","name":"scanelf","version":"1.3.8-r1","licenses":[{"license":{"id":"GPL-2.0-only"}}],"cpe":"cpe:2.3:a:scanelf:scanelf:1.3.8-r1:*:*:*:*:*:*:*","purl":"pkg:apk/alpine/scanelf@1.3.8-r1?arch=x86_64&distro=alpine-3.21.0&upstream=pax-utils","properties":[{"name":"syft:package:type","value":"apk"},{"name":"syft:package:metadataType","value":"apk-db-entry"},{"name":"syft:location:0:layerID","value":"sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d"},{"name":"syft:location:0:path","value":"/lib/apk/db/installed"},{"name":"syft:metadata:installedSize","value":"0"},{"name":"syft:metadata:originPackage","value":"pax-utils"},{"name":"syft:metadata:size","value":"0"}]},{"bom-ref":"pkg:apk/alpine/ssl_client@1.37.0-r8?arch=x86_64&distro=alpine-3.21.0&package-id=3b2d5f71701ee5a7&upstream=busybox","type":"library","name":"ssl_client","version":"1.37.0-r8","licenses":[{"license":{"id":"GPL-2.0-only"}}],"cpe":"cpe:2.3:a:ssl-client:ssl-client:1.37.0-r8:*:*:*:*:*:*:*","purl":"pkg:apk/alpine/ssl_client@1.37.0-r8?arch=x86_64&distro=alpine-3.21.0&upstream=busybox","properties":[{"name":"syft:package:type","value":"apk"},{"name":"syft:package:metadataType","value":"apk-db-entry"},{"name":"syft:cpe23","value":"cpe:2.3:a:ssl-client:ssl_client:1.37.0-r8:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:ssl_client:ssl-client:1.37.0-r8:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:ssl_client:ssl_client:1.37.0-r8:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:ssl:ssl-client:1.37.0-r8:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:ssl:ssl_client:1.37.0-r8:*:*:*:*:*:*:*"},{"name":"syft:location:0:layerID","value":"sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d"},{"name":"syft:location:0:path","value":"/lib/apk/db/installed"},{"name":"syft:metadata:installedSize","value":"0"},{"name":"syft:metadata:originPackage","value":"busybox"},{"name":"syft:metadata:size","value":"0"}]},{"bom-ref":"pkg:apk/alpine/zlib@1.3.1-r2?arch=x86_64&distro=alpine-3.21.0&package-id=bb1801fcfd6835ff","type":"library","name":"zlib","version":"1.3.1-r2","licenses":[{"license":{"id":"Zlib"}}],"cpe":"cpe:2.3:a:zlib:zlib:1.3.1-r2:*:*:*:*:*:*:*","purl":"pkg:apk/alpine/zlib@1.3.1-r2?arch=x86_64&distro=alpine-3.21.0","properties":[{"name":"syft:package:type","value":"apk"},{"name":"syft:package:metadataType","value":"apk-db-entry"},{"name":"syft:location:0:layerID","value":"sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d"},{"name":"syft:location:0:path","value":"/lib/apk/db/installed"},{"name":"syft:metadata:installedSize","value":"0"},{"name":"syft:metadata:originPackage","value":"zlib"},{"name":"syft:metadata:size","value":"0"}]},{"bom-ref":"pkg:apk/alpine/zstd@1.5.6-r1?arch=x86_64&distro=alpine-3.21.0&package-id=986677a068b531ae","type":"library","name":"zstd","version":"1.5.6-r1","licenses":[{"expression":"BSD-3-Clause OR GPL-2.0-or-later"}],"cpe":"cpe:2.3:a:zstd:zstd:1.5.6-r1:*:*:*:*:*:*:*","purl":"pkg:apk/alpine/zstd@1.5.6-r1?arch=x86_64&distro=alpine-3.21.0","properties":[{"name":"syft:package:type","value":"apk"},{"name":"syft:package:metadataType","value":"apk-db-entry"},{"name":"syft:location:0:layerID","value":"sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d"},{"name":"syft:location:0:path","value":"/lib/apk/db/installed"},{"name":"syft:metadata:installedSize","value":"0"},{"name":"syft:metadata:originPackage","value":"zstd"},{"name":"syft:metadata:size","value":"0"}]},{"bom-ref":"pkg:apk/alpine/zstd-libs@1.5.6-r1?arch=x86_64&distro=alpine-3.21.0&package-id=9661e4d8b6fc57db&upstream=zstd","type":"library","name":"zstd-libs","version":"1.5.6-r1","licenses":[{"expression":"BSD-3-Clause OR GPL-2.0-or-later"}],"cpe":"cpe:2.3:a:zstd-libs:zstd-libs:1.5.6-r1:*:*:*:*:*:*:*","purl":"pkg:apk/alpine/zstd-libs@1.5.6-r1?arch=x86_64&distro=alpine-3.21.0&upstream=zstd","properties":[{"name":"syft:package:type","value":"apk"},{"name":"syft:package:metadataType","value":"apk-db-entry"},{"name":"syft:cpe23","value":"cpe:2.3:a:zstd-libs:zstd_libs:1.5.6-r1:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:zstd_libs:zstd-libs:1.5.6-r1:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:zstd_libs:zstd_libs:1.5.6-r1:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:zstd:zstd-libs:1.5.6-r1:*:*:*:*:*:*:*"},{"name":"syft:cpe23","value":"cpe:2.3:a:zstd:zstd_libs:1.5.6-r1:*:*:*:*:*:*:*"},{"name":"syft:location:0:layerID","value":"sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d"},{"name":"syft:location:0:path","value":"/lib/apk/db/installed"},{"name":"syft:metadata:installedSize","value":"0"},{"name":"syft:metadata:originPackage","value":"zstd"},{"name":"syft:metadata:size","value":"0"}]},{"bom-ref":"os:alpine@3.21.0","type":"operating-system","name":"alpine","version":"3.21.0","description":"Alpine Linux v3.21","swid":{"tagId":"alpine","name":"alpine","version":"3.21.0"},"externalReferences":[{"url":"https://gitlab.alpinelinux.org/alpine/aports/-/issues","type":"issue-tracker"},{"url":"https://alpinelinux.org/","type":"website"}],"properties":[{"name":"syft:distro:extendedSupport","value":"false"},{"name":"syft:distro:id","value":"alpine"},{"name":"syft:distro:prettyName","value":"Alpine Linux v3.21"},{"name":"syft:distro:versionID","value":"3.21.0"}]}],"dependencies":[{"ref":"pkg:apk/alpine/alpine-baselayout@3.6.8-r0?arch=x86_64&distro=alpine-3.21.0&package-id=ceeb874c4a135dac","dependsOn":["pkg:apk/alpine/alpine-baselayout-data@3.6.8-r0?arch=x86_64&distro=alpine-3.21.0&package-id=1a41f8b73cec7362&upstream=alpine-baselayout","pkg:apk/alpine/busybox-binsh@1.37.0-r8?arch=x86_64&distro=alpine-3.21.0&package-id=3a2e3359d51d0045&upstream=busybox"]},{"ref":"pkg:apk/alpine/alpine-release@3.21.0-r0?arch=x86_64&distro=alpine-3.21.0&package-id=456e920c4331bde6&upstream=alpine-base","dependsOn":["pkg:apk/alpine/alpine-keys@2.5-r0?arch=x86_64&distro=alpine-3.21.0&package-id=f3faf4bc1f174ce9"]},{"ref":"pkg:apk/alpine/apk-tools@2.14.6-r2?arch=x86_64&distro=alpine-3.21.0&package-id=1095a39f80569c66","dependsOn":["pkg:apk/alpine/ca-certificates-bundle@20241010-r0?arch=x86_64&distro=alpine-3.21.0&package-id=a906f1b13dd9bf14&upstream=ca-certificates","pkg:apk/alpine/libcrypto3@3.3.2-r4?arch=x86_64&distro=alpine-3.21.0&package-id=88109714e4e7213c&upstream=openssl","pkg:apk/alpine/libssl3@3.3.2-r4?arch=x86_64&distro=alpine-3.21.0&package-id=0f1bae216faefb4b&upstream=openssl","pkg:apk/alpine/musl@1.2.5-r8?arch=x86_64&distro=alpine-3.21.0&package-id=25a411dcbf4c73ba","pkg:apk/alpine/zlib@1.3.1-r2?arch=x86_64&distro=alpine-3.21.0&package-id=bb1801fcfd6835ff"]},{"ref":"pkg:apk/alpine/brotli-dev@1.1.0-r2?arch=x86_64&distro=alpine-3.21.0&package-id=037ba9595fe77699&upstream=brotli","dependsOn":["pkg:apk/alpine/brotli-libs@1.1.0-r2?arch=x86_64&distro=alpine-3.21.0&package-id=8f69c879d6b38e38&upstream=brotli","pkg:apk/alpine/brotli@1.1.0-r2?arch=x86_64&distro=alpine-3.21.0&package-id=e3ed65b6dc9144b9","pkg:apk/alpine/pkgconf@2.3.0-r0?arch=x86_64&distro=alpine-3.21.0&package-id=c86c3c8f976d31b1"]},{"ref":"pkg:apk/alpine/brotli-libs@1.1.0-r2?arch=x86_64&distro=alpine-3.21.0&package-id=8f69c879d6b38e38&upstream=brotli","dependsOn":["pkg:apk/alpine/musl@1.2.5-r8?arch=x86_64&distro=alpine-3.21.0&package-id=25a411dcbf4c73ba"]},{"ref":"pkg:apk/alpine/brotli@1.1.0-r2?arch=x86_64&distro=alpine-3.21.0&package-id=e3ed65b6dc9144b9","dependsOn":["pkg:apk/alpine/brotli-libs@1.1.0-r2?arch=x86_64&distro=alpine-3.21.0&package-id=8f69c879d6b38e38&upstream=brotli","pkg:apk/alpine/musl@1.2.5-r8?arch=x86_64&distro=alpine-3.21.0&package-id=25a411dcbf4c73ba"]},{"ref":"pkg:apk/alpine/busybox-binsh@1.37.0-r8?arch=x86_64&distro=alpine-3.21.0&package-id=3a2e3359d51d0045&upstream=busybox","dependsOn":["pkg:apk/alpine/busybox@1.37.0-r8?arch=x86_64&distro=alpine-3.21.0&package-id=7cbfd25df10d2724"]},{"ref":"pkg:apk/alpine/busybox@1.37.0-r8?arch=x86_64&distro=alpine-3.21.0&package-id=7cbfd25df10d2724","dependsOn":["pkg:apk/alpine/musl@1.2.5-r8?arch=x86_64&distro=alpine-3.21.0&package-id=25a411dcbf4c73ba"]},{"ref":"pkg:apk/alpine/e2fsprogs-libs@1.47.1-r1?arch=x86_64&distro=alpine-3.21.0&package-id=412b42ad943bf8cb&upstream=e2fsprogs","dependsOn":["pkg:apk/alpine/libcom_err@1.47.1-r1?arch=x86_64&distro=alpine-3.21.0&package-id=dbfb4d1dbecb422f&upstream=e2fsprogs","pkg:apk/alpine/musl@1.2.5-r8?arch=x86_64&distro=alpine-3.21.0&package-id=25a411dcbf4c73ba"]},{"ref":"pkg:apk/alpine/keyutils-libs@1.6.3-r4?arch=x86_64&distro=alpine-3.21.0&package-id=5c84d53d49f221e1&upstream=keyutils","dependsOn":["pkg:apk/alpine/musl@1.2.5-r8?arch=x86_64&distro=alpine-3.21.0&package-id=25a411dcbf4c73ba"]},{"ref":"pkg:apk/alpine/krb5-libs@1.21.3-r0?arch=x86_64&distro=alpine-3.21.0&package-id=67d8ec97d517d9c2&upstream=krb5","dependsOn":["pkg:apk/alpine/keyutils-libs@1.6.3-r4?arch=x86_64&distro=alpine-3.21.0&package-id=5c84d53d49f221e1&upstream=keyutils","pkg:apk/alpine/krb5-conf@1.0-r2?arch=x86_64&distro=alpine-3.21.0&package-id=5449192ad1853f9c","pkg:apk/alpine/libcom_err@1.47.1-r1?arch=x86_64&distro=alpine-3.21.0&package-id=dbfb4d1dbecb422f&upstream=e2fsprogs","pkg:apk/alpine/libcrypto3@3.3.2-r4?arch=x86_64&distro=alpine-3.21.0&package-id=88109714e4e7213c&upstream=openssl","pkg:apk/alpine/libssl3@3.3.2-r4?arch=x86_64&distro=alpine-3.21.0&package-id=0f1bae216faefb4b&upstream=openssl","pkg:apk/alpine/libverto@0.3.2-r2?arch=x86_64&distro=alpine-3.21.0&package-id=00fb4f5e90a57319","pkg:apk/alpine/musl@1.2.5-r8?arch=x86_64&distro=alpine-3.21.0&package-id=25a411dcbf4c73ba"]},{"ref":"pkg:apk/alpine/krb5@1.21.3-r0?arch=x86_64&distro=alpine-3.21.0&package-id=e13769c76f74134e","dependsOn":["pkg:apk/alpine/e2fsprogs-libs@1.47.1-r1?arch=x86_64&distro=alpine-3.21.0&package-id=412b42ad943bf8cb&upstream=e2fsprogs","pkg:apk/alpine/krb5-conf@1.0-r2?arch=x86_64&distro=alpine-3.21.0&package-id=5449192ad1853f9c","pkg:apk/alpine/krb5-libs@1.21.3-r0?arch=x86_64&distro=alpine-3.21.0&package-id=67d8ec97d517d9c2&upstream=krb5","pkg:apk/alpine/libcom_err@1.47.1-r1?arch=x86_64&distro=alpine-3.21.0&package-id=dbfb4d1dbecb422f&upstream=e2fsprogs","pkg:apk/alpine/musl@1.2.5-r8?arch=x86_64&distro=alpine-3.21.0&package-id=25a411dcbf4c73ba"]},{"ref":"pkg:apk/alpine/libcom_err@1.47.1-r1?arch=x86_64&distro=alpine-3.21.0&package-id=dbfb4d1dbecb422f&upstream=e2fsprogs","dependsOn":["pkg:apk/alpine/musl@1.2.5-r8?arch=x86_64&distro=alpine-3.21.0&package-id=25a411dcbf4c73ba"]},{"ref":"pkg:apk/alpine/libcrypto3@3.3.2-r4?arch=x86_64&distro=alpine-3.21.0&package-id=88109714e4e7213c&upstream=openssl","dependsOn":["pkg:apk/alpine/musl@1.2.5-r8?arch=x86_64&distro=alpine-3.21.0&package-id=25a411dcbf4c73ba"]},{"ref":"pkg:apk/alpine/libgcc@14.2.0-r4?arch=x86_64&distro=alpine-3.21.0&package-id=3d4d4f53df422ab4&upstream=gcc","dependsOn":["pkg:apk/alpine/musl@1.2.5-r8?arch=x86_64&distro=alpine-3.21.0&package-id=25a411dcbf4c73ba"]},{"ref":"pkg:apk/alpine/libidn2@2.3.7-r0?arch=x86_64&distro=alpine-3.21.0&package-id=97c06e6e1903e776","dependsOn":["pkg:apk/alpine/libunistring@1.2-r0?arch=x86_64&distro=alpine-3.21.0&package-id=07c4841b11e2910b","pkg:apk/alpine/musl@1.2.5-r8?arch=x86_64&distro=alpine-3.21.0&package-id=25a411dcbf4c73ba"]},{"ref":"pkg:apk/alpine/libpsl@0.21.5-r3?arch=x86_64&distro=alpine-3.21.0&package-id=fc2164ebd15bcb82","dependsOn":["pkg:apk/alpine/libidn2@2.3.7-r0?arch=x86_64&distro=alpine-3.21.0&package-id=97c06e6e1903e776","pkg:apk/alpine/libunistring@1.2-r0?arch=x86_64&distro=alpine-3.21.0&package-id=07c4841b11e2910b","pkg:apk/alpine/musl@1.2.5-r8?arch=x86_64&distro=alpine-3.21.0&package-id=25a411dcbf4c73ba"]},{"ref":"pkg:apk/alpine/libssh2@1.11.1-r0?arch=x86_64&distro=alpine-3.21.0&package-id=ba1753b9ff7d9303","dependsOn":["pkg:apk/alpine/libcrypto3@3.3.2-r4?arch=x86_64&distro=alpine-3.21.0&package-id=88109714e4e7213c&upstream=openssl","pkg:apk/alpine/musl@1.2.5-r8?arch=x86_64&distro=alpine-3.21.0&package-id=25a411dcbf4c73ba","pkg:apk/alpine/zlib@1.3.1-r2?arch=x86_64&distro=alpine-3.21.0&package-id=bb1801fcfd6835ff"]},{"ref":"pkg:apk/alpine/libssl3@3.3.2-r4?arch=x86_64&distro=alpine-3.21.0&package-id=0f1bae216faefb4b&upstream=openssl","dependsOn":["pkg:apk/alpine/libcrypto3@3.3.2-r4?arch=x86_64&distro=alpine-3.21.0&package-id=88109714e4e7213c&upstream=openssl","pkg:apk/alpine/musl@1.2.5-r8?arch=x86_64&distro=alpine-3.21.0&package-id=25a411dcbf4c73ba"]},{"ref":"pkg:apk/alpine/libstdc%2B%2B@14.2.0-r4?arch=x86_64&distro=alpine-3.21.0&package-id=f579a97329d24094&upstream=gcc","dependsOn":["pkg:apk/alpine/libgcc@14.2.0-r4?arch=x86_64&distro=alpine-3.21.0&package-id=3d4d4f53df422ab4&upstream=gcc","pkg:apk/alpine/musl@1.2.5-r8?arch=x86_64&distro=alpine-3.21.0&package-id=25a411dcbf4c73ba"]},{"ref":"pkg:apk/alpine/libunistring@1.2-r0?arch=x86_64&distro=alpine-3.21.0&package-id=07c4841b11e2910b","dependsOn":["pkg:apk/alpine/musl@1.2.5-r8?arch=x86_64&distro=alpine-3.21.0&package-id=25a411dcbf4c73ba"]},{"ref":"pkg:apk/alpine/libverto@0.3.2-r2?arch=x86_64&distro=alpine-3.21.0&package-id=00fb4f5e90a57319","dependsOn":["pkg:apk/alpine/musl@1.2.5-r8?arch=x86_64&distro=alpine-3.21.0&package-id=25a411dcbf4c73ba"]},{"ref":"pkg:apk/alpine/musl-utils@1.2.5-r8?arch=x86_64&distro=alpine-3.21.0&package-id=9ed24d5185052508&upstream=musl","dependsOn":["pkg:apk/alpine/musl@1.2.5-r8?arch=x86_64&distro=alpine-3.21.0&package-id=25a411dcbf4c73ba","pkg:apk/alpine/scanelf@1.3.8-r1?arch=x86_64&distro=alpine-3.21.0&package-id=e70542a40cdca454&upstream=pax-utils"]},{"ref":"pkg:apk/alpine/nghttp2-dev@1.64.0-r0?arch=x86_64&distro=alpine-3.21.0&package-id=c9d137d8d21db486&upstream=nghttp2","dependsOn":["pkg:apk/alpine/nghttp2-libs@1.64.0-r0?arch=x86_64&distro=alpine-3.21.0&package-id=c16da0382f03c90a&upstream=nghttp2","pkg:apk/alpine/pkgconf@2.3.0-r0?arch=x86_64&distro=alpine-3.21.0&package-id=c86c3c8f976d31b1"]},{"ref":"pkg:apk/alpine/nghttp2-libs@1.64.0-r0?arch=x86_64&distro=alpine-3.21.0&package-id=c16da0382f03c90a&upstream=nghttp2","dependsOn":["pkg:apk/alpine/musl@1.2.5-r8?arch=x86_64&distro=alpine-3.21.0&package-id=25a411dcbf4c73ba"]},{"ref":"pkg:apk/alpine/pkgconf@2.3.0-r0?arch=x86_64&distro=alpine-3.21.0&package-id=c86c3c8f976d31b1","dependsOn":["pkg:apk/alpine/musl@1.2.5-r8?arch=x86_64&distro=alpine-3.21.0&package-id=25a411dcbf4c73ba"]},{"ref":"pkg:apk/alpine/scanelf@1.3.8-r1?arch=x86_64&distro=alpine-3.21.0&package-id=e70542a40cdca454&upstream=pax-utils","dependsOn":["pkg:apk/alpine/musl@1.2.5-r8?arch=x86_64&distro=alpine-3.21.0&package-id=25a411dcbf4c73ba"]},{"ref":"pkg:apk/alpine/ssl_client@1.37.0-r8?arch=x86_64&distro=alpine-3.21.0&package-id=3b2d5f71701ee5a7&upstream=busybox","dependsOn":["pkg:apk/alpine/libcrypto3@3.3.2-r4?arch=x86_64&distro=alpine-3.21.0&package-id=88109714e4e7213c&upstream=openssl","pkg:apk/alpine/libssl3@3.3.2-r4?arch=x86_64&distro=alpine-3.21.0&package-id=0f1bae216faefb4b&upstream=openssl","pkg:apk/alpine/musl@1.2.5-r8?arch=x86_64&distro=alpine-3.21.0&package-id=25a411dcbf4c73ba"]},{"ref":"pkg:apk/alpine/zlib@1.3.1-r2?arch=x86_64&distro=alpine-3.21.0&package-id=bb1801fcfd6835ff","dependsOn":["pkg:apk/alpine/musl@1.2.5-r8?arch=x86_64&distro=alpine-3.21.0&package-id=25a411dcbf4c73ba"]},{"ref":"pkg:apk/alpine/zstd-libs@1.5.6-r1?arch=x86_64&distro=alpine-3.21.0&package-id=9661e4d8b6fc57db&upstream=zstd","dependsOn":["pkg:apk/alpine/musl@1.2.5-r8?arch=x86_64&distro=alpine-3.21.0&package-id=25a411dcbf4c73ba"]},{"ref":"pkg:apk/alpine/zstd@1.5.6-r1?arch=x86_64&distro=alpine-3.21.0&package-id=986677a068b531ae","dependsOn":["pkg:apk/alpine/libgcc@14.2.0-r4?arch=x86_64&distro=alpine-3.21.0&package-id=3d4d4f53df422ab4&upstream=gcc","pkg:apk/alpine/libstdc%2B%2B@14.2.0-r4?arch=x86_64&distro=alpine-3.21.0&package-id=f579a97329d24094&upstream=gcc","pkg:apk/alpine/musl@1.2.5-r8?arch=x86_64&distro=alpine-3.21.0&package-id=25a411dcbf4c73ba","pkg:apk/alpine/zstd-libs@1.5.6-r1?arch=x86_64&distro=alpine-3.21.0&package-id=9661e4d8b6fc57db&upstream=zstd"]}]} diff --git a/sbomexport/testdata/curl-8.11.1.syft.json b/sbomexport/testdata/curl-8.11.1.syft.json new file mode 100644 index 00000000..6679ceaf --- /dev/null +++ b/sbomexport/testdata/curl-8.11.1.syft.json @@ -0,0 +1 @@ +{"artifacts": [{"id": "ceeb874c4a135dac", "name": "alpine-baselayout", "version": "3.6.8-r0", "type": "apk", "foundBy": "", "locations": [{"path": "/lib/apk/db/installed", "layerID": "sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d", "accessPath": ""}], "licenses": [{"value": "GPL-2.0-only", "spdxExpression": "GPL-2.0-only", "type": "declared", "urls": null, "locations": null}], "language": "", "cpes": [{"cpe": "cpe:2.3:a:alpine-baselayout:alpine-baselayout:3.6.8-r0:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:alpine-baselayout:alpine_baselayout:3.6.8-r0:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:alpine_baselayout:alpine-baselayout:3.6.8-r0:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:alpine_baselayout:alpine_baselayout:3.6.8-r0:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:alpine:alpine-baselayout:3.6.8-r0:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:alpine:alpine_baselayout:3.6.8-r0:*:*:*:*:*:*:*", "source": "syft-generated"}], "purl": "pkg:apk/alpine/alpine-baselayout@3.6.8-r0?arch=x86_64&distro=alpine-3.21.0", "metadataType": "apk-db-entry", "metadata": {"package": "", "originPackage": "alpine-baselayout", "maintainer": "", "version": "", "architecture": "", "url": "", "description": "", "size": 0, "installedSize": 0, "pullDependencies": null, "provides": null, "pullChecksum": "", "gitCommitOfApkPort": "", "files": null}}, {"id": "1a41f8b73cec7362", "name": "alpine-baselayout-data", "version": "3.6.8-r0", "type": "apk", "foundBy": "", "locations": [{"path": "/lib/apk/db/installed", "layerID": "sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d", "accessPath": ""}], "licenses": [{"value": "GPL-2.0-only", "spdxExpression": "GPL-2.0-only", "type": "declared", "urls": null, "locations": null}], "language": "", "cpes": [{"cpe": "cpe:2.3:a:alpine-baselayout-data:alpine-baselayout-data:3.6.8-r0:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:alpine-baselayout-data:alpine_baselayout_data:3.6.8-r0:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:alpine_baselayout_data:alpine-baselayout-data:3.6.8-r0:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:alpine_baselayout_data:alpine_baselayout_data:3.6.8-r0:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:alpine-baselayout:alpine-baselayout-data:3.6.8-r0:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:alpine-baselayout:alpine_baselayout_data:3.6.8-r0:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:alpine_baselayout:alpine-baselayout-data:3.6.8-r0:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:alpine_baselayout:alpine_baselayout_data:3.6.8-r0:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:alpine:alpine-baselayout-data:3.6.8-r0:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:alpine:alpine_baselayout_data:3.6.8-r0:*:*:*:*:*:*:*", "source": "syft-generated"}], "purl": "pkg:apk/alpine/alpine-baselayout-data@3.6.8-r0?arch=x86_64&distro=alpine-3.21.0&upstream=alpine-baselayout", "metadataType": "apk-db-entry", "metadata": {"package": "", "originPackage": "alpine-baselayout", "maintainer": "", "version": "", "architecture": "", "url": "", "description": "", "size": 0, "installedSize": 0, "pullDependencies": null, "provides": null, "pullChecksum": "", "gitCommitOfApkPort": "", "files": null}}, {"id": "f3faf4bc1f174ce9", "name": "alpine-keys", "version": "2.5-r0", "type": "apk", "foundBy": "", "locations": [{"path": "/lib/apk/db/installed", "layerID": "sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d", "accessPath": ""}], "licenses": [{"value": "MIT", "spdxExpression": "MIT", "type": "declared", "urls": null, "locations": null}], "language": "", "cpes": [{"cpe": "cpe:2.3:a:alpine-keys:alpine-keys:2.5-r0:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:alpine-keys:alpine_keys:2.5-r0:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:alpine_keys:alpine-keys:2.5-r0:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:alpine_keys:alpine_keys:2.5-r0:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:alpine:alpine-keys:2.5-r0:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:alpine:alpine_keys:2.5-r0:*:*:*:*:*:*:*", "source": "syft-generated"}], "purl": "pkg:apk/alpine/alpine-keys@2.5-r0?arch=x86_64&distro=alpine-3.21.0", "metadataType": "apk-db-entry", "metadata": {"package": "", "originPackage": "alpine-keys", "maintainer": "", "version": "", "architecture": "", "url": "", "description": "", "size": 0, "installedSize": 0, "pullDependencies": null, "provides": null, "pullChecksum": "", "gitCommitOfApkPort": "", "files": null}}, {"id": "456e920c4331bde6", "name": "alpine-release", "version": "3.21.0-r0", "type": "apk", "foundBy": "", "locations": [{"path": "/lib/apk/db/installed", "layerID": "sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d", "accessPath": ""}], "licenses": [{"value": "MIT", "spdxExpression": "MIT", "type": "declared", "urls": null, "locations": null}], "language": "", "cpes": [{"cpe": "cpe:2.3:a:alpine-release:alpine-release:3.21.0-r0:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:alpine-release:alpine_release:3.21.0-r0:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:alpine_release:alpine-release:3.21.0-r0:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:alpine_release:alpine_release:3.21.0-r0:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:alpine:alpine-release:3.21.0-r0:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:alpine:alpine_release:3.21.0-r0:*:*:*:*:*:*:*", "source": "syft-generated"}], "purl": "pkg:apk/alpine/alpine-release@3.21.0-r0?arch=x86_64&distro=alpine-3.21.0&upstream=alpine-base", "metadataType": "apk-db-entry", "metadata": {"package": "", "originPackage": "alpine-base", "maintainer": "", "version": "", "architecture": "", "url": "", "description": "", "size": 0, "installedSize": 0, "pullDependencies": null, "provides": null, "pullChecksum": "", "gitCommitOfApkPort": "", "files": null}}, {"id": "1095a39f80569c66", "name": "apk-tools", "version": "2.14.6-r2", "type": "apk", "foundBy": "", "locations": [{"path": "/lib/apk/db/installed", "layerID": "sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d", "accessPath": ""}], "licenses": [{"value": "GPL-2.0-only", "spdxExpression": "GPL-2.0-only", "type": "declared", "urls": null, "locations": null}], "language": "", "cpes": [{"cpe": "cpe:2.3:a:apk-tools:apk-tools:2.14.6-r2:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:apk-tools:apk_tools:2.14.6-r2:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:apk_tools:apk-tools:2.14.6-r2:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:apk_tools:apk_tools:2.14.6-r2:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:apk:apk-tools:2.14.6-r2:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:apk:apk_tools:2.14.6-r2:*:*:*:*:*:*:*", "source": "syft-generated"}], "purl": "pkg:apk/alpine/apk-tools@2.14.6-r2?arch=x86_64&distro=alpine-3.21.0", "metadataType": "apk-db-entry", "metadata": {"package": "", "originPackage": "apk-tools", "maintainer": "", "version": "", "architecture": "", "url": "", "description": "", "size": 0, "installedSize": 0, "pullDependencies": null, "provides": null, "pullChecksum": "", "gitCommitOfApkPort": "", "files": null}}, {"id": "e3ed65b6dc9144b9", "name": "brotli", "version": "1.1.0-r2", "type": "apk", "foundBy": "", "locations": [{"path": "/lib/apk/db/installed", "layerID": "sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d", "accessPath": ""}], "licenses": [{"value": "MIT", "spdxExpression": "MIT", "type": "declared", "urls": null, "locations": null}], "language": "", "cpes": [{"cpe": "cpe:2.3:a:brotli:brotli:1.1.0-r2:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:google:brotli:1.1.0-r2:*:*:*:*:*:*:*", "source": "syft-generated"}], "purl": "pkg:apk/alpine/brotli@1.1.0-r2?arch=x86_64&distro=alpine-3.21.0", "metadataType": "apk-db-entry", "metadata": {"package": "", "originPackage": "brotli", "maintainer": "", "version": "", "architecture": "", "url": "", "description": "", "size": 0, "installedSize": 0, "pullDependencies": null, "provides": null, "pullChecksum": "", "gitCommitOfApkPort": "", "files": null}}, {"id": "037ba9595fe77699", "name": "brotli-dev", "version": "1.1.0-r2", "type": "apk", "foundBy": "", "locations": [{"path": "/lib/apk/db/installed", "layerID": "sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d", "accessPath": ""}], "licenses": [{"value": "MIT", "spdxExpression": "MIT", "type": "declared", "urls": null, "locations": null}], "language": "", "cpes": [{"cpe": "cpe:2.3:a:brotli-dev:brotli-dev:1.1.0-r2:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:brotli-dev:brotli_dev:1.1.0-r2:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:brotli_dev:brotli-dev:1.1.0-r2:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:brotli_dev:brotli_dev:1.1.0-r2:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:brotli:brotli-dev:1.1.0-r2:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:brotli:brotli_dev:1.1.0-r2:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:google:brotli-dev:1.1.0-r2:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:google:brotli_dev:1.1.0-r2:*:*:*:*:*:*:*", "source": "syft-generated"}], "purl": "pkg:apk/alpine/brotli-dev@1.1.0-r2?arch=x86_64&distro=alpine-3.21.0&upstream=brotli", "metadataType": "apk-db-entry", "metadata": {"package": "", "originPackage": "brotli", "maintainer": "", "version": "", "architecture": "", "url": "", "description": "", "size": 0, "installedSize": 0, "pullDependencies": null, "provides": null, "pullChecksum": "", "gitCommitOfApkPort": "", "files": null}}, {"id": "8f69c879d6b38e38", "name": "brotli-libs", "version": "1.1.0-r2", "type": "apk", "foundBy": "", "locations": [{"path": "/lib/apk/db/installed", "layerID": "sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d", "accessPath": ""}], "licenses": [{"value": "MIT", "spdxExpression": "MIT", "type": "declared", "urls": null, "locations": null}], "language": "", "cpes": [{"cpe": "cpe:2.3:a:brotli-libs:brotli-libs:1.1.0-r2:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:brotli-libs:brotli_libs:1.1.0-r2:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:brotli_libs:brotli-libs:1.1.0-r2:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:brotli_libs:brotli_libs:1.1.0-r2:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:brotli:brotli-libs:1.1.0-r2:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:brotli:brotli_libs:1.1.0-r2:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:google:brotli-libs:1.1.0-r2:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:google:brotli_libs:1.1.0-r2:*:*:*:*:*:*:*", "source": "syft-generated"}], "purl": "pkg:apk/alpine/brotli-libs@1.1.0-r2?arch=x86_64&distro=alpine-3.21.0&upstream=brotli", "metadataType": "apk-db-entry", "metadata": {"package": "", "originPackage": "brotli", "maintainer": "", "version": "", "architecture": "", "url": "", "description": "", "size": 0, "installedSize": 0, "pullDependencies": null, "provides": null, "pullChecksum": "", "gitCommitOfApkPort": "", "files": null}}, {"id": "7cbfd25df10d2724", "name": "busybox", "version": "1.37.0-r8", "type": "apk", "foundBy": "", "locations": [{"path": "/lib/apk/db/installed", "layerID": "sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d", "accessPath": ""}], "licenses": [{"value": "GPL-2.0-only", "spdxExpression": "GPL-2.0-only", "type": "declared", "urls": null, "locations": null}], "language": "", "cpes": [{"cpe": "cpe:2.3:a:busybox:busybox:1.37.0-r8:*:*:*:*:*:*:*", "source": "syft-generated"}], "purl": "pkg:apk/alpine/busybox@1.37.0-r8?arch=x86_64&distro=alpine-3.21.0", "metadataType": "apk-db-entry", "metadata": {"package": "", "originPackage": "busybox", "maintainer": "", "version": "", "architecture": "", "url": "", "description": "", "size": 0, "installedSize": 0, "pullDependencies": null, "provides": null, "pullChecksum": "", "gitCommitOfApkPort": "", "files": null}}, {"id": "3a2e3359d51d0045", "name": "busybox-binsh", "version": "1.37.0-r8", "type": "apk", "foundBy": "", "locations": [{"path": "/lib/apk/db/installed", "layerID": "sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d", "accessPath": ""}], "licenses": [{"value": "GPL-2.0-only", "spdxExpression": "GPL-2.0-only", "type": "declared", "urls": null, "locations": null}], "language": "", "cpes": [{"cpe": "cpe:2.3:a:busybox-binsh:busybox-binsh:1.37.0-r8:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:busybox-binsh:busybox_binsh:1.37.0-r8:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:busybox_binsh:busybox-binsh:1.37.0-r8:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:busybox_binsh:busybox_binsh:1.37.0-r8:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:busybox:busybox-binsh:1.37.0-r8:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:busybox:busybox_binsh:1.37.0-r8:*:*:*:*:*:*:*", "source": "syft-generated"}], "purl": "pkg:apk/alpine/busybox-binsh@1.37.0-r8?arch=x86_64&distro=alpine-3.21.0&upstream=busybox", "metadataType": "apk-db-entry", "metadata": {"package": "", "originPackage": "busybox", "maintainer": "", "version": "", "architecture": "", "url": "", "description": "", "size": 0, "installedSize": 0, "pullDependencies": null, "provides": null, "pullChecksum": "", "gitCommitOfApkPort": "", "files": null}}, {"id": "a906f1b13dd9bf14", "name": "ca-certificates-bundle", "version": "20241010-r0", "type": "apk", "foundBy": "", "locations": [{"path": "/lib/apk/db/installed", "layerID": "sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d", "accessPath": ""}], "licenses": [{"value": "MPL-2.0 AND MIT", "spdxExpression": "MPL-2.0 AND MIT", "type": "declared", "urls": null, "locations": null}], "language": "", "cpes": [{"cpe": "cpe:2.3:a:ca-certificates-bundle:ca-certificates-bundle:20241010-r0:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:ca-certificates-bundle:ca_certificates_bundle:20241010-r0:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:ca_certificates_bundle:ca-certificates-bundle:20241010-r0:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:ca_certificates_bundle:ca_certificates_bundle:20241010-r0:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:ca-certificates:ca-certificates-bundle:20241010-r0:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:ca-certificates:ca_certificates_bundle:20241010-r0:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:ca_certificates:ca-certificates-bundle:20241010-r0:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:ca_certificates:ca_certificates_bundle:20241010-r0:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:mozilla:ca-certificates-bundle:20241010-r0:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:mozilla:ca_certificates_bundle:20241010-r0:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:ca:ca-certificates-bundle:20241010-r0:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:ca:ca_certificates_bundle:20241010-r0:*:*:*:*:*:*:*", "source": "syft-generated"}], "purl": "pkg:apk/alpine/ca-certificates-bundle@20241010-r0?arch=x86_64&distro=alpine-3.21.0&upstream=ca-certificates", "metadataType": "apk-db-entry", "metadata": {"package": "", "originPackage": "ca-certificates", "maintainer": "", "version": "", "architecture": "", "url": "", "description": "", "size": 0, "installedSize": 0, "pullDependencies": null, "provides": null, "pullChecksum": "", "gitCommitOfApkPort": "", "files": null}}, {"id": "3e249a6f2317504e", "name": "curl", "version": "8.11.1", "type": "binary", "foundBy": "", "locations": [{"path": "/usr/bin/curl", "layerID": "sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d", "accessPath": ""}], "licenses": null, "language": "", "cpes": [{"cpe": "cpe:2.3:a:haxx:curl:8.11.1:*:*:*:*:*:*:*", "source": "nvd-cpe-dictionary"}], "purl": "pkg:generic/curl@8.11.1", "metadata": null}, {"id": "412b42ad943bf8cb", "name": "e2fsprogs-libs", "version": "1.47.1-r1", "type": "apk", "foundBy": "", "locations": [{"path": "/lib/apk/db/installed", "layerID": "sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d", "accessPath": ""}], "licenses": [{"value": "GPL-2.0-or-later AND LGPL-2.0-or-later AND BSD-3-Clause AND MIT", "spdxExpression": "GPL-2.0-or-later AND LGPL-2.0-or-later AND BSD-3-Clause AND MIT", "type": "declared", "urls": null, "locations": null}], "language": "", "cpes": [{"cpe": "cpe:2.3:a:e2fsprogs-libs:e2fsprogs-libs:1.47.1-r1:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:e2fsprogs-libs:e2fsprogs_libs:1.47.1-r1:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:e2fsprogs_libs:e2fsprogs-libs:1.47.1-r1:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:e2fsprogs_libs:e2fsprogs_libs:1.47.1-r1:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:e2fsprogs:e2fsprogs-libs:1.47.1-r1:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:e2fsprogs:e2fsprogs_libs:1.47.1-r1:*:*:*:*:*:*:*", "source": "syft-generated"}], "purl": "pkg:apk/alpine/e2fsprogs-libs@1.47.1-r1?arch=x86_64&distro=alpine-3.21.0&upstream=e2fsprogs", "metadataType": "apk-db-entry", "metadata": {"package": "", "originPackage": "e2fsprogs", "maintainer": "", "version": "", "architecture": "", "url": "", "description": "", "size": 0, "installedSize": 0, "pullDependencies": null, "provides": null, "pullChecksum": "", "gitCommitOfApkPort": "", "files": null}}, {"id": "5c84d53d49f221e1", "name": "keyutils-libs", "version": "1.6.3-r4", "type": "apk", "foundBy": "", "locations": [{"path": "/lib/apk/db/installed", "layerID": "sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d", "accessPath": ""}], "licenses": [{"value": "GPL-2.0-or-later AND LGPL-2.0-or-later", "spdxExpression": "GPL-2.0-or-later AND LGPL-2.0-or-later", "type": "declared", "urls": null, "locations": null}], "language": "", "cpes": [{"cpe": "cpe:2.3:a:keyutils-libs:keyutils-libs:1.6.3-r4:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:keyutils-libs:keyutils_libs:1.6.3-r4:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:keyutils_libs:keyutils-libs:1.6.3-r4:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:keyutils_libs:keyutils_libs:1.6.3-r4:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:keyutils:keyutils-libs:1.6.3-r4:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:keyutils:keyutils_libs:1.6.3-r4:*:*:*:*:*:*:*", "source": "syft-generated"}], "purl": "pkg:apk/alpine/keyutils-libs@1.6.3-r4?arch=x86_64&distro=alpine-3.21.0&upstream=keyutils", "metadataType": "apk-db-entry", "metadata": {"package": "", "originPackage": "keyutils", "maintainer": "", "version": "", "architecture": "", "url": "", "description": "", "size": 0, "installedSize": 0, "pullDependencies": null, "provides": null, "pullChecksum": "", "gitCommitOfApkPort": "", "files": null}}, {"id": "e13769c76f74134e", "name": "krb5", "version": "1.21.3-r0", "type": "apk", "foundBy": "", "locations": [{"path": "/lib/apk/db/installed", "layerID": "sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d", "accessPath": ""}], "licenses": [{"value": "MIT", "spdxExpression": "MIT", "type": "declared", "urls": null, "locations": null}], "language": "", "cpes": [{"cpe": "cpe:2.3:a:krb5:krb5:1.21.3-r0:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:krb5:krb:1.21.3-r0:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:krb:krb5:1.21.3-r0:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:krb:krb:1.21.3-r0:*:*:*:*:*:*:*", "source": "syft-generated"}], "purl": "pkg:apk/alpine/krb5@1.21.3-r0?arch=x86_64&distro=alpine-3.21.0", "metadataType": "apk-db-entry", "metadata": {"package": "", "originPackage": "krb5", "maintainer": "", "version": "", "architecture": "", "url": "", "description": "", "size": 0, "installedSize": 0, "pullDependencies": null, "provides": null, "pullChecksum": "", "gitCommitOfApkPort": "", "files": null}}, {"id": "5449192ad1853f9c", "name": "krb5-conf", "version": "1.0-r2", "type": "apk", "foundBy": "", "locations": [{"path": "/lib/apk/db/installed", "layerID": "sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d", "accessPath": ""}], "licenses": [{"value": "MIT", "spdxExpression": "MIT", "type": "declared", "urls": null, "locations": null}], "language": "", "cpes": [{"cpe": "cpe:2.3:a:krb5-conf:krb5-conf:1.0-r2:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:krb5-conf:krb5_conf:1.0-r2:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:krb5_conf:krb5-conf:1.0-r2:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:krb5_conf:krb5_conf:1.0-r2:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:krb-conf:krb5-conf:1.0-r2:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:krb-conf:krb5_conf:1.0-r2:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:krb5-conf:krb-conf:1.0-r2:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:krb5-conf:krb_conf:1.0-r2:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:krb5_conf:krb-conf:1.0-r2:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:krb5_conf:krb_conf:1.0-r2:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:krb_conf:krb5-conf:1.0-r2:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:krb_conf:krb5_conf:1.0-r2:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:krb-conf:krb-conf:1.0-r2:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:krb-conf:krb_conf:1.0-r2:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:krb_conf:krb-conf:1.0-r2:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:krb_conf:krb_conf:1.0-r2:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:krb5:krb5-conf:1.0-r2:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:krb5:krb5_conf:1.0-r2:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:krb5:krb-conf:1.0-r2:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:krb5:krb_conf:1.0-r2:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:krb:krb5-conf:1.0-r2:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:krb:krb5_conf:1.0-r2:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:krb:krb-conf:1.0-r2:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:krb:krb_conf:1.0-r2:*:*:*:*:*:*:*", "source": "syft-generated"}], "purl": "pkg:apk/alpine/krb5-conf@1.0-r2?arch=x86_64&distro=alpine-3.21.0", "metadataType": "apk-db-entry", "metadata": {"package": "", "originPackage": "krb5-conf", "maintainer": "", "version": "", "architecture": "", "url": "", "description": "", "size": 0, "installedSize": 0, "pullDependencies": null, "provides": null, "pullChecksum": "", "gitCommitOfApkPort": "", "files": null}}, {"id": "67d8ec97d517d9c2", "name": "krb5-libs", "version": "1.21.3-r0", "type": "apk", "foundBy": "", "locations": [{"path": "/lib/apk/db/installed", "layerID": "sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d", "accessPath": ""}], "licenses": [{"value": "MIT", "spdxExpression": "MIT", "type": "declared", "urls": null, "locations": null}], "language": "", "cpes": [{"cpe": "cpe:2.3:a:krb5-libs:krb5-libs:1.21.3-r0:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:krb5-libs:krb5_libs:1.21.3-r0:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:krb5_libs:krb5-libs:1.21.3-r0:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:krb5_libs:krb5_libs:1.21.3-r0:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:krb-libs:krb5-libs:1.21.3-r0:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:krb-libs:krb5_libs:1.21.3-r0:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:krb5-libs:krb-libs:1.21.3-r0:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:krb5-libs:krb_libs:1.21.3-r0:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:krb5_libs:krb-libs:1.21.3-r0:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:krb5_libs:krb_libs:1.21.3-r0:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:krb_libs:krb5-libs:1.21.3-r0:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:krb_libs:krb5_libs:1.21.3-r0:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:krb-libs:krb-libs:1.21.3-r0:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:krb-libs:krb_libs:1.21.3-r0:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:krb_libs:krb-libs:1.21.3-r0:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:krb_libs:krb_libs:1.21.3-r0:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:krb5:krb5-libs:1.21.3-r0:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:krb5:krb5_libs:1.21.3-r0:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:krb5:krb-libs:1.21.3-r0:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:krb5:krb_libs:1.21.3-r0:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:krb:krb5-libs:1.21.3-r0:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:krb:krb5_libs:1.21.3-r0:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:krb:krb-libs:1.21.3-r0:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:krb:krb_libs:1.21.3-r0:*:*:*:*:*:*:*", "source": "syft-generated"}], "purl": "pkg:apk/alpine/krb5-libs@1.21.3-r0?arch=x86_64&distro=alpine-3.21.0&upstream=krb5", "metadataType": "apk-db-entry", "metadata": {"package": "", "originPackage": "krb5", "maintainer": "", "version": "", "architecture": "", "url": "", "description": "", "size": 0, "installedSize": 0, "pullDependencies": null, "provides": null, "pullChecksum": "", "gitCommitOfApkPort": "", "files": null}}, {"id": "dbfb4d1dbecb422f", "name": "libcom_err", "version": "1.47.1-r1", "type": "apk", "foundBy": "", "locations": [{"path": "/lib/apk/db/installed", "layerID": "sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d", "accessPath": ""}], "licenses": [{"value": "GPL-2.0-or-later AND LGPL-2.0-or-later AND BSD-3-Clause AND MIT", "spdxExpression": "GPL-2.0-or-later AND LGPL-2.0-or-later AND BSD-3-Clause AND MIT", "type": "declared", "urls": null, "locations": null}], "language": "", "cpes": [{"cpe": "cpe:2.3:a:libcom-err:libcom-err:1.47.1-r1:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:libcom-err:libcom_err:1.47.1-r1:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:libcom_err:libcom-err:1.47.1-r1:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:libcom_err:libcom_err:1.47.1-r1:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:libcom:libcom-err:1.47.1-r1:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:libcom:libcom_err:1.47.1-r1:*:*:*:*:*:*:*", "source": "syft-generated"}], "purl": "pkg:apk/alpine/libcom_err@1.47.1-r1?arch=x86_64&distro=alpine-3.21.0&upstream=e2fsprogs", "metadataType": "apk-db-entry", "metadata": {"package": "", "originPackage": "e2fsprogs", "maintainer": "", "version": "", "architecture": "", "url": "", "description": "", "size": 0, "installedSize": 0, "pullDependencies": null, "provides": null, "pullChecksum": "", "gitCommitOfApkPort": "", "files": null}}, {"id": "88109714e4e7213c", "name": "libcrypto3", "version": "3.3.2-r4", "type": "apk", "foundBy": "", "locations": [{"path": "/lib/apk/db/installed", "layerID": "sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d", "accessPath": ""}], "licenses": [{"value": "Apache-2.0", "spdxExpression": "Apache-2.0", "type": "declared", "urls": null, "locations": null}], "language": "", "cpes": [{"cpe": "cpe:2.3:a:libcrypto3:libcrypto3:3.3.2-r4:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:libcrypto3:libcrypto:3.3.2-r4:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:libcrypto:libcrypto3:3.3.2-r4:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:libcrypto:libcrypto:3.3.2-r4:*:*:*:*:*:*:*", "source": "syft-generated"}], "purl": "pkg:apk/alpine/libcrypto3@3.3.2-r4?arch=x86_64&distro=alpine-3.21.0&upstream=openssl", "metadataType": "apk-db-entry", "metadata": {"package": "", "originPackage": "openssl", "maintainer": "", "version": "", "architecture": "", "url": "", "description": "", "size": 0, "installedSize": 0, "pullDependencies": null, "provides": null, "pullChecksum": "", "gitCommitOfApkPort": "", "files": null}}, {"id": "3d4d4f53df422ab4", "name": "libgcc", "version": "14.2.0-r4", "type": "apk", "foundBy": "", "locations": [{"path": "/lib/apk/db/installed", "layerID": "sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d", "accessPath": ""}], "licenses": [{"value": "GPL-2.0-or-later AND LGPL-2.1-or-later", "spdxExpression": "GPL-2.0-or-later AND LGPL-2.1-or-later", "type": "declared", "urls": null, "locations": null}], "language": "", "cpes": [{"cpe": "cpe:2.3:a:libgcc:libgcc:14.2.0-r4:*:*:*:*:*:*:*", "source": "syft-generated"}], "purl": "pkg:apk/alpine/libgcc@14.2.0-r4?arch=x86_64&distro=alpine-3.21.0&upstream=gcc", "metadataType": "apk-db-entry", "metadata": {"package": "", "originPackage": "gcc", "maintainer": "", "version": "", "architecture": "", "url": "", "description": "", "size": 0, "installedSize": 0, "pullDependencies": null, "provides": null, "pullChecksum": "", "gitCommitOfApkPort": "", "files": null}}, {"id": "97c06e6e1903e776", "name": "libidn2", "version": "2.3.7-r0", "type": "apk", "foundBy": "", "locations": [{"path": "/lib/apk/db/installed", "layerID": "sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d", "accessPath": ""}], "licenses": [{"value": "GPL-2.0-or-later OR LGPL-3.0-or-later", "spdxExpression": "GPL-2.0-or-later OR LGPL-3.0-or-later", "type": "declared", "urls": null, "locations": null}], "language": "", "cpes": [{"cpe": "cpe:2.3:a:libidn2:libidn2:2.3.7-r0:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:libidn2:libidn:2.3.7-r0:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:libidn:libidn2:2.3.7-r0:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:libidn:libidn:2.3.7-r0:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:gnu:libidn2:2.3.7-r0:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:gnu:libidn:2.3.7-r0:*:*:*:*:*:*:*", "source": "syft-generated"}], "purl": "pkg:apk/alpine/libidn2@2.3.7-r0?arch=x86_64&distro=alpine-3.21.0", "metadataType": "apk-db-entry", "metadata": {"package": "", "originPackage": "libidn2", "maintainer": "", "version": "", "architecture": "", "url": "", "description": "", "size": 0, "installedSize": 0, "pullDependencies": null, "provides": null, "pullChecksum": "", "gitCommitOfApkPort": "", "files": null}}, {"id": "fc2164ebd15bcb82", "name": "libpsl", "version": "0.21.5-r3", "type": "apk", "foundBy": "", "locations": [{"path": "/lib/apk/db/installed", "layerID": "sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d", "accessPath": ""}], "licenses": [{"value": "MIT", "spdxExpression": "MIT", "type": "declared", "urls": null, "locations": null}], "language": "", "cpes": [{"cpe": "cpe:2.3:a:libpsl:libpsl:0.21.5-r3:*:*:*:*:*:*:*", "source": "syft-generated"}], "purl": "pkg:apk/alpine/libpsl@0.21.5-r3?arch=x86_64&distro=alpine-3.21.0", "metadataType": "apk-db-entry", "metadata": {"package": "", "originPackage": "libpsl", "maintainer": "", "version": "", "architecture": "", "url": "", "description": "", "size": 0, "installedSize": 0, "pullDependencies": null, "provides": null, "pullChecksum": "", "gitCommitOfApkPort": "", "files": null}}, {"id": "ba1753b9ff7d9303", "name": "libssh2", "version": "1.11.1-r0", "type": "apk", "foundBy": "", "locations": [{"path": "/lib/apk/db/installed", "layerID": "sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d", "accessPath": ""}], "licenses": [{"value": "BSD-3-Clause", "spdxExpression": "BSD-3-Clause", "type": "declared", "urls": null, "locations": null}], "language": "", "cpes": [{"cpe": "cpe:2.3:a:libssh2:libssh2:1.11.1-r0:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:libssh2:libssh:1.11.1-r0:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:libssh:libssh2:1.11.1-r0:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:libssh:libssh:1.11.1-r0:*:*:*:*:*:*:*", "source": "syft-generated"}], "purl": "pkg:apk/alpine/libssh2@1.11.1-r0?arch=x86_64&distro=alpine-3.21.0", "metadataType": "apk-db-entry", "metadata": {"package": "", "originPackage": "libssh2", "maintainer": "", "version": "", "architecture": "", "url": "", "description": "", "size": 0, "installedSize": 0, "pullDependencies": null, "provides": null, "pullChecksum": "", "gitCommitOfApkPort": "", "files": null}}, {"id": "0f1bae216faefb4b", "name": "libssl3", "version": "3.3.2-r4", "type": "apk", "foundBy": "", "locations": [{"path": "/lib/apk/db/installed", "layerID": "sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d", "accessPath": ""}], "licenses": [{"value": "Apache-2.0", "spdxExpression": "Apache-2.0", "type": "declared", "urls": null, "locations": null}], "language": "", "cpes": [{"cpe": "cpe:2.3:a:libssl3:libssl3:3.3.2-r4:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:libssl3:libssl:3.3.2-r4:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:libssl:libssl3:3.3.2-r4:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:libssl:libssl:3.3.2-r4:*:*:*:*:*:*:*", "source": "syft-generated"}], "purl": "pkg:apk/alpine/libssl3@3.3.2-r4?arch=x86_64&distro=alpine-3.21.0&upstream=openssl", "metadataType": "apk-db-entry", "metadata": {"package": "", "originPackage": "openssl", "maintainer": "", "version": "", "architecture": "", "url": "", "description": "", "size": 0, "installedSize": 0, "pullDependencies": null, "provides": null, "pullChecksum": "", "gitCommitOfApkPort": "", "files": null}}, {"id": "f579a97329d24094", "name": "libstdc++", "version": "14.2.0-r4", "type": "apk", "foundBy": "", "locations": [{"path": "/lib/apk/db/installed", "layerID": "sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d", "accessPath": ""}], "licenses": [{"value": "GPL-2.0-or-later AND LGPL-2.1-or-later", "spdxExpression": "GPL-2.0-or-later AND LGPL-2.1-or-later", "type": "declared", "urls": null, "locations": null}], "language": "", "cpes": [{"cpe": "cpe:2.3:a:libstdc\\+\\+:libstdc\\+\\+:14.2.0-r4:*:*:*:*:*:*:*", "source": "syft-generated"}], "purl": "pkg:apk/alpine/libstdc%2B%2B@14.2.0-r4?arch=x86_64&distro=alpine-3.21.0&upstream=gcc", "metadataType": "apk-db-entry", "metadata": {"package": "", "originPackage": "gcc", "maintainer": "", "version": "", "architecture": "", "url": "", "description": "", "size": 0, "installedSize": 0, "pullDependencies": null, "provides": null, "pullChecksum": "", "gitCommitOfApkPort": "", "files": null}}, {"id": "07c4841b11e2910b", "name": "libunistring", "version": "1.2-r0", "type": "apk", "foundBy": "", "locations": [{"path": "/lib/apk/db/installed", "layerID": "sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d", "accessPath": ""}], "licenses": [{"value": "GPL-2.0-or-later OR LGPL-3.0-or-later", "spdxExpression": "GPL-2.0-or-later OR LGPL-3.0-or-later", "type": "declared", "urls": null, "locations": null}], "language": "", "cpes": [{"cpe": "cpe:2.3:a:libunistring:libunistring:1.2-r0:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:gnu:libunistring:1.2-r0:*:*:*:*:*:*:*", "source": "syft-generated"}], "purl": "pkg:apk/alpine/libunistring@1.2-r0?arch=x86_64&distro=alpine-3.21.0", "metadataType": "apk-db-entry", "metadata": {"package": "", "originPackage": "libunistring", "maintainer": "", "version": "", "architecture": "", "url": "", "description": "", "size": 0, "installedSize": 0, "pullDependencies": null, "provides": null, "pullChecksum": "", "gitCommitOfApkPort": "", "files": null}}, {"id": "00fb4f5e90a57319", "name": "libverto", "version": "0.3.2-r2", "type": "apk", "foundBy": "", "locations": [{"path": "/lib/apk/db/installed", "layerID": "sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d", "accessPath": ""}], "licenses": [{"value": "MIT", "spdxExpression": "MIT", "type": "declared", "urls": null, "locations": null}], "language": "", "cpes": [{"cpe": "cpe:2.3:a:latchset:libverto:0.3.2-r2:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:libverto:libverto:0.3.2-r2:*:*:*:*:*:*:*", "source": "syft-generated"}], "purl": "pkg:apk/alpine/libverto@0.3.2-r2?arch=x86_64&distro=alpine-3.21.0", "metadataType": "apk-db-entry", "metadata": {"package": "", "originPackage": "libverto", "maintainer": "", "version": "", "architecture": "", "url": "", "description": "", "size": 0, "installedSize": 0, "pullDependencies": null, "provides": null, "pullChecksum": "", "gitCommitOfApkPort": "", "files": null}}, {"id": "25a411dcbf4c73ba", "name": "musl", "version": "1.2.5-r8", "type": "apk", "foundBy": "", "locations": [{"path": "/lib/apk/db/installed", "layerID": "sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d", "accessPath": ""}], "licenses": [{"value": "MIT", "spdxExpression": "MIT", "type": "declared", "urls": null, "locations": null}], "language": "", "cpes": [{"cpe": "cpe:2.3:a:musl-libc:musl:1.2.5-r8:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:musl_libc:musl:1.2.5-r8:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:musl:musl:1.2.5-r8:*:*:*:*:*:*:*", "source": "syft-generated"}], "purl": "pkg:apk/alpine/musl@1.2.5-r8?arch=x86_64&distro=alpine-3.21.0", "metadataType": "apk-db-entry", "metadata": {"package": "", "originPackage": "musl", "maintainer": "", "version": "", "architecture": "", "url": "", "description": "", "size": 0, "installedSize": 0, "pullDependencies": null, "provides": null, "pullChecksum": "", "gitCommitOfApkPort": "", "files": null}}, {"id": "9ed24d5185052508", "name": "musl-utils", "version": "1.2.5-r8", "type": "apk", "foundBy": "", "locations": [{"path": "/lib/apk/db/installed", "layerID": "sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d", "accessPath": ""}], "licenses": [{"value": "MIT AND BSD-2-Clause AND GPL-2.0-or-later", "spdxExpression": "MIT AND BSD-2-Clause AND GPL-2.0-or-later", "type": "declared", "urls": null, "locations": null}], "language": "", "cpes": [{"cpe": "cpe:2.3:a:musl-utils:musl-utils:1.2.5-r8:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:musl-utils:musl_utils:1.2.5-r8:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:musl_utils:musl-utils:1.2.5-r8:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:musl_utils:musl_utils:1.2.5-r8:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:musl-libc:musl-utils:1.2.5-r8:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:musl-libc:musl_utils:1.2.5-r8:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:musl:musl-utils:1.2.5-r8:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:musl:musl_utils:1.2.5-r8:*:*:*:*:*:*:*", "source": "syft-generated"}], "purl": "pkg:apk/alpine/musl-utils@1.2.5-r8?arch=x86_64&distro=alpine-3.21.0&upstream=musl", "metadataType": "apk-db-entry", "metadata": {"package": "", "originPackage": "musl", "maintainer": "", "version": "", "architecture": "", "url": "", "description": "", "size": 0, "installedSize": 0, "pullDependencies": null, "provides": null, "pullChecksum": "", "gitCommitOfApkPort": "", "files": null}}, {"id": "c9d137d8d21db486", "name": "nghttp2-dev", "version": "1.64.0-r0", "type": "apk", "foundBy": "", "locations": [{"path": "/lib/apk/db/installed", "layerID": "sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d", "accessPath": ""}], "licenses": [{"value": "MIT", "spdxExpression": "MIT", "type": "declared", "urls": null, "locations": null}], "language": "", "cpes": [{"cpe": "cpe:2.3:a:nghttp2-dev:nghttp2-dev:1.64.0-r0:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:nghttp2-dev:nghttp2_dev:1.64.0-r0:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:nghttp2_dev:nghttp2-dev:1.64.0-r0:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:nghttp2_dev:nghttp2_dev:1.64.0-r0:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:nghttp-dev:nghttp2-dev:1.64.0-r0:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:nghttp-dev:nghttp2_dev:1.64.0-r0:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:nghttp2-dev:nghttp-dev:1.64.0-r0:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:nghttp2-dev:nghttp_dev:1.64.0-r0:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:nghttp2_dev:nghttp-dev:1.64.0-r0:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:nghttp2_dev:nghttp_dev:1.64.0-r0:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:nghttp_dev:nghttp2-dev:1.64.0-r0:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:nghttp_dev:nghttp2_dev:1.64.0-r0:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:nghttp-dev:nghttp-dev:1.64.0-r0:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:nghttp-dev:nghttp_dev:1.64.0-r0:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:nghttp_dev:nghttp-dev:1.64.0-r0:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:nghttp_dev:nghttp_dev:1.64.0-r0:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:nghttp2:nghttp2-dev:1.64.0-r0:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:nghttp2:nghttp2_dev:1.64.0-r0:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:nghttp2:nghttp-dev:1.64.0-r0:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:nghttp2:nghttp_dev:1.64.0-r0:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:nghttp:nghttp2-dev:1.64.0-r0:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:nghttp:nghttp2_dev:1.64.0-r0:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:nghttp:nghttp-dev:1.64.0-r0:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:nghttp:nghttp_dev:1.64.0-r0:*:*:*:*:*:*:*", "source": "syft-generated"}], "purl": "pkg:apk/alpine/nghttp2-dev@1.64.0-r0?arch=x86_64&distro=alpine-3.21.0&upstream=nghttp2", "metadataType": "apk-db-entry", "metadata": {"package": "", "originPackage": "nghttp2", "maintainer": "", "version": "", "architecture": "", "url": "", "description": "", "size": 0, "installedSize": 0, "pullDependencies": null, "provides": null, "pullChecksum": "", "gitCommitOfApkPort": "", "files": null}}, {"id": "c16da0382f03c90a", "name": "nghttp2-libs", "version": "1.64.0-r0", "type": "apk", "foundBy": "", "locations": [{"path": "/lib/apk/db/installed", "layerID": "sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d", "accessPath": ""}], "licenses": [{"value": "MIT", "spdxExpression": "MIT", "type": "declared", "urls": null, "locations": null}], "language": "", "cpes": [{"cpe": "cpe:2.3:a:nghttp2-libs:nghttp2-libs:1.64.0-r0:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:nghttp2-libs:nghttp2_libs:1.64.0-r0:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:nghttp2_libs:nghttp2-libs:1.64.0-r0:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:nghttp2_libs:nghttp2_libs:1.64.0-r0:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:nghttp-libs:nghttp2-libs:1.64.0-r0:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:nghttp-libs:nghttp2_libs:1.64.0-r0:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:nghttp2-libs:nghttp-libs:1.64.0-r0:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:nghttp2-libs:nghttp_libs:1.64.0-r0:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:nghttp2_libs:nghttp-libs:1.64.0-r0:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:nghttp2_libs:nghttp_libs:1.64.0-r0:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:nghttp_libs:nghttp2-libs:1.64.0-r0:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:nghttp_libs:nghttp2_libs:1.64.0-r0:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:nghttp-libs:nghttp-libs:1.64.0-r0:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:nghttp-libs:nghttp_libs:1.64.0-r0:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:nghttp_libs:nghttp-libs:1.64.0-r0:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:nghttp_libs:nghttp_libs:1.64.0-r0:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:nghttp2:nghttp2-libs:1.64.0-r0:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:nghttp2:nghttp2_libs:1.64.0-r0:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:nghttp2:nghttp-libs:1.64.0-r0:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:nghttp2:nghttp_libs:1.64.0-r0:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:nghttp:nghttp2-libs:1.64.0-r0:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:nghttp:nghttp2_libs:1.64.0-r0:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:nghttp:nghttp-libs:1.64.0-r0:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:nghttp:nghttp_libs:1.64.0-r0:*:*:*:*:*:*:*", "source": "syft-generated"}], "purl": "pkg:apk/alpine/nghttp2-libs@1.64.0-r0?arch=x86_64&distro=alpine-3.21.0&upstream=nghttp2", "metadataType": "apk-db-entry", "metadata": {"package": "", "originPackage": "nghttp2", "maintainer": "", "version": "", "architecture": "", "url": "", "description": "", "size": 0, "installedSize": 0, "pullDependencies": null, "provides": null, "pullChecksum": "", "gitCommitOfApkPort": "", "files": null}}, {"id": "c86c3c8f976d31b1", "name": "pkgconf", "version": "2.3.0-r0", "type": "apk", "foundBy": "", "locations": [{"path": "/lib/apk/db/installed", "layerID": "sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d", "accessPath": ""}], "licenses": [{"value": "ISC", "spdxExpression": "ISC", "type": "declared", "urls": null, "locations": null}], "language": "", "cpes": [{"cpe": "cpe:2.3:a:pkgconf:pkgconf:2.3.0-r0:*:*:*:*:*:*:*", "source": "syft-generated"}], "purl": "pkg:apk/alpine/pkgconf@2.3.0-r0?arch=x86_64&distro=alpine-3.21.0", "metadataType": "apk-db-entry", "metadata": {"package": "", "originPackage": "pkgconf", "maintainer": "", "version": "", "architecture": "", "url": "", "description": "", "size": 0, "installedSize": 0, "pullDependencies": null, "provides": null, "pullChecksum": "", "gitCommitOfApkPort": "", "files": null}}, {"id": "e70542a40cdca454", "name": "scanelf", "version": "1.3.8-r1", "type": "apk", "foundBy": "", "locations": [{"path": "/lib/apk/db/installed", "layerID": "sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d", "accessPath": ""}], "licenses": [{"value": "GPL-2.0-only", "spdxExpression": "GPL-2.0-only", "type": "declared", "urls": null, "locations": null}], "language": "", "cpes": [{"cpe": "cpe:2.3:a:scanelf:scanelf:1.3.8-r1:*:*:*:*:*:*:*", "source": "syft-generated"}], "purl": "pkg:apk/alpine/scanelf@1.3.8-r1?arch=x86_64&distro=alpine-3.21.0&upstream=pax-utils", "metadataType": "apk-db-entry", "metadata": {"package": "", "originPackage": "pax-utils", "maintainer": "", "version": "", "architecture": "", "url": "", "description": "", "size": 0, "installedSize": 0, "pullDependencies": null, "provides": null, "pullChecksum": "", "gitCommitOfApkPort": "", "files": null}}, {"id": "3b2d5f71701ee5a7", "name": "ssl_client", "version": "1.37.0-r8", "type": "apk", "foundBy": "", "locations": [{"path": "/lib/apk/db/installed", "layerID": "sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d", "accessPath": ""}], "licenses": [{"value": "GPL-2.0-only", "spdxExpression": "GPL-2.0-only", "type": "declared", "urls": null, "locations": null}], "language": "", "cpes": [{"cpe": "cpe:2.3:a:ssl-client:ssl-client:1.37.0-r8:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:ssl-client:ssl_client:1.37.0-r8:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:ssl_client:ssl-client:1.37.0-r8:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:ssl_client:ssl_client:1.37.0-r8:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:ssl:ssl-client:1.37.0-r8:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:ssl:ssl_client:1.37.0-r8:*:*:*:*:*:*:*", "source": "syft-generated"}], "purl": "pkg:apk/alpine/ssl_client@1.37.0-r8?arch=x86_64&distro=alpine-3.21.0&upstream=busybox", "metadataType": "apk-db-entry", "metadata": {"package": "", "originPackage": "busybox", "maintainer": "", "version": "", "architecture": "", "url": "", "description": "", "size": 0, "installedSize": 0, "pullDependencies": null, "provides": null, "pullChecksum": "", "gitCommitOfApkPort": "", "files": null}}, {"id": "bb1801fcfd6835ff", "name": "zlib", "version": "1.3.1-r2", "type": "apk", "foundBy": "", "locations": [{"path": "/lib/apk/db/installed", "layerID": "sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d", "accessPath": ""}], "licenses": [{"value": "Zlib", "spdxExpression": "Zlib", "type": "declared", "urls": null, "locations": null}], "language": "", "cpes": [{"cpe": "cpe:2.3:a:zlib:zlib:1.3.1-r2:*:*:*:*:*:*:*", "source": "syft-generated"}], "purl": "pkg:apk/alpine/zlib@1.3.1-r2?arch=x86_64&distro=alpine-3.21.0", "metadataType": "apk-db-entry", "metadata": {"package": "", "originPackage": "zlib", "maintainer": "", "version": "", "architecture": "", "url": "", "description": "", "size": 0, "installedSize": 0, "pullDependencies": null, "provides": null, "pullChecksum": "", "gitCommitOfApkPort": "", "files": null}}, {"id": "986677a068b531ae", "name": "zstd", "version": "1.5.6-r1", "type": "apk", "foundBy": "", "locations": [{"path": "/lib/apk/db/installed", "layerID": "sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d", "accessPath": ""}], "licenses": [{"value": "BSD-3-Clause OR GPL-2.0-or-later", "spdxExpression": "BSD-3-Clause OR GPL-2.0-or-later", "type": "declared", "urls": null, "locations": null}], "language": "", "cpes": [{"cpe": "cpe:2.3:a:zstd:zstd:1.5.6-r1:*:*:*:*:*:*:*", "source": "syft-generated"}], "purl": "pkg:apk/alpine/zstd@1.5.6-r1?arch=x86_64&distro=alpine-3.21.0", "metadataType": "apk-db-entry", "metadata": {"package": "", "originPackage": "zstd", "maintainer": "", "version": "", "architecture": "", "url": "", "description": "", "size": 0, "installedSize": 0, "pullDependencies": null, "provides": null, "pullChecksum": "", "gitCommitOfApkPort": "", "files": null}}, {"id": "9661e4d8b6fc57db", "name": "zstd-libs", "version": "1.5.6-r1", "type": "apk", "foundBy": "", "locations": [{"path": "/lib/apk/db/installed", "layerID": "sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d", "accessPath": ""}], "licenses": [{"value": "BSD-3-Clause OR GPL-2.0-or-later", "spdxExpression": "BSD-3-Clause OR GPL-2.0-or-later", "type": "declared", "urls": null, "locations": null}], "language": "", "cpes": [{"cpe": "cpe:2.3:a:zstd-libs:zstd-libs:1.5.6-r1:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:zstd-libs:zstd_libs:1.5.6-r1:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:zstd_libs:zstd-libs:1.5.6-r1:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:zstd_libs:zstd_libs:1.5.6-r1:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:zstd:zstd-libs:1.5.6-r1:*:*:*:*:*:*:*", "source": "syft-generated"}, {"cpe": "cpe:2.3:a:zstd:zstd_libs:1.5.6-r1:*:*:*:*:*:*:*", "source": "syft-generated"}], "purl": "pkg:apk/alpine/zstd-libs@1.5.6-r1?arch=x86_64&distro=alpine-3.21.0&upstream=zstd", "metadataType": "apk-db-entry", "metadata": {"package": "", "originPackage": "zstd", "maintainer": "", "version": "", "architecture": "", "url": "", "description": "", "size": 0, "installedSize": 0, "pullDependencies": null, "provides": null, "pullChecksum": "", "gitCommitOfApkPort": "", "files": null}}], "artifactRelationships": [{"parent": "00fb4f5e90a57319", "child": "05d2eab904a84e7e", "type": "evident-by"}, {"parent": "00fb4f5e90a57319", "child": "550be067c8009c6f", "type": "contains"}, {"parent": "00fb4f5e90a57319", "child": "67d8ec97d517d9c2", "type": "dependency-of"}, {"parent": "037ba9595fe77699", "child": "00c4e2d01d480665", "type": "contains"}, {"parent": "037ba9595fe77699", "child": "05d2eab904a84e7e", "type": "evident-by"}, {"parent": "037ba9595fe77699", "child": "29c097271e009ca3", "type": "contains"}, {"parent": "037ba9595fe77699", "child": "43c67ab0a2c85c2b", "type": "contains"}, {"parent": "037ba9595fe77699", "child": "6a9ff817a6522527", "type": "contains"}, {"parent": "037ba9595fe77699", "child": "70c81ce36219cef2", "type": "contains"}, {"parent": "037ba9595fe77699", "child": "72fca5accef8f2a0", "type": "contains"}, {"parent": "037ba9595fe77699", "child": "75f32f96c2d07d86", "type": "contains"}, {"parent": "037ba9595fe77699", "child": "a1e5642bfd332967", "type": "contains"}, {"parent": "037ba9595fe77699", "child": "ab50f8a62f73e17c", "type": "contains"}, {"parent": "037ba9595fe77699", "child": "e1780308bd2745ec", "type": "contains"}, {"parent": "037ba9595fe77699", "child": "e580594caad1a9a9", "type": "contains"}, {"parent": "07c4841b11e2910b", "child": "05d2eab904a84e7e", "type": "evident-by"}, {"parent": "07c4841b11e2910b", "child": "472a25f3f0ecdbd7", "type": "contains"}, {"parent": "07c4841b11e2910b", "child": "97c06e6e1903e776", "type": "dependency-of"}, {"parent": "07c4841b11e2910b", "child": "fc2164ebd15bcb82", "type": "dependency-of"}, {"parent": "0f1bae216faefb4b", "child": "05d2eab904a84e7e", "type": "evident-by"}, {"parent": "0f1bae216faefb4b", "child": "1095a39f80569c66", "type": "dependency-of"}, {"parent": "0f1bae216faefb4b", "child": "3b2d5f71701ee5a7", "type": "dependency-of"}, {"parent": "0f1bae216faefb4b", "child": "67d8ec97d517d9c2", "type": "dependency-of"}, {"parent": "0f1bae216faefb4b", "child": "69cc85b55482eba1", "type": "contains"}, {"parent": "1095a39f80569c66", "child": "05d2eab904a84e7e", "type": "evident-by"}, {"parent": "1095a39f80569c66", "child": "0c90571c31cc24d4", "type": "contains"}, {"parent": "1095a39f80569c66", "child": "4e2deeb47f39a10e", "type": "contains"}, {"parent": "1a41f8b73cec7362", "child": "05d2eab904a84e7e", "type": "evident-by"}, {"parent": "1a41f8b73cec7362", "child": "2ebaf2d5275a87c3", "type": "contains"}, {"parent": "1a41f8b73cec7362", "child": "4d414d1737405098", "type": "contains"}, {"parent": "1a41f8b73cec7362", "child": "4d8d5f0587154018", "type": "contains"}, {"parent": "1a41f8b73cec7362", "child": "5e64bee7d0512520", "type": "contains"}, {"parent": "1a41f8b73cec7362", "child": "5f3d3b1ea52cd203", "type": "contains"}, {"parent": "1a41f8b73cec7362", "child": "5f409797065e8a24", "type": "contains"}, {"parent": "1a41f8b73cec7362", "child": "668425e791126652", "type": "contains"}, {"parent": "1a41f8b73cec7362", "child": "773e11b623fa2206", "type": "contains"}, {"parent": "1a41f8b73cec7362", "child": "86a05755e77e07fc", "type": "contains"}, {"parent": "1a41f8b73cec7362", "child": "a3196be7d01a0aec", "type": "contains"}, {"parent": "1a41f8b73cec7362", "child": "b3633bc811ad48c9", "type": "contains"}, {"parent": "1a41f8b73cec7362", "child": "b80c3cb56c980b08", "type": "contains"}, {"parent": "1a41f8b73cec7362", "child": "c068f4c2abc469f7", "type": "contains"}, {"parent": "1a41f8b73cec7362", "child": "ceeb874c4a135dac", "type": "dependency-of"}, {"parent": "1a41f8b73cec7362", "child": "e7d0d8adb0625ce4", "type": "contains"}, {"parent": "25a411dcbf4c73ba", "child": "00fb4f5e90a57319", "type": "dependency-of"}, {"parent": "25a411dcbf4c73ba", "child": "05d2eab904a84e7e", "type": "evident-by"}, {"parent": "25a411dcbf4c73ba", "child": "07c4841b11e2910b", "type": "dependency-of"}, {"parent": "25a411dcbf4c73ba", "child": "0f1bae216faefb4b", "type": "dependency-of"}, {"parent": "25a411dcbf4c73ba", "child": "1095a39f80569c66", "type": "dependency-of"}, {"parent": "25a411dcbf4c73ba", "child": "3b2d5f71701ee5a7", "type": "dependency-of"}, {"parent": "25a411dcbf4c73ba", "child": "3d4d4f53df422ab4", "type": "dependency-of"}, {"parent": "25a411dcbf4c73ba", "child": "412b42ad943bf8cb", "type": "dependency-of"}, {"parent": "25a411dcbf4c73ba", "child": "5c84d53d49f221e1", "type": "dependency-of"}, {"parent": "25a411dcbf4c73ba", "child": "67d8ec97d517d9c2", "type": "dependency-of"}, {"parent": "25a411dcbf4c73ba", "child": "6e7e3004534b86d1", "type": "contains"}, {"parent": "25a411dcbf4c73ba", "child": "7cbfd25df10d2724", "type": "dependency-of"}, {"parent": "25a411dcbf4c73ba", "child": "88109714e4e7213c", "type": "dependency-of"}, {"parent": "25a411dcbf4c73ba", "child": "8f69c879d6b38e38", "type": "dependency-of"}, {"parent": "25a411dcbf4c73ba", "child": "9661e4d8b6fc57db", "type": "dependency-of"}, {"parent": "25a411dcbf4c73ba", "child": "97c06e6e1903e776", "type": "dependency-of"}, {"parent": "25a411dcbf4c73ba", "child": "986677a068b531ae", "type": "dependency-of"}, {"parent": "25a411dcbf4c73ba", "child": "9ed24d5185052508", "type": "dependency-of"}, {"parent": "25a411dcbf4c73ba", "child": "ba1753b9ff7d9303", "type": "dependency-of"}, {"parent": "25a411dcbf4c73ba", "child": "bb1801fcfd6835ff", "type": "dependency-of"}, {"parent": "25a411dcbf4c73ba", "child": "c16da0382f03c90a", "type": "dependency-of"}, {"parent": "25a411dcbf4c73ba", "child": "c86c3c8f976d31b1", "type": "dependency-of"}, {"parent": "25a411dcbf4c73ba", "child": "dbfb4d1dbecb422f", "type": "dependency-of"}, {"parent": "25a411dcbf4c73ba", "child": "e13769c76f74134e", "type": "dependency-of"}, {"parent": "25a411dcbf4c73ba", "child": "e3ed65b6dc9144b9", "type": "dependency-of"}, {"parent": "25a411dcbf4c73ba", "child": "e70542a40cdca454", "type": "dependency-of"}, {"parent": "25a411dcbf4c73ba", "child": "f579a97329d24094", "type": "dependency-of"}, {"parent": "25a411dcbf4c73ba", "child": "fc2164ebd15bcb82", "type": "dependency-of"}, {"parent": "3a2e3359d51d0045", "child": "05d2eab904a84e7e", "type": "evident-by"}, {"parent": "3a2e3359d51d0045", "child": "468f4889734362f5", "type": "contains"}, {"parent": "3a2e3359d51d0045", "child": "ceeb874c4a135dac", "type": "dependency-of"}, {"parent": "3b2d5f71701ee5a7", "child": "05d2eab904a84e7e", "type": "evident-by"}, {"parent": "3b2d5f71701ee5a7", "child": "b4bab8e79035429e", "type": "contains"}, {"parent": "3d4d4f53df422ab4", "child": "05d2eab904a84e7e", "type": "evident-by"}, {"parent": "3d4d4f53df422ab4", "child": "986677a068b531ae", "type": "dependency-of"}, {"parent": "3d4d4f53df422ab4", "child": "c394fbc850ea8753", "type": "contains"}, {"parent": "3d4d4f53df422ab4", "child": "f579a97329d24094", "type": "dependency-of"}, {"parent": "3e249a6f2317504e", "child": "0607fb48d6a867d9", "type": "evident-by"}, {"parent": "412b42ad943bf8cb", "child": "05d2eab904a84e7e", "type": "evident-by"}, {"parent": "412b42ad943bf8cb", "child": "16db6b7867c5e19b", "type": "contains"}, {"parent": "412b42ad943bf8cb", "child": "55e808d1502ce1f5", "type": "contains"}, {"parent": "412b42ad943bf8cb", "child": "c08f16a4a723e201", "type": "contains"}, {"parent": "412b42ad943bf8cb", "child": "e13769c76f74134e", "type": "dependency-of"}, {"parent": "456e920c4331bde6", "child": "05d2eab904a84e7e", "type": "evident-by"}, {"parent": "456e920c4331bde6", "child": "31f4ab357226cd60", "type": "contains"}, {"parent": "456e920c4331bde6", "child": "b413295f2e1636b9", "type": "contains"}, {"parent": "456e920c4331bde6", "child": "c36f73b65f306d73", "type": "contains"}, {"parent": "456e920c4331bde6", "child": "fd2f197c9c548c66", "type": "contains"}, {"parent": "5449192ad1853f9c", "child": "05d2eab904a84e7e", "type": "evident-by"}, {"parent": "5449192ad1853f9c", "child": "614c65d7d5699747", "type": "contains"}, {"parent": "5449192ad1853f9c", "child": "67d8ec97d517d9c2", "type": "dependency-of"}, {"parent": "5449192ad1853f9c", "child": "e13769c76f74134e", "type": "dependency-of"}, {"parent": "5c84d53d49f221e1", "child": "05d2eab904a84e7e", "type": "evident-by"}, {"parent": "5c84d53d49f221e1", "child": "67d8ec97d517d9c2", "type": "dependency-of"}, {"parent": "5c84d53d49f221e1", "child": "d500a230de1b6100", "type": "contains"}, {"parent": "67d8ec97d517d9c2", "child": "05d2eab904a84e7e", "type": "evident-by"}, {"parent": "67d8ec97d517d9c2", "child": "1b4fe1c27bee3d6f", "type": "contains"}, {"parent": "67d8ec97d517d9c2", "child": "319aab2b06d5c638", "type": "contains"}, {"parent": "67d8ec97d517d9c2", "child": "3c1e7db652a4d4c1", "type": "contains"}, {"parent": "67d8ec97d517d9c2", "child": "4e51d5fe7f02b74f", "type": "contains"}, {"parent": "67d8ec97d517d9c2", "child": "5ae7577620ec6cc6", "type": "contains"}, {"parent": "67d8ec97d517d9c2", "child": "6a3ad7d0df5ad0b8", "type": "contains"}, {"parent": "67d8ec97d517d9c2", "child": "74a405952a8bacd5", "type": "contains"}, {"parent": "67d8ec97d517d9c2", "child": "803e7fbfe5b428cc", "type": "contains"}, {"parent": "67d8ec97d517d9c2", "child": "8663ae9af4fed258", "type": "contains"}, {"parent": "67d8ec97d517d9c2", "child": "a490c4f82f21f144", "type": "contains"}, {"parent": "67d8ec97d517d9c2", "child": "a6ee5aa1579ea184", "type": "contains"}, {"parent": "67d8ec97d517d9c2", "child": "aa1f2ef0278a32b8", "type": "contains"}, {"parent": "67d8ec97d517d9c2", "child": "bf5b8a5792ae8ce3", "type": "contains"}, {"parent": "67d8ec97d517d9c2", "child": "c042c0248a70b846", "type": "contains"}, {"parent": "67d8ec97d517d9c2", "child": "e13769c76f74134e", "type": "dependency-of"}, {"parent": "7cbfd25df10d2724", "child": "05d2eab904a84e7e", "type": "evident-by"}, {"parent": "7cbfd25df10d2724", "child": "378d389540b30911", "type": "contains"}, {"parent": "7cbfd25df10d2724", "child": "3a2e3359d51d0045", "type": "dependency-of"}, {"parent": "7cbfd25df10d2724", "child": "3cbfe16761873575", "type": "contains"}, {"parent": "7cbfd25df10d2724", "child": "431ddc0b26fa5372", "type": "contains"}, {"parent": "7cbfd25df10d2724", "child": "468f4889734362f5", "type": "contains"}, {"parent": "7cbfd25df10d2724", "child": "ae53711b4c2ae508", "type": "contains"}, {"parent": "7cbfd25df10d2724", "child": "e680a539c74e700d", "type": "contains"}, {"parent": "7cbfd25df10d2724", "child": "f5ddc6e8bf552536", "type": "contains"}, {"parent": "88109714e4e7213c", "child": "05d2eab904a84e7e", "type": "evident-by"}, {"parent": "88109714e4e7213c", "child": "0f1bae216faefb4b", "type": "dependency-of"}, {"parent": "88109714e4e7213c", "child": "1095a39f80569c66", "type": "dependency-of"}, {"parent": "88109714e4e7213c", "child": "2230852154a764a9", "type": "contains"}, {"parent": "88109714e4e7213c", "child": "2cac453818c05153", "type": "contains"}, {"parent": "88109714e4e7213c", "child": "3b2d5f71701ee5a7", "type": "dependency-of"}, {"parent": "88109714e4e7213c", "child": "51e3b429173363b7", "type": "contains"}, {"parent": "88109714e4e7213c", "child": "67d8ec97d517d9c2", "type": "dependency-of"}, {"parent": "88109714e4e7213c", "child": "70e74fc481bbd7b7", "type": "contains"}, {"parent": "88109714e4e7213c", "child": "7386b7ea31c8254f", "type": "contains"}, {"parent": "88109714e4e7213c", "child": "97ee0f53ad135fdd", "type": "contains"}, {"parent": "88109714e4e7213c", "child": "9afb2be8c7429f0d", "type": "contains"}, {"parent": "88109714e4e7213c", "child": "a36b11ef91e748da", "type": "contains"}, {"parent": "88109714e4e7213c", "child": "b2bd5535e09327d5", "type": "contains"}, {"parent": "88109714e4e7213c", "child": "ba1753b9ff7d9303", "type": "dependency-of"}, {"parent": "88109714e4e7213c", "child": "ff43617ec381da18", "type": "contains"}, {"parent": "8f69c879d6b38e38", "child": "00c4e2d01d480665", "type": "contains"}, {"parent": "8f69c879d6b38e38", "child": "037ba9595fe77699", "type": "dependency-of"}, {"parent": "8f69c879d6b38e38", "child": "05d2eab904a84e7e", "type": "evident-by"}, {"parent": "8f69c879d6b38e38", "child": "29c097271e009ca3", "type": "contains"}, {"parent": "8f69c879d6b38e38", "child": "75f32f96c2d07d86", "type": "contains"}, {"parent": "8f69c879d6b38e38", "child": "e3ed65b6dc9144b9", "type": "dependency-of"}, {"parent": "9661e4d8b6fc57db", "child": "05d2eab904a84e7e", "type": "evident-by"}, {"parent": "9661e4d8b6fc57db", "child": "986677a068b531ae", "type": "dependency-of"}, {"parent": "9661e4d8b6fc57db", "child": "bf9888330a19323c", "type": "contains"}, {"parent": "97c06e6e1903e776", "child": "05d2eab904a84e7e", "type": "evident-by"}, {"parent": "97c06e6e1903e776", "child": "9c5437be4d2bd035", "type": "contains"}, {"parent": "97c06e6e1903e776", "child": "fc2164ebd15bcb82", "type": "dependency-of"}, {"parent": "986677a068b531ae", "child": "05d2eab904a84e7e", "type": "evident-by"}, {"parent": "986677a068b531ae", "child": "1c988daa02be9ff4", "type": "contains"}, {"parent": "986677a068b531ae", "child": "1d49d01d1338ec6e", "type": "contains"}, {"parent": "986677a068b531ae", "child": "94931d779ffec08e", "type": "contains"}, {"parent": "986677a068b531ae", "child": "e9a806dcd1a078d7", "type": "contains"}, {"parent": "9ed24d5185052508", "child": "05d2eab904a84e7e", "type": "evident-by"}, {"parent": "9ed24d5185052508", "child": "485cab4248b11e3f", "type": "contains"}, {"parent": "9ed24d5185052508", "child": "b33e7a49b30565e7", "type": "contains"}, {"parent": "9ed24d5185052508", "child": "cb10741d9c217d07", "type": "contains"}, {"parent": "9ed24d5185052508", "child": "d36d4602a8c0ad50", "type": "contains"}, {"parent": "9ed24d5185052508", "child": "f058d85fa2770b4f", "type": "contains"}, {"parent": "a906f1b13dd9bf14", "child": "05d2eab904a84e7e", "type": "evident-by"}, {"parent": "a906f1b13dd9bf14", "child": "1095a39f80569c66", "type": "dependency-of"}, {"parent": "a906f1b13dd9bf14", "child": "860b9d53645feb47", "type": "contains"}, {"parent": "ba1753b9ff7d9303", "child": "05d2eab904a84e7e", "type": "evident-by"}, {"parent": "ba1753b9ff7d9303", "child": "ca2554e971e8d3da", "type": "contains"}, {"parent": "bb1801fcfd6835ff", "child": "014a43ace0388eab", "type": "contains"}, {"parent": "bb1801fcfd6835ff", "child": "05d2eab904a84e7e", "type": "evident-by"}, {"parent": "bb1801fcfd6835ff", "child": "1095a39f80569c66", "type": "dependency-of"}, {"parent": "bb1801fcfd6835ff", "child": "ba1753b9ff7d9303", "type": "dependency-of"}, {"parent": "c16da0382f03c90a", "child": "05d2eab904a84e7e", "type": "evident-by"}, {"parent": "c16da0382f03c90a", "child": "43052306802633cb", "type": "contains"}, {"parent": "c16da0382f03c90a", "child": "c9d137d8d21db486", "type": "dependency-of"}, {"parent": "c86c3c8f976d31b1", "child": "037ba9595fe77699", "type": "dependency-of"}, {"parent": "c86c3c8f976d31b1", "child": "05d2eab904a84e7e", "type": "evident-by"}, {"parent": "c86c3c8f976d31b1", "child": "6f304251929469c4", "type": "contains"}, {"parent": "c86c3c8f976d31b1", "child": "affcf55f6df321b5", "type": "contains"}, {"parent": "c86c3c8f976d31b1", "child": "c9d137d8d21db486", "type": "dependency-of"}, {"parent": "c86c3c8f976d31b1", "child": "e70de53d7f62bd47", "type": "contains"}, {"parent": "c86c3c8f976d31b1", "child": "ffcacae116b11153", "type": "contains"}, {"parent": "c9d137d8d21db486", "child": "05d2eab904a84e7e", "type": "evident-by"}, {"parent": "c9d137d8d21db486", "child": "43052306802633cb", "type": "contains"}, {"parent": "c9d137d8d21db486", "child": "4eb2fae82928af57", "type": "contains"}, {"parent": "c9d137d8d21db486", "child": "b8714ac0c5e52f87", "type": "contains"}, {"parent": "c9d137d8d21db486", "child": "f62213b368d29ad6", "type": "contains"}, {"parent": "ceeb874c4a135dac", "child": "05d2eab904a84e7e", "type": "evident-by"}, {"parent": "ceeb874c4a135dac", "child": "22834df769d2095e", "type": "contains"}, {"parent": "ceeb874c4a135dac", "child": "4eb653645b7f13ee", "type": "contains"}, {"parent": "ceeb874c4a135dac", "child": "86d9785f3abff156", "type": "contains"}, {"parent": "ceeb874c4a135dac", "child": "96f77289dc1c6105", "type": "contains"}, {"parent": "ceeb874c4a135dac", "child": "b29978812ba855fa", "type": "contains"}, {"parent": "ceeb874c4a135dac", "child": "b58b1a80d90b95b5", "type": "contains"}, {"parent": "ceeb874c4a135dac", "child": "bae009a41fd01a1d", "type": "contains"}, {"parent": "ceeb874c4a135dac", "child": "d70650009c2bb036", "type": "contains"}, {"parent": "ceeb874c4a135dac", "child": "f54933ed0cb86bd2", "type": "contains"}, {"parent": "ceeb874c4a135dac", "child": "ff541b2c108c7e57", "type": "contains"}, {"parent": "dbfb4d1dbecb422f", "child": "05d2eab904a84e7e", "type": "evident-by"}, {"parent": "dbfb4d1dbecb422f", "child": "412b42ad943bf8cb", "type": "dependency-of"}, {"parent": "dbfb4d1dbecb422f", "child": "67d8ec97d517d9c2", "type": "dependency-of"}, {"parent": "dbfb4d1dbecb422f", "child": "e13769c76f74134e", "type": "dependency-of"}, {"parent": "dbfb4d1dbecb422f", "child": "ef996e1f6676f341", "type": "contains"}, {"parent": "docker.io/curlimages/curl@c1fe1679c34d9784c1b0d1e5f62ac0a79fca01fb6377cdd33e90473c6f9f9a69", "child": "00fb4f5e90a57319", "type": "contains"}, {"parent": "docker.io/curlimages/curl@c1fe1679c34d9784c1b0d1e5f62ac0a79fca01fb6377cdd33e90473c6f9f9a69", "child": "037ba9595fe77699", "type": "contains"}, {"parent": "docker.io/curlimages/curl@c1fe1679c34d9784c1b0d1e5f62ac0a79fca01fb6377cdd33e90473c6f9f9a69", "child": "07c4841b11e2910b", "type": "contains"}, {"parent": "docker.io/curlimages/curl@c1fe1679c34d9784c1b0d1e5f62ac0a79fca01fb6377cdd33e90473c6f9f9a69", "child": "0f1bae216faefb4b", "type": "contains"}, {"parent": "docker.io/curlimages/curl@c1fe1679c34d9784c1b0d1e5f62ac0a79fca01fb6377cdd33e90473c6f9f9a69", "child": "1095a39f80569c66", "type": "contains"}, {"parent": "docker.io/curlimages/curl@c1fe1679c34d9784c1b0d1e5f62ac0a79fca01fb6377cdd33e90473c6f9f9a69", "child": "1a41f8b73cec7362", "type": "contains"}, {"parent": "docker.io/curlimages/curl@c1fe1679c34d9784c1b0d1e5f62ac0a79fca01fb6377cdd33e90473c6f9f9a69", "child": "25a411dcbf4c73ba", "type": "contains"}, {"parent": "docker.io/curlimages/curl@c1fe1679c34d9784c1b0d1e5f62ac0a79fca01fb6377cdd33e90473c6f9f9a69", "child": "3a2e3359d51d0045", "type": "contains"}, {"parent": "docker.io/curlimages/curl@c1fe1679c34d9784c1b0d1e5f62ac0a79fca01fb6377cdd33e90473c6f9f9a69", "child": "3b2d5f71701ee5a7", "type": "contains"}, {"parent": "docker.io/curlimages/curl@c1fe1679c34d9784c1b0d1e5f62ac0a79fca01fb6377cdd33e90473c6f9f9a69", "child": "3d4d4f53df422ab4", "type": "contains"}, {"parent": "docker.io/curlimages/curl@c1fe1679c34d9784c1b0d1e5f62ac0a79fca01fb6377cdd33e90473c6f9f9a69", "child": "3e249a6f2317504e", "type": "contains"}, {"parent": "docker.io/curlimages/curl@c1fe1679c34d9784c1b0d1e5f62ac0a79fca01fb6377cdd33e90473c6f9f9a69", "child": "412b42ad943bf8cb", "type": "contains"}, {"parent": "docker.io/curlimages/curl@c1fe1679c34d9784c1b0d1e5f62ac0a79fca01fb6377cdd33e90473c6f9f9a69", "child": "456e920c4331bde6", "type": "contains"}, {"parent": "docker.io/curlimages/curl@c1fe1679c34d9784c1b0d1e5f62ac0a79fca01fb6377cdd33e90473c6f9f9a69", "child": "5449192ad1853f9c", "type": "contains"}, {"parent": "docker.io/curlimages/curl@c1fe1679c34d9784c1b0d1e5f62ac0a79fca01fb6377cdd33e90473c6f9f9a69", "child": "5c84d53d49f221e1", "type": "contains"}, {"parent": "docker.io/curlimages/curl@c1fe1679c34d9784c1b0d1e5f62ac0a79fca01fb6377cdd33e90473c6f9f9a69", "child": "67d8ec97d517d9c2", "type": "contains"}, {"parent": "docker.io/curlimages/curl@c1fe1679c34d9784c1b0d1e5f62ac0a79fca01fb6377cdd33e90473c6f9f9a69", "child": "7cbfd25df10d2724", "type": "contains"}, {"parent": "docker.io/curlimages/curl@c1fe1679c34d9784c1b0d1e5f62ac0a79fca01fb6377cdd33e90473c6f9f9a69", "child": "88109714e4e7213c", "type": "contains"}, {"parent": "docker.io/curlimages/curl@c1fe1679c34d9784c1b0d1e5f62ac0a79fca01fb6377cdd33e90473c6f9f9a69", "child": "8f69c879d6b38e38", "type": "contains"}, {"parent": "docker.io/curlimages/curl@c1fe1679c34d9784c1b0d1e5f62ac0a79fca01fb6377cdd33e90473c6f9f9a69", "child": "9661e4d8b6fc57db", "type": "contains"}, {"parent": "docker.io/curlimages/curl@c1fe1679c34d9784c1b0d1e5f62ac0a79fca01fb6377cdd33e90473c6f9f9a69", "child": "97c06e6e1903e776", "type": "contains"}, {"parent": "docker.io/curlimages/curl@c1fe1679c34d9784c1b0d1e5f62ac0a79fca01fb6377cdd33e90473c6f9f9a69", "child": "986677a068b531ae", "type": "contains"}, {"parent": "docker.io/curlimages/curl@c1fe1679c34d9784c1b0d1e5f62ac0a79fca01fb6377cdd33e90473c6f9f9a69", "child": "9ed24d5185052508", "type": "contains"}, {"parent": "docker.io/curlimages/curl@c1fe1679c34d9784c1b0d1e5f62ac0a79fca01fb6377cdd33e90473c6f9f9a69", "child": "a906f1b13dd9bf14", "type": "contains"}, {"parent": "docker.io/curlimages/curl@c1fe1679c34d9784c1b0d1e5f62ac0a79fca01fb6377cdd33e90473c6f9f9a69", "child": "ba1753b9ff7d9303", "type": "contains"}, {"parent": "docker.io/curlimages/curl@c1fe1679c34d9784c1b0d1e5f62ac0a79fca01fb6377cdd33e90473c6f9f9a69", "child": "bb1801fcfd6835ff", "type": "contains"}, {"parent": "docker.io/curlimages/curl@c1fe1679c34d9784c1b0d1e5f62ac0a79fca01fb6377cdd33e90473c6f9f9a69", "child": "c16da0382f03c90a", "type": "contains"}, {"parent": "docker.io/curlimages/curl@c1fe1679c34d9784c1b0d1e5f62ac0a79fca01fb6377cdd33e90473c6f9f9a69", "child": "c86c3c8f976d31b1", "type": "contains"}, {"parent": "docker.io/curlimages/curl@c1fe1679c34d9784c1b0d1e5f62ac0a79fca01fb6377cdd33e90473c6f9f9a69", "child": "c9d137d8d21db486", "type": "contains"}, {"parent": "docker.io/curlimages/curl@c1fe1679c34d9784c1b0d1e5f62ac0a79fca01fb6377cdd33e90473c6f9f9a69", "child": "ceeb874c4a135dac", "type": "contains"}, {"parent": "docker.io/curlimages/curl@c1fe1679c34d9784c1b0d1e5f62ac0a79fca01fb6377cdd33e90473c6f9f9a69", "child": "dbfb4d1dbecb422f", "type": "contains"}, {"parent": "docker.io/curlimages/curl@c1fe1679c34d9784c1b0d1e5f62ac0a79fca01fb6377cdd33e90473c6f9f9a69", "child": "e13769c76f74134e", "type": "contains"}, {"parent": "docker.io/curlimages/curl@c1fe1679c34d9784c1b0d1e5f62ac0a79fca01fb6377cdd33e90473c6f9f9a69", "child": "e3ed65b6dc9144b9", "type": "contains"}, {"parent": "docker.io/curlimages/curl@c1fe1679c34d9784c1b0d1e5f62ac0a79fca01fb6377cdd33e90473c6f9f9a69", "child": "e70542a40cdca454", "type": "contains"}, {"parent": "docker.io/curlimages/curl@c1fe1679c34d9784c1b0d1e5f62ac0a79fca01fb6377cdd33e90473c6f9f9a69", "child": "f3faf4bc1f174ce9", "type": "contains"}, {"parent": "docker.io/curlimages/curl@c1fe1679c34d9784c1b0d1e5f62ac0a79fca01fb6377cdd33e90473c6f9f9a69", "child": "f579a97329d24094", "type": "contains"}, {"parent": "docker.io/curlimages/curl@c1fe1679c34d9784c1b0d1e5f62ac0a79fca01fb6377cdd33e90473c6f9f9a69", "child": "fc2164ebd15bcb82", "type": "contains"}, {"parent": "e13769c76f74134e", "child": "05d2eab904a84e7e", "type": "evident-by"}, {"parent": "e13769c76f74134e", "child": "0c04ff01e9c104c2", "type": "contains"}, {"parent": "e13769c76f74134e", "child": "4f2c93dd51c19e51", "type": "contains"}, {"parent": "e13769c76f74134e", "child": "6bd9499f1bd4c600", "type": "contains"}, {"parent": "e13769c76f74134e", "child": "86cfbda7088c3928", "type": "contains"}, {"parent": "e13769c76f74134e", "child": "a7f7ae0f1df842f0", "type": "contains"}, {"parent": "e13769c76f74134e", "child": "a81fad2d2679f3f4", "type": "contains"}, {"parent": "e13769c76f74134e", "child": "adf89964d7825b5e", "type": "contains"}, {"parent": "e13769c76f74134e", "child": "c218e0319aa46823", "type": "contains"}, {"parent": "e13769c76f74134e", "child": "cdd9a493d13f00a7", "type": "contains"}, {"parent": "e13769c76f74134e", "child": "d078169da5d9c73c", "type": "contains"}, {"parent": "e13769c76f74134e", "child": "d7f92369591e398c", "type": "contains"}, {"parent": "e13769c76f74134e", "child": "df66d7815e8ae92d", "type": "contains"}, {"parent": "e13769c76f74134e", "child": "f331aed8094b0bc8", "type": "contains"}, {"parent": "e3ed65b6dc9144b9", "child": "037ba9595fe77699", "type": "dependency-of"}, {"parent": "e3ed65b6dc9144b9", "child": "05d2eab904a84e7e", "type": "evident-by"}, {"parent": "e3ed65b6dc9144b9", "child": "c373d9de6a379d23", "type": "contains"}, {"parent": "e70542a40cdca454", "child": "05d2eab904a84e7e", "type": "evident-by"}, {"parent": "e70542a40cdca454", "child": "97db8aae69dc1643", "type": "contains"}, {"parent": "e70542a40cdca454", "child": "9ed24d5185052508", "type": "dependency-of"}, {"parent": "f3faf4bc1f174ce9", "child": "05d2eab904a84e7e", "type": "evident-by"}, {"parent": "f3faf4bc1f174ce9", "child": "0c253a006a51a055", "type": "contains"}, {"parent": "f3faf4bc1f174ce9", "child": "1e36d9959ceb1540", "type": "contains"}, {"parent": "f3faf4bc1f174ce9", "child": "20c139d549b80964", "type": "contains"}, {"parent": "f3faf4bc1f174ce9", "child": "3104c985f7217a91", "type": "contains"}, {"parent": "f3faf4bc1f174ce9", "child": "33dbeb08a729965e", "type": "contains"}, {"parent": "f3faf4bc1f174ce9", "child": "41bcca0722df483b", "type": "contains"}, {"parent": "f3faf4bc1f174ce9", "child": "456e920c4331bde6", "type": "dependency-of"}, {"parent": "f3faf4bc1f174ce9", "child": "58dfe972c27db41d", "type": "contains"}, {"parent": "f3faf4bc1f174ce9", "child": "5d2bce7c30d4842e", "type": "contains"}, {"parent": "f3faf4bc1f174ce9", "child": "60f3660667c882d5", "type": "contains"}, {"parent": "f3faf4bc1f174ce9", "child": "6277d3a7f9f78fa0", "type": "contains"}, {"parent": "f3faf4bc1f174ce9", "child": "7430793c9843ac71", "type": "contains"}, {"parent": "f3faf4bc1f174ce9", "child": "8503eca0329855ac", "type": "contains"}, {"parent": "f3faf4bc1f174ce9", "child": "9174b40bd64a4afa", "type": "contains"}, {"parent": "f3faf4bc1f174ce9", "child": "9d58e602c9201b03", "type": "contains"}, {"parent": "f3faf4bc1f174ce9", "child": "9ee7f6bfafbd04f5", "type": "contains"}, {"parent": "f3faf4bc1f174ce9", "child": "9fc2b76378071420", "type": "contains"}, {"parent": "f3faf4bc1f174ce9", "child": "abac01a20548e31f", "type": "contains"}, {"parent": "f3faf4bc1f174ce9", "child": "c0f8875bed907f16", "type": "contains"}, {"parent": "f3faf4bc1f174ce9", "child": "dd82cc7a531a4f8a", "type": "contains"}, {"parent": "f3faf4bc1f174ce9", "child": "e174ab22f2beeb60", "type": "contains"}, {"parent": "f3faf4bc1f174ce9", "child": "e31ca5e7df53db52", "type": "contains"}, {"parent": "f3faf4bc1f174ce9", "child": "e8e728f7eb9ac206", "type": "contains"}, {"parent": "f3faf4bc1f174ce9", "child": "ee960619cbfa52da", "type": "contains"}, {"parent": "f579a97329d24094", "child": "05d2eab904a84e7e", "type": "evident-by"}, {"parent": "f579a97329d24094", "child": "6ddbc46067f19656", "type": "contains"}, {"parent": "f579a97329d24094", "child": "986677a068b531ae", "type": "dependency-of"}, {"parent": "fc2164ebd15bcb82", "child": "05d2eab904a84e7e", "type": "evident-by"}, {"parent": "fc2164ebd15bcb82", "child": "fd6f398d3af29611", "type": "contains"}], "files": [{"id": "468f4889734362f5", "location": {"path": "/bin/busybox", "layerID": "sha256:3e01818d79cd3467f1d60e54224f3f6ce5170eceb54e265d96bb82344b8c24e7"}}, {"id": "fd2f197c9c548c66", "location": {"path": "/etc/alpine-release", "layerID": "sha256:3e01818d79cd3467f1d60e54224f3f6ce5170eceb54e265d96bb82344b8c24e7"}}, {"id": "e31ca5e7df53db52", "location": {"path": "/etc/apk/keys/alpine-devel@lists.alpinelinux.org-4a6a0840.rsa.pub", "layerID": "sha256:3e01818d79cd3467f1d60e54224f3f6ce5170eceb54e265d96bb82344b8c24e7"}}, {"id": "dd82cc7a531a4f8a", "location": {"path": "/etc/apk/keys/alpine-devel@lists.alpinelinux.org-5243ef4b.rsa.pub", "layerID": "sha256:3e01818d79cd3467f1d60e54224f3f6ce5170eceb54e265d96bb82344b8c24e7"}}, {"id": "8503eca0329855ac", "location": {"path": "/etc/apk/keys/alpine-devel@lists.alpinelinux.org-5261cecb.rsa.pub", "layerID": "sha256:3e01818d79cd3467f1d60e54224f3f6ce5170eceb54e265d96bb82344b8c24e7"}}, {"id": "9fc2b76378071420", "location": {"path": "/etc/apk/keys/alpine-devel@lists.alpinelinux.org-6165ee59.rsa.pub", "layerID": "sha256:3e01818d79cd3467f1d60e54224f3f6ce5170eceb54e265d96bb82344b8c24e7"}}, {"id": "e174ab22f2beeb60", "location": {"path": "/etc/apk/keys/alpine-devel@lists.alpinelinux.org-61666e3f.rsa.pub", "layerID": "sha256:3e01818d79cd3467f1d60e54224f3f6ce5170eceb54e265d96bb82344b8c24e7"}}, {"id": "3cbfe16761873575", "location": {"path": "/etc/busybox-paths.d/busybox", "layerID": "sha256:3e01818d79cd3467f1d60e54224f3f6ce5170eceb54e265d96bb82344b8c24e7"}}, {"id": "86d9785f3abff156", "location": {"path": "/etc/crontabs/root", "layerID": "sha256:3e01818d79cd3467f1d60e54224f3f6ce5170eceb54e265d96bb82344b8c24e7"}}, {"id": "b80c3cb56c980b08", "location": {"path": "/etc/fstab", "layerID": "sha256:3e01818d79cd3467f1d60e54224f3f6ce5170eceb54e265d96bb82344b8c24e7"}}, {"id": "e7d0d8adb0625ce4", "location": {"path": "/etc/hostname", "layerID": "sha256:3e01818d79cd3467f1d60e54224f3f6ce5170eceb54e265d96bb82344b8c24e7"}}, {"id": "4d8d5f0587154018", "location": {"path": "/etc/inittab", "layerID": "sha256:3e01818d79cd3467f1d60e54224f3f6ce5170eceb54e265d96bb82344b8c24e7"}}, {"id": "b413295f2e1636b9", "location": {"path": "/etc/issue", "layerID": "sha256:3e01818d79cd3467f1d60e54224f3f6ce5170eceb54e265d96bb82344b8c24e7"}}, {"id": "e680a539c74e700d", "location": {"path": "/etc/logrotate.d/acpid", "layerID": "sha256:3e01818d79cd3467f1d60e54224f3f6ce5170eceb54e265d96bb82344b8c24e7"}}, {"id": "b29978812ba855fa", "location": {"path": "/etc/modprobe.d/aliases.conf", "layerID": "sha256:3e01818d79cd3467f1d60e54224f3f6ce5170eceb54e265d96bb82344b8c24e7"}}, {"id": "b58b1a80d90b95b5", "location": {"path": "/etc/modprobe.d/blacklist.conf", "layerID": "sha256:3e01818d79cd3467f1d60e54224f3f6ce5170eceb54e265d96bb82344b8c24e7"}}, {"id": "f54933ed0cb86bd2", "location": {"path": "/etc/modprobe.d/i386.conf", "layerID": "sha256:3e01818d79cd3467f1d60e54224f3f6ce5170eceb54e265d96bb82344b8c24e7"}}, {"id": "bae009a41fd01a1d", "location": {"path": "/etc/modprobe.d/kms.conf", "layerID": "sha256:3e01818d79cd3467f1d60e54224f3f6ce5170eceb54e265d96bb82344b8c24e7"}}, {"id": "a3196be7d01a0aec", "location": {"path": "/etc/modules", "layerID": "sha256:3e01818d79cd3467f1d60e54224f3f6ce5170eceb54e265d96bb82344b8c24e7"}}, {"id": "4eb653645b7f13ee", "location": {"path": "/etc/motd", "layerID": "sha256:3e01818d79cd3467f1d60e54224f3f6ce5170eceb54e265d96bb82344b8c24e7"}}, {"id": "ae53711b4c2ae508", "location": {"path": "/etc/network/if-up.d/dad", "layerID": "sha256:3e01818d79cd3467f1d60e54224f3f6ce5170eceb54e265d96bb82344b8c24e7"}}, {"id": "668425e791126652", "location": {"path": "/etc/nsswitch.conf", "layerID": "sha256:3e01818d79cd3467f1d60e54224f3f6ce5170eceb54e265d96bb82344b8c24e7"}}, {"id": "773e11b623fa2206", "location": {"path": "/etc/profile", "layerID": "sha256:3e01818d79cd3467f1d60e54224f3f6ce5170eceb54e265d96bb82344b8c24e7"}}, {"id": "ff541b2c108c7e57", "location": {"path": "/etc/profile.d/20locale.sh", "layerID": "sha256:3e01818d79cd3467f1d60e54224f3f6ce5170eceb54e265d96bb82344b8c24e7"}}, {"id": "22834df769d2095e", "location": {"path": "/etc/profile.d/README", "layerID": "sha256:3e01818d79cd3467f1d60e54224f3f6ce5170eceb54e265d96bb82344b8c24e7"}}, {"id": "96f77289dc1c6105", "location": {"path": "/etc/profile.d/color_prompt.sh.disabled", "layerID": "sha256:3e01818d79cd3467f1d60e54224f3f6ce5170eceb54e265d96bb82344b8c24e7"}}, {"id": "2ebaf2d5275a87c3", "location": {"path": "/etc/protocols", "layerID": "sha256:3e01818d79cd3467f1d60e54224f3f6ce5170eceb54e265d96bb82344b8c24e7"}}, {"id": "31f4ab357226cd60", "location": {"path": "/etc/secfixes.d/alpine", "layerID": "sha256:3e01818d79cd3467f1d60e54224f3f6ce5170eceb54e265d96bb82344b8c24e7"}}, {"id": "f5ddc6e8bf552536", "location": {"path": "/etc/securetty", "layerID": "sha256:3e01818d79cd3467f1d60e54224f3f6ce5170eceb54e265d96bb82344b8c24e7"}}, {"id": "5e64bee7d0512520", "location": {"path": "/etc/services", "layerID": "sha256:3e01818d79cd3467f1d60e54224f3f6ce5170eceb54e265d96bb82344b8c24e7"}}, {"id": "5f409797065e8a24", "location": {"path": "/etc/shells", "layerID": "sha256:3e01818d79cd3467f1d60e54224f3f6ce5170eceb54e265d96bb82344b8c24e7"}}, {"id": "860b9d53645feb47", "location": {"path": "/etc/ssl/certs/ca-certificates.crt", "layerID": "sha256:3e01818d79cd3467f1d60e54224f3f6ce5170eceb54e265d96bb82344b8c24e7"}}, {"id": "97ee0f53ad135fdd", "location": {"path": "/etc/ssl/ct_log_list.cnf", "layerID": "sha256:3e01818d79cd3467f1d60e54224f3f6ce5170eceb54e265d96bb82344b8c24e7"}}, {"id": "7386b7ea31c8254f", "location": {"path": "/etc/ssl/ct_log_list.cnf.dist", "layerID": "sha256:3e01818d79cd3467f1d60e54224f3f6ce5170eceb54e265d96bb82344b8c24e7"}}, {"id": "9afb2be8c7429f0d", "location": {"path": "/etc/ssl/openssl.cnf", "layerID": "sha256:3e01818d79cd3467f1d60e54224f3f6ce5170eceb54e265d96bb82344b8c24e7"}}, {"id": "70e74fc481bbd7b7", "location": {"path": "/etc/ssl/openssl.cnf.dist", "layerID": "sha256:3e01818d79cd3467f1d60e54224f3f6ce5170eceb54e265d96bb82344b8c24e7"}}, {"id": "c068f4c2abc469f7", "location": {"path": "/etc/sysctl.conf", "layerID": "sha256:3e01818d79cd3467f1d60e54224f3f6ce5170eceb54e265d96bb82344b8c24e7"}}, {"id": "378d389540b30911", "location": {"path": "/etc/udhcpc/udhcpc.conf", "layerID": "sha256:3e01818d79cd3467f1d60e54224f3f6ce5170eceb54e265d96bb82344b8c24e7"}}, {"id": "6e7e3004534b86d1", "location": {"path": "/lib/ld-musl-x86_64.so.1", "layerID": "sha256:3e01818d79cd3467f1d60e54224f3f6ce5170eceb54e265d96bb82344b8c24e7"}}, {"id": "0c90571c31cc24d4", "location": {"path": "/sbin/apk", "layerID": "sha256:3e01818d79cd3467f1d60e54224f3f6ce5170eceb54e265d96bb82344b8c24e7"}}, {"id": "f058d85fa2770b4f", "location": {"path": "/sbin/ldconfig", "layerID": "sha256:3e01818d79cd3467f1d60e54224f3f6ce5170eceb54e265d96bb82344b8c24e7"}}, {"id": "cb10741d9c217d07", "location": {"path": "/usr/bin/getconf", "layerID": "sha256:3e01818d79cd3467f1d60e54224f3f6ce5170eceb54e265d96bb82344b8c24e7"}}, {"id": "d36d4602a8c0ad50", "location": {"path": "/usr/bin/getent", "layerID": "sha256:3e01818d79cd3467f1d60e54224f3f6ce5170eceb54e265d96bb82344b8c24e7"}}, {"id": "b33e7a49b30565e7", "location": {"path": "/usr/bin/iconv", "layerID": "sha256:3e01818d79cd3467f1d60e54224f3f6ce5170eceb54e265d96bb82344b8c24e7"}}, {"id": "485cab4248b11e3f", "location": {"path": "/usr/bin/ldd", "layerID": "sha256:3e01818d79cd3467f1d60e54224f3f6ce5170eceb54e265d96bb82344b8c24e7"}}, {"id": "97db8aae69dc1643", "location": {"path": "/usr/bin/scanelf", "layerID": "sha256:3e01818d79cd3467f1d60e54224f3f6ce5170eceb54e265d96bb82344b8c24e7"}}, {"id": "b4bab8e79035429e", "location": {"path": "/usr/bin/ssl_client", "layerID": "sha256:3e01818d79cd3467f1d60e54224f3f6ce5170eceb54e265d96bb82344b8c24e7"}}, {"id": "51e3b429173363b7", "location": {"path": "/usr/lib/engines-3/afalg.so", "layerID": "sha256:3e01818d79cd3467f1d60e54224f3f6ce5170eceb54e265d96bb82344b8c24e7"}}, {"id": "2cac453818c05153", "location": {"path": "/usr/lib/engines-3/capi.so", "layerID": "sha256:3e01818d79cd3467f1d60e54224f3f6ce5170eceb54e265d96bb82344b8c24e7"}}, {"id": "b2bd5535e09327d5", "location": {"path": "/usr/lib/engines-3/loader_attic.so", "layerID": "sha256:3e01818d79cd3467f1d60e54224f3f6ce5170eceb54e265d96bb82344b8c24e7"}}, {"id": "2230852154a764a9", "location": {"path": "/usr/lib/engines-3/padlock.so", "layerID": "sha256:3e01818d79cd3467f1d60e54224f3f6ce5170eceb54e265d96bb82344b8c24e7"}}, {"id": "4e2deeb47f39a10e", "location": {"path": "/usr/lib/libapk.so.2.14.0", "layerID": "sha256:3e01818d79cd3467f1d60e54224f3f6ce5170eceb54e265d96bb82344b8c24e7"}}, {"id": "a36b11ef91e748da", "location": {"path": "/usr/lib/libcrypto.so.3", "layerID": "sha256:3e01818d79cd3467f1d60e54224f3f6ce5170eceb54e265d96bb82344b8c24e7"}}, {"id": "69cc85b55482eba1", "location": {"path": "/usr/lib/libssl.so.3", "layerID": "sha256:3e01818d79cd3467f1d60e54224f3f6ce5170eceb54e265d96bb82344b8c24e7"}}, {"id": "014a43ace0388eab", "location": {"path": "/usr/lib/libz.so.1.3.1", "layerID": "sha256:3e01818d79cd3467f1d60e54224f3f6ce5170eceb54e265d96bb82344b8c24e7"}}, {"id": "c36f73b65f306d73", "location": {"path": "/usr/lib/os-release", "layerID": "sha256:3e01818d79cd3467f1d60e54224f3f6ce5170eceb54e265d96bb82344b8c24e7"}}, {"id": "ff43617ec381da18", "location": {"path": "/usr/lib/ossl-modules/legacy.so", "layerID": "sha256:3e01818d79cd3467f1d60e54224f3f6ce5170eceb54e265d96bb82344b8c24e7"}}, {"id": "d70650009c2bb036", "location": {"path": "/usr/lib/sysctl.d/00-alpine.conf", "layerID": "sha256:3e01818d79cd3467f1d60e54224f3f6ce5170eceb54e265d96bb82344b8c24e7"}}, {"id": "0c253a006a51a055", "location": {"path": "/usr/share/apk/keys/alpine-devel@lists.alpinelinux.org-4a6a0840.rsa.pub", "layerID": "sha256:3e01818d79cd3467f1d60e54224f3f6ce5170eceb54e265d96bb82344b8c24e7"}}, {"id": "7430793c9843ac71", "location": {"path": "/usr/share/apk/keys/alpine-devel@lists.alpinelinux.org-5243ef4b.rsa.pub", "layerID": "sha256:3e01818d79cd3467f1d60e54224f3f6ce5170eceb54e265d96bb82344b8c24e7"}}, {"id": "9174b40bd64a4afa", "location": {"path": "/usr/share/apk/keys/alpine-devel@lists.alpinelinux.org-524d27bb.rsa.pub", "layerID": "sha256:3e01818d79cd3467f1d60e54224f3f6ce5170eceb54e265d96bb82344b8c24e7"}}, {"id": "41bcca0722df483b", "location": {"path": "/usr/share/apk/keys/alpine-devel@lists.alpinelinux.org-5261cecb.rsa.pub", "layerID": "sha256:3e01818d79cd3467f1d60e54224f3f6ce5170eceb54e265d96bb82344b8c24e7"}}, {"id": "33dbeb08a729965e", "location": {"path": "/usr/share/apk/keys/alpine-devel@lists.alpinelinux.org-58199dcc.rsa.pub", "layerID": "sha256:3e01818d79cd3467f1d60e54224f3f6ce5170eceb54e265d96bb82344b8c24e7"}}, {"id": "ee960619cbfa52da", "location": {"path": "/usr/share/apk/keys/alpine-devel@lists.alpinelinux.org-58cbb476.rsa.pub", "layerID": "sha256:3e01818d79cd3467f1d60e54224f3f6ce5170eceb54e265d96bb82344b8c24e7"}}, {"id": "3104c985f7217a91", "location": {"path": "/usr/share/apk/keys/alpine-devel@lists.alpinelinux.org-58e4f17d.rsa.pub", "layerID": "sha256:3e01818d79cd3467f1d60e54224f3f6ce5170eceb54e265d96bb82344b8c24e7"}}, {"id": "1e36d9959ceb1540", "location": {"path": "/usr/share/apk/keys/alpine-devel@lists.alpinelinux.org-5e69ca50.rsa.pub", "layerID": "sha256:3e01818d79cd3467f1d60e54224f3f6ce5170eceb54e265d96bb82344b8c24e7"}}, {"id": "abac01a20548e31f", "location": {"path": "/usr/share/apk/keys/alpine-devel@lists.alpinelinux.org-60ac2099.rsa.pub", "layerID": "sha256:3e01818d79cd3467f1d60e54224f3f6ce5170eceb54e265d96bb82344b8c24e7"}}, {"id": "60f3660667c882d5", "location": {"path": "/usr/share/apk/keys/alpine-devel@lists.alpinelinux.org-6165ee59.rsa.pub", "layerID": "sha256:3e01818d79cd3467f1d60e54224f3f6ce5170eceb54e265d96bb82344b8c24e7"}}, {"id": "20c139d549b80964", "location": {"path": "/usr/share/apk/keys/alpine-devel@lists.alpinelinux.org-61666e3f.rsa.pub", "layerID": "sha256:3e01818d79cd3467f1d60e54224f3f6ce5170eceb54e265d96bb82344b8c24e7"}}, {"id": "9ee7f6bfafbd04f5", "location": {"path": "/usr/share/apk/keys/alpine-devel@lists.alpinelinux.org-616a9724.rsa.pub", "layerID": "sha256:3e01818d79cd3467f1d60e54224f3f6ce5170eceb54e265d96bb82344b8c24e7"}}, {"id": "c0f8875bed907f16", "location": {"path": "/usr/share/apk/keys/alpine-devel@lists.alpinelinux.org-616abc23.rsa.pub", "layerID": "sha256:3e01818d79cd3467f1d60e54224f3f6ce5170eceb54e265d96bb82344b8c24e7"}}, {"id": "5d2bce7c30d4842e", "location": {"path": "/usr/share/apk/keys/alpine-devel@lists.alpinelinux.org-616ac3bc.rsa.pub", "layerID": "sha256:3e01818d79cd3467f1d60e54224f3f6ce5170eceb54e265d96bb82344b8c24e7"}}, {"id": "6277d3a7f9f78fa0", "location": {"path": "/usr/share/apk/keys/alpine-devel@lists.alpinelinux.org-616adfeb.rsa.pub", "layerID": "sha256:3e01818d79cd3467f1d60e54224f3f6ce5170eceb54e265d96bb82344b8c24e7"}}, {"id": "e8e728f7eb9ac206", "location": {"path": "/usr/share/apk/keys/alpine-devel@lists.alpinelinux.org-616ae350.rsa.pub", "layerID": "sha256:3e01818d79cd3467f1d60e54224f3f6ce5170eceb54e265d96bb82344b8c24e7"}}, {"id": "58dfe972c27db41d", "location": {"path": "/usr/share/apk/keys/alpine-devel@lists.alpinelinux.org-616db30d.rsa.pub", "layerID": "sha256:3e01818d79cd3467f1d60e54224f3f6ce5170eceb54e265d96bb82344b8c24e7"}}, {"id": "9d58e602c9201b03", "location": {"path": "/usr/share/apk/keys/alpine-devel@lists.alpinelinux.org-66ba20fe.rsa.pub", "layerID": "sha256:3e01818d79cd3467f1d60e54224f3f6ce5170eceb54e265d96bb82344b8c24e7"}}, {"id": "431ddc0b26fa5372", "location": {"path": "/usr/share/udhcpc/default.script", "layerID": "sha256:3e01818d79cd3467f1d60e54224f3f6ce5170eceb54e265d96bb82344b8c24e7"}}, {"id": "b3633bc811ad48c9", "location": {"path": "/etc/group", "layerID": "sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d"}}, {"id": "4d414d1737405098", "location": {"path": "/etc/hosts", "layerID": "sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d"}}, {"id": "614c65d7d5699747", "location": {"path": "/etc/krb5.conf", "layerID": "sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d"}}, {"id": "86a05755e77e07fc", "location": {"path": "/etc/passwd", "layerID": "sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d"}}, {"id": "5f3d3b1ea52cd203", "location": {"path": "/etc/shadow", "layerID": "sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d"}}, {"id": "05d2eab904a84e7e", "location": {"path": "/lib/apk/db/installed", "layerID": "sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d"}}, {"id": "affcf55f6df321b5", "location": {"path": "/usr/bin/bomtool", "layerID": "sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d"}}, {"id": "c373d9de6a379d23", "location": {"path": "/usr/bin/brotli", "layerID": "sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d"}}, {"id": "0607fb48d6a867d9", "location": {"path": "/usr/bin/curl", "layerID": "sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d"}}, {"id": "c218e0319aa46823", "location": {"path": "/usr/bin/gss-client", "layerID": "sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d"}}, {"id": "d078169da5d9c73c", "location": {"path": "/usr/bin/k5srvutil", "layerID": "sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d"}}, {"id": "f331aed8094b0bc8", "location": {"path": "/usr/bin/kadmin", "layerID": "sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d"}}, {"id": "a7f7ae0f1df842f0", "location": {"path": "/usr/bin/kdestroy", "layerID": "sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d"}}, {"id": "86cfbda7088c3928", "location": {"path": "/usr/bin/kinit", "layerID": "sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d"}}, {"id": "d7f92369591e398c", "location": {"path": "/usr/bin/klist", "layerID": "sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d"}}, {"id": "0c04ff01e9c104c2", "location": {"path": "/usr/bin/kpasswd", "layerID": "sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d"}}, {"id": "4f2c93dd51c19e51", "location": {"path": "/usr/bin/ksu", "layerID": "sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d"}}, {"id": "a81fad2d2679f3f4", "location": {"path": "/usr/bin/kswitch", "layerID": "sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d"}}, {"id": "df66d7815e8ae92d", "location": {"path": "/usr/bin/ktutil", "layerID": "sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d"}}, {"id": "6bd9499f1bd4c600", "location": {"path": "/usr/bin/kvno", "layerID": "sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d"}}, {"id": "e70de53d7f62bd47", "location": {"path": "/usr/bin/pkgconf", "layerID": "sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d"}}, {"id": "e9a806dcd1a078d7", "location": {"path": "/usr/bin/pzstd", "layerID": "sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d"}}, {"id": "adf89964d7825b5e", "location": {"path": "/usr/bin/sim_client", "layerID": "sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d"}}, {"id": "cdd9a493d13f00a7", "location": {"path": "/usr/bin/uuclient", "layerID": "sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d"}}, {"id": "94931d779ffec08e", "location": {"path": "/usr/bin/zstd", "layerID": "sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d"}}, {"id": "1d49d01d1338ec6e", "location": {"path": "/usr/bin/zstdgrep", "layerID": "sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d"}}, {"id": "1c988daa02be9ff4", "location": {"path": "/usr/bin/zstdless", "layerID": "sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d"}}, {"id": "e580594caad1a9a9", "location": {"path": "/usr/include/brotli/decode.h", "layerID": "sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d"}}, {"id": "e1780308bd2745ec", "location": {"path": "/usr/include/brotli/encode.h", "layerID": "sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d"}}, {"id": "70c81ce36219cef2", "location": {"path": "/usr/include/brotli/port.h", "layerID": "sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d"}}, {"id": "6a9ff817a6522527", "location": {"path": "/usr/include/brotli/shared_dictionary.h", "layerID": "sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d"}}, {"id": "72fca5accef8f2a0", "location": {"path": "/usr/include/brotli/types.h", "layerID": "sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d"}}, {"id": "b8714ac0c5e52f87", "location": {"path": "/usr/include/nghttp2/nghttp2.h", "layerID": "sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d"}}, {"id": "f62213b368d29ad6", "location": {"path": "/usr/include/nghttp2/nghttp2ver.h", "layerID": "sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d"}}, {"id": "bf5b8a5792ae8ce3", "location": {"path": "/usr/lib/krb5/plugins/kdb/db2.so", "layerID": "sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d"}}, {"id": "74a405952a8bacd5", "location": {"path": "/usr/lib/krb5/plugins/preauth/otp.so", "layerID": "sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d"}}, {"id": "1b4fe1c27bee3d6f", "location": {"path": "/usr/lib/krb5/plugins/preauth/spake.so", "layerID": "sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d"}}, {"id": "5ae7577620ec6cc6", "location": {"path": "/usr/lib/krb5/plugins/preauth/test.so", "layerID": "sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d"}}, {"id": "aa1f2ef0278a32b8", "location": {"path": "/usr/lib/krb5/plugins/tls/k5tls.so", "layerID": "sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d"}}, {"id": "29c097271e009ca3", "location": {"path": "/usr/lib/libbrotlicommon.so.1.1.0", "layerID": "sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d"}}, {"id": "00c4e2d01d480665", "location": {"path": "/usr/lib/libbrotlidec.so.1.1.0", "layerID": "sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d"}}, {"id": "75f32f96c2d07d86", "location": {"path": "/usr/lib/libbrotlienc.so.1.1.0", "layerID": "sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d"}}, {"id": "ef996e1f6676f341", "location": {"path": "/usr/lib/libcom_err.so.2.1", "layerID": "sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d"}}, {"id": "16db6b7867c5e19b", "location": {"path": "/usr/lib/libe2p.so.2.3", "layerID": "sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d"}}, {"id": "c08f16a4a723e201", "location": {"path": "/usr/lib/libext2fs.so.2.4", "layerID": "sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d"}}, {"id": "c394fbc850ea8753", "location": {"path": "/usr/lib/libgcc_s.so.1", "layerID": "sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d"}}, {"id": "4e51d5fe7f02b74f", "location": {"path": "/usr/lib/libgssapi_krb5.so.2.2", "layerID": "sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d"}}, {"id": "319aab2b06d5c638", "location": {"path": "/usr/lib/libgssrpc.so.4.2", "layerID": "sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d"}}, {"id": "9c5437be4d2bd035", "location": {"path": "/usr/lib/libidn2.so.0.4.0", "layerID": "sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d"}}, {"id": "a6ee5aa1579ea184", "location": {"path": "/usr/lib/libk5crypto.so.3.1", "layerID": "sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d"}}, {"id": "a490c4f82f21f144", "location": {"path": "/usr/lib/libkadm5clnt_mit.so.12.0", "layerID": "sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d"}}, {"id": "c042c0248a70b846", "location": {"path": "/usr/lib/libkadm5srv_mit.so.12.0", "layerID": "sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d"}}, {"id": "6a3ad7d0df5ad0b8", "location": {"path": "/usr/lib/libkdb5.so.10.0", "layerID": "sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d"}}, {"id": "d500a230de1b6100", "location": {"path": "/usr/lib/libkeyutils.so.1.10", "layerID": "sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d"}}, {"id": "8663ae9af4fed258", "location": {"path": "/usr/lib/libkrad.so.0.0", "layerID": "sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d"}}, {"id": "3c1e7db652a4d4c1", "location": {"path": "/usr/lib/libkrb5.so.3.3", "layerID": "sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d"}}, {"id": "803e7fbfe5b428cc", "location": {"path": "/usr/lib/libkrb5support.so.0.1", "layerID": "sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d"}}, {"id": "43052306802633cb", "location": {"path": "/usr/lib/libnghttp2.so.14.28.3", "layerID": "sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d"}}, {"id": "ffcacae116b11153", "location": {"path": "/usr/lib/libpkgconf.so.5.0.0", "layerID": "sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d"}}, {"id": "fd6f398d3af29611", "location": {"path": "/usr/lib/libpsl.so.5.3.5", "layerID": "sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d"}}, {"id": "55e808d1502ce1f5", "location": {"path": "/usr/lib/libss.so.2.0", "layerID": "sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d"}}, {"id": "ca2554e971e8d3da", "location": {"path": "/usr/lib/libssh2.so.1.0.1", "layerID": "sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d"}}, {"id": "6ddbc46067f19656", "location": {"path": "/usr/lib/libstdc++.so.6.0.33", "layerID": "sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d"}}, {"id": "472a25f3f0ecdbd7", "location": {"path": "/usr/lib/libunistring.so.5.1.0", "layerID": "sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d"}}, {"id": "550be067c8009c6f", "location": {"path": "/usr/lib/libverto.so.1.0.0", "layerID": "sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d"}}, {"id": "bf9888330a19323c", "location": {"path": "/usr/lib/libzstd.so.1.5.6", "layerID": "sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d"}}, {"id": "ab50f8a62f73e17c", "location": {"path": "/usr/lib/pkgconfig/libbrotlicommon.pc", "layerID": "sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d"}}, {"id": "43c67ab0a2c85c2b", "location": {"path": "/usr/lib/pkgconfig/libbrotlidec.pc", "layerID": "sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d"}}, {"id": "a1e5642bfd332967", "location": {"path": "/usr/lib/pkgconfig/libbrotlienc.pc", "layerID": "sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d"}}, {"id": "4eb2fae82928af57", "location": {"path": "/usr/lib/pkgconfig/libnghttp2.pc", "layerID": "sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d"}}, {"id": "6f304251929469c4", "location": {"path": "/usr/share/aclocal/pkg.m4", "layerID": "sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d"}}], "source": {"id": "docker.io/curlimages/curl@c1fe1679c34d9784c1b0d1e5f62ac0a79fca01fb6377cdd33e90473c6f9f9a69", "name": "docker.io/curlimages/curl:8.11.1", "version": "docker.io/curlimages/curl@sha256:c1fe1679c34d9784c1b0d1e5f62ac0a79fca01fb6377cdd33e90473c6f9f9a69", "type": "image", "metadata": {"userInput": "docker.io/curlimages/curl@sha256:c1fe1679c34d9784c1b0d1e5f62ac0a79fca01fb6377cdd33e90473c6f9f9a69", "imageID": "sha256:7551dbeefe0d9a88a7134dd903f97727315e9fd150b4aefa6da5dcf3a4757f2a", "manifestDigest": "", "mediaType": "", "tags": ["docker.io/curlimages/curl:8.11.1"], "imageSize": 21753027, "layers": [{"mediaType": "application/vnd.oci.image.layer.v1.tar+gzip", "digest": "sha256:3e01818d79cd3467f1d60e54224f3f6ce5170eceb54e265d96bb82344b8c24e7", "size": 7837773}, {"mediaType": "application/vnd.oci.image.layer.v1.tar+gzip", "digest": "sha256:47f700d46e8a78ec5278107460349b8392e454014ed0bb48a1cd04fe1781f69d", "size": 13915254}, {"mediaType": "application/vnd.oci.image.layer.v1.tar+gzip", "digest": "sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef", "size": 0}], "manifest": null, "config": "eyJhcmNoaXRlY3R1cmUiOiJhbWQ2NCIsImNyZWF0ZWQiOiIyMDI0LTEyLTExVDA4OjQ2OjU4LjIzMTMyNjg3WiIsImhpc3RvcnkiOlt7ImNyZWF0ZWQiOiIyMDI0LTEyLTA1VDEyOjQ5OjA0WiIsImNyZWF0ZWRfYnkiOiJBREQgYWxwaW5lLW1pbmlyb290ZnMtMy4yMS4wLXg4Nl82NC50YXIuZ3ogLyAjIGJ1aWxka2l0IiwiY29tbWVudCI6ImJ1aWxka2l0LmRvY2tlcmZpbGUudjAifSx7ImNyZWF0ZWQiOiIyMDI0LTEyLTA1VDEyOjQ5OjA0WiIsImNyZWF0ZWRfYnkiOiJDTUQgW1wiL2Jpbi9zaFwiXSIsImNvbW1lbnQiOiJidWlsZGtpdC5kb2NrZXJmaWxlLnYwIiwiZW1wdHlfbGF5ZXIiOnRydWV9LHsiY3JlYXRlZCI6IjIwMjQtMTItMTFUMDg6NDY6NTcuNjUyMzgyODkxWiIsImNyZWF0ZWRfYnkiOiIvYmluL3NoIiwiY29tbWVudCI6IkZST00gZG9ja2VyLmlvL2xpYnJhcnkvYWxwaW5lOjMuMjEuMCJ9LHsiY3JlYXRlZCI6IjIwMjQtMTItMTFUMDg6NDY6NTguMjMxOTQxMDExWiIsImNyZWF0ZWRfYnkiOiIvYmluL3NoIiwiY29tbWVudCI6IkZST00gbG9jYWxob3N0L2N1cmwtYmFzZS1saW51eC1hbWQ2NDo4LjExLjEifV0sIm9zIjoibGludXgiLCJyb290ZnMiOnsidHlwZSI6ImxheWVycyIsImRpZmZfaWRzIjpbInNoYTI1NjozZTAxODE4ZDc5Y2QzNDY3ZjFkNjBlNTQyMjRmM2Y2Y2U1MTcwZWNlYjU0ZTI2NWQ5NmJiODIzNDRiOGMyNGU3Iiwic2hhMjU2OjQ3ZjcwMGQ0NmU4YTc4ZWM1Mjc4MTA3NDYwMzQ5YjgzOTJlNDU0MDE0ZWQwYmI0OGExY2QwNGZlMTc4MWY2OWQiLCJzaGEyNTY6NWY3MGJmMThhMDg2MDA3MDE2ZTk0OGIwNGFlZDNiODIxMDNhMzZiZWE0MTc1NWI2Y2RkZmFmMTBhY2UzYzZlZiJdfSwiY29uZmlnIjp7IkNtZCI6WyJjdXJsIl0sIkhlYWx0aGNoZWNrIjp7fSwiRW50cnlwb2ludCI6WyIvZW50cnlwb2ludC5zaCJdLCJFbnYiOlsiUEFUSD0vdXNyL2xvY2FsL3NiaW46L3Vzci9sb2NhbC9iaW46L3Vzci9zYmluOi91c3IvYmluOi9zYmluOi9iaW4iLCJDVVJMX0NBX0JVTkRMRT0vY2FjZXJ0LnBlbSJdLCJMYWJlbHMiOnsiZG9ja2VyLmNtZCI6InBvZG1hbiBydW4gLWl0IHF1YXkuaW8vY3VybC86OC4xMS4xIiwiaW8uYnVpbGRhaC52ZXJzaW9uIjoiMS4yMy4xIiwibWFpbnRhaW5lciI6IkphbWVzIEZ1bGxlciBcdTAwM2NqaW0uZnVsbGVyQHdlYmNvbXBvc2l0ZS5jb21cdTAwM2UiLCJuYW1lIjoiY3VybC1saW51eC1hbWQ2NDo4LjExLjEiLCJvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UuZGVzY3JpcHRpb24iOiJtaW5pbWFsIGltYWdlIGZvciBjdXJsIiwib3JnLm9wZW5jb250YWluZXJzLmltYWdlLmxpY2Vuc2VzIjoiTUlUIiwib3JnLm9wZW5jb250YWluZXJzLmltYWdlLnNvdXJjZSI6Imh0dHBzOi8vZ2l0aHViLmNvbS9jdXJsL2N1cmwtY29udGFpbmVyIiwidmVyc2lvbiI6IjguMTEuMSJ9LCJXb3JraW5nRGlyIjoiL2hvbWUvY3VybF91c2VyIn19", "repoDigests": ["docker.io/curlimages/curl@sha256:c1fe1679c34d9784c1b0d1e5f62ac0a79fca01fb6377cdd33e90473c6f9f9a69"], "architecture": "amd64", "os": "linux", "labels": {"docker.cmd": "podman run -it quay.io/curl/:8.11.1", "io.buildah.version": "1.23.1", "maintainer": "James Fuller ", "name": "curl-linux-amd64:8.11.1", "org.opencontainers.image.description": "minimal image for curl", "org.opencontainers.image.licenses": "MIT", "org.opencontainers.image.source": "https://github.com/curl/curl-container", "version": "8.11.1"}}}, "distro": {"prettyName": "Alpine Linux v3.21", "name": "Alpine Linux", "id": "alpine", "versionID": "3.21.0", "homeURL": "https://alpinelinux.org/", "bugReportURL": "https://gitlab.alpinelinux.org/alpine/aports/-/issues"}, "descriptor": {"name": "syft", "version": "v1.55.0", "configuration": null}, "schema": {"version": "16.1.11", "url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-16.1.11.json"}} \ No newline at end of file diff --git a/watcher/sbomobserver_test.go b/watcher/sbomobserver_test.go new file mode 100644 index 00000000..32bcce05 --- /dev/null +++ b/watcher/sbomobserver_test.go @@ -0,0 +1,157 @@ +package watcher + +import ( + "context" + "sync" + "testing" + "time" + + "github.com/armosec/armoapi-go/apis" + helpersv1 "github.com/kubescape/k8s-interface/instanceidhandler/v1/helpers" + "github.com/kubescape/operator/utils" + spdxv1beta1 "github.com/kubescape/storage/pkg/apis/softwarecomposition/v1beta1" + "github.com/panjf2000/ants/v2" + "github.com/stretchr/testify/assert" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/watch" +) + +// recordingObserver records what SBOMWatch tells an SBOMObserver. +type recordingObserver struct { + mu sync.Mutex + pods []string + sboms []string +} + +func (o *recordingObserver) ObservePod(eventType watch.EventType, pod *corev1.Pod) { + o.mu.Lock() + defer o.mu.Unlock() + o.pods = append(o.pods, string(eventType)+" "+pod.Namespace+"/"+pod.Name) +} + +func (o *recordingObserver) ObserveSBOM(eventType watch.EventType, sbom *spdxv1beta1.SBOMSyft) { + o.mu.Lock() + defer o.mu.Unlock() + o.sboms = append(o.sboms, string(eventType)+" "+sbom.Name) +} + +func (o *recordingObserver) seen() (pods, sboms []string) { + o.mu.Lock() + defer o.mu.Unlock() + return append([]string(nil), o.pods...), append([]string(nil), o.sboms...) +} + +func testSBOM(name string) *spdxv1beta1.SBOMSyft { + return &spdxv1beta1.SBOMSyft{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: "kubescape", + Annotations: map[string]string{ + helpersv1.ImageIDMetadataKey: testImageID, + helpersv1.ImageTagMetadataKey: testImageTag, + helpersv1.StatusMetadataKey: helpersv1.Learning, + }, + }, + } +} + +// startHandleSBOMEvents runs HandleSBOMEvents until the test ends. +func startHandleSBOMEvents(t *testing.T, wh *WatchHandler) (*CooldownQueue, chan *apis.Command, chan error) { + t.Helper() + eventQueue := NewCooldownQueueWithParams(50*time.Millisecond, 10*time.Millisecond) + cmdCh := make(chan *apis.Command, 4) + errorCh := make(chan error, 4) + handlerDone := make(chan struct{}) + go func() { + defer close(handlerDone) + wh.HandleSBOMEvents(eventQueue, cmdCh, errorCh) + }() + t.Cleanup(func() { + eventQueue.Stop() + <-handlerDone + }) + return eventQueue, cmdCh, errorCh +} + +// With kubevuln off, SBOMWatch runs only for the observer: it is told about +// the SBOM, and no scan command goes out, although the Wlid is known. +func TestHandleSBOMEvents_DisabledImageScansOnlyTellTheObserver(t *testing.T) { + wh := newTestHandler(t) + observer := &recordingObserver{} + wh.SetSBOMObserver(observer) + wh.DisableImageScans() + wh.ImageToContainerData.Set(testImageHashOnly, utils.ContainerData{ContainerName: testContainerName, Wlid: testWlid}) + eventQueue, cmdCh, errorCh := startHandleSBOMEvents(t, wh) + + eventQueue.Enqueue(watch.Event{Type: watch.Added, Object: testSBOM("nginx-sbom")}) + + assert.Eventually(t, func() bool { + _, sboms := observer.seen() + return len(sboms) == 1 + }, 3*time.Second, 10*time.Millisecond) + time.Sleep(100 * time.Millisecond) + assert.Empty(t, cmdCh) + assert.Empty(t, errorCh) +} + +// An SBOM re-enqueued while its Wlid is unknown reaches the observer once, not +// once per retry. +func TestHandleSBOMEvents_TellsTheObserverOnceWhileWaitingForTheWlid(t *testing.T) { + origInit, origMax, origAttempts := sbomRetryInitialDelay, sbomRetryMaxDelay, sbomRetryMaxAttempts + sbomRetryInitialDelay = 20 * time.Millisecond + sbomRetryMaxDelay = 40 * time.Millisecond + sbomRetryMaxAttempts = 3 + t.Cleanup(func() { + sbomRetryInitialDelay = origInit + sbomRetryMaxDelay = origMax + sbomRetryMaxAttempts = origAttempts + }) + wh := newTestHandler(t) + observer := &recordingObserver{} + wh.SetSBOMObserver(observer) + eventQueue, _, errorCh := startHandleSBOMEvents(t, wh) + + eventQueue.Enqueue(watch.Event{Type: watch.Added, Object: testSBOM("nginx-sbom")}) + + select { + case err := <-errorCh: + assert.ErrorIs(t, err, ErrMissingWlid) + case <-time.After(5 * time.Second): + t.Fatal("expected the retries to run out") + } + _, sboms := observer.seen() + assert.Equal(t, []string{"ADDED nginx-sbom"}, sboms) +} + +// SBOMWatch passes its pod events to the observer, and with image scans off +// it does not resolve their workloads. +func TestSBOMWatch_TellsTheObserverAboutPods(t *testing.T) { + wh := newTestHandler(t) + observer := &recordingObserver{} + wh.SetSBOMObserver(observer) + wh.DisableImageScans() + wh.eventQueue = NewCooldownQueueWithParams(50*time.Millisecond, 10*time.Millisecond) + + pod := &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: "nginx", Namespace: "default"}, + Status: corev1.PodStatus{ + Phase: corev1.PodRunning, + ContainerStatuses: []corev1.ContainerStatus{{Name: testContainerName, ImageID: testImageID}}, + }, + } + _, err := wh.k8sAPI.KubernetesClient.CoreV1().Pods(pod.Namespace).Create(context.Background(), pod, metav1.CreateOptions{}) + assert.NoError(t, err) + + pool, err := ants.NewPoolWithFunc(1, func(interface{}) {}) + assert.NoError(t, err) + defer pool.Release() + + go wh.SBOMWatch(context.Background(), pool) + + assert.Eventually(t, func() bool { + pods, _ := observer.seen() + return len(pods) > 0 && pods[0] == "ADDED default/nginx" + }, 3*time.Second, 20*time.Millisecond) + assert.Empty(t, wh.ImageToContainerData.Get(testImageHashOnly).Wlid) +} diff --git a/watcher/sbomwatcher.go b/watcher/sbomwatcher.go index fe27de72..f91540a7 100644 --- a/watcher/sbomwatcher.go +++ b/watcher/sbomwatcher.go @@ -91,6 +91,12 @@ func (wh *WatchHandler) SBOMWatch(ctx context.Context, workerPool *ants.PoolWith if !ok { continue } + if wh.sbomObserver != nil { + wh.sbomObserver.ObservePod(event.Type, pod) + } + if wh.imageScansDisabled { + continue + } wlid, err := utils.GetParentIDForPod(wh.k8sAPI, pod, wh.cfg.ClusterName()) if err != nil { logger.L().Ctx(ctx).Error("failed to get wlid for pod", helpers.Error(err), helpers.String("pod", pod.Name), helpers.String("namespace", pod.Namespace)) @@ -139,6 +145,8 @@ func (wh *WatchHandler) SBOMWatch(ctx context.Context, workerPool *ants.PoolWith } +// HandleSBOMEvents passes SBOMSyft events to the SBOM observer, if any, and, +// unless image scans are disabled, turns them into vulnerability scan commands. func (wh *WatchHandler) HandleSBOMEvents(eventQueue *CooldownQueue, producedCommands chan<- *apis.Command, errorCh chan<- error) { defer close(errorCh) @@ -149,6 +157,14 @@ func (wh *WatchHandler) HandleSBOMEvents(eventQueue *CooldownQueue, producedComm continue } + // a re-enqueued event (waiting for its Wlid below) was observed already + if wh.sbomObserver != nil && wh.sbomRetryAttempts.Get(obj.Namespace+"/"+obj.Name) == 0 { + wh.sbomObserver.ObserveSBOM(e.Type, obj) + } + if wh.imageScansDisabled { + continue + } + switch e.Type { case watch.Added: // diff --git a/watcher/watchhandler.go b/watcher/watchhandler.go index 861d98d3..91c3b883 100644 --- a/watcher/watchhandler.go +++ b/watcher/watchhandler.go @@ -10,7 +10,10 @@ import ( "github.com/kubescape/k8s-interface/k8sinterface" "github.com/kubescape/operator/config" "github.com/kubescape/operator/utils" + spdxv1beta1 "github.com/kubescape/storage/pkg/apis/softwarecomposition/v1beta1" kssc "github.com/kubescape/storage/pkg/generated/clientset/versioned" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/watch" ) const retryInterval = 1 * time.Second @@ -35,6 +38,26 @@ type WatchHandler struct { cfg config.IConfig k8sAPI *k8sinterface.KubernetesApi eventQueue *CooldownQueue + sbomObserver SBOMObserver // also told about SBOMWatch's events, when set + imageScansDisabled bool // SBOMWatch sends no scan commands (kubevuln is off) +} + +// SBOMObserver is told about the pod and SBOM events SBOMWatch receives, such +// as the SBOM exporter. +type SBOMObserver interface { + ObservePod(eventType watch.EventType, pod *corev1.Pod) + ObserveSBOM(eventType watch.EventType, sbom *spdxv1beta1.SBOMSyft) +} + +// SetSBOMObserver makes SBOMWatch pass its pod and SBOM events to o. +func (wh *WatchHandler) SetSBOMObserver(o SBOMObserver) { + wh.sbomObserver = o +} + +// DisableImageScans stops SBOMWatch from sending scan commands, for when it +// runs only for an SBOMObserver. +func (wh *WatchHandler) DisableImageScans() { + wh.imageScansDisabled = true } // NewWatchHandler creates a new WatchHandler, initializes the maps and returns it