diff --git a/Dockerfile b/Dockerfile index 0ae695c..72a7e68 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,17 +1,9 @@ -FROM alpine:3.16@sha256:bc41182d7ef5ffc53a40b044e725193bc10142a1243f395ee852a8d9730fc2ad as alpine - -ARG TARGETARCH - -RUN set -eux; \ - apk add -U --no-cache ca-certificates - - -FROM scratch +FROM ghcr.io/ckotzbauer/distroless-git-slim ARG TARGETOS ARG TARGETARCH -COPY --from=alpine /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ COPY dist/vulnerability-operator_${TARGETOS}_${TARGETARCH}*/vulnerability-operator /usr/local/bin/vulnerability-operator +COPY hack/git-ask-pass.sh /usr/local/bin/git-ask-pass.sh ENTRYPOINT ["/usr/local/bin/vulnerability-operator"] diff --git a/README.md b/README.md index 8f005da..45cbe91 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # vulnerability-operator -> Scans SBOMs for vulnerabilities +> Scans SBOMs and Images for vulnerabilities [![test](https://github.com/ckotzbauer/vulnerability-operator/actions/workflows/test.yml/badge.svg)](https://github.com/ckotzbauer/vulnerability-operator/actions/workflows/test.yml) @@ -21,7 +21,8 @@ The image contains versions of `k8s.io/client-go`. Kubernetes aims to provide fo | vulnerability-operator | k8s.io/{api,apimachinery,client-go} | expected kubernetes compatibility | |--------------------------|-------------------------------------|-----------------------------------| -| main | v0.24.3 | 1.23.x, 1.24.x, 1.25.x | +| main | v0.25.1 | 1.24.x, 1.25.x, 1.26.x | +| 0.9.0 | v0.25.1 | 1.24.x, 1.25.x, 1.26.x | | 0.8.0 | v0.24.3 | 1.23.x, 1.24.x, 1.25.x | | 0.7.0 | v0.24.2 | 1.23.x, 1.24.x, 1.25.x | | 0.6.0 | v0.24.0 | 1.23.x, 1.24.x, 1.25.x | @@ -69,7 +70,12 @@ All parameters are cli-flags. | `git-repository` | `true` when `git` target is used. | `""` | Git-Repository-URL (HTTPS). | | `git-branch` | `false` | `main` | Git-Branch to checkout. | | `git-path` | `false` | `""` | Folder-Path inside the Git-Repository. | -| `git-access-token` | `true` when `git` target is used. | `""` | Git-Personal-Access-Token with write-permissions. | +| `git-access-token` | `false` | `""` | Git-Personal-Access-Token with read-permissions. | +| `git-username` | `false` | `""` | Git-Username | +| `git-password` | `false` | `""` | Git-Password | +| `github-app-id` | `false` | `""` | GitHub App-ID. | +| `github-app-installation-id` | `false` | `""` | GitHub App-Installation-ID. | +| `git-fallback-clone` | `false` | `false` | Use git binary for clones. | | `reports-dir` | `false` | `/reports` | Directory to place the reports. | @@ -103,6 +109,11 @@ servicemonitor: The contents of this git-repository are typically generated from the [sbom-generator](https://github.com/ckotzbauer/sbom-operator). All files named `sbom.json`, `sbom.txt`, `sbom.xml` or `sbom.spdx` are gathered regarding the `git-*` config-flags. +You can use a token-based authentication (e.g. a PAT for GitHub) with `--git-access-token`, BasicAuth with username and password (`--git-username`, `--git-password`) or +Github App Authentication (`--github-app-id`, `--github-app-installation-id`, env: `VULN_GITHUB_APP_PRIVATE_KEY`) The private-key has to be Base64 encoded. +To clone repositories with a git-binary instead of the pure golang implementation, add the `--git-fallback-clone` flag. This is needed for Git-Providers which +need `multi_ack` e.g. Azure DevOps or AWS CodeCommit. +See [this issue](https://github.com/go-git/go-git/issues/64) for more details. ## Targets @@ -344,9 +355,9 @@ is owned by the corresponding pod to enable autocleanup by Kubernetes. ## Security -The docker-image is based on `scratch` to reduce the attack-surface and keep the image small. Furthermore the image and release-artifacts are signed -with [cosign](https://github.com/sigstore/cosign) and attested with provenance-files. The release-process satisfies SLSA Level 2. All of those "metadata files" are -also stored in a dedicated repository `ghcr.io/ckotzbauer/vulnerability-operator-metadata`. +The docker-image is based on on a [distroless git-image](https://github.com/ckotzbauer/distroless-git-slim) to reduce the attack-surface and keep the image small. +Furthermore the image and release-artifacts are signed with [cosign](https://github.com/sigstore/cosign) and attested with provenance-files. The release-process +satisfies SLSA Level 2. All of those "metadata files" are also stored in a dedicated repository `ghcr.io/ckotzbauer/vulnerability-operator-metadata`. Both, SLSA and the signatures are still experimental for this project. When discovering security issues please refer to the [Security process](https://github.com/ckotzbauer/.github/blob/main/SECURITY.md). diff --git a/go.mod b/go.mod index afcb17a..8a83dbd 100644 --- a/go.mod +++ b/go.mod @@ -292,7 +292,7 @@ require ( require ( github.com/ckotzbauer/libk8soci v0.0.0-20220917113119-cfc4b7682664 github.com/fsnotify/fsnotify v1.5.4 // indirect - github.com/go-git/go-git/v5 v5.4.2 + github.com/go-git/go-git/v5 v5.4.2 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/inconshreveable/mousetrap v1.0.0 // indirect github.com/magiconair/properties v1.8.6 // indirect diff --git a/go.sum b/go.sum index bc9fc43..8b3ad70 100644 --- a/go.sum +++ b/go.sum @@ -372,8 +372,6 @@ github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMn github.com/cilium/ebpf v0.4.0/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs= github.com/cilium/ebpf v0.6.2/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs= github.com/cilium/ebpf v0.7.0/go.mod h1:/oI2+1shJiTGAMgl6/RgJr36Eo1jzrRcAWbcXO2usCA= -github.com/ckotzbauer/libk8soci v0.0.0-20220903073536-1b257a42db3e h1:ESs3uPw3lCtPJRawMEbETVAP0sPLktR7BGwmZwL9SxM= -github.com/ckotzbauer/libk8soci v0.0.0-20220903073536-1b257a42db3e/go.mod h1:Ha7CyFvpgSMkrzkGkLtGGiqT1+GAv2TscHubjBunf9o= github.com/ckotzbauer/libk8soci v0.0.0-20220917113119-cfc4b7682664 h1:iRgtgS+Z+8WY8P1dZCuak5rWZ4oy1nHReKEjj12WMGc= github.com/ckotzbauer/libk8soci v0.0.0-20220917113119-cfc4b7682664/go.mod h1:3y9g/UyTis8fYNG4Enu7a2OcHAfoZQ9XC3KgGfjY00o= github.com/ckotzbauer/libstandard v0.0.0-20220903141541-869498553a91 h1:gEWEtrlzJt36HtXNVVhkC7B0ivOHD3gGmuYutXh5vIw= diff --git a/hack/git-ask-pass.sh b/hack/git-ask-pass.sh new file mode 100644 index 0000000..cae3184 --- /dev/null +++ b/hack/git-ask-pass.sh @@ -0,0 +1,7 @@ +#!/bin/sh +# This script is used as the commaned supplied to GIT_ASKPASS as a way to supply username/password +# credentials to git, without having to use git credentials helpers, or having on-disk config. +case "$1" in +Username*) echo "${GIT_USERNAME}" ;; +Password*) echo "${GIT_PASSWORD}" ;; +esac diff --git a/internal/vuln/config.go b/internal/vuln/config.go index 2901813..178dbea 100644 --- a/internal/vuln/config.go +++ b/internal/vuln/config.go @@ -1,38 +1,49 @@ package vuln type Config struct { - Cron string `yaml:"cron" env:"VULN_CRON" flag:"cron"` - Sources []string `yaml:"sources" env:"VULN_SOURCES" flag:"sources"` - Targets []string `yaml:"targets" env:"VULN_TARGETS" flag:"targets"` - GrypeConfigFile string `yaml:"grypeConfigFile" env:"VULN_GRYPE_CONFIG_FILE" flag:"grype-config-file"` - OnlyFixed bool `yaml:"onlyFixes" env:"VULN_ONLY_FIXED" flag:"only-fixed"` - MinSeverity string `yaml:"minSeverity" env:"VULN_MIN_SEVERITY" flag:"min-severity"` - GitWorkingTree string `yaml:"gitWorkingTree" env:"VULN_GIT_WORKINGTREE" flag:"git-workingtree"` - GitRepository string `yaml:"gitRepository" env:"VULN_GIT_REPOSITORY" flag:"git-repository"` - GitBranch string `yaml:"gitBranch" env:"VULN_GIT_BRANCH" flag:"git-branch"` - GitPath string `yaml:"gitPath" env:"VULN_GIT_PATH" flag:"git-path"` - GitAccessToken string `yaml:"gitAccessToken" env:"VULN_GIT_ACCESS_TOKEN" flag:"git-access-token"` - ReportsDir string `yaml:"reportsDir" env:"VULN_REPORTS_DIR" flag:"reports-dir"` - PodLabelSelector string `yaml:"podLabelSelector" env:"VULN_POD_LABEL_SELECTOR" flag:"pod-label-selector"` - NamespaceLabelSelector string `yaml:"namespaceLabelSelector" env:"VULN_NAMESPACE_LABEL_SELECTOR" flag:"namespace-label-selector"` - Verbosity string `env:"VULN_VERBOSITY" flag:"verbosity"` + Cron string `yaml:"cron" env:"VULN_CRON" flag:"cron"` + Sources []string `yaml:"sources" env:"VULN_SOURCES" flag:"sources"` + Targets []string `yaml:"targets" env:"VULN_TARGETS" flag:"targets"` + GrypeConfigFile string `yaml:"grypeConfigFile" env:"VULN_GRYPE_CONFIG_FILE" flag:"grype-config-file"` + OnlyFixed bool `yaml:"onlyFixes" env:"VULN_ONLY_FIXED" flag:"only-fixed"` + MinSeverity string `yaml:"minSeverity" env:"VULN_MIN_SEVERITY" flag:"min-severity"` + GitWorkingTree string `yaml:"gitWorkingTree" env:"VULN_GIT_WORKINGTREE" flag:"git-workingtree"` + GitRepository string `yaml:"gitRepository" env:"VULN_GIT_REPOSITORY" flag:"git-repository"` + GitBranch string `yaml:"gitBranch" env:"VULN_GIT_BRANCH" flag:"git-branch"` + GitPath string `yaml:"gitPath" env:"VULN_GIT_PATH" flag:"git-path"` + GitAccessToken string `yaml:"gitAccessToken" env:"VULN_GIT_ACCESS_TOKEN" flag:"git-access-token"` + GitUserName string `yaml:"gitUserName" env:"VULN_GIT_USERNAME" flag:"git-username"` + GitPassword string `yaml:"gitPassword" env:"VULN_GIT_PASSWORD" flag:"git-password"` + GitFallbackClone bool `yaml:"gitFallbackClone" env:"VULN_GIT_FALLBACK_CLONE" flag:"git-fallback-clone"` + GitHubAppId string `yaml:"githubAppId" env:"VULN_GITHUB_APP_ID" flag:"github-app-id"` + GitHubAppInstallationId string `yaml:"githubAppInstallationId" env:"VULN_GITHUB_APP_INSTALLATION_ID" flag:"github-app-installation-id"` + GitHubPrivateKey string `yaml:"githubAppPrivateKey" env:"VULN_GITHUB_APP_PRIVATE_KEY"` + ReportsDir string `yaml:"reportsDir" env:"VULN_REPORTS_DIR" flag:"reports-dir"` + PodLabelSelector string `yaml:"podLabelSelector" env:"VULN_POD_LABEL_SELECTOR" flag:"pod-label-selector"` + NamespaceLabelSelector string `yaml:"namespaceLabelSelector" env:"VULN_NAMESPACE_LABEL_SELECTOR" flag:"namespace-label-selector"` + Verbosity string `env:"VULN_VERBOSITY" flag:"verbosity"` } var ( - ConfigKeyCron = "cron" - ConfigKeySources = "sources" - ConfigKeyTargets = "targets" - ConfigKeyGrypeConfigFile = "grype-config-file" - ConfigKeyOnlyFixed = "only-fixed" - ConfigKeyMinSeverity = "min-severity" - ConfigKeyGitWorkingTree = "git-workingtree" - ConfigKeyGitRepository = "git-repository" - ConfigKeyGitBranch = "git-branch" - ConfigKeyGitPath = "git-path" - ConfigKeyGitAccessToken = "git-access-token" - ConfigKeyReportsDir = "reports-dir" - ConfigKeyPodLabelSelector = "pod-label-selector" - ConfigKeyNamespaceLabelSelector = "namespace-label-selector" + ConfigKeyCron = "cron" + ConfigKeySources = "sources" + ConfigKeyTargets = "targets" + ConfigKeyGrypeConfigFile = "grype-config-file" + ConfigKeyOnlyFixed = "only-fixed" + ConfigKeyMinSeverity = "min-severity" + ConfigKeyGitWorkingTree = "git-workingtree" + ConfigKeyGitRepository = "git-repository" + ConfigKeyGitBranch = "git-branch" + ConfigKeyGitPath = "git-path" + ConfigKeyGitAccessToken = "git-access-token" + ConfigKeyGitUserName = "git-username" + ConfigKeyGitPassword = "git-password" + ConfigKeyGitFallbackClone = "git-fallback-clone" + ConfigKeyGitHubAppId = "github-app-id" + ConfigKeyGitHubAppInstallationId = "github-app-installation-id" + ConfigKeyReportsDir = "reports-dir" + ConfigKeyPodLabelSelector = "pod-label-selector" + ConfigKeyNamespaceLabelSelector = "namespace-label-selector" OperatorConfig *Config ) diff --git a/internal/vuln/daemon/daemon.go b/internal/vuln/daemon/daemon.go index f1526b9..3330aad 100644 --- a/internal/vuln/daemon/daemon.go +++ b/internal/vuln/daemon/daemon.go @@ -127,7 +127,13 @@ func initSources(sourceKeys []string) []source.Source { repository := vuln.OperatorConfig.GitRepository branch := vuln.OperatorConfig.GitBranch token := vuln.OperatorConfig.GitAccessToken - s := git.NewGitSource(workingTree, workPath, repository, branch, token) + userName := vuln.OperatorConfig.GitUserName + password := vuln.OperatorConfig.GitPassword + fallbackClone := vuln.OperatorConfig.GitFallbackClone + githubAppId := vuln.OperatorConfig.GitHubAppId + githubAppInstallationId := vuln.OperatorConfig.GitHubAppInstallationId + githubAppPrivateKey := vuln.OperatorConfig.GitHubPrivateKey + s := git.NewGitSource(workingTree, workPath, repository, branch, token, userName, password, githubAppId, githubAppInstallationId, githubAppPrivateKey, fallbackClone) err = s.ValidateConfig() sources = append(sources, s) } else if sa == "kubernetes" { diff --git a/internal/vuln/source/git/git.go b/internal/vuln/source/git/git.go deleted file mode 100644 index 18954a2..0000000 --- a/internal/vuln/source/git/git.go +++ /dev/null @@ -1,76 +0,0 @@ -package git - -import ( - "errors" - "os" - - git "github.com/go-git/go-git/v5" - "github.com/go-git/go-git/v5/plumbing" - "github.com/go-git/go-git/v5/plumbing/transport" - "github.com/go-git/go-git/v5/plumbing/transport/http" - "github.com/sirupsen/logrus" -) - -type GitAccount struct { - Token string -} - -func New(token string) GitAccount { - return GitAccount{Token: token} -} - -func (g *GitAccount) PrepareRepository(repo, path, branch string) { - cloned := false - r, err := git.PlainOpen(path) - - if err == git.ErrRepositoryNotExists { - cloned = true - r, err = git.PlainClone(path, false, &git.CloneOptions{ - URL: repo, - Progress: os.Stdout, - Auth: g.tokenAuth(), - }) - } - - if err != nil { - logrus.WithError(err).Error("Open or clone failed") - return - } - - w, err := r.Worktree() - - if err != nil { - logrus.WithError(err).Error("Worktree failed") - return - } - - err = w.Checkout(&git.CheckoutOptions{ - Branch: plumbing.NewBranchReferenceName(branch), - }) - - if err != nil { - logrus.WithError(err).Error("Checkout failed") - return - } - - if !cloned { - err = w.Pull(&git.PullOptions{ - Auth: g.tokenAuth(), - ReferenceName: plumbing.NewBranchReferenceName(branch), - }) - - if err != nil && !errors.Is(err, git.NoErrAlreadyUpToDate) { - logrus.WithError(err).Error("Pull failed") - return - } - } - - logrus.Debug("Git-Repository is prepared!") -} - -func (g *GitAccount) tokenAuth() transport.AuthMethod { - return &http.BasicAuth{ - Username: "", // this can be anything except an empty string - Password: g.Token, - } -} diff --git a/internal/vuln/source/git/git_source.go b/internal/vuln/source/git/git_source.go index 3768bd0..3974af7 100644 --- a/internal/vuln/source/git/git_source.go +++ b/internal/vuln/source/git/git_source.go @@ -6,6 +6,7 @@ import ( "path/filepath" "strings" + "github.com/ckotzbauer/libk8soci/pkg/git" "github.com/ckotzbauer/vulnerability-operator/internal/vuln" "github.com/ckotzbauer/vulnerability-operator/internal/vuln/source" "github.com/sirupsen/logrus" @@ -16,12 +17,12 @@ type GitSource struct { workPath string repository string branch string - gitAccount GitAccount + gitAccount git.GitAccount sboms []source.ScanItem } -func NewGitSource(workingTree, workPath, repository, branch, token string) *GitSource { - gitAccount := New(token) +func NewGitSource(workingTree, workPath, repository, branch, token, userName, password, githubAppID, githubInstallationID, githubPrivateKey string, fallbackClone bool) *GitSource { + gitAccount := git.New("", "", token, userName, password, githubAppID, githubInstallationID, githubPrivateKey, fallbackClone) return &GitSource{ workingTree: workingTree, workPath: workPath, diff --git a/main.go b/main.go index c2ed501..49f5ec5 100644 --- a/main.go +++ b/main.go @@ -58,6 +58,11 @@ func newRootCmd() *cobra.Command { rootCmd.PersistentFlags().String(vuln.ConfigKeyGitBranch, "main", "Git-Branch to checkout.") rootCmd.PersistentFlags().String(vuln.ConfigKeyGitPath, "", "Folder-Path inside the Git-Repository.") rootCmd.PersistentFlags().String(vuln.ConfigKeyGitAccessToken, "", "Git-Access-Token.") + rootCmd.PersistentFlags().String(vuln.ConfigKeyGitUserName, "", "Git-Username.") + rootCmd.PersistentFlags().String(vuln.ConfigKeyGitPassword, "", "Git-Password.") + rootCmd.PersistentFlags().String(vuln.ConfigKeyGitHubAppId, "", "GitHub App ID (for authentication).") + rootCmd.PersistentFlags().String(vuln.ConfigKeyGitHubAppInstallationId, "", "GitHub App Installation ID (for authentication).") + rootCmd.PersistentFlags().Bool(vuln.ConfigKeyGitFallbackClone, false, "Use git binary for clones.") rootCmd.PersistentFlags().String(vuln.ConfigKeyReportsDir, "/reports", "Directory to place the reports.") rootCmd.PersistentFlags().String(vuln.ConfigKeyPodLabelSelector, "", "Kubernetes Label-Selector for pods.") rootCmd.PersistentFlags().String(vuln.ConfigKeyNamespaceLabelSelector, "", "Kubernetes Label-Selector for namespaces.")