diff --git a/.github/workflows/on-push-test.yaml b/.github/workflows/on-push-test.yaml index 2b234302..6c05df80 100644 --- a/.github/workflows/on-push-test.yaml +++ b/.github/workflows/on-push-test.yaml @@ -33,3 +33,5 @@ jobs: with: go-version: ^1.20.1 scan-severity: 'CRITICAL,HIGH,MEDIUM' + secrets: + access_token: ${{ secrets.HOMEBREW_TAP_GITHUB_TOKEN }} diff --git a/.github/workflows/on-tag-cli.yaml b/.github/workflows/on-tag-cli.yaml index b4f4b8d2..ce376779 100644 --- a/.github/workflows/on-tag-cli.yaml +++ b/.github/workflows/on-tag-cli.yaml @@ -31,6 +31,8 @@ jobs: with: go-version: ^1.20.1 scan-severity: 'CRITICAL,HIGH,MEDIUM' + secrets: + access_token: ${{ secrets.HOMEBREW_TAP_GITHUB_TOKEN }} release: outputs: diff --git a/.github/workflows/on-tag-img.yaml b/.github/workflows/on-tag-img.yaml index 08cf9482..6dd94a7e 100644 --- a/.github/workflows/on-tag-img.yaml +++ b/.github/workflows/on-tag-img.yaml @@ -139,3 +139,5 @@ jobs: auth_user: ${{ needs.conf.outputs.service_account }} target_project: ${{ needs.conf.outputs.project_id }} image_digest: ${{ needs.image.outputs.digest }} + secrets: + access_token: ${{ secrets.HOMEBREW_TAP_GITHUB_TOKEN }} diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 49947182..e36f877c 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -31,6 +31,10 @@ on: required: false type: string default: 'v1.51' + secrets: + access_token: + description: 'Workflow token' + required: true permissions: contents: read jobs: @@ -55,6 +59,8 @@ jobs: ${{ runner.os }}-go- - name: Checkout Code uses: actions/checkout@24cb9080177205b6e8c946b17badbe402adc938f # v3.4.0 + with: + token: ${{ secrets.access_token }} - name: Tidy Modules run: | make tidy @@ -87,6 +93,8 @@ jobs: ${{ runner.os }}-go- - name: Checkout Code uses: actions/checkout@24cb9080177205b6e8c946b17badbe402adc938f # v3.4.0 + with: + token: ${{ secrets.access_token }} - name: Tidy Modules run: | make tidy @@ -132,6 +140,8 @@ jobs: ${{ runner.os }}-go- - name: Checkout Code uses: actions/checkout@24cb9080177205b6e8c946b17badbe402adc938f # v3.4.0 + with: + token: ${{ secrets.access_token }} - name: Tidy Modules run: | make tidy @@ -149,6 +159,8 @@ jobs: steps: - name: Checkout Code uses: actions/checkout@24cb9080177205b6e8c946b17badbe402adc938f # v3.4.0 + with: + token: ${{ secrets.access_token }} - name: Scan Repo uses: aquasecurity/trivy-action@1f0aa582c8c8f5f7639610d6d38baddfea4fdcee # master with: @@ -159,7 +171,8 @@ jobs: output: 'trivy-results.sarif' severity: ${{ inputs.scan-severity }} exit-code: '1' - - name: Upload Report - uses: github/codeql-action/upload-sarif@168b99b3c22180941ae7dbdd5f5c9678ede476ba # v2.11.6 - with: - sarif_file: 'trivy-results.sarif' \ No newline at end of file + # - name: Upload Report + # uses: github/codeql-action/upload-sarif@168b99b3c22180941ae7dbdd5f5c9678ede476ba # v2.11.6 + # with: + # sarif_file: 'trivy-results.sarif' + # token: ${{ secrets.access_token }} \ No newline at end of file diff --git a/cmd/aactl/cli/vuln.go b/cmd/aactl/cli/vuln.go index 24d33bd5..0b5c511f 100644 --- a/cmd/aactl/cli/vuln.go +++ b/cmd/aactl/cli/vuln.go @@ -26,7 +26,7 @@ var ( Name: "vulnerability", Aliases: []string{"vul", "vuln", "vulns"}, Usage: "import vulnerabilities from file", - Action: importCmd, + Action: vulnerabilityCmd, Flags: []c.Flag{ projectFlag, sourceFlag, @@ -36,7 +36,7 @@ var ( } ) -func importCmd(c *c.Context) error { +func vulnerabilityCmd(c *c.Context) error { f, err := types.ParseSourceFormat(c.String(formatFlag.Name)) if err != nil { return errors.Wrap(err, "error parsing source format") diff --git a/cmd/aactl/cli/vuln_test.go b/cmd/aactl/cli/vuln_test.go index 57449565..1b01fb54 100644 --- a/cmd/aactl/cli/vuln_test.go +++ b/cmd/aactl/cli/vuln_test.go @@ -29,7 +29,7 @@ func TestImport(t *testing.T) { // test no arg import set := flag.NewFlagSet("", flag.ContinueOnError) c := cli.NewContext(newTestApp(t), set, nil) - err := importCmd(c) + err := vulnerabilityCmd(c) assert.Error(t, err) // test all formats @@ -41,7 +41,7 @@ func TestImport(t *testing.T) { set.String(formatFlag.Name, f, "") c = cli.NewContext(newTestApp(t), set, nil) - err = importCmd(c) + err = vulnerabilityCmd(c) assert.NoError(t, err) } } diff --git a/pkg/attestation/convert/convert.go b/pkg/attestation/convert/convert.go new file mode 100644 index 00000000..b2d31cfc --- /dev/null +++ b/pkg/attestation/convert/convert.go @@ -0,0 +1,38 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package convert + +import ( + "github.com/GoogleCloudPlatform/aactl/pkg/attestation/convert/provenance02" + "github.com/GoogleCloudPlatform/aactl/pkg/provenance" + "github.com/GoogleCloudPlatform/aactl/pkg/types" + "github.com/GoogleCloudPlatform/aactl/pkg/utils" + "github.com/pkg/errors" + g "google.golang.org/genproto/googleapis/grafeas/v1" +) + +type Converter func(nr utils.NoteResource, resourceURL string, env *provenance.Envelope) (*g.Note, *g.Occurrence, error) + +// GetConverter returns an attestation converter for the given format. +func GetConverter(intotoType string, intotoPredicateType string) (Converter, error) { + if intotoType == "https://in-toto.io/Statement/v0.1" { + switch intotoPredicateType { + case "https://slsa.dev/provenance/v0.2": + return provenance02.Convert, nil + } + } + return nil, errors.Wrapf(types.ErrorNotSupported, + "unimplemented env format: %s, %s", intotoType, intotoPredicateType) +} diff --git a/pkg/attestation/convert.go b/pkg/attestation/convert/provenance02/convert.go similarity index 99% rename from pkg/attestation/convert.go rename to pkg/attestation/convert/provenance02/convert.go index 2f127af5..0434b9fe 100644 --- a/pkg/attestation/convert.go +++ b/pkg/attestation/convert/provenance02/convert.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package attestation +package provenance02 import ( "encoding/json" diff --git a/pkg/attestation/import.go b/pkg/attestation/import.go index c8d2de81..e1b56ffb 100644 --- a/pkg/attestation/import.go +++ b/pkg/attestation/import.go @@ -20,6 +20,7 @@ import ( "fmt" ca "cloud.google.com/go/containeranalysis/apiv1" + "github.com/GoogleCloudPlatform/aactl/pkg/attestation/convert" "github.com/GoogleCloudPlatform/aactl/pkg/container" "github.com/GoogleCloudPlatform/aactl/pkg/provenance" "github.com/GoogleCloudPlatform/aactl/pkg/types" @@ -33,11 +34,12 @@ import ( ) // Import imports attestation metadata from a source. -func Import(ctx context.Context, opt *types.AttestationOptions) error { - if opt == nil { - return errors.New("options required") +func Import(ctx context.Context, options types.Options) error { + opt, ok := options.(*types.AttestationOptions) + if !ok || opt == nil { + return errors.New("valid options required") } - if err := opt.Validate(); err != nil { + if err := options.Validate(); err != nil { return errors.Wrap(err, "error validating options") } @@ -75,7 +77,12 @@ func importEnvelopes(ctx context.Context, envs []*provenance.Envelope, nr utils. defer c.Close() for _, env := range envs { - n, o, err := Convert(nr, resourceURL, env) + converter, err := convert.GetConverter(env.IntotoType, env.IntotoPredicateType) + if err != nil && !errors.Is(err, types.ErrorNotSupported) { + return errors.Wrap(err, "error getting envelope converter") + } + + n, o, err := converter(nr, resourceURL, env) if err != nil { return errors.Wrap(err, "error importing envelopes") } diff --git a/pkg/importer/importer.go b/pkg/importer/importer.go new file mode 100644 index 00000000..8a9d9e37 --- /dev/null +++ b/pkg/importer/importer.go @@ -0,0 +1,37 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package importer + +import ( + "context" + + "github.com/GoogleCloudPlatform/aactl/pkg/attestation" + "github.com/GoogleCloudPlatform/aactl/pkg/types" + "github.com/GoogleCloudPlatform/aactl/pkg/vul" + "github.com/pkg/errors" +) + +type Importer func(ctx context.Context, options types.Options) error + +func GetImporter(format types.SourceFormat) (Importer, error) { + switch format { + case types.SourceFormatSnykJSON: + return vul.Import, nil + case types.SourceFormatTrivyJSON: + return attestation.Import, nil + default: + return nil, errors.Errorf("unimplemented importer format: %s", format) + } +} diff --git a/pkg/provenance/envelopes.go b/pkg/provenance/envelopes.go index bec01b09..4d8ca72c 100644 --- a/pkg/provenance/envelopes.go +++ b/pkg/provenance/envelopes.go @@ -57,11 +57,6 @@ func GetVerifiedEnvelopes(ctx context.Context, resourceURI string) ([]*Envelope, log.Debug().Msgf("In-Toto Type (%s), PredicateType (%s)", penv.IntotoType, penv.IntotoPredicateType) - // TODO: Currently only one slsa version supported - if penv.IntotoType != "https://in-toto.io/Statement/v0.1" || penv.IntotoPredicateType != "https://slsa.dev/provenance/v0.2" { - continue - } - envs = append(envs, penv) } diff --git a/pkg/types/attest.go b/pkg/types/attest.go index a3edd590..8ee7da1b 100644 --- a/pkg/types/attest.go +++ b/pkg/types/attest.go @@ -47,9 +47,7 @@ func (o *AttestationOptions) Validate() error { if err != nil { return errors.Wrap(ErrInvalidSource, err.Error()) } - if u.Scheme == "" { - u.Scheme = "" - } + u.Scheme = "" o.Source = u.String() return nil diff --git a/pkg/types/convert.go b/pkg/types/convert.go index 0597f11b..51fc1715 100644 --- a/pkg/types/convert.go +++ b/pkg/types/convert.go @@ -26,3 +26,5 @@ type NoteOccurrences struct { // Occurrences that belong to the Note. Occurrences []*g.Occurrence } + +type NoteOccurrencesMap map[string]NoteOccurrences diff --git a/pkg/types/errors.go b/pkg/types/errors.go new file mode 100644 index 00000000..a17ec517 --- /dev/null +++ b/pkg/types/errors.go @@ -0,0 +1,21 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package types + +import "errors" + +var ( + ErrorNotSupported = errors.New("not supported") +) diff --git a/pkg/types/options.go b/pkg/types/options.go new file mode 100644 index 00000000..a855e1ca --- /dev/null +++ b/pkg/types/options.go @@ -0,0 +1,19 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package types + +type Options interface { + Validate() error +} diff --git a/pkg/types/vuln.go b/pkg/types/vuln.go index 5d2afd8b..57739d5f 100644 --- a/pkg/types/vuln.go +++ b/pkg/types/vuln.go @@ -62,9 +62,7 @@ func (o *VulnerabilityOptions) Validate() error { if err != nil { return errors.Wrap(ErrInvalidSource, err.Error()) } - if u.Scheme == "" { - u.Scheme = "https" - } + u.Scheme = "" o.Source = u.String() if o.File == "" { diff --git a/pkg/convert/convert.go b/pkg/vul/convert/convert.go similarity index 82% rename from pkg/convert/convert.go rename to pkg/vul/convert/convert.go index 743418da..9f74ee78 100644 --- a/pkg/convert/convert.go +++ b/pkg/vul/convert/convert.go @@ -15,16 +15,16 @@ package convert import ( - "github.com/GoogleCloudPlatform/aactl/pkg/convert/grype" - "github.com/GoogleCloudPlatform/aactl/pkg/convert/snyk" - "github.com/GoogleCloudPlatform/aactl/pkg/convert/trivy" "github.com/GoogleCloudPlatform/aactl/pkg/types" "github.com/GoogleCloudPlatform/aactl/pkg/utils" + "github.com/GoogleCloudPlatform/aactl/pkg/vul/convert/grype" + "github.com/GoogleCloudPlatform/aactl/pkg/vul/convert/snyk" + "github.com/GoogleCloudPlatform/aactl/pkg/vul/convert/trivy" "github.com/pkg/errors" ) // VulnerabilityConverter is a function that converts a source to a list of AA notes. -type VulnerabilityConverter func(s *utils.Source) (map[string]types.NoteOccurrences, error) +type VulnerabilityConverter func(s *utils.Source) (types.NoteOccurrencesMap, error) // GetConverter returns a vulnerability converter for the given format. func GetConverter(format types.SourceFormat) (VulnerabilityConverter, error) { diff --git a/pkg/convert/grype/grype.go b/pkg/vul/convert/grype/grype.go similarity index 97% rename from pkg/convert/grype/grype.go rename to pkg/vul/convert/grype/grype.go index d122070a..b7d85d5e 100644 --- a/pkg/convert/grype/grype.go +++ b/pkg/vul/convert/grype/grype.go @@ -15,6 +15,8 @@ package grype import ( + "fmt" + "github.com/GoogleCloudPlatform/aactl/pkg/types" "github.com/GoogleCloudPlatform/aactl/pkg/utils" "github.com/Jeffail/gabs/v2" @@ -23,7 +25,7 @@ import ( ) // Convert converts Snyk JSON to Grafeas Note/Occurrence format. -func Convert(s *utils.Source) (map[string]types.NoteOccurrences, error) { +func Convert(s *utils.Source) (types.NoteOccurrencesMap, error) { if s == nil || s.Data == nil { return nil, errors.New("valid source required") } @@ -32,7 +34,7 @@ func Convert(s *utils.Source) (map[string]types.NoteOccurrences, error) { return nil, errors.New("unable to find vulnerabilities in source data") } - list := make(map[string]types.NoteOccurrences, 0) + list := make(types.NoteOccurrencesMap, 0) for _, v := range s.Data.Search("matches").Children() { // create note @@ -91,7 +93,7 @@ func convertOccurrence(s *utils.Source, v *gabs.Container, noteName string) *g.O // Create Occurrence o := g.Occurrence{ - ResourceUri: s.URI, + ResourceUri: fmt.Sprintf("https://%s", s.URI), NoteName: noteName, Details: &g.Occurrence_Vulnerability{ Vulnerability: &g.VulnerabilityOccurrence{ diff --git a/pkg/convert/grype/grype_test.go b/pkg/vul/convert/grype/grype_test.go similarity index 97% rename from pkg/convert/grype/grype_test.go rename to pkg/vul/convert/grype/grype_test.go index 65274253..00502470 100644 --- a/pkg/convert/grype/grype_test.go +++ b/pkg/vul/convert/grype/grype_test.go @@ -26,7 +26,7 @@ func TestGrypeConverter(t *testing.T) { opt := &types.VulnerabilityOptions{ Project: types.TestProjectID, Source: "us-docker.pkg.dev/project/repo/img@sha256:f6efe...", - File: "../../../examples/data/grype.json", + File: "../../../../examples/data/grype.json", Format: types.SourceFormatGrypeJSON, } s, err := utils.NewFileSource(opt.File, opt.Source) diff --git a/pkg/convert/snyk/snyk.go b/pkg/vul/convert/snyk/snyk.go similarity index 97% rename from pkg/convert/snyk/snyk.go rename to pkg/vul/convert/snyk/snyk.go index 448137c2..1e159c0d 100644 --- a/pkg/convert/snyk/snyk.go +++ b/pkg/vul/convert/snyk/snyk.go @@ -25,7 +25,7 @@ import ( ) // Convert converts Snyk JSON to Grafeas Note/Occurrence format. -func Convert(s *utils.Source) (map[string]types.NoteOccurrences, error) { +func Convert(s *utils.Source) (types.NoteOccurrencesMap, error) { if s == nil || s.Data == nil { return nil, errors.New("valid source required") } @@ -34,7 +34,7 @@ func Convert(s *utils.Source) (map[string]types.NoteOccurrences, error) { return nil, errors.New("unable to find vulnerabilities in source data") } - list := make(map[string]types.NoteOccurrences, 0) + list := make(types.NoteOccurrencesMap, 0) for _, v := range s.Data.Search("vulnerabilities").Children() { cve := v.Search("identifiers", "CVE").Index(0).Data().(string) @@ -137,7 +137,7 @@ func convertOccurrence(s *utils.Source, v *gabs.Container, noteName string) *g.O // Create Occurrence o := g.Occurrence{ - ResourceUri: s.URI, + ResourceUri: fmt.Sprintf("https://%s", s.URI), NoteName: noteName, Details: &g.Occurrence_Vulnerability{ Vulnerability: &g.VulnerabilityOccurrence{ diff --git a/pkg/convert/snyk/snyk_test.go b/pkg/vul/convert/snyk/snyk_test.go similarity index 97% rename from pkg/convert/snyk/snyk_test.go rename to pkg/vul/convert/snyk/snyk_test.go index 30c8bd54..807d9669 100644 --- a/pkg/convert/snyk/snyk_test.go +++ b/pkg/vul/convert/snyk/snyk_test.go @@ -26,7 +26,7 @@ func TestSnykConverter(t *testing.T) { opt := &types.VulnerabilityOptions{ Project: types.TestProjectID, Source: "us-docker.pkg.dev/project/repo/img@sha256:f6efe...", - File: "../../../examples/data/snyk.json", + File: "../../../../examples/data/snyk.json", Format: types.SourceFormatSnykJSON, } s, err := utils.NewFileSource(opt.File, opt.Source) diff --git a/pkg/convert/trivy/trivy.go b/pkg/vul/convert/trivy/trivy.go similarity index 97% rename from pkg/convert/trivy/trivy.go rename to pkg/vul/convert/trivy/trivy.go index ed45a319..782aec0e 100644 --- a/pkg/convert/trivy/trivy.go +++ b/pkg/vul/convert/trivy/trivy.go @@ -25,7 +25,7 @@ import ( ) // Convert converts Trivy JSON to Grafeas Note/Occurrence format. -func Convert(s *utils.Source) (map[string]types.NoteOccurrences, error) { +func Convert(s *utils.Source) (types.NoteOccurrencesMap, error) { if s == nil || s.Data == nil { return nil, errors.New("valid source required") } @@ -34,7 +34,7 @@ func Convert(s *utils.Source) (map[string]types.NoteOccurrences, error) { return nil, errors.New("unable to find Results in source data") } - list := make(map[string]types.NoteOccurrences, 0) + list := make(types.NoteOccurrencesMap, 0) for _, r := range s.Data.Search("Results").Children() { for _, v := range r.Search("Vulnerabilities").Children() { @@ -137,7 +137,7 @@ func convertOccurrence(s *utils.Source, v *gabs.Container, noteName string, pack // Create Occurrence o := g.Occurrence{ - ResourceUri: s.URI, + ResourceUri: fmt.Sprintf("https://%s", s.URI), NoteName: noteName, Details: &g.Occurrence_Vulnerability{ Vulnerability: &g.VulnerabilityOccurrence{ diff --git a/pkg/convert/trivy/trivy_test.go b/pkg/vul/convert/trivy/trivy_test.go similarity index 97% rename from pkg/convert/trivy/trivy_test.go rename to pkg/vul/convert/trivy/trivy_test.go index 306a86d6..39133ea8 100644 --- a/pkg/convert/trivy/trivy_test.go +++ b/pkg/vul/convert/trivy/trivy_test.go @@ -26,7 +26,7 @@ func TestTrivyConverter(t *testing.T) { opt := &types.VulnerabilityOptions{ Project: types.TestProjectID, Source: "us-docker.pkg.dev/project/repo/img@sha256:f6efe...", - File: "../../../examples/data/trivy.json", + File: "../../../../examples/data/trivy.json", Format: types.SourceFormatSnykJSON, } s, err := utils.NewFileSource(opt.File, opt.Source) diff --git a/pkg/vul/import.go b/pkg/vul/import.go index 9c84bab0..0ef2da00 100644 --- a/pkg/vul/import.go +++ b/pkg/vul/import.go @@ -20,9 +20,9 @@ import ( "sync" ca "cloud.google.com/go/containeranalysis/apiv1" - "github.com/GoogleCloudPlatform/aactl/pkg/convert" "github.com/GoogleCloudPlatform/aactl/pkg/types" "github.com/GoogleCloudPlatform/aactl/pkg/utils" + "github.com/GoogleCloudPlatform/aactl/pkg/vul/convert" "github.com/pkg/errors" "github.com/rs/zerolog/log" g "google.golang.org/genproto/googleapis/grafeas/v1" @@ -30,11 +30,12 @@ import ( "google.golang.org/grpc/status" ) -func Import(ctx context.Context, opt *types.VulnerabilityOptions) error { - if opt == nil { - return errors.New("options required") +func Import(ctx context.Context, options types.Options) error { + opt, ok := options.(*types.VulnerabilityOptions) + if !ok || opt == nil { + return errors.New("valid options required") } - if err := opt.Validate(); err != nil { + if err := options.Validate(); err != nil { return errors.Wrap(err, "error validating options") } s, err := utils.NewFileSource(opt.File, opt.Source) @@ -57,6 +58,10 @@ func Import(ctx context.Context, opt *types.VulnerabilityOptions) error { log.Info().Msgf("found %d vulnerabilities", len(list)) + return post(ctx, list, opt) +} + +func post(ctx context.Context, list types.NoteOccurrencesMap, opt *types.VulnerabilityOptions) error { if list == nil { return errors.New("expected non-nil result") } diff --git a/tools/snyk-test b/tools/snyk-test index f5184450..8ca911dc 100755 --- a/tools/snyk-test +++ b/tools/snyk-test @@ -8,10 +8,10 @@ digest=$2 snyk container test --app-vulns \ --json-file-output=report.json $digest -bin/aactl --debug import --project cloudy-labz \ - --source $digest \ - --file report.json \ - --format snyk +bin/aactl --debug vulnerability --project cloudy-labz \ + --source $digest \ + --file report.json \ + --format snyk gcloud artifacts docker images describe $digest \ --project $project \