From 20fc72fb3d9ba9836571393c76fa1a15ff64e36e Mon Sep 17 00:00:00 2001 From: Ahmad Ibrahim Date: Thu, 30 May 2024 11:21:14 -0700 Subject: [PATCH] ci: setup ci --- .github/workflows/bulwark-gitleaks.yaml | 39 ++++++++++++++++ .github/workflows/bulwark-golicenses.yaml | 31 +++++++++++++ .github/workflows/bulwark-gosec.yaml | 49 ++++++++++++++++++++ .github/workflows/bulwark-govulncheck.yaml | 27 +++++++++++ .github/workflows/ci.yaml | 19 ++++++++ .gitignore | 4 ++ .golangci.yaml | 53 ++++++++++++++++++++++ Makefile | 48 ++++++++++++++++++++ bin/.gitkeep | 0 prompts/prompts.go | 35 +++----------- prompts/prompts_test.go | 2 + 11 files changed, 278 insertions(+), 29 deletions(-) create mode 100644 .github/workflows/bulwark-gitleaks.yaml create mode 100644 .github/workflows/bulwark-golicenses.yaml create mode 100644 .github/workflows/bulwark-gosec.yaml create mode 100644 .github/workflows/bulwark-govulncheck.yaml create mode 100644 .github/workflows/ci.yaml create mode 100644 .gitignore create mode 100644 .golangci.yaml create mode 100644 Makefile create mode 100644 bin/.gitkeep diff --git a/.github/workflows/bulwark-gitleaks.yaml b/.github/workflows/bulwark-gitleaks.yaml new file mode 100644 index 0000000..caa9451 --- /dev/null +++ b/.github/workflows/bulwark-gitleaks.yaml @@ -0,0 +1,39 @@ +name: BulwarkGitLeaks + +on: + pull_request: + workflow_dispatch: + +concurrency: + group: gitleaks-${{ github.ref }} + cancel-in-progress: true + +jobs: + gitleaks-pr-scan: + runs-on: ubuntu-latest + container: + image: gcr.io/spectro-dev-public/bulwark/gitleaks:latest + env: + REPO: ${{ github.event.repository.name }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITLEAKS_CONFIG: /workspace/config.toml + steps: + + - name: run-bulwark-gitleaks-scan + shell: sh + env: + BRANCH: ${{ github.head_ref || github.ref_name }} + run: /workspace/bulwark -name CodeSASTGitLeaks -organization spectrocloud-labs -target $REPO -tags "branch:$BRANCH,options:--log-opts origin..HEAD" + + - name: check-result + shell: sh + run: | + resultPath=./$REPO/gitleaks.json + cat $resultPath | grep -v \"Match\"\: | grep -v \"Secret\"\: + total_failed_tests=`cat $resultPath | grep \"Fingerprint\"\: | wc -l` + if [ "$total_failed_tests" -gt 0 ]; then + echo "GitLeaks validation check failed with above findings..." + exit 1 + else + echo "GitLeaks validation check passed" + fi diff --git a/.github/workflows/bulwark-golicenses.yaml b/.github/workflows/bulwark-golicenses.yaml new file mode 100644 index 0000000..410fd85 --- /dev/null +++ b/.github/workflows/bulwark-golicenses.yaml @@ -0,0 +1,31 @@ +name: GoLicenses + +on: + pull_request: + workflow_dispatch: + +concurrency: + group: golicenses-${{ github.ref }} + cancel-in-progress: true + +jobs: + golicense-pr-scan: + runs-on: ubuntu-latest + steps: + - name: install-git + run: sudo apt-get install -y git + + - name: install-golicenses + run: GOBIN=/usr/local/bin go install github.com/google/go-licenses@v1.0.0 + + - name: checkout + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4 + + - name: Set up Go + uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4 + with: + go-version: '1.22' + + - name: golicense-scan + run: | + go-licenses check ./... diff --git a/.github/workflows/bulwark-gosec.yaml b/.github/workflows/bulwark-gosec.yaml new file mode 100644 index 0000000..33cb30f --- /dev/null +++ b/.github/workflows/bulwark-gosec.yaml @@ -0,0 +1,49 @@ +name: BulwarkGoSec + +on: + pull_request: + workflow_dispatch: + +concurrency: + group: gosec-${{ github.ref }} + cancel-in-progress: true + +jobs: + gosec-pr-scan: + runs-on: ubuntu-latest + container: + image: gcr.io/spectro-dev-public/bulwark/gosec:latest + env: + REPO: ${{ github.event.repository.name }} + steps: + + - name: Set up Go + uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v4 + with: + go-version: '1.22' + + - name: checkout + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4 + + - name: run-gosec-scan + shell: sh + env: + BRANCH: ${{ github.head_ref || github.ref_name }} + GO111MODULE: on + run: /workspace/bulwark -name CodeSASTGoSec -verbose -organization spectrocloud-labs -target $REPO -tags "branch:$BRANCH" + + - name: check-result + shell: sh + run: | + resultPath=$REPO-result.json + issues=$(cat $resultPath | jq -r '.Stats.found') + echo "Found ${issues} issues" + echo "Issues by Rule ID" + jq -r '.Issues | group_by (.rule_id)[] | {rule: .[0].rule_id, count: length}' $resultPath + if [ "$issues" -gt 0 ]; then + echo "GoSec SAST scan failed with below findings..." + cat $resultPath + exit 1 + else + echo "GoSec SAST scan passed" + fi diff --git a/.github/workflows/bulwark-govulncheck.yaml b/.github/workflows/bulwark-govulncheck.yaml new file mode 100644 index 0000000..d298ec2 --- /dev/null +++ b/.github/workflows/bulwark-govulncheck.yaml @@ -0,0 +1,27 @@ +name: GoVulnCheck + +on: + pull_request: + workflow_dispatch: + +concurrency: + group: govulncheck-${{ github.ref }} + cancel-in-progress: true + +jobs: + govulncheck-pr-scan: + runs-on: ubuntu-latest + container: + image: gcr.io/spectro-images-public/golang:1.22-alpine + steps: + - name: install-govulncheck + run: GOBIN=/usr/local/bin go install golang.org/x/vuln/cmd/govulncheck@latest + + - name: checkout + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4 + + - name: govulncheck-scan + run: | + go version + govulncheck -mode source ./... + diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 0000000..c09fd0b --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,19 @@ +on: + push: + workflow_dispatch: + +jobs: + lint-and-test: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Unshallow + run: git fetch --prune --unshallow + - name: Set up Go + uses: actions/setup-go@v3 + with: + go-version: 1.22 + - name: Test + run: make test + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3a2ce8b --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +bin/* +!bin/.gitkeep +_build +.DS_Store diff --git a/.golangci.yaml b/.golangci.yaml new file mode 100644 index 0000000..e236b1b --- /dev/null +++ b/.golangci.yaml @@ -0,0 +1,53 @@ +## golangci-lint v1.55.2 + +# References: +# - https://golangci-lint.run/usage/linters/ +# - https://gist.github.com/maratori/47a4d00457a92aa426dbd48a18776322 + +run: + timeout: 10m # default 1m + +linters-settings: + gosimple: + go: "1.21" # default 1.13 + govet: + enable-all: true + disable: + - fieldalignment # too strict + - shadow # too strict + staticcheck: + go: "1.21" # default 1.13 + + # Non-default + cyclop: + max-complexity: 12 # maximal code complexity to report; default 10 + package-average: 0.0 # maximal average package complexity to report; default 0.0 + gocognit: + min-complexity: 30 # minimal code complexity to report; default: 30 + +linters: + disable-all: true + enable: + ## enabled by default + - errcheck # Errcheck is a program for checking for unchecked errors in go programs. These unchecked errors can be critical bugs in some cases + - ineffassign # Detects when assignments to existing variables are not used + - typecheck # Like the front-end of a Go compiler, parses and type-checks Go code + - gosimple # Linter for Go source code that specializes in simplifying a code + - govet # Vet examines Go source code and reports suspicious constructs, such as Printf calls whose arguments do not align with the format string + - unused # Checks Go code for unused constants, variables, functions and types + - staticcheck # Staticcheck is a go vet on steroids, applying a ton of static analysis checks + ## disabled by default + - cyclop # checks function and package cyclomatic complexity + - gocognit # Computes and checks the cognitive complexity of functions + +issues: + max-issues-per-linter: 0 + max-same-issues: 0 + exclude-rules: + - path: _test\.go + linters: + - errcheck + - gosimple + - ineffassign + - staticcheck + - unused diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..ca7c960 --- /dev/null +++ b/Makefile @@ -0,0 +1,48 @@ +# If you update this file, please follow: +# https://suva.sh/posts/well-documented-makefiles/ + +.DEFAULT_GOAL:=help + +# binary versions +BIN_DIR ?= ./bin +GOLANGCI_VERSION ?= 1.55.2 + +GOOS ?= $(shell go env GOOS) +GOARCH ?= $(shell go env GOARCH) + +##@ Help Targets +help: ## Display this help + @awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m\033[0m\n"} /^[a-zA-Z_-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[0m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST) + +##@ Test Targets +.PHONY: test +test: static ## Run tests + @mkdir -p _build/cov + go test -covermode=atomic -coverpkg=./... -coverprofile _build/cov/coverage.out ./... -timeout 120m + +##@ Static Analysis Targets +static: fmt lint vet +fmt: ## Run go fmt against code + go fmt ./... +lint: golangci-lint ## Run golangci-lint + $(GOLANGCI_LINT) run +vet: ## Run go vet against code + go vet ./... + +## Tools & binaries +golangci-lint: + if ! test -f $(BIN_DIR)/golangci-lint-linux-amd64; then \ + curl -LOs https://github.com/golangci/golangci-lint/releases/download/v$(GOLANGCI_VERSION)/golangci-lint-$(GOLANGCI_VERSION)-linux-amd64.tar.gz; \ + tar -zxf golangci-lint-$(GOLANGCI_VERSION)-linux-amd64.tar.gz; \ + mv golangci-lint-$(GOLANGCI_VERSION)-*/golangci-lint $(BIN_DIR)/golangci-lint-linux-amd64; \ + chmod +x $(BIN_DIR)/golangci-lint-linux-amd64; \ + rm -rf ./golangci-lint-$(GOLANGCI_VERSION)-linux-amd64*; \ + fi + if ! test -f $(BIN_DIR)/golangci-lint-$(GOOS)-$(GOARCH); then \ + curl -LOs https://github.com/golangci/golangci-lint/releases/download/v$(GOLANGCI_VERSION)/golangci-lint-$(GOLANGCI_VERSION)-$(GOOS)-$(GOARCH).tar.gz; \ + tar -zxf golangci-lint-$(GOLANGCI_VERSION)-$(GOOS)-$(GOARCH).tar.gz; \ + mv golangci-lint-$(GOLANGCI_VERSION)-*/golangci-lint $(BIN_DIR)/golangci-lint-$(GOOS)-$(GOARCH); \ + chmod +x $(BIN_DIR)/golangci-lint-$(GOOS)-$(GOARCH); \ + rm -rf ./golangci-lint-$(GOLANGCI_VERSION)-$(GOOS)-$(GOARCH)*; \ + fi +GOLANGCI_LINT=$(BIN_DIR)/golangci-lint-$(GOOS)-$(GOARCH) diff --git a/bin/.gitkeep b/bin/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/prompts/prompts.go b/prompts/prompts.go index 40bba4f..bca6af3 100644 --- a/prompts/prompts.go +++ b/prompts/prompts.go @@ -39,19 +39,8 @@ var ( // Exported regex patterns for use with ReadTextRegex KindClusterRegex = "^[a-z0-9]{1}[a-z0-9-]{0,30}[a-z0-9]{1}$" - MaasApiRegex = "^.*\\/MAAS$" - // Source: https://github.com/kubevirt/containerized-data-importer/blob/main/pkg/apiserver/webhooks/util.go#L122C3-L122C3 - CDIImageRegistryRegex = "^(docker|oci-archive):\\/\\/([a-z0-9.\\-\\/]+)([:]{0})$" - // Allowed chars are alphanumerics plus '.', '-', and '_', but cannot start or end with a symbol. - // Additionally, 2+ consecutive symbols are disallowed. - UsernameRegex = "[a-zA-Z0-9]+(?:\\.[a-zA-Z0-9]+)*(?:-[a-zA-Z0-9]+)*(?:_[a-zA-Z0-9]+)*" - PaletteResourceNameRegex = "[a-z][a-z0-9-]{1,31}[a-z0-9]" - VSphereUsernameRegex = "^" + UsernameRegex + "@" + domain + "$" - CPUReqRegex = "(^\\d+\\.?\\d*[M,G]Hz)" - MemoryReqRegex = "(^\\d+\\.?\\d*[M,G,T]i)" - DiskReqRegex = "(^\\d+\\.?\\d*[M,G,T]i)" - ArtifactRefRegex = "^[a-z0-9_.\\-\\/]+(:.*)?(@sha256:.*)?$" - UUIDRegex = "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$" + ArtifactRefRegex = "^[a-z0-9_.\\-\\/]+(:.*)?(@sha256:.*)?$" + UUIDRegex = "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$" noProxyExceptions = []string{"*", "localhost", "kubernetes"} domainRegex = regexp.MustCompile("^" + domain + "$") @@ -70,8 +59,7 @@ type TUI interface { type PtermTUI struct{} -// GetBool prompts a bool from the user while automatically appending a ? character to the end of -// the prompt message. +// GetBool prompts a bool from the user while automatically appending a ? character to the end of the prompt message. func (p PtermTUI) GetBool(prompt string, defaultVal bool) (bool, error) { return pterm.DefaultInteractiveConfirm. WithDefaultText(prompt + "?"). @@ -86,9 +74,9 @@ func (p PtermTUI) GetText(prompt, defaultVal, mask string, optional bool, valida prompt = fmt.Sprintf("%s (optional, hit enter to skip)", prompt) } - // shoddy workaround for https://github.com/pterm/pterm/issues/560 - // inputs longer than the terminal width are handled better with multiline - // enabled... but the prompt is still repeated after every key press + // workaround for https://github.com/pterm/pterm/issues/560: + // inputs longer than the terminal width are handled better with + // multiline enabled, but the prompt is still repeated after every key press var multiline bool if len(defaultVal) > 60 { multiline = true @@ -237,7 +225,6 @@ func ReadText(label, defaultVal string, optional bool, maxLen int) (string, erro } func ReadTextRegex(label, defaultVal, errMsg, regexPattern string) (string, error) { - validate := func(input string) error { if input == "" { return InputMandatoryError @@ -261,7 +248,6 @@ func ReadTextRegex(label, defaultVal, errMsg, regexPattern string) (string, erro } func ReadSemVer(label, defaultVal, errMsg string) (string, error) { - validate := func(input string) error { if input == "" { return InputMandatoryError @@ -319,7 +305,6 @@ func ReadBasicCreds(usernamePrompt, passwordPrompt, defaultUsername, defaultPass } func ReadURL(label, defaultVal, errMsg string, optional bool) (string, error) { - validate := func(input string) error { if input == "" { if !optional { @@ -351,7 +336,6 @@ func ReadURL(label, defaultVal, errMsg string, optional bool) (string, error) { } func ReadURLRegex(label, defaultVal, errMsg, regexPattern string) (string, error) { - validate := func(input string) error { r, err := regexp.Compile(regexPattern) if err != nil { @@ -386,7 +370,6 @@ func ReadURLRegex(label, defaultVal, errMsg, regexPattern string) (string, error } func ReadDomains(label, defaultVal, errMsg string, optional bool, maxVals int) (string, error) { - validate := func(input string) error { if input == "" { if !optional { @@ -415,7 +398,6 @@ func ReadDomains(label, defaultVal, errMsg string, optional bool, maxVals int) ( } func ReadIPs(label, defaultVal, errMsg string, optional bool, maxVals int) (string, error) { - validate := func(input string) error { if input == "" { if !optional { @@ -444,7 +426,6 @@ func ReadIPs(label, defaultVal, errMsg string, optional bool, maxVals int) (stri } func ReadDomainsOrIPs(label, defaultVal, errMsg string, optional bool, maxVals int) (string, error) { - validate := func(input string) error { if input == "" { if !optional { @@ -477,7 +458,6 @@ func ReadDomainsOrIPs(label, defaultVal, errMsg string, optional bool, maxVals i } func ReadDomainOrIPNoPort(label, defaultVal, errMsg string, optional bool) (string, error) { - validate := func(input string) error { if input == "" { if !optional { @@ -503,7 +483,6 @@ func ReadDomainOrIPNoPort(label, defaultVal, errMsg string, optional bool) (stri } func ReadCIDRs(label, defaultVal, errMsg string, optional bool, maxVals int) (string, error) { - validate := func(input string) error { if input == "" { if !optional { @@ -532,7 +511,6 @@ func ReadCIDRs(label, defaultVal, errMsg string, optional bool, maxVals int) (st } func ReadFilePath(label, defaultVal, errMsg string, optional bool) (string, error) { - validate := func(input string) error { if input == "" { if !optional { @@ -559,7 +537,6 @@ func ReadFilePath(label, defaultVal, errMsg string, optional bool) (string, erro } func ReadK8sName(label, defaultVal string, optional bool) (string, error) { - validate := func(input string) error { if err := validateStringFunc(optional, -1)(input); err != nil { return err diff --git a/prompts/prompts_test.go b/prompts/prompts_test.go index cc4c80e..a3a577d 100644 --- a/prompts/prompts_test.go +++ b/prompts/prompts_test.go @@ -870,6 +870,8 @@ func TestReadTextRegex(t *testing.T) { } func TestReadURLRegex(t *testing.T) { + MaasApiRegex := "^.*\\/MAAS$" + subtests := []struct { name string tui *mocks.MockTUI