diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6f40bcf..e947586 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,7 +1,19 @@ name: build on: - - push - - pull_request + push: + branches: + - master + paths-ignore: + - '*.md' + - 'docs/**' + - 'LICENSE' + - 'NOTICE' + pull_request: + paths-ignore: + - '*.md' + - 'docs/**' + - 'LICENSE' + - 'NOTICE' jobs: build: name: Build diff --git a/.goreleaser.yml b/.goreleaser.yml index 9699ac7..755f041 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -25,6 +25,7 @@ changelog: exclude: - '^docs' - '^test' + - '^release' dockers: - image_templates: - "docker.io/aquasec/harbor-scanner-aqua:{{ .Version }}" diff --git a/NOTICE b/NOTICE new file mode 100644 index 0000000..98a0d36 --- /dev/null +++ b/NOTICE @@ -0,0 +1,4 @@ +harbor-scanner-aqua +Copyright 2019-2020 Aqua Security Software Ltd. + +This product includes software developed by Aqua Security (https://aquasec.com). diff --git a/README.md b/README.md index cbea1bc..345d0ec 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,6 @@ [![GitHub release][release-img]][release] -[![Build Actions][build-action-img]][build-action] +[![GitHub Build Action][build-action-img]][actions] +[![GitHub Release Action][release-action-img]][actions] [![Codecov][codecov-img]][codecov] [![Go Report Card][report-card-img]][report-card] [![License][license-img]][license] @@ -95,6 +96,8 @@ $ scannercli scan \ --password=$SCANNER_AQUA_PASSWORD \ --host=$SCANNER_AQUA_HOST \ --registry=$SCANNER_AQUA_REGISTRY \ + --robot-username=$HARBOR_ROBOT_ACCOUNT_NAME \ + --robot-password=$HARBOR_ROBOT_ACCOUNT_PASSWORD \ --no-verify=$SCANNER_CLI_NO_VERIFY \ --show-negligible=$SCANNER_CLI_SHOW_NEGLIGIBLE \ --show-will-not-fix=$SCANNER_CLI_SHOW_WILL_NOT_FIX \ @@ -345,47 +348,51 @@ it out based on the following instructions. Configuration of the adapter is done via environment variables at startup. -| Name | Default | Description | -|---------------------------------------|----------|---------------------------------------------------------------------------| -| `SCANNER_LOG_LEVEL` | `info` | The log level of `trace`, `debug`, `info`, `warn`, `warning`, `error`, `fatal` or `panic`. The standard logger logs entries with that level or anything above it. | -| `SCANNER_API_ADDR` | `:8080` | Binding address for the API HTTP server | -| `SCANNER_API_TLS_CERTIFICATE` | | The absolute path to the x509 certificate file | -| `SCANNER_API_TLS_KEY` | | The absolute path to the x509 private key file | -| `SCANNER_API_READ_TIMEOUT` | `15s` | The maximum duration for reading the entire request, including the body | -| `SCANNER_API_WRITE_TIMEOUT` | `15s` | The maximum duration before timing out writes of the response | -| `SCANNER_API_IDLE_TIMEOUT` | `60s` | The maximum amount of time to wait for the next request when keep-alives are enabled | -| `SCANNER_AQUA_USERNAME` | N/A | Aqua management console username (required) | -| `SCANNER_AQUA_PASSWORD` | N/A | Aqua management console password (required) | -| `SCANNER_AQUA_HOST` | `http://csp-console-svc.aqua:8080` | Aqua management console address | -| `SCANNER_AQUA_REGISTRY` | `Harbor` | The name of the Harbor registry configured in Aqua management console | -| `SCANNER_AQUA_REPORTS_DIR` | `/var/lib/scanner/reports` | Directory to save temporary scan reports | -| `SCANNER_AQUA_USE_IMAGE_TAG` | `true` | The flag to determine whether the image tag or digest is used in the image reference passed to `scannercli` | -| `SCANNER_CLI_NO_VERIFY` | `false` | The flag passed to `scannercli` to skip verifying TLS certificates | -| `SCANNER_CLI_SHOW_NEGLIGIBLE` | `true` | The flag passed to `scannercli` to show negligible/unknown severity vulnerabilities | -| `SCANNER_CLI_SHOW_WILL_NOT_FIX` | `false` | The flag passed to `scannercli` to show vulnerabilities that will not be fixed | -| `SCANNER_CLI_HIDE_BASE` | `true` | The flag passed to `scannercli` to hide vulnerabilities in the base image | -| `SCANNER_STORE_REDIS_URL` | `redis://harbor-harbor-redis:6379` | Redis server URI for a redis store | -| `SCANNER_STORE_REDIS_NAMESPACE` | `harbor.scanner.aqua:store` | A namespace for keys in a redis store | -| `SCANNER_STORE_REDIS_POOL_MAX_ACTIVE` | `5` | The max number of connections allocated by the pool for a redis store | -| `SCANNER_STORE_REDIS_POOL_MAX_IDLE` | `5` | The max number of idle connections in the pool for a redis store | -| `SCANNER_STORE_REDIS_SCAN_JOB_TTL` | `1h` | The time to live for persisting scan jobs and associated scan reports | +| Name | Default | Description | +|---------------------------------------------|----------|---------------------------------------------------------------------------| +| `SCANNER_LOG_LEVEL` | `info` | The log level of `trace`, `debug`, `info`, `warn`, `warning`, `error`, `fatal` or `panic`. The standard logger logs entries with that level or anything above it. | +| `SCANNER_API_ADDR` | `:8080` | Binding address for the API HTTP server | +| `SCANNER_API_TLS_CERTIFICATE` | | The absolute path to the x509 certificate file | +| `SCANNER_API_TLS_KEY` | | The absolute path to the x509 private key file | +| `SCANNER_API_READ_TIMEOUT` | `15s` | The maximum duration for reading the entire request, including the body | +| `SCANNER_API_WRITE_TIMEOUT` | `15s` | The maximum duration before timing out writes of the response | +| `SCANNER_API_IDLE_TIMEOUT` | `60s` | The maximum amount of time to wait for the next request when keep-alives are enabled | +| `SCANNER_AQUA_USERNAME` | N/A | Aqua management console username (required) | +| `SCANNER_AQUA_PASSWORD` | N/A | Aqua management console password (required) | +| `SCANNER_AQUA_HOST` | `http://csp-console-svc.aqua:8080` | Aqua management console address | +| `SCANNER_AQUA_REGISTRY` | `Harbor` | The name of the Harbor registry configured in Aqua management console | +| `SCANNER_AQUA_REPORTS_DIR` | `/var/lib/scanner/reports` | Directory to save temporary scan reports | +| `SCANNER_AQUA_USE_IMAGE_TAG` | `true` | The flag to determine whether the image tag or digest is used in the image reference passed to `scannercli` | +| `SCANNER_CLI_NO_VERIFY` | `false` | The flag passed to `scannercli` to skip verifying TLS certificates | +| `SCANNER_CLI_SHOW_NEGLIGIBLE` | `true` | The flag passed to `scannercli` to show negligible/unknown severity vulnerabilities | +| `SCANNER_CLI_SHOW_WILL_NOT_FIX` | `false` | The flag passed to `scannercli` to show vulnerabilities that will not be fixed | +| `SCANNER_CLI_HIDE_BASE` | `true` | The flag passed to `scannercli` to hide vulnerabilities in the base image | +| `SCANNER_CLI_OVERRIDE_REGISTRY_CREDENTIALS` | `false` | The flag to enable passing `--robot-username` and `--robot-password` flags to the `scannercli` executable binary | +| `SCANNER_STORE_REDIS_URL` | `redis://harbor-harbor-redis:6379` | Redis server URI for a redis store | +| `SCANNER_STORE_REDIS_NAMESPACE` | `harbor.scanner.aqua:store` | A namespace for keys in a redis store | +| `SCANNER_STORE_REDIS_POOL_MAX_ACTIVE` | `5` | The max number of connections allocated by the pool for a redis store | +| `SCANNER_STORE_REDIS_POOL_MAX_IDLE` | `5` | The max number of idle connections in the pool for a redis store | +| `SCANNER_STORE_REDIS_SCAN_JOB_TTL` | `1h` | The time to live for persisting scan jobs and associated scan reports | ## Troubleshooting ### Error: failed getting image manifest: 412 Precondition Failed -Currently, there's a limitation of `scannercli` which does not accept Harbor robot account credentials passed by a -Harbor scan job to the adapter service. This effectively means that the Aqua CSP scanner is using the credentials -provided in Aqua CSP management console under the **Integrations** / **Image Registries** section. However, these -credentials do not have enough permissions to bypass the deployment security checker when it's enabled in the Harbor -project configuration. In other words, the deployment security checker prevents the Aqua CSP scanner from pulling -an image, which it needs to be able to do in order to scan it. - +Currently, there's a limitation of `scannercli` in Aqua CSP version < **TBD** which does not accept Harbor robot account +credentials passed by a Harbor scan job to the adapter service. This effectively means that the Aqua CSP scanner is +using the credentials provided in Aqua CSP management console under the **Integrations** / **Image Registries** section. +However, these credentials do not have enough permissions to bypass the deployment security checker when it's enabled in +the Harbor project configuration. In other words, the deployment security checker prevents the Aqua CSP scanner from +pulling an image, which it needs to be able to do in order to scan it. + ![](docs/images/harbor_deployment_security.png) -The only available solution to that problem is disabling deployment security checks in Harbor. +The available solution depends on the version of your Aqua CSP deployment. In `scannercli` version >= **TBD** we've +introduced new `--rebot-username` and `--robot-password` args to respect credentials provided by Harbor. -> We're working with the Harbor team on the [enhancement to robot accounts](https://github.com/goharbor/harbor/issues/11574). +- For Aqua CSP version < **TBD** you can only disable deployment security checks in the Harbor interface under the + project configuration. +- For Aqua CSP version >= **TBD** set the value of the `SCANNER_CLI_OVERRIDE_REGISTRY_CREDENTIALS` env to `true`. ## Contributing @@ -396,10 +403,11 @@ requests. This project is licensed under the [Apache 2.0](LICENSE). -[release-img]: https://img.shields.io/github/release/aquasecurity/harbor-scanner-aqua.svg +[release-img]: https://img.shields.io/github/release/aquasecurity/harbor-scanner-aqua.svg?logo=github [release]: https://github.com/aquasecurity/harbor-scanner-aqua/releases [build-action-img]: https://github.com/aquasecurity/harbor-scanner-aqua/workflows/build/badge.svg -[build-action]: https://github.com/aquasecurity/harbor-scanner-aqua/actions +[release-action-img]: https://github.com/aquasecurity/harbor-scanner-aqua/workflows/release/badge.svg +[actions]: https://github.com/aquasecurity/harbor-scanner-aqua/actions [codecov-img]: https://codecov.io/gh/aquasecurity/harbor-scanner-aqua/branch/master/graph/badge.svg [codecov]: https://codecov.io/gh/aquasecurity/harbor-scanner-aqua [report-card-img]: https://goreportcard.com/badge/github.com/aquasecurity/harbor-scanner-aqua diff --git a/helm/harbor-scanner-aqua/README.md b/helm/harbor-scanner-aqua/README.md index ce3646c..443a0e5 100644 --- a/helm/harbor-scanner-aqua/README.md +++ b/helm/harbor-scanner-aqua/README.md @@ -8,14 +8,14 @@ Aqua CSP Scanner as a plug-in vulnerability scanner in the Harbor registry. ``` $ helm install harbor-scanner-aqua . \ - --namespace harbor \ - --set aqua.version=$AQUA_VERSION \ - --set aqua.registry.server=registry.aquasec.com \ - --set aqua.registry.username=$AQUA_REGISTRY_USERNAME \ - --set aqua.registry.password=$AQUA_REGISTRY_PASSWORD \ - --set scanner.aqua.username=$AQUA_CONSOLE_USERNAME \ - --set scanner.aqua.password=$AQUA_CONSOLE_PASSWORD \ - --set scanner.aqua.host=http://csp-console-svc.aqua:8080 + --namespace harbor \ + --set aqua.version=$AQUA_VERSION \ + --set aqua.registry.server=registry.aquasec.com \ + --set aqua.registry.username=$AQUA_REGISTRY_USERNAME \ + --set aqua.registry.password=$AQUA_REGISTRY_PASSWORD \ + --set scanner.aqua.username=$AQUA_CONSOLE_USERNAME \ + --set scanner.aqua.password=$AQUA_CONSOLE_PASSWORD \ + --set scanner.aqua.host=http://csp-console-svc.aqua:8080 ``` ### With TLS @@ -24,26 +24,26 @@ $ helm install harbor-scanner-aqua . \ ``` $ openssl genrsa -out tls.key 2048 $ openssl req -new -x509 \ - -key tls.key \ - -out tls.crt \ - -days 365 \ - -subj /CN=harbor-scanner-aqua.harbor + -key tls.key \ + -out tls.crt \ + -days 365 \ + -subj /CN=harbor-scanner-aqua.harbor ``` 2. Install the `harbor-scanner-aqua` chart: ``` $ helm install harbor-scanner-aqua . \ - --namespace harbor \ - --set service.port=8443 \ - --set scanner.api.tlsEnabled=true \ - --set scanner.api.tlsCertificate="`cat tls.crt`" \ - --set scanner.api.tlsKey="`cat tls.key`" \ - --set aqua.version=$AQUA_VERSION \ - --set aqua.registry.server=registry.aquasec.com \ - --set aqua.registry.username=$AQUA_REGISTRY_USERNAME \ - --set aqua.registry.password=$AQUA_REGISTRY_PASSWORD \ - --set scanner.aqua.username=$AQUA_CONSOLE_USERNAME \ - --set scanner.aqua.password=$AQUA_CONSOLE_PASSWORD \ - --set scanner.aqua.host=http://csp-console-svc.aqua:8080 + --namespace harbor \ + --set service.port=8443 \ + --set scanner.api.tlsEnabled=true \ + --set scanner.api.tlsCertificate="`cat tls.crt`" \ + --set scanner.api.tlsKey="`cat tls.key`" \ + --set aqua.version=$AQUA_VERSION \ + --set aqua.registry.server=registry.aquasec.com \ + --set aqua.registry.username=$AQUA_REGISTRY_USERNAME \ + --set aqua.registry.password=$AQUA_REGISTRY_PASSWORD \ + --set scanner.aqua.username=$AQUA_CONSOLE_USERNAME \ + --set scanner.aqua.password=$AQUA_CONSOLE_PASSWORD \ + --set scanner.aqua.host=http://csp-console-svc.aqua:8080 ``` ## Introduction @@ -102,6 +102,7 @@ The following table lists the configurable parameters of the scanner adapter cha | `scanner.aqua.scannerCLIShowNegligible` | The flag passed to `scannercli` to show negligible/unknown severity vulnerabilities | `true` | | `scanner.aqua.scannerCLIShowWillNotFix` | The flag passed to `scannercli` to show vulnerabilities that will not be fixed | `false` | | `scanner.aqua.scannerCLIHideBase` | The flag passed to `scannercli` to hide vulnerabilities in the base image | `true` | +| `scanner.aqua.scannerCLIOverrideRegistryCredentials` | The flag to enable passing `--robot-username` and `--robot-password` flags to the `scannercli` executable binary | `false` | | `scanner.aqua.reportsDir` | Directory to save temporary scan reports | `/var/lib/scanner/reports` | | `scanner.aqua.useImageTag` | The flag to determine whether the image tag or digest is used in the image reference passed to `scannercli` | `true` | | `scanner.api.tlsEnabled` | The flag to enable or disable TLS for HTTP | `true` | @@ -125,7 +126,7 @@ Specify each parameter using the `--set key=value[,key=value]` argument to `helm ``` $ helm install my-release . \ - --namespace my-namespace \ - --set scanner.aqua.username=$AQUA_CONSOLE_USERNAME \ - --set scanner.aqua.password=$AQUA_CONSOLE_PASSWORD + --namespace my-namespace \ + --set scanner.aqua.username=$AQUA_CONSOLE_USERNAME \ + --set scanner.aqua.password=$AQUA_CONSOLE_PASSWORD ``` diff --git a/helm/harbor-scanner-aqua/templates/deployment.yaml b/helm/harbor-scanner-aqua/templates/deployment.yaml index c70e48f..8708dfc 100644 --- a/helm/harbor-scanner-aqua/templates/deployment.yaml +++ b/helm/harbor-scanner-aqua/templates/deployment.yaml @@ -92,6 +92,8 @@ spec: value: {{ .Values.scanner.aqua.reportsDir | quote }} - name: "SCANNER_CLI_HIDE_BASE" value: {{ .Values.scanner.aqua.scannerCLIHideBase | default true | quote }} + - name: "SCANNER_CLI_OVERRIDE_REGISTRY_CREDENTIALS" + value: {{ .Values.scanner.aqua.scannerCLIOverrideRegistryCredentials | default false | quote }} - name: "SCANNER_AQUA_USE_IMAGE_TAG" value: {{ .Values.scanner.aqua.useImageTag | quote }} - name: "SCANNER_STORE_REDIS_URL" diff --git a/helm/harbor-scanner-aqua/values.yaml b/helm/harbor-scanner-aqua/values.yaml index 590e5bd..c3ce61b 100644 --- a/helm/harbor-scanner-aqua/values.yaml +++ b/helm/harbor-scanner-aqua/values.yaml @@ -74,6 +74,9 @@ scanner: scannerCLIShowWillNotFix: false ## scannerCLIHideBase the flag passed to `scannercli` to hide vulnerabilities in the base image scannerCLIHideBase: true + ## scannerCLIOverrideRegistryCredentials the flag to enable passing `--robot-username` and `--robot-password` + ## flags to the `scannercli` executable binary + scannerCLIOverrideRegistryCredentials: false store: redisURL: "redis://harbor-harbor-redis:6379" redisNamespace: "harbor.scanner.aqua:store" diff --git a/pkg/aqua/command.go b/pkg/aqua/command.go index 85b44fd..a8a3aeb 100644 --- a/pkg/aqua/command.go +++ b/pkg/aqua/command.go @@ -14,6 +14,12 @@ type ImageRef struct { Repository string Tag string Digest string + Auth RegistryAuth +} + +type RegistryAuth struct { + Username string + Password string } func (ir *ImageRef) WithTag() string { @@ -82,6 +88,10 @@ func (c *command) Scan(imageRef ImageRef) (report ScanReport, err error) { log.WithFields(log.Fields{"exec": executable, "args": args}).Debug("Running scannercli") + if c.cfg.ScannerCLIOverrideRegistryCredentials { + args = append(args, fmt.Sprintf("--robot-username=%s", imageRef.Auth.Username), + fmt.Sprintf("--robot-password=%s", imageRef.Auth.Password)) + } args = append(args, fmt.Sprintf("--password=%s", c.cfg.Password), image) cmd := exec.Command(executable, args...) @@ -89,17 +99,21 @@ func (c *command) Scan(imageRef ImageRef) (report ScanReport, err error) { stdout, exitCode, err := c.ambassador.RunCmd(cmd) if err != nil { log.WithFields(log.Fields{ - "image_ref": imageRef, - "exit_code": exitCode, - "std_out": string(stdout), + "image_ref_repository": imageRef.Repository, + "image_ref_tag": imageRef.Tag, + "image_ref_digest": imageRef.Digest, + "exit_code": exitCode, + "std_out": string(stdout), }).Error("Error while running scannercli command") return report, fmt.Errorf("running command: %v: %v", err, string(stdout)) } log.WithFields(log.Fields{ - "image_ref": imageRef, - "exit_code": exitCode, - "std_out": string(stdout), + "image_ref_repository": imageRef.Repository, + "image_ref_tag": imageRef.Tag, + "image_ref_digest": imageRef.Digest, + "exit_code": exitCode, + "std_out": string(stdout), }).Trace("Running scannercli command finished") err = json.NewDecoder(reportFile).Decode(&report) diff --git a/pkg/aqua/command_test.go b/pkg/aqua/command_test.go index d44f34b..4361a7f 100644 --- a/pkg/aqua/command_test.go +++ b/pkg/aqua/command_test.go @@ -20,21 +20,26 @@ var ( func TestCommand_Scan(t *testing.T) { config := etc.AquaCSP{ - ReportsDir: "/var/lib/reports", - Username: "scanner", - Password: "ch@ng3me!", - Host: "https://aqua.domain:8080", - Registry: "Harbor", - UseImageTag: true, - ScannerCLINoVerify: true, - ScannerCLIShowNegligible: true, - ScannerCLIShowWillNotFix: true, - ScannerCLIHideBase: true, + ReportsDir: "/var/lib/reports", + Username: "scanner", + Password: "ch@ng3me!", + Host: "https://aqua.domain:8080", + Registry: "Harbor", + UseImageTag: true, + ScannerCLINoVerify: true, + ScannerCLIShowNegligible: true, + ScannerCLIShowWillNotFix: true, + ScannerCLIHideBase: true, + ScannerCLIOverrideRegistryCredentials: true, } imageRef := ImageRef{ Repository: "library/alpine", Tag: "3.10.2", + Auth: RegistryAuth{ + Username: "robotName", + Password: "robotPassword", + }, } t.Run("Should return error when scannercli lookup returns error", func(t *testing.T) { @@ -81,6 +86,8 @@ func TestCommand_Scan(t *testing.T) { "--show-will-not-fix=true", "--hide-base=true", "--jsonfile=/var/lib/scanner/reports/aqua_scan_report_1234567890.json", + "--robot-username=robotName", + "--robot-password=robotPassword", "--password=ch@ng3me!", "library/alpine:3.10.2", }, @@ -118,6 +125,8 @@ func TestCommand_Scan(t *testing.T) { "--show-will-not-fix=true", "--hide-base=true", "--jsonfile=/var/lib/scanner/reports/aqua_scan_report_1234567890.json", + "--robot-username=robotName", + "--robot-password=robotPassword", "--password=ch@ng3me!", "library/alpine:3.10.2", }, diff --git a/pkg/etc/config.go b/pkg/etc/config.go index d766101..234e5bd 100644 --- a/pkg/etc/config.go +++ b/pkg/etc/config.go @@ -53,6 +53,8 @@ type AquaCSP struct { ScannerCLIShowNegligible bool `env:"SCANNER_CLI_SHOW_NEGLIGIBLE" envDefault:"true"` ScannerCLIShowWillNotFix bool `env:"SCANNER_CLI_SHOW_WILL_NOT_FIX" envDefault:"false"` ScannerCLIHideBase bool `env:"SCANNER_CLI_HIDE_BASE" envDefault:"true"` + + ScannerCLIOverrideRegistryCredentials bool `env:"SCANNER_CLI_OVERRIDE_REGISTRY_CREDENTIALS" envDefault:"false"` } type Store struct { diff --git a/pkg/etc/config_test.go b/pkg/etc/config_test.go index 63eec6e..6080ae4 100644 --- a/pkg/etc/config_test.go +++ b/pkg/etc/config_test.go @@ -40,6 +40,8 @@ func TestGetConfig(t *testing.T) { ScannerCLIShowNegligible: true, ScannerCLIShowWillNotFix: false, ScannerCLIHideBase: true, + + ScannerCLIOverrideRegistryCredentials: false, }, Store: Store{ RedisURL: "redis://harbor-harbor-redis:6379", @@ -53,21 +55,22 @@ func TestGetConfig(t *testing.T) { { name: "Should overwrite default config with environment variables", envs: envs{ - "SCANNER_API_ADDR": ":4200", - "SCANNER_API_TLS_CERTIFICATE": "/certs/tls.crt", - "SCANNER_API_TLS_KEY": "/certs/tls.key", - "SCANNER_API_READ_TIMEOUT": "1h", - "SCANNER_API_WRITE_TIMEOUT": "2m", - "SCANNER_API_IDLE_TIMEOUT": "1h2m3s", - "SCANNER_AQUA_REPORTS_DIR": "/somewhere/else", - "SCANNER_AQUA_USE_IMAGE_TAG": "false", - "SCANNER_AQUA_HOST": "http://aqua-web.aqua-security:8080", - "SCANNER_AQUA_USERNAME": "scanner", - "SCANNER_AQUA_PASSWORD": "s3cret", - "SCANNER_CLI_NO_VERIFY": "true", - "SCANNER_CLI_SHOW_NEGLIGIBLE": "false", - "SCANNER_CLI_SHOW_WILL_NOT_FIX": "true", - "SCANNER_CLI_HIDE_BASE": "false", + "SCANNER_API_ADDR": ":4200", + "SCANNER_API_TLS_CERTIFICATE": "/certs/tls.crt", + "SCANNER_API_TLS_KEY": "/certs/tls.key", + "SCANNER_API_READ_TIMEOUT": "1h", + "SCANNER_API_WRITE_TIMEOUT": "2m", + "SCANNER_API_IDLE_TIMEOUT": "1h2m3s", + "SCANNER_AQUA_REPORTS_DIR": "/somewhere/else", + "SCANNER_AQUA_USE_IMAGE_TAG": "false", + "SCANNER_AQUA_HOST": "http://aqua-web.aqua-security:8080", + "SCANNER_AQUA_USERNAME": "scanner", + "SCANNER_AQUA_PASSWORD": "s3cret", + "SCANNER_CLI_NO_VERIFY": "true", + "SCANNER_CLI_SHOW_NEGLIGIBLE": "false", + "SCANNER_CLI_SHOW_WILL_NOT_FIX": "true", + "SCANNER_CLI_HIDE_BASE": "false", + "SCANNER_CLI_OVERRIDE_REGISTRY_CREDENTIALS": "true", }, expectedConfig: Config{ API: API{ @@ -79,16 +82,17 @@ func TestGetConfig(t *testing.T) { IdleTimeout: parseDuration(t, "1h2m3s"), }, AquaCSP: AquaCSP{ - Username: "scanner", - Password: "s3cret", - Host: "http://aqua-web.aqua-security:8080", - Registry: "Harbor", - ReportsDir: "/somewhere/else", - UseImageTag: false, - ScannerCLINoVerify: true, - ScannerCLIShowNegligible: false, - ScannerCLIShowWillNotFix: true, - ScannerCLIHideBase: false, + Username: "scanner", + Password: "s3cret", + Host: "http://aqua-web.aqua-security:8080", + Registry: "Harbor", + ReportsDir: "/somewhere/else", + UseImageTag: false, + ScannerCLINoVerify: true, + ScannerCLIShowNegligible: false, + ScannerCLIShowWillNotFix: true, + ScannerCLIHideBase: false, + ScannerCLIOverrideRegistryCredentials: true, }, Store: Store{ RedisURL: "redis://harbor-harbor-redis:6379", diff --git a/pkg/harbor/model.go b/pkg/harbor/model.go index 21548c7..c10da8d 100644 --- a/pkg/harbor/model.go +++ b/pkg/harbor/model.go @@ -2,7 +2,11 @@ package harbor import ( "bytes" + "encoding/base64" "encoding/json" + "errors" + "fmt" + "strings" "time" ) @@ -66,6 +70,37 @@ type Registry struct { Authorization string `json:"authorization"` } +func (r Registry) GetBasicCredentials() (username, password string, err error) { + tokens := strings.Split(r.Authorization, " ") + if len(tokens) != 2 { + err = fmt.Errorf("parsing authorization: expected got [%s]", r.Authorization) + return + } + switch tokens[0] { + case "Basic": + return r.decodeBasicAuthentication(tokens[1]) + } + err = fmt.Errorf("unsupported authorization type: %s", tokens[0]) + return +} + +func (r Registry) decodeBasicAuthentication(value string) (username, password string, err error) { + creds, err := base64.StdEncoding.DecodeString(value) + if err != nil { + return + } + tokens := strings.Split(string(creds), ":") + if len(tokens) != 2 { + err = errors.New("username and password not split by single colon") + return + } + + username = tokens[0] + password = tokens[1] + + return +} + type Artifact struct { Repository string `json:"repository"` Digest string `json:"digest"` diff --git a/pkg/harbor/model_test.go b/pkg/harbor/model_test.go index 60178da..66339e8 100644 --- a/pkg/harbor/model_test.go +++ b/pkg/harbor/model_test.go @@ -1 +1,55 @@ package harbor + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestRegistry_GetBasicCredentials(t *testing.T) { + testCases := []struct { + Authorization string + + ExpectedUsername string + ExpectedPassword string + + ExpectedError string + }{ + { + Authorization: "", + ExpectedError: "parsing authorization: expected got []", + }, + { + Authorization: "Basic aGFyYm9yOnMzY3JldA==", + ExpectedUsername: "harbor", + ExpectedPassword: "s3cret", + }, + { + Authorization: "Basic aGFyYm9yTmFtZQ==", + ExpectedError: "username and password not split by single colon", + }, + { + Authorization: "Basic invalidbase64", + ExpectedError: "illegal base64 data at input byte 12", + }, + { + Authorization: "APIKey 0123456789", + ExpectedError: "unsupported authorization type: APIKey", + }, + } + for _, tc := range testCases { + t.Run(tc.Authorization, func(t *testing.T) { + username, password, err := Registry{Authorization: tc.Authorization}.GetBasicCredentials() + switch { + case tc.ExpectedError != "": + assert.EqualError(t, err, tc.ExpectedError) + default: + require.NoError(t, err) + assert.Equal(t, tc.ExpectedUsername, username) + assert.Equal(t, tc.ExpectedPassword, password) + } + + }) + } +} diff --git a/pkg/scanner/adapter.go b/pkg/scanner/adapter.go index b4ad638..369c900 100644 --- a/pkg/scanner/adapter.go +++ b/pkg/scanner/adapter.go @@ -1,6 +1,8 @@ package scanner import ( + "fmt" + "github.com/aquasecurity/harbor-scanner-aqua/pkg/aqua" "github.com/aquasecurity/harbor-scanner-aqua/pkg/harbor" ) @@ -21,15 +23,25 @@ func NewAdapter(command aqua.Command, transformer Transformer) Adapter { } } -func (s *adapter) Scan(req harbor.ScanRequest) (harbor.ScanReport, error) { - aquaScanReport, err := s.command.Scan(aqua.ImageRef{ +func (s *adapter) Scan(req harbor.ScanRequest) (harborReport harbor.ScanReport, err error) { + username, password, err := req.Registry.GetBasicCredentials() + if err != nil { + err = fmt.Errorf("getting basic credentials from scan request: %w", err) + return + } + + aquaReport, err := s.command.Scan(aqua.ImageRef{ Repository: req.Artifact.Repository, Tag: req.Artifact.Tag, Digest: req.Artifact.Digest, + Auth: aqua.RegistryAuth{ + Username: username, + Password: password, + }, }) if err != nil { - return harbor.ScanReport{}, err + return } - harborScanReport := s.transformer.Transform(req.Artifact, aquaScanReport) - return harborScanReport, nil + harborReport = s.transformer.Transform(req.Artifact, aquaReport) + return } diff --git a/pkg/scanner/adapter_test.go b/pkg/scanner/adapter_test.go index 758b59e..b4ee877 100644 --- a/pkg/scanner/adapter_test.go +++ b/pkg/scanner/adapter_test.go @@ -1,35 +1,70 @@ package scanner import ( + "testing" + "github.com/aquasecurity/harbor-scanner-aqua/pkg/aqua" "github.com/aquasecurity/harbor-scanner-aqua/pkg/harbor" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "testing" ) +var NoError error + func TestAdapter_Scan(t *testing.T) { - command := &aqua.MockCommand{} - transformer := &MockTransformer{} - - artifact := harbor.Artifact{ - Repository: "library/golang", - Tag: "1.12.4", - } - scanRequest := harbor.ScanRequest{ - Artifact: artifact, - } - - aquaReport := aqua.ScanReport{} - harborReport := harbor.ScanReport{} - - command.On("Scan", aqua.ImageRef{Repository: "library/golang", Tag: "1.12.4"}).Return(aquaReport, nil) - transformer.On("Transform", artifact, aquaReport).Return(harborReport) - - adapter := NewAdapter(command, transformer) - r, err := adapter.Scan(scanRequest) - require.NoError(t, err) - require.Equal(t, harborReport, r) - - command.AssertExpectations(t) - transformer.AssertExpectations(t) + + t.Run("Should return error when getting registry credentials fails", func(t *testing.T) { + command := &aqua.MockCommand{} + transformer := &MockTransformer{} + + scanRequest := harbor.ScanRequest{ + Registry: harbor.Registry{ + Authorization: "Bearer 0123456789", + }, + } + + _, err := NewAdapter(command, transformer).Scan(scanRequest) + assert.EqualError(t, err, "getting basic credentials from scan request: unsupported authorization type: Bearer") + + command.AssertExpectations(t) + transformer.AssertExpectations(t) + }) + + t.Run("Should return Harbor report", func(t *testing.T) { + command := &aqua.MockCommand{} + transformer := &MockTransformer{} + + artifact := harbor.Artifact{ + Repository: "library/golang", + Tag: "1.12.4", + } + scanRequest := harbor.ScanRequest{ + Registry: harbor.Registry{ + Authorization: "Basic cm9ib3ROYW1lOnJvYm90UGFzc3dvcmQ=", + }, + Artifact: artifact, + } + imageRef := aqua.ImageRef{ + Repository: "library/golang", + Tag: "1.12.4", + Auth: aqua.RegistryAuth{ + Username: "robotName", + Password: "robotPassword", + }, + } + + aquaReport := aqua.ScanReport{} + harborReport := harbor.ScanReport{} + + command.On("Scan", imageRef).Return(aquaReport, NoError) + transformer.On("Transform", artifact, aquaReport).Return(harborReport) + + r, err := NewAdapter(command, transformer).Scan(scanRequest) + require.NoError(t, err) + require.Equal(t, harborReport, r) + + command.AssertExpectations(t) + transformer.AssertExpectations(t) + }) + }