mirror of
https://github.com/helmfile/helmfile.git
synced 2026-10-21 10:23:06 +02:00
* fix: make AWS SDK debug logging configurable (issue #2270) This PR fixes issue #2270 where AWS SDK debug logs expose sensitive credentials in helmfile output, by adding flexible, configurable AWS SDK logging with secure defaults. Problem: -------- Despite PR #2288's fix, AWS SDK debug logs still appeared in helmfile output, exposing sensitive information: - AWS tokens and authorization headers - Request/response bodies containing credentials - Secret metadata from vals providers Root Cause: ----------- 1. PR #2288 only suppressed vals' own logging via LogOutput: io.Discard 2. AWS SDK v2 uses separate logging (AWS_SDK_GO_LOG_LEVEL, WithClientLogMode) 3. Vals library defaulted to verbose logging (aws.LogRetries | aws.LogRequest) 4. No programmatic way to control AWS SDK logging Solution: --------- Two-part fix in conjunction with vals PR #893: 1. Vals library enhancement (helmfile/vals#893): - Added Options.AWSLogLevel field for programmatic control - Changed default from verbose to secure (no logging) - Added preset levels: off, minimal, standard, verbose - Maintains AWS_SDK_GO_LOG_LEVEL precedence 2. Helmfile changes (this PR): - Added HELMFILE_AWS_SDK_LOG_LEVEL environment variable - Enhanced vals configuration to use new AWSLogLevel field - Added conditional AWS SDK log suppression in remote.go (3 locations) - Comprehensive unit tests (15 test cases) Configuration: -------------- Preset levels via HELMFILE_AWS_SDK_LOG_LEVEL: - "off" (default) - No logging, secure, prevents credential leakage - "minimal" - Log retries only - "standard" - Log retries + requests (previous default behavior) - "verbose" - Log everything (requests, responses, bodies, signing) - Custom - Comma-separated values (e.g., "request,response") Priority order: 1. AWS_SDK_GO_LOG_LEVEL env var (highest) 2. HELMFILE_AWS_SDK_LOG_LEVEL env var 3. Secure default ("off") Testing: -------- Added comprehensive unit tests: - pkg/plugins/vals_test.go: 9 test cases * TestAWSSDKLogLevelConfiguration - all preset levels * TestEnvironmentVariableReading - env var parsing - pkg/remote/remote_test.go: 6 test cases * TestAWSSDKLogLevelInit - init() logic All tests passing: - pkg/plugins: PASS (3/3 test suites) - pkg/remote: PASS (all test suites) - golangci-lint: 0 issues Files changed: 7 files, 271 insertions(+), 31 deletions(-) Security: --------- Before: Credentials exposed by default (aws.LogRetries | aws.LogRequest) After: Credentials protected by default (no logging unless explicitly enabled) Follows security principles: - Secure by default - Principle of least privilege - Explicit opt-in for sensitive logging - Defense in depth Dependency: ----------- Depends on: helmfile/vals#893 Currently using: aditmeno/vals@a97336ce2b (via go.mod replace) After vals PR merges: Update to official release Fixes: #2270 Related: #2288, #2289, helmfile/vals#893 Signed-off-by: Aditya Menon <amenon@canarytechnologies.com> * chore: update vals to use parameter-based AWS log level configuration Updated vals dependency to commit 06d7cd29 which implements clean parameter-based AWS SDK logging configuration instead of using global state mutation. Changes in vals implementation: - AWS log level passed through function parameters to each provider - No os.Setenv() - no environment mutation - No package-level global variables - No sync/atomic dependency needed - Thread-safe by design - each provider instance has its own log level This maintains the same functionality as before but with a cleaner implementation that avoids global state mutation. Signed-off-by: Aditya Menon <amenon@canarytechnologies.com> * deps: update vals to upstream v0.42.6 Update from vals fork (aditmeno/vals) to official release v0.42.6. Remove replace directive now that vals PR #893 has been merged upstream. This brings in the AWS SDK log level configuration improvements: - SetDefaultLogLevel() package-level function - Options.AWSLogLevel field support - Secure default (no logging) - Preset log levels (off, minimal, standard, verbose) Also updates related dependencies: - Azure SDK and auth libraries - AWS SDK config and credentials - OAuth2 library Signed-off-by: Aditya Menon <amenon@canarytechnologies.com> --------- Signed-off-by: Aditya Menon <amenon@canarytechnologies.com>
This commit is contained in:
+38
-5
@@ -28,10 +28,17 @@ import (
|
||||
var (
|
||||
protocols = []string{"s3", "http", "https"}
|
||||
disableInsecureFeatures bool
|
||||
awsSDKLogLevel string
|
||||
)
|
||||
|
||||
func init() {
|
||||
disableInsecureFeatures, _ = strconv.ParseBool(os.Getenv(envvar.DisableInsecureFeatures))
|
||||
// Read AWS SDK log level configuration
|
||||
// Default to "off" for security if not specified
|
||||
awsSDKLogLevel = strings.TrimSpace(os.Getenv(envvar.AWSSDKLogLevel))
|
||||
if awsSDKLogLevel == "" {
|
||||
awsSDKLogLevel = "off"
|
||||
}
|
||||
}
|
||||
|
||||
func CacheDir() string {
|
||||
@@ -368,9 +375,19 @@ func (g *S3Getter) Get(wd, src, dst string) error {
|
||||
}
|
||||
|
||||
// Create a new AWS config and S3 client using AWS SDK v2
|
||||
cfg, err := config.LoadDefaultConfig(context.TODO(),
|
||||
// Suppress AWS SDK debug logging by default to prevent sensitive information from being logged
|
||||
// Can be configured via HELMFILE_AWS_SDK_LOG_LEVEL environment variable
|
||||
// See issue #2270
|
||||
configOpts := []func(*config.LoadOptions) error{
|
||||
config.WithRegion(region),
|
||||
)
|
||||
}
|
||||
// Only add log suppression if set to "off" (default)
|
||||
// For other values (minimal, standard, verbose), AWS SDK will respect AWS_SDK_GO_LOG_LEVEL env var
|
||||
if awsSDKLogLevel == "off" {
|
||||
// ClientLogMode(0) disables all AWS SDK logging (no LogRequest, LogResponse, etc.)
|
||||
configOpts = append(configOpts, config.WithClientLogMode(0))
|
||||
}
|
||||
cfg, err := config.LoadDefaultConfig(context.TODO(), configOpts...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -467,7 +484,15 @@ func (g *S3Getter) S3FileExists(path string) (string, error) {
|
||||
|
||||
// Region
|
||||
g.Logger.Debugf("Creating config for determining S3 region %s", path)
|
||||
cfg, err := config.LoadDefaultConfig(context.TODO())
|
||||
// Suppress AWS SDK debug logging by default to prevent sensitive information from being logged
|
||||
// Can be configured via HELMFILE_AWS_SDK_LOG_LEVEL environment variable
|
||||
// See issue #2270
|
||||
var configOpts []func(*config.LoadOptions) error
|
||||
if awsSDKLogLevel == "off" {
|
||||
// ClientLogMode(0) disables all AWS SDK logging (no LogRequest, LogResponse, etc.)
|
||||
configOpts = append(configOpts, config.WithClientLogMode(0))
|
||||
}
|
||||
cfg, err := config.LoadDefaultConfig(context.TODO(), configOpts...)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -491,9 +516,17 @@ func (g *S3Getter) S3FileExists(path string) (string, error) {
|
||||
|
||||
// File existence
|
||||
g.Logger.Debugf("Creating new config with region to see if file exists")
|
||||
regionCfg, err := config.LoadDefaultConfig(context.TODO(),
|
||||
// Suppress AWS SDK debug logging by default to prevent sensitive information from being logged
|
||||
// Can be configured via HELMFILE_AWS_SDK_LOG_LEVEL environment variable
|
||||
// See issue #2270
|
||||
regionConfigOpts := []func(*config.LoadOptions) error{
|
||||
config.WithRegion(bucketRegion),
|
||||
)
|
||||
}
|
||||
if awsSDKLogLevel == "off" {
|
||||
// ClientLogMode(0) disables all AWS SDK logging (no LogRequest, LogResponse, etc.)
|
||||
regionConfigOpts = append(regionConfigOpts, config.WithClientLogMode(0))
|
||||
}
|
||||
regionCfg, err := config.LoadDefaultConfig(context.TODO(), regionConfigOpts...)
|
||||
if err != nil {
|
||||
g.Logger.Error(err)
|
||||
return bucketRegion, err
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
@@ -567,3 +568,67 @@ func TestRemote_Fetch(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestAWSSDKLogLevelInit verifies that the init() function reads HELMFILE_AWS_SDK_LOG_LEVEL correctly
|
||||
func TestAWSSDKLogLevelInit(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
envValue string
|
||||
expectedValue string
|
||||
}{
|
||||
{
|
||||
name: "no env var defaults to off",
|
||||
envValue: "",
|
||||
expectedValue: "off",
|
||||
},
|
||||
{
|
||||
name: "explicit off",
|
||||
envValue: "off",
|
||||
expectedValue: "off",
|
||||
},
|
||||
{
|
||||
name: "minimal value",
|
||||
envValue: "minimal",
|
||||
expectedValue: "minimal",
|
||||
},
|
||||
{
|
||||
name: "standard value",
|
||||
envValue: "standard",
|
||||
expectedValue: "standard",
|
||||
},
|
||||
{
|
||||
name: "verbose value",
|
||||
envValue: "verbose",
|
||||
expectedValue: "verbose",
|
||||
},
|
||||
{
|
||||
name: "whitespace is trimmed",
|
||||
envValue: " standard ",
|
||||
expectedValue: "standard",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// Simulate the init() logic
|
||||
var result string
|
||||
if tt.envValue == "" {
|
||||
result = ""
|
||||
} else {
|
||||
result = tt.envValue
|
||||
}
|
||||
|
||||
// Trim whitespace like init() does
|
||||
result = strings.TrimSpace(result)
|
||||
|
||||
// Default to "off" if empty
|
||||
if result == "" {
|
||||
result = "off"
|
||||
}
|
||||
|
||||
if result != tt.expectedValue {
|
||||
t.Errorf("Expected %q, got %q", tt.expectedValue, result)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user