Files
helmfile/pkg/plugins/vals.go
T
yxxhero 472e8c7a2d fix: error on missing secret key when using vals (#2496)
* fix: error on missing secret key when using vals

Add HELMFILE_VALS_FAIL_ON_MISSING_KEY_IN_MAP environment variable
to control whether vals should fail when a referenced key does not
exist in the secret map.

Previously, when a secret reference like ref+vault://path#/nonexistent-key
pointed to a non-existent key, vals would silently return an empty string
without error. This could lead to deployments with missing configuration.

Default behavior remains backward compatible (returns empty string).
Set HELMFILE_VALS_FAIL_ON_MISSING_KEY_IN_MAP=true to enable strict mode.

Fixes #1563

Signed-off-by: yxxhero <aiopsclub@163.com>

* refactor: extract buildValsOptions helper and improve tests

- Extract buildValsOptions() to make vals configuration testable
- Use t.Setenv instead of manual env save/restore in tests
- Test actual vals.Options output including FailOnMissingKeyInMap

Addresses PR review comments on #2496

Signed-off-by: yxxhero <aiopsclub@163.com>

* fix: use strconv.ParseBool and make tests hermetic

- Use strconv.ParseBool for FailOnMissingKeyInMap parsing to support
  common boolean values like 'TRUE', '1', '0', etc.
- Always set env vars explicitly in tests (even to empty string) to
  prevent flaky tests when env vars are set externally
- Add test cases for various boolean formats

Signed-off-by: yxxhero <aiopsclub@163.com>

* docs: add documentation for vals-related environment variables

Add documentation for:
- HELMFILE_AWS_SDK_LOG_LEVEL: configure AWS SDK logging for vals
- HELMFILE_VALS_FAIL_ON_MISSING_KEY_IN_MAP: enable strict mode for secret refs

Signed-off-by: yxxhero <aiopsclub@163.com>

* fix: improve error handling and case-insensitive comparison

- buildValsOptions now returns error for invalid boolean values
  instead of silently defaulting to false
- Use strings.EqualFold for case-insensitive 'off' comparison
  to handle OFF, Off, etc.
- Add test cases for invalid boolean and uppercase OFF
- Update docs to mention case-insensitive and error behavior

Signed-off-by: yxxhero <aiopsclub@163.com>

* fix: normalize log level and improve singleton initialization

- Normalize AWS log level 'off' to lowercase for true case-insensitivity
- Replace sync.Once with mutex to allow recovery from config errors
- Update tests to expect normalized 'off' value
- Update docs to clarify when error is raised

Signed-off-by: yxxhero <aiopsclub@163.com>

---------

Signed-off-by: yxxhero <aiopsclub@163.com>
2026-03-24 09:42:54 +08:00

101 lines
2.7 KiB
Go

package plugins
import (
"fmt"
"io"
"os"
"strconv"
"strings"
"sync"
"github.com/helmfile/vals"
"github.com/helmfile/helmfile/pkg/envvar"
)
const (
// cache size for improving performance of ref+.* secrets rendering
valsCacheSize = 512
)
var instance *vals.Runtime
var mu sync.Mutex
func buildValsOptions() (vals.Options, error) {
// Configure AWS SDK logging via HELMFILE_AWS_SDK_LOG_LEVEL environment variable
// Default: "off" to prevent sensitive information (tokens, auth headers) from being exposed
// See issue #2270 and vals PR helmfile/vals#893
//
// Valid values:
// - "off" (default): No AWS SDK logging - secure, prevents credential leakage
// - "minimal": Log retries only - minimal debugging info
// - "standard": Log retries + requests - moderate debugging (previous default)
// - "verbose": Log everything - full debugging (requests, responses, bodies, signing)
// - Custom: Comma-separated values like "request,response"
//
// Note: AWS_SDK_GO_LOG_LEVEL environment variable always takes precedence over this setting
// Note: Case-insensitive for known values like "off", "OFF", "Off"
logLevel := strings.TrimSpace(os.Getenv(envvar.AWSSDKLogLevel))
// Configure fail on missing key behavior
// Default to false for backward compatibility
// Set HELMFILE_VALS_FAIL_ON_MISSING_KEY_IN_MAP=true to enable strict mode
// Supports common boolean values: "true", "TRUE", "1", etc.
// See issue #1563
envVal := strings.TrimSpace(os.Getenv(envvar.ValsFailOnMissingKeyInMap))
var failOnMissingKey bool
if envVal != "" {
var err error
failOnMissingKey, err = strconv.ParseBool(envVal)
if err != nil {
return vals.Options{}, fmt.Errorf("invalid value for %s: %q (must be a valid boolean)", envvar.ValsFailOnMissingKeyInMap, envVal)
}
}
// Default to "off" for security if not specified
if logLevel == "" {
logLevel = "off"
}
// Normalize known values to lowercase for case-insensitive handling
if strings.EqualFold(logLevel, "off") {
logLevel = "off"
}
opts := vals.Options{
CacheSize: valsCacheSize,
FailOnMissingKeyInMap: failOnMissingKey,
AWSLogLevel: logLevel,
}
// Also suppress vals' own internal logging unless user wants verbose output
// This prevents vals' log messages (separate from AWS SDK logs) from exposing credentials
if logLevel == "off" {
opts.LogOutput = io.Discard
}
// For other levels, allow vals to log to default output for debugging
return opts, nil
}
func ValsInstance() (*vals.Runtime, error) {
mu.Lock()
defer mu.Unlock()
if instance != nil {
return instance, nil
}
opts, err := buildValsOptions()
if err != nil {
return nil, err
}
instance, err = vals.New(opts)
if err != nil {
return nil, err
}
return instance, nil
}