Skip to content

Commit

Permalink
Introduce gosec for Static Application Security Testing (SAST) (#952)
Browse files Browse the repository at this point in the history
* Introduce gosec-based SAST

* Adapt pipeline_definitions to include SAST linting logs in OCM descriptor

* Fix/supress gosec findings
  • Loading branch information
shreyas-s-rao authored Dec 20, 2024
1 parent 4d853c4 commit 0579040
Show file tree
Hide file tree
Showing 13 changed files with 150 additions and 23 deletions.
9 changes: 9 additions & 0 deletions .ci/check
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,14 @@ mkdir -p /go/src/github.com/gardener/etcd-druid
cp -r . /go/src/github.com/gardener/etcd-druid
cd /go/src/github.com/gardener/etcd-druid

echo "> Running check..."
make check

echo "> Running check-generate..."
make check-generate

# Run Static Application Security Testing (SAST) using gosec
echo "> Running SAST checks..."
make sast-report

echo -e "\n> All checks successful"
17 changes: 17 additions & 0 deletions .ci/pipeline_definitions
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
etcd-druid:
base_definition:
repo:
source_labels:
- name: cloud.gardener.cnudie/dso/scanning-hints/source_analysis/v1
value:
policy: skip
comment: |
we use gosec for sast scanning. See attached log.
traits:
version:
preprocess: 'inject-commit-hash'
Expand Down Expand Up @@ -67,6 +74,16 @@ etcd-druid:
tag_as_latest: True
release:
nextversion: 'bump_minor'
assets:
- type: build-step-log
step_name: check
purposes:
- lint
- sast
- gosec
comment: |
we use gosec (linter) for SAST scans
see: https://github.com/securego/gosec
git_tags:
- ref_template: 'refs/tags/{VERSION}'
release_callback: .ci/bump_tag
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,6 @@ hack/e2e-test/infrastructure/kind/kubeconfig

.cache_ggshield
.gitguardian.yaml

# gosec
gosec-report.sarif
7 changes: 7 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,13 @@ check: $(GOLANGCI_LINT) $(GOIMPORTS) format manifests
check-apidiff: $(GO_APIDIFF)
@$(HACK_DIR)/check-apidiff.sh

.PHONY: sast
sast: $(GOSEC)
@./hack/sast.sh

.PHONY: sast-report
sast-report: $(GOSEC)
@./hack/sast.sh --gosec-report true

# Rules for testing (unit, integration and end-2-end)
# -------------------------------------------------------------------------
Expand Down
41 changes: 41 additions & 0 deletions hack/sast.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#!/usr/bin/env bash
#
# SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Gardener contributors
#
# SPDX-License-Identifier: Apache-2.0

set -e

root_dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )/.." &> /dev/null && pwd )"

gosec_report="false"
gosec_report_parse_flags=""

parse_flags() {
while test $# -gt 1; do
case "$1" in
--gosec-report)
shift; gosec_report="$1"
;;
*)
echo "Unknown argument: $1"
exit 1
;;
esac
shift
done
}

parse_flags "$@"

echo "> Running gosec"
gosec --version
if [[ "$gosec_report" != "false" ]]; then
echo "Exporting report to $root_dir/gosec-report.sarif"
gosec_report_parse_flags="-track-suppressions -fmt=sarif -out=gosec-report.sarif -stdout"
fi

# exclude generated code, hack directory (where hack scripts reside)
# and tmp directory (where temporary mod files are downloaded)
# shellcheck disable=SC2086
gosec -exclude-generated -exclude-dir=hack -exclude-dir=tmp $gosec_report_parse_flags ./...
6 changes: 5 additions & 1 deletion hack/tools.mk
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ GOLANGCI_LINT := $(TOOLS_BIN_DIR)/golangci-lint
GOIMPORTS := $(TOOLS_BIN_DIR)/goimports
CONTROLLER_GEN := $(TOOLS_BIN_DIR)/controller-gen
GINKGO := $(TOOLS_BIN_DIR)/ginkgo
GOSEC := $(TOOLS_BIN_DIR)/gosec
MOCKGEN := $(TOOLS_BIN_DIR)/mockgen
SETUP_ENVTEST := $(TOOLS_BIN_DIR)/setup-envtest
KIND := $(TOOLS_BIN_DIR)/kind
Expand All @@ -32,6 +33,7 @@ KUSTOMIZE_VERSION := v4.5.7
GOLANGCI_LINT_VERSION ?= v1.60.3
CONTROLLER_GEN_VERSION ?= $(call version_gomod,sigs.k8s.io/controller-tools)
GINKGO_VERSION ?= $(call version_gomod,github.com/onsi/ginkgo/v2)
GOSEC_VERSION ?= v2.21.4
MOCKGEN_VERSION ?= $(call version_gomod,go.uber.org/mock)
KIND_VERSION ?= v0.26.0
HELM_VERSION ?= v3.15.2
Expand Down Expand Up @@ -78,6 +80,9 @@ $(CONTROLLER_GEN):
$(GINKGO):
go build -o $(GINKGO) github.com/onsi/ginkgo/v2/ginkgo

$(GOSEC): $(call tool_version_file,$(GOSEC),$(GOSEC_VERSION))
@GOSEC_VERSION=$(GOSEC_VERSION) $(TOOLS_DIR)/install-gosec.sh

$(MOCKGEN):
GOBIN=$(abspath $(TOOLS_BIN_DIR)) go install go.uber.org/mock/mockgen@$(MOCKGEN_VERSION)

Expand Down Expand Up @@ -105,7 +110,6 @@ $(VGOPATH):
$(GO_ADD_LICENSE):
GOBIN=$(abspath $(TOOLS_BIN_DIR)) go install github.com/google/addlicense@$(GO_ADD_LICENSE_VERSION)


$(GO_APIDIFF):
GOBIN=$(abspath $(TOOLS_BIN_DIR)) go install github.com/joelanford/go-apidiff@$(GO_APIDIFF_VERSION)

Expand Down
45 changes: 45 additions & 0 deletions hack/tools/install-gosec.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
#!/usr/bin/env bash
#
# SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Gardener contributors
#
# SPDX-License-Identifier: Apache-2.0

set -e

echo "> Installing gosec"

TOOLS_BIN_DIR=${TOOLS_BIN_DIR:-$(dirname $0)/bin}
if [[ ! -d $TOOLS_BIN_DIR ]]; then
mkdir -p $TOOLS_BIN_DIR
fi

platform=$(uname -s | tr '[:upper:]' '[:lower:]')
version=$GOSEC_VERSION
echo "gosec version:$GOSEC_VERSION"
case $(uname -m) in
aarch64 | arm64)
arch="arm64"
;;
x86_64)
arch="amd64"
;;
*)
echo "Unknown architecture"
exit 1
;;
esac

archive_name="gosec_${version#v}_${platform}_${arch}"
file_name="${archive_name}.tar.gz"

temp_dir="$(mktemp -d)"
function cleanup {
rm -rf "${temp_dir}"
}
trap cleanup EXIT ERR INT TERM
echo "Downloading from: https://github.com/securego/gosec/releases/download/${version}/${file_name}"
curl -L -o ${temp_dir}/${file_name} "https://github.com/securego/gosec/releases/download/${version}/${file_name}"

tar -xzm -C "${temp_dir}" -f "${temp_dir}/${file_name}"
mv "${temp_dir}/gosec" $TOOLS_BIN_DIR
chmod +x $TOOLS_BIN_DIR/gosec
18 changes: 9 additions & 9 deletions internal/common/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,23 +64,23 @@ const (
// EnvAWSApplicationCredentials is the environment variable key for AWS application credentials.
EnvAWSApplicationCredentials = "AWS_APPLICATION_CREDENTIALS"
// EnvAzureApplicationCredentials is the environment variable key for Azure application credentials.
EnvAzureApplicationCredentials = "AZURE_APPLICATION_CREDENTIALS"
EnvAzureApplicationCredentials = "AZURE_APPLICATION_CREDENTIALS" // #nosec G101 -- this is the name of an env var, and not the credential itself.
// EnvAzureEmulatorEnabled is the environment variable key that is checked to see if the Azure storage emulator Azurite is enabled.
EnvAzureEmulatorEnabled = "AZURE_EMULATOR_ENABLED"
// EnvGoogleApplicationCredentials is the environment variable key for Google application credentials.
EnvGoogleApplicationCredentials = "GOOGLE_APPLICATION_CREDENTIALS"
EnvGoogleApplicationCredentials = "GOOGLE_APPLICATION_CREDENTIALS" // #nosec G101 -- this is the name of an env var, and not the credential itself.
// EnvOpenstackApplicationCredentials is the environment variable key for OpenStack application credentials.
EnvOpenstackApplicationCredentials = "OPENSTACK_APPLICATION_CREDENTIALS"
EnvOpenstackApplicationCredentials = "OPENSTACK_APPLICATION_CREDENTIALS" // #nosec G101 -- this is the name of an env var, and not the credential itself.
// EnvAlicloudApplicationCredentials is the environment variable key for Alicloud application credentials.
EnvAlicloudApplicationCredentials = "ALICLOUD_APPLICATION_CREDENTIALS"
EnvAlicloudApplicationCredentials = "ALICLOUD_APPLICATION_CREDENTIALS" // #nosec G101 -- this is the name of an env var, and not the credential itself.
// EnvOpenshiftApplicationCredentials is the environment variable key for OpenShift application credentials.
EnvOpenshiftApplicationCredentials = "OPENSHIFT_APPLICATION_CREDENTIALS"
EnvOpenshiftApplicationCredentials = "OPENSHIFT_APPLICATION_CREDENTIALS" // #nosec G101 -- this is the name of an env var, and not the credential itself.
// EnvECSEndpoint is the environment variable key for Dell ECS endpoint.
EnvECSEndpoint = "ECS_ENDPOINT"
// EnvECSAccessKeyID is the environment variable key for Dell ECS access key ID.
EnvECSAccessKeyID = "ECS_ACCESS_KEY_ID"
// EnvECSSecretAccessKey is the environment variable key for Dell ECS secret access key.
EnvECSSecretAccessKey = "ECS_SECRET_ACCESS_KEY"
EnvECSSecretAccessKey = "ECS_SECRET_ACCESS_KEY" // #nosec G101 -- this is the name of an env var, and not the credential itself.
)

// Constants for values to be set against druidv1alpha1.LabelComponentKey
Expand Down Expand Up @@ -141,7 +141,7 @@ const (
// VolumeNameLocalBackup is the name of the volume that contains the local backup.
VolumeNameLocalBackup = "local-backup"
// VolumeNameProviderBackupSecret is the name of the volume that contains the provider backup secret.
VolumeNameProviderBackupSecret = "etcd-backup-secret"
VolumeNameProviderBackupSecret = "etcd-backup-secret" // #nosec G101 -- this is the name of the mounted volume for backup secret, and not the credential itself.
)

// EtcdConfigFileName is the name of the etcd configuration file.
Expand Down Expand Up @@ -170,9 +170,9 @@ const (
VolumeMountPathBackupRestoreClientTLS = "/var/etcdbr/ssl/client"

// VolumeMountPathGCSBackupSecret is the path on a container where the GCS backup secret is mounted.
VolumeMountPathGCSBackupSecret = "/var/.gcp/"
VolumeMountPathGCSBackupSecret = "/var/.gcp/" // #nosec G101 -- this is a path to the GCP backup credentials file, and not the credential itself.
// VolumeMountPathNonGCSProviderBackupSecret is the path on a container where the non-GCS provider backup secret is mounted.
VolumeMountPathNonGCSProviderBackupSecret = "/var/etcd-backup"
VolumeMountPathNonGCSProviderBackupSecret = "/var/etcd-backup" // #nosec G101 -- this is a path to the backup credentials dir, and not the credential itself.

// VolumeMountPathEtcdData is the path on a container where the etcd data directory is mounted.
VolumeMountPathEtcdData = "/var/etcd/data"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -148,5 +148,5 @@ func computePDBMinAvailable(etcdReplicas int) int32 {
if etcdReplicas <= 1 {
return 0
}
return int32(etcdReplicas/2 + 1)
return int32(etcdReplicas/2 + 1) // #nosec G115 -- etcdReplicas will never cross the size of int32, so conversion is safe.
}
1 change: 1 addition & 0 deletions internal/component/statefulset/statefulset.go
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ func (r _resource) checkAndRecreateOrphanDeletedSts(ctx component.OperatorContex
if numOrphanedPods > 0 {
r.logger.Info("Recreating StatefulSet with previous replicas to adopt orphan pods", "numOrphanedPods", numOrphanedPods)
sts := emptyStatefulSet(etcd.ObjectMeta)
// #nosec G115 -- numOrphanedPods will never cross the size of int32, so conversion is safe.
if err = r.createOrPatchWithReplicas(ctx, etcd, sts, int32(numOrphanedPods), false); err != nil {
return druiderr.WrapError(err,
ErrSyncStatefulSet,
Expand Down
2 changes: 1 addition & 1 deletion internal/health/condition/check_all_members.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ func (a *allMembersReady) Check(_ context.Context, etcd druidv1alpha1.Etcd) Resu
message: "At least one member is not ready",
}

if int32(len(etcd.Status.Members)) < etcd.Spec.Replicas {
if len(etcd.Status.Members) < int(etcd.Spec.Replicas) {
// not all members are registered yet
return res
}
Expand Down
2 changes: 1 addition & 1 deletion internal/webhook/etcdcomponents/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const (
etcdComponentsWebhookExemptServiceAccountsFlagName = "etcd-components-webhook-exempt-service-accounts"
defaultEnableWebhook = false
defaultReconcilerServiceAccount = "system:serviceaccount:default:etcd-druid"
reconcilerServiceAccountTokenPath = "/var/run/secrets/kubernetes.io/serviceaccount/token"
reconcilerServiceAccountTokenPath = "/var/run/secrets/kubernetes.io/serviceaccount/token" // #nosec G101 -- this is a path to a token file, and not the credential itself.
)

// Config defines the configuration for the EtcdComponents Webhook.
Expand Down
20 changes: 10 additions & 10 deletions test/e2e/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ const (
envProviders = "INFRA_PROVIDERS"

envS3AccessKeyID = "AWS_ACCESS_KEY_ID"
envS3SecretAccessKey = "AWS_SECRET_ACCESS_KEY"
envS3SecretAccessKey = "AWS_SECRET_ACCESS_KEY" // #nosec G101 -- refers to the env var, and is not the credential itself.
envS3Region = "AWS_REGION"

envABSStorageAccount = "STORAGE_ACCOUNT"
Expand Down Expand Up @@ -371,7 +371,7 @@ func getProviders() ([]TestProvider, error) {
case providerGCP:
gcsServiceAccountPath := getEnvOrFallback(envGCSServiceAccount, "")
if gcsServiceAccountPath != "" {
gcsSA, err := os.ReadFile(gcsServiceAccountPath)
gcsSA, err := os.ReadFile(gcsServiceAccountPath) // #nosec G304 -- test files.
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -444,19 +444,19 @@ func buildAndDeployTLSSecrets(ctx context.Context, cl client.Client, logger logr
caSecretName = "ca-etcd"
tlsServerCertFile = "server.crt"
tlsServerKeyFile = "server.key"
tlsServerSecretName = "etcd-server-cert"
tlsServerSecretName = "etcd-server-cert" // #nosec G101 -- refers to the name of the secret, and is not the credential itself.
tlsClientCertFile = "client.crt"
tlsClientKeyFile = "client.key"
tlsClientSecretName = "etcd-client-tls"
tlsClientSecretName = "etcd-client-tls" // #nosec G101 -- refers to the name of the secret, and is not the credential itself.
secretData map[string][]byte
)

for _, provider := range providers {
caCert, err := os.ReadFile(path.Join(certsPath, caCertFile))
caCert, err := os.ReadFile(path.Join(certsPath, caCertFile)) // #nosec G304 -- test files.
if err != nil {
return err
}
caKey, err := os.ReadFile(path.Join(certsPath, caKeyFile))
caKey, err := os.ReadFile(path.Join(certsPath, caKeyFile)) // #nosec G304 -- test files.
if err != nil {
return err
}
Expand All @@ -469,11 +469,11 @@ func buildAndDeployTLSSecrets(ctx context.Context, cl client.Client, logger logr
return err
}

tlsServerCert, err := os.ReadFile(path.Join(certsPath, tlsServerCertFile))
tlsServerCert, err := os.ReadFile(path.Join(certsPath, tlsServerCertFile)) // #nosec G304 -- test files.
if err != nil {
return err
}
tlsServerKey, err := os.ReadFile(path.Join(certsPath, tlsServerKeyFile))
tlsServerKey, err := os.ReadFile(path.Join(certsPath, tlsServerKeyFile)) // #nosec G304 -- test files.
if err != nil {
return err
}
Expand All @@ -487,11 +487,11 @@ func buildAndDeployTLSSecrets(ctx context.Context, cl client.Client, logger logr
return err
}

tlsClientCert, err := os.ReadFile(path.Join(certsPath, tlsClientCertFile))
tlsClientCert, err := os.ReadFile(path.Join(certsPath, tlsClientCertFile)) // #nosec G304 -- test files.
if err != nil {
return err
}
tlsClientKey, err := os.ReadFile(path.Join(certsPath, tlsClientKeyFile))
tlsClientKey, err := os.ReadFile(path.Join(certsPath, tlsClientKeyFile)) // #nosec G304 -- test files.
if err != nil {
return err
}
Expand Down

0 comments on commit 0579040

Please sign in to comment.