Skip to content
Merged
69 changes: 62 additions & 7 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -318,11 +318,35 @@ jobs:
fi

- name: Run gosec Security Scanner
uses: securego/gosec@4a3bd8af174872c778439083ded7adbf3747e770 # v2.26.1
with:
args: '-fmt sarif -out gosec-results.sarif ./...'
run: |
# Install pinned gosec using the job's existing setup-go (GO_VERSION 1.26.5).
# The securego/gosec Docker action bundles its own Go toolchain (1.26.1) which
# cannot satisfy the "go 1.26.5" module requirement in go.mod, causing it to
# load 0 files and exit 1 with a toolchain mismatch rather than real findings.
go install github.com/securego/gosec/v2/cmd/gosec@v2.26.1
# Multi-module repo: each ./... only walks the current module so scanning root
# alone silently misses pkg/ and providers/*. Mirror the govulncheck per-module
# loop, collect per-module SARIF, then merge for the upload step.
set -e
for mod in . pkg providers/aws providers/azure providers/gcp tests/e2e; do
tag=$(echo "$mod" | tr './' '--' | sed 's/^-/root/')
out="$RUNNER_TEMP/gosec-${tag}.sarif"
echo "==> gosec in $mod"
(cd "$mod" && gosec -fmt sarif -out "$out" ./...)
# Code scanning rejects a SARIF file whose runs share a category
# (github.blog changelog 2025-07-21), so give each module's run a
# unique automationDetails.id before merging.
jq --arg id "gosec-${tag}/" '.runs |= map(.automationDetails = {id: $id})' \
"$out" > "$out.tmp" && mv "$out.tmp" "$out"
done
# Merge per-module SARIF runs into one file for the upload step.
# jq is preinstalled on the GitHub Ubuntu runner image (no new deps).
jq -s '{version: "2.1.0", "$schema": "https://json.schemastore.org/sarif-2.1.0.json", runs: [.[].runs[]]}' \
"$RUNNER_TEMP"/gosec-*.sarif > gosec-results.sarif
echo "Merged $(jq '.runs | length' gosec-results.sarif) SARIF runs"

- name: Upload gosec results to GitHub Security
if: always()
uses: github/codeql-action/upload-sarif@7211b7c8077ea37d8641b6271f6a365a22a5fbfa # v4.36.0
with:
sarif_file: gosec-results.sarif
Expand All @@ -335,17 +359,48 @@ jobs:
format: 'sarif'
output: 'trivy-results.sarif'
severity: 'CRITICAL,HIGH'
# Pin the Trivy binary independently of the action SHA. v0.36.0 is
# the latest trivy-action release but bundles Trivy v0.70.0, which
# panics in adaptDefaultTags on terraform/environments/aws/main.tf
# (null default_tags vars). Fixed in Trivy >= v0.72.0. The action
# forwards this input to aquasecurity/setup-trivy, so both scan
# steps run the same pinned binary. Keep both steps on this version.
version: 'v0.72.0'

- name: Upload Trivy results to GitHub Security
uses: github/codeql-action/upload-sarif@7211b7c8077ea37d8641b6271f6a365a22a5fbfa # v4.36.0
with:
sarif_file: 'trivy-results.sarif'

- name: Run tfsec (Terraform Security)
uses: aquasecurity/tfsec-action@b466648d6e39e7c75324f25d83891162a721f2d6 # v1.0.3
# Terraform IaC misconfiguration scanning. Replaces the deprecated
# aquasecurity/tfsec-action, whose bundled HCL parser rejects Terraform
# 1.5+ `check {}` blocks (e.g. terraform/modules/deployment-checks/main.tf)
# with a hard "scan failed" parse error that soft_fail does not suppress
# (soft_fail only downgrades findings, not scan errors). Trivy is tfsec's
# official successor, parses `check {}` blocks, and (like the fs scan
# above) uses the default exit-code 0 so misconfig findings are reported
# to the Security tab without gating the job -- matching tfsec's prior
# soft_fail: true behaviour while preserving Terraform IaC coverage.
- name: Run Trivy IaC misconfiguration scanner (Terraform)
uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0
with:
scan-type: 'config'
scan-ref: 'terraform/'
format: 'sarif'
output: 'trivy-config-results.sarif'
severity: 'CRITICAL,HIGH'
# Same pinned Trivy binary as the filesystem scan above (>= v0.72.0
# avoids the adaptDefaultTags panic on null default_tags vars).
version: 'v0.72.0'

- name: Upload Trivy IaC results to GitHub Security
if: always()
uses: github/codeql-action/upload-sarif@7211b7c8077ea37d8641b6271f6a365a22a5fbfa # v4.36.0
with:
working_directory: terraform/
soft_fail: true
sarif_file: 'trivy-config-results.sarif'
# Distinct category so this IaC analysis does not overwrite the
# filesystem Trivy analysis uploaded above (both report as "Trivy").
category: 'trivy-iac'

# Snyk security scanning
snyk-scan:
Expand Down
19 changes: 15 additions & 4 deletions ci_cd_sanity_tests/cmd/ri-exchange/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,15 @@
Error string `json:"error,omitempty"`
}

// validateTargetCount exits with an error message when n is outside the int32
// range. Extracted to keep main's cyclomatic complexity within the project limit.
func validateTargetCount(n int) {
if n < 1 || n > (1<<31-1) {
fmt.Fprintln(os.Stderr, "ERROR: --target-count must be between 1 and math.MaxInt32")
os.Exit(2)
}
}

func parseIDs(s string) []string {
var out []string
for _, p := range strings.Split(s, ",") {
Expand Down Expand Up @@ -56,13 +65,15 @@
)
flag.Parse()

validateTargetCount(*targetCount)

ctx, cancel := context.WithTimeout(context.Background(), time.Duration(*timeoutSec)*time.Second)
defer cancel()

ids := parseIDs(*riIDsCSV)
if len(ids) == 0 {
fmt.Fprintln(os.Stderr, "ERROR: --ri-ids is required (comma-separated)")
os.Exit(2)

Check failure on line 76 in ci_cd_sanity_tests/cmd/ri-exchange/main.go

View workflow job for this annotation

GitHub Actions / Lint Code

exitAfterDefer: os.Exit will exit, and `defer cancel()` will not run (gocritic)
}
if strings.TrimSpace(*targetOffering) == "" {
fmt.Fprintln(os.Stderr, "ERROR: --target-offering-id is required")
Expand All @@ -74,7 +85,7 @@
AccountChk: *expectedAccount,
ReservedIDs: ids,
TargetOfferingID: *targetOffering,
TargetCount: int32(*targetCount),
TargetCount: int32(*targetCount), // #nosec G115 -- range-validated above (1 <= targetCount <= math.MaxInt32); int->int32 cannot overflow
}

if !*execute {
Expand All @@ -84,8 +95,8 @@
ExpectedAccount: *expectedAccount,
ReservedIDs: ids,
TargetOfferingID: *targetOffering,
TargetCount: int32(*targetCount),
DryRun: false, // IAMCheckOnly: false = real quote, true = only verify IAM permissions
TargetCount: int32(*targetCount), // #nosec G115 -- range-validated above (1 <= targetCount <= math.MaxInt32); int->int32 cannot overflow
DryRun: false, // IAMCheckOnly: false = real quote, true = only verify IAM permissions
})
if err != nil {
o.Error = err.Error()
Expand Down Expand Up @@ -133,7 +144,7 @@
ExpectedAccount: *expectedAccount,
ReservedIDs: ids,
TargetOfferingID: *targetOffering,
TargetCount: int32(*targetCount),
TargetCount: int32(*targetCount), // #nosec G115 -- range-validated above (1 <= targetCount <= math.MaxInt32); int->int32 cannot overflow
MaxPaymentDueUSD: maxRat,
})
o.Quote = q
Expand Down
11 changes: 10 additions & 1 deletion ci_cd_sanity_tests/cmd/sanity/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,14 @@
"github.com/LeanerCloud/CUDly/ci_cd_sanity_tests/pkg/sanity/aws"
)

// requireInt32Range exits with an error when n is outside [1, math.MaxInt32].
func requireInt32Range(flag string, n int) {

Check failure on line 14 in ci_cd_sanity_tests/cmd/sanity/main.go

View workflow job for this annotation

GitHub Actions / Lint Code

importShadow: shadow of imported package 'flag' (gocritic)
if n < 1 || n > (1<<31-1) {
fmt.Fprintf(os.Stderr, "ERROR: %s must be between 1 and math.MaxInt32\n", flag)
os.Exit(2)
}
}

func main() {
var (
region = flag.String("region", "us-east-1", "AWS region for sanity checks")
Expand All @@ -18,18 +26,19 @@
outPath = flag.String("out", "sanity_report.json", "Output JSON report path")
)
flag.Parse()
requireInt32Range("--max-list", *maxList)

ctx, cancel := context.WithTimeout(context.Background(), 3*time.Minute)
defer cancel()

rep, err := aws.Run(ctx, aws.Options{
Region: *region,
ExpectedAccount: *expectedAccount,
MaxList: int32(*maxList),
MaxList: int32(*maxList), // #nosec G115 -- range-validated above (1 <= maxList <= math.MaxInt32); int->int32 cannot overflow
})
if err != nil {
fmt.Fprintf(os.Stderr, "sanity run failed: %v\n", err)
os.Exit(2)

Check failure on line 41 in ci_cd_sanity_tests/cmd/sanity/main.go

View workflow job for this annotation

GitHub Actions / Lint Code

exitAfterDefer: os.Exit will exit, and `defer cancel()` will not run (gocritic)
}

if err := rep.WriteJSON(*outPath); err != nil {
Expand Down
2 changes: 1 addition & 1 deletion ci_cd_sanity_tests/pkg/sanity/azure/azure.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
} `json:"user"`
}

func truncate(s string, max int) string {

Check failure on line 33 in ci_cd_sanity_tests/pkg/sanity/azure/azure.go

View workflow job for this annotation

GitHub Actions / Lint Code

builtinShadow: shadowing of predeclared identifier: max (gocritic)
if len(s) <= max {
return s
}
Expand Down Expand Up @@ -103,7 +103,7 @@

runCmd := func(name string, args ...string) ([]byte, report.CheckResult) {
start := time.Now().UTC()
cmd := exec.CommandContext(rctx, "az", args...)
cmd := exec.CommandContext(rctx, "az", args...) // #nosec G702,G204 -- CI sanity test tooling; binary is hardcoded "az" (Azure CLI), args are Azure CLI subcommands constructed within the test code
out, err := cmd.CombinedOutput()
end := time.Now().UTC()

Expand Down
6 changes: 3 additions & 3 deletions cmd/configure_azure.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ func storeAzureCredentials(ctx context.Context, store SecretsStore, stackName st
}

// Marshal credentials to JSON
credJSON, err := json.Marshal(creds)
credJSON, err := json.Marshal(creds) // #nosec G117 -- intentional: marshaling Azure credential struct (contains ClientSecret field) for secure storage in the credential store
if err != nil {
return fmt.Errorf("failed to marshal credentials: %w", err)
}
Expand Down Expand Up @@ -341,7 +341,7 @@ func createAzureServicePrincipal(reader *bufio.Reader, subscriptionID string) er
if choice == "r" || choice == "run" || choice == "" {
fmt.Println()
fmt.Println(strings.Repeat("-", 60))
cmd := exec.Command("az", "ad", "sp", "create-for-rbac",
cmd := exec.Command("az", "ad", "sp", "create-for-rbac", // #nosec G204 -- binary "az" is hardcoded; subscriptionID validated by validateAzureUUID before exec
"--name", "CUDly",
"--role", "Reservations Administrator",
"--scopes", fmt.Sprintf("/subscriptions/%s", subscriptionID))
Expand Down Expand Up @@ -401,7 +401,7 @@ func executeExplicitCommand(reader *bufio.Reader, displayCmd string, program str
fmt.Printf("Executing: %s\n", displayCmd)
fmt.Println(strings.Repeat("-", 60))

cmd := exec.Command(program, args...)
cmd := exec.Command(program, args...) // #nosec G204 -- configure CLI tool; program is always "az" (Azure CLI) per all callers; no user input reaches this function
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Stdin = os.Stdin
Expand Down
8 changes: 4 additions & 4 deletions cmd/configure_gcp.go
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ func loadAWSConfigForGCP(ctx context.Context) (aws.Config, error) {
func loadAndUpdateGCPCredentials(credsFile string) (GCPCredentials, []byte, error) {
expandedPath := expandHomeDirectory(credsFile)

credsData, err := os.ReadFile(expandedPath)
credsData, err := os.ReadFile(expandedPath) // #nosec G304 -- GCP credentials file path is operator-supplied via CLI argument; operator controls the value
if err != nil {
return GCPCredentials{}, nil, fmt.Errorf("failed to read credentials file: %w", err)
}
Expand All @@ -222,7 +222,7 @@ func loadAndUpdateGCPCredentials(credsFile string) (GCPCredentials, []byte, erro

if gcpOpts.ProjectID != "" {
creds.ProjectID = gcpOpts.ProjectID
credsData, err = json.Marshal(creds)
credsData, err = json.Marshal(creds) // #nosec G117 -- intentional: marshaling GCP credential struct (contains PrivateKey field) for secure storage in the credential store
if err != nil {
return GCPCredentials{}, nil, fmt.Errorf("failed to marshal updated credentials: %w", err)
}
Expand Down Expand Up @@ -289,7 +289,7 @@ func runGCPSetupCommands(reader *bufio.Reader) (string, error) {
// Set the project - use exec.Command with arguments instead of shell
fmt.Println()
fmt.Println("Setting project...")
cmd := exec.Command("gcloud", "config", "set", "project", projectID)
cmd := exec.Command("gcloud", "config", "set", "project", projectID) // #nosec G204 -- binary "gcloud" is hardcoded; projectID validated by validateGCPProjectID before exec
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
Expand Down Expand Up @@ -407,7 +407,7 @@ func executeGCPCommand(reader *bufio.Reader, displayCmd string, program string,
fmt.Printf("Executing: %s\n", displayCmd)
fmt.Println(strings.Repeat("-", 60))

cmd := exec.Command(program, args...)
cmd := exec.Command(program, args...) // #nosec G204 -- configure CLI tool; program is always "gcloud" per all callers; no user input reaches this function
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Stdin = os.Stdin
Expand Down
2 changes: 1 addition & 1 deletion cmd/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -533,7 +533,7 @@ func ConfirmPurchase(totalInstances int, totalSavings float64, skipConfirmation
// CheckAuditLogWritable opens the audit log file in append mode to verify it is writable.
// Returns an error if the path cannot be opened for writing.
func CheckAuditLogWritable(path string) error {
f, err := os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0600)
f, err := os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0600) // #nosec G304 -- audit log path is operator-configured; value is not reachable from user input
if err != nil {
return fmt.Errorf("audit log %q not writable: %w", path, err)
}
Expand Down
4 changes: 2 additions & 2 deletions cmd/multi_service_csv.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ func determineCSVCoverage(cfg Config) float64 {

// loadRecommendationsFromCSV reads and returns recommendations from a CSV file.
func loadRecommendationsFromCSV(csvPath string) ([]common.Recommendation, error) {
file, err := os.Open(csvPath)
file, err := os.Open(csvPath) // #nosec G304 -- CLI tool: csvPath is an operator-supplied command-line argument
if err != nil {
return nil, fmt.Errorf("failed to open CSV file: %w", err)
}
Expand Down Expand Up @@ -195,7 +195,7 @@ func writeMultiServiceCSVReport(results []common.PurchaseResult, filepath string
return nil
}

file, err := os.Create(filepath)
file, err := os.Create(filepath) // #nosec G304 -- CLI tool: filepath is an operator-supplied output path argument
if err != nil {
return fmt.Errorf("failed to create CSV file: %w", err)
}
Expand Down
14 changes: 9 additions & 5 deletions cmd/server/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,12 @@ func main() {
// Export BUILD_TIME and GIT_SHA to the environment so the api package can
// read them without importing main (import cycle). VERSION is passed
// directly to NewApplication to avoid the env round-trip (04-N1).
os.Setenv("BUILD_TIME", BuildTime)
os.Setenv("GIT_SHA", GitSHA)
if err := os.Setenv("BUILD_TIME", BuildTime); err != nil {
log.Printf("failed to export BUILD_TIME: %v", err)
}
if err := os.Setenv("GIT_SHA", GitSHA); err != nil {
log.Printf("failed to export GIT_SHA: %v", err)
}

ctx := context.Background()

Expand Down Expand Up @@ -88,11 +92,11 @@ func getTaskTimeout() time.Duration {
if v := os.Getenv("TASK_TIMEOUT"); v != "" {
secs, err := strconv.Atoi(v)
if err != nil {
log.Printf("WARNING: TASK_TIMEOUT=%q is not a valid integer; using default %v", v, defaultTimeout)
log.Printf("WARNING: TASK_TIMEOUT=%q is not a valid integer; using default %v", v, defaultTimeout) // #nosec G706 -- TASK_TIMEOUT env var is operator-controlled; logged for diagnostics
return defaultTimeout
}
if secs <= 0 {
log.Printf("WARNING: TASK_TIMEOUT=%q must be a positive number; using default %v", v, defaultTimeout)
log.Printf("WARNING: TASK_TIMEOUT=%q must be a positive number; using default %v", v, defaultTimeout) // #nosec G706 -- TASK_TIMEOUT env var is operator-controlled; logged for diagnostics
return defaultTimeout
}
return time.Duration(secs) * time.Second
Expand Down Expand Up @@ -120,7 +124,7 @@ func determineRuntimeMode(modeFlag string) string {
case "lambda", "http":
return runtimeMode
default:
log.Printf("Warning: unrecognized RUNTIME_MODE %q, falling back to auto-detection", runtimeMode)
log.Printf("Warning: unrecognized RUNTIME_MODE %q, falling back to auto-detection", runtimeMode) // #nosec G706 -- RUNTIME_MODE env var is operator-controlled; logged for diagnostics
}
}

Expand Down
4 changes: 2 additions & 2 deletions internal/api/handler_purchases_revoke.go
Original file line number Diff line number Diff line change
Expand Up @@ -421,7 +421,7 @@ func (h *Handler) calculateAzureRevoke(ctx context.Context, req *events.LambdaFu
return nil, fmt.Errorf("revoke/calculate: create calculate-refund client: %w", err)
}

quantity := int32(count) //nolint:gosec
quantity := int32(count) // #nosec G115 -- Azure reservation count bounded by API limits (<<math.MaxInt32) //nolint:gosec
calcResp, err := calcClient.Post(ctx, orderID, armreservations.CalculateRefundRequest{
Properties: &armreservations.CalculateRefundRequestProperties{
ReservationToReturn: &armreservations.ReservationToReturn{
Expand Down Expand Up @@ -604,7 +604,7 @@ func (h *Handler) callAzureReturn(
}

// Step 1: CalculateRefund -> sessionID + quoted amount (TOCTOU check).
quantity := int32(record.Count) //nolint:gosec // Count > 0 validated at purchase
quantity := int32(record.Count) // #nosec G115 -- Azure reservation count validated at purchase; bounded by API limits (<<math.MaxInt32) //nolint:gosec
sessionID, calcRefundAmount, calcRefundCurrency, err := h.azureCalculateRefund(ctx, calcClient, orderID, reservationID, quantity)
if err != nil {
return nil, err
Expand Down
2 changes: 1 addition & 1 deletion internal/api/handler_ri_exchange.go
Original file line number Diff line number Diff line change
Expand Up @@ -1298,7 +1298,7 @@ func (h *Handler) executeApprovedExchange(ctx context.Context, id string, record
Region: region,
ReservedIDs: record.SourceRIIDs,
TargetOfferingID: record.TargetOfferingID,
TargetCount: int32(record.TargetCount),
TargetCount: int32(record.TargetCount), // #nosec G115 -- RI quantity stored from validated API request; AWS limits RI counts well below math.MaxInt32
MaxPaymentDueUSD: perExchangeCap,
})
if execErr != nil {
Expand Down
2 changes: 1 addition & 1 deletion internal/auth/service_mfa.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import (
"context"
"crypto/hmac"
"crypto/rand"
"crypto/sha1"
"crypto/sha1" // #nosec G505 -- SHA-1 retained for broad authenticator app compatibility and existing otpauth provisioning; RFC 6238 permits SHA-256/SHA-512 but most authenticator apps default to SHA-1
"crypto/subtle"
"encoding/base32"
"fmt"
Expand Down
2 changes: 1 addition & 1 deletion internal/auth/service_password.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ const bcryptCost = 12
// Generated once at compile time with cost bcryptCost (12).
//
//nolint:gosec // this is a public sentinel hash, not a credential
var dummyPasswordHash = "$2a$12$iAMeexq41AwZ2Dj9oAvGfeVHQxK5ffLPPTNxwPB8bsf7olA730dxO"
var dummyPasswordHash = "$2a$12$iAMeexq41AwZ2Dj9oAvGfeVHQxK5ffLPPTNxwPB8bsf7olA730dxO" // #nosec G101 -- public sentinel hash for constant-time compare on missing accounts; not a real credential //nolint:gosec

// Password validation constants following NIST guidelines.
const (
Expand Down
2 changes: 1 addition & 1 deletion internal/config/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ const (
RampImmediate = "immediate"

// RampWeekly25Pct means 25% per week for 4 weeks.
RampWeekly25Pct = "weekly-25pct"
RampWeekly25Pct = "weekly-25pct" // #nosec G101 -- schedule constant; gosec misidentifies "pct" suffix as a credential pattern

// RampMonthly10Pct means 10% per month for 10 months.
RampMonthly10Pct = "monthly-10pct"
Expand Down
6 changes: 3 additions & 3 deletions internal/credentials/cipher.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,9 @@ var ErrMultipleKeys = errors.New("credentials: multiple encryption-key env vars

// Env var names used by LoadKey, in priority order.
const (
EnvSecretARN = "CREDENTIAL_ENCRYPTION_KEY_SECRET_ARN" // AWS Secrets Manager ARN
EnvSecretName = "CREDENTIAL_ENCRYPTION_KEY_SECRET_NAME" // Azure Key Vault secret name
EnvSecretID = "CREDENTIAL_ENCRYPTION_KEY_SECRET_ID" // GCP Secret Manager secret ID
EnvSecretARN = "CREDENTIAL_ENCRYPTION_KEY_SECRET_ARN" // #nosec G101 -- env var name for AWS Secrets Manager ARN; not a credential value
EnvSecretName = "CREDENTIAL_ENCRYPTION_KEY_SECRET_NAME" // #nosec G101 -- env var name for Azure Key Vault secret; not a credential value
EnvSecretID = "CREDENTIAL_ENCRYPTION_KEY_SECRET_ID" // #nosec G101 -- env var name for GCP Secret Manager secret; not a credential value
EnvRawKey = "CREDENTIAL_ENCRYPTION_KEY" // Raw 64-char hex (ops/dev)
EnvAllowDev = "CREDENTIAL_ENCRYPTION_ALLOW_DEV_KEY" // 1 = permit zero-key fallback
)
Expand Down
2 changes: 1 addition & 1 deletion internal/credentials/gcp_federated.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ func BuildGCPFederatedCredential(
return nil, fmt.Errorf("credentials: gcp federated credential requires a target service account email")
}

cfg := externalaccount.Config{
cfg := externalaccount.Config{ // #nosec G101 -- TokenURL and ServiceAccountImpersonationURL are public Google API endpoints, not hardcoded credentials
Audience: audience,
SubjectTokenType: "urn:ietf:params:oauth:token-type:jwt",
TokenURL: "https://sts.googleapis.com/v1/token",
Expand Down
Loading
Loading