Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@
* [BUGFIX] Querier: Fix flake in integration tests TestQuerierWithStoreGatewayDataBytesLimits and TestQuerierWithBlocksStorageLimits by waiting for the querier to see the store-gateway ACTIVE in the ring before querying. #7614
* [BUGFIX] Ruler: Register xfunctions (xincrease, xrate, xdelta) in the global parser before loading rule files. #7621
* [BUGFIX] Security: Reject empty entries in `-distributor.sign-write-requests-keys` caused by stray or trailing commas (e.g. `newkey,`). Previously these were silently accepted and produced an empty signing key, which downgraded HMAC stream-push authentication to a forgeable signature. Misconfigured flags now fail at process startup; audit your configs before upgrading. #7587
* [BUGFIX] Config: Fix validation of explicit zero values in non-empty YAML root sections, allowing configs such as `flusher: { exit_after_flush: false }` while continuing to reject empty root sections. #7700
* [BUGFIX] Querier: Fix panic due to request tracker truncating multi-byte UTF-8 character #7640
* [BUGFIX] Ingester: Fix panic (`HistogramProtoToHistogram called with a float histogram`) when ingesting a float native histogram with a zero count (e.g. a staleness marker or empty histogram). The decoder is now selected by histogram type via `IsFloatHistogram()` instead of by count value. #7645
* [BUGFIX] Querier: Fix parquet queryable fallback returning a nil error instead of the actual query error in `LabelValues` and `LabelNames`. #7638
Expand Down
5 changes: 5 additions & 0 deletions cmd/cortex/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,11 @@ func LoadConfig(filename string, expandENV bool, cfg *cortex.Config) error {
buf = expandEnv(buf)
}

err = cfg.SetYAMLRootNodeStates(buf)
if err != nil {
return errors.Wrap(err, "Error parsing config file")
}

err = yaml.UnmarshalStrict(buf, cfg)
if err != nil {
return errors.Wrap(err, "Error parsing config file")
Expand Down
5 changes: 5 additions & 0 deletions cmd/cortex/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,11 @@ func TestFlagParsing(t *testing.T) {
stderrMessage: "the Querier configuration in YAML has been specified as an empty YAML node",
},

"root level configuration option specified with explicit zero values": {
yaml: "flusher: { exit_after_flush: false }",
stderrExcluded: "empty YAML node",
},

"version": {
arguments: []string{"-version"},
stdoutMessage: "Cortex, version",
Expand Down
61 changes: 61 additions & 0 deletions pkg/cortex/cortex.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ type Config struct {
PrintConfig bool `yaml:"-"`
HTTPPrefix string `yaml:"http_prefix"`
NameValidationScheme model.ValidationScheme `yaml:"name_validation_scheme"`
yamlRootNodeStates map[string]yamlRootNodeState

ResourceMonitor configs.ResourceMonitor `yaml:"resource_monitor"`

Expand Down Expand Up @@ -135,6 +136,54 @@ type Config struct {
Tracing tracing.Config `yaml:"tracing"`
}

type yamlRootNodeState int

const (
yamlRootNodeEmpty yamlRootNodeState = iota
yamlRootNodeNonEmpty
)

// SetYAMLRootNodeStates records whether root-level YAML config keys were empty.
// This lets validation distinguish "querier:" from explicit zero values such
// as "flusher: { exit_after_flush: false }" after unmarshalling.
func (c *Config) SetYAMLRootNodeStates(buf []byte) error {
var root map[any]any
if err := yaml.Unmarshal(buf, &root); err != nil {
return err
}

c.yamlRootNodeStates = map[string]yamlRootNodeState{}
for key, value := range root {
name, ok := key.(string)
if !ok {
continue
}

if isEmptyYAMLRootNode(value) {
c.yamlRootNodeStates[name] = yamlRootNodeEmpty
} else {
c.yamlRootNodeStates[name] = yamlRootNodeNonEmpty
}
}

return nil
}

func isEmptyYAMLRootNode(value any) bool {
if value == nil {
return true
}

switch typed := value.(type) {
case map[any]any:
return len(typed) == 0
case []any:
return len(typed) == 0
default:
return false
}
}

// RegisterFlags registers flag.
func (c *Config) RegisterFlags(f *flag.FlagSet) {
c.Server.MetricsNamespace = "cortex"
Expand Down Expand Up @@ -296,13 +345,25 @@ func (c *Config) validateYAMLEmptyNodes() error {
// If the struct has been reset due to empty YAML value and the zero struct value
// doesn't match the default one, then we should warn the user about the issue.
if cfgStruct.Field(i).Kind() == reflect.Struct && cfgStruct.Field(i).IsZero() && !defStruct.Field(i).IsZero() {
if c.yamlRootNodeStates != nil {
yamlName := yamlFieldName(cfgStruct.Type().Field(i))
if c.yamlRootNodeStates[yamlName] == yamlRootNodeNonEmpty {
continue
}
}
return fmt.Errorf("the %s configuration in YAML has been specified as an empty YAML node", cfgStruct.Type().Field(i).Name)
}
}

return nil
}

func yamlFieldName(field reflect.StructField) string {
tag := field.Tag.Get("yaml")
name, _, _ := strings.Cut(tag, ",")
return name
}

func (c *Config) registerServerFlagsWithChangedDefaultValues(fs *flag.FlagSet) {
throwaway := flag.NewFlagSet("throwaway", flag.PanicOnError)

Expand Down