Browse Source

Bolt/v1.117 patch (#3324)

Signed-off-by: sneax <paladesh600@gmail.com>
Signed-off-by: Matt Bolt <mbolt35@gmail.com>
Co-authored-by: segfault_bits <149077711+sneaxhuh@users.noreply.github.com>
Matt Bolt 8 months ago
parent
commit
aab4273444

+ 74 - 0
.github/workflows/vulnerability-scan.yaml

@@ -0,0 +1,74 @@
+name: Trivy Vulnerability Scanner
+
+permissions:
+  issues: write
+  contents: read
+  security-events: write
+
+on:
+  pull_request:
+    branches:
+      - develop
+  push:
+    branches:
+      - develop
+
+jobs:
+  scan:
+    name: Scan for Vulnerabilities
+    runs-on: ubuntu-latest
+    steps:
+      - name: Checkout code
+        uses: actions/checkout@v4
+
+      - name: Install Trivy
+        run: |
+          curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin
+
+      - name: Run Trivy scan
+        id: trivy-scan
+        continue-on-error: true
+        run: |
+          # Generate SARIF report first
+          trivy fs \
+            --format template \
+            --template '@/contrib/sarif.tpl' \
+            --output trivy-results.sarif \
+            --severity CRITICAL,HIGH \
+            --vuln-type os,library \
+            --no-progress .
+
+          # Generate JSON report and fail step if vulnerabilities are found
+          trivy fs \
+            --format json \
+            --output trivy-results.json \
+            --severity CRITICAL,HIGH \
+            --vuln-type os,library \
+            --no-progress \
+            --exit-code 1 .
+
+      - name: Upload Trivy JSON report as artifact
+        if: steps.trivy-scan.outcome == 'failure'
+        uses: actions/upload-artifact@v4
+        with:
+          name: trivy-json-report
+          path: trivy-results.json
+          retention-days: 1
+
+      - name: Upload SARIF to GitHub Security tab
+        if: always()
+        uses: github/codeql-action/upload-sarif@v3
+        with:
+          sarif_file: 'trivy-results.sarif'
+          category: trivy-fs
+
+      - name: Print vulnerability details and fail job
+        
+        if: steps.trivy-scan.outcome == 'failure'
+        run: |
+          echo "🛑 Trivy scan found CRITICAL or HIGH severity vulnerabilities. Details:"
+          echo "--------------------------------------------------------------------"
+          # Parse the JSON report and print a summary of each vulnerability
+          jq -r '.Results[] | .Target as $target | if .Vulnerabilities then .Vulnerabilities[] | "File: \($target)\nPackage: \(.PkgName) (\(.InstalledVersion))\nID: \(.VulnerabilityID)\nSeverity: \(.Severity)\nLink: \(.PrimaryURL)\n--------------------------------------------------------------------" else empty end' trivy-results.json
+          # Exit with a failure code to fail the workflow
+          exit 1

+ 12 - 1
core/pkg/storage/storefactory.go

@@ -7,7 +7,8 @@ import (
 	"github.com/opencost/opencost/core/pkg/env"
 )
 
-// GetDefaultStorage initializes the default shared storage which is required for kubecost
+// GetDefaultStorage initializes the default shared storage which is required for kubecost. Panics
+// if the storage cannot be initialized.
 func GetDefaultStorage() Storage {
 	store, err := InitializeStorage(env.GetDefaultStorageConfigFilePath())
 	if err != nil {
@@ -16,6 +17,16 @@ func GetDefaultStorage() Storage {
 	return store
 }
 
+// TryGetDefaultStorage will attempt to load the default bucket configuration, but will not panic
+// if the config file does not exist.
+func TryGetDefaultStorage() (Storage, error) {
+	store, err := InitializeStorage(env.GetDefaultStorageConfigFilePath())
+	if err != nil {
+		return nil, fmt.Errorf("failed to initialize default storage: %w", err)
+	}
+	return store, nil
+}
+
 // InitializeStorage creates a storage from the config file at the given path
 func InitializeStorage(configPath string) (Storage, error) {
 	storageConfig, err := os.ReadFile(configPath)

+ 33 - 1
pkg/costmodel/router.go

@@ -510,7 +510,7 @@ func Initialize(router *httprouter.Router, additionalConfigWatchers ...*watcher.
 	}
 	if env.IsCollectorDataSourceEnabled() {
 		fn = func() (source.OpenCostDataSource, error) {
-			store := storage.GetDefaultStorage()
+			store := GetDefaultCollectorStorage()
 			nodeStatConf, err := NewNodeClientConfigFromEnv()
 			if err != nil {
 				return nil, fmt.Errorf("failed to get node client config: %w", err)
@@ -603,6 +603,38 @@ func Initialize(router *httprouter.Router, additionalConfigWatchers ...*watcher.
 	return a
 }
 
+// GetDefaultStorage retrieves the default shared storage which is required for running an opencost collector.
+func GetDefaultCollectorStorage() storage.Storage {
+	const warningMessage = `Failed to create local collector directory '%s' - %s.
+		Did you mean to enable to collector? For persistent storage, it's recommended to use Prometheus, 
+		or set a storage bucket configuration at %s. 
+
+		%s`
+
+	// Try bucket storage if it exists
+	store, err := storage.TryGetDefaultStorage()
+	if err == nil {
+		return store
+	}
+
+	// Fallback to a local storage bucket
+	dir := env.GetLocalCollectorDirectory()
+	err = os.MkdirAll(dir, os.ModePerm)
+	if err != nil {
+		log.Warnf(
+			warningMessage,
+			dir,
+			err.Error(),
+			sysenv.GetDefaultStorageConfigFilePath(),
+			"Falling back to an in-memory file system for collector, which will lose any persistent storage upon restart.",
+		)
+
+		return storage.NewMemoryStorage()
+	}
+
+	return storage.NewFileStorage(dir)
+}
+
 // InitializeCloudCost Initializes Cloud Cost pipeline and querier and registers endpoints
 func InitializeCloudCost(router *httprouter.Router, providerConfig models.ProviderConfig) {
 	log.Debugf("Cloud Cost config path: %s", env.GetCloudCostConfigPath())

+ 9 - 2
pkg/env/costmodel.go

@@ -8,8 +8,9 @@ import (
 const (
 	ClusterInfoFile = "cluster-info.json"
 	ClusterCacheFile
-	GCPAuthSecretFile = "key.json"
-	MetricConfigFile  = "metrics.json"
+	GCPAuthSecretFile        = "key.json"
+	MetricConfigFile         = "metrics.json"
+	DefaultLocalCollectorDir = "collector"
 )
 
 // Env Variables
@@ -45,6 +46,7 @@ const (
 
 	CloudProviderAPIKeyEnvVar        = "CLOUD_PROVIDER_API_KEY"
 	CollectorDataSourceEnabledEnvVar = "COLLECTOR_DATA_SOURCE_ENABLED"
+	LocalCollectorDirectoryEnvVar    = "LOCAL_COLLECTOR_DIRECTORY"
 
 	EmitPodAnnotationsMetricEnvVar       = "EMIT_POD_ANNOTATIONS_METRIC"
 	EmitNamespaceAnnotationsMetricEnvVar = "EMIT_NAMESPACE_ANNOTATIONS_METRIC"
@@ -366,3 +368,8 @@ func GetCloudProvider() string {
 func GetMetricConfigFile() string {
 	return env.GetPathFromConfig(MetricConfigFile)
 }
+
+func GetLocalCollectorDirectory() string {
+	dir := env.Get(LocalCollectorDirectoryEnvVar, DefaultLocalCollectorDir)
+	return env.GetPathFromConfig(dir)
+}