Files
minio/internal/config/notify/legacy_test.go
T
Feng Ruohang 162ded3438 fix: register NATS/AMQP notify config keys read by parsers
GetNotifyNATS reads user_credentials, nkey_seed and tls_handshake_first
and GetNotifyAMQP reads immediate, but none of them were registered in
DefaultNATSKVS/DefaultAMQPKVS or the help schema, so CheckValidKeys
rejected any enable=on target carrying them. Worse, the legacy config
migration wrote exactly these keys - including the env var name
MINIO_NOTIFY_NATS_USER_CREDENTIALS used as a config key, because the
NATSUserCredentials constant doubled as both - so a migrated NATS config
failed validation on every load, and the FetchEnabledTargets fail-fast
then silently disabled all bucket notification targets.

- Register user_credentials/nkey_seed/tls_handshake_first (NATS) and
  immediate (AMQP) in the default KVS and help schema; split
  NATSUserCredentials into a real config key plus EnvNATSUserCredentials
  (all env var names byte-stable)
- Fix legacy migration: SetNotifyNATS writes the proper key;
  SetNotifyAMQP no longer writes cfg.Immediate under the internal key
  and now carries both immediate and internal
- Tolerate the legacy MINIO_NOTIFY_NATS_USER_CREDENTIALS key written by
  pre-fix migrations (NATS-scoped, load path only) with fallback read;
  env > user_credentials > legacy key
- Print key names only, never values, in the invalid-keys error of both
  CheckValidKeys forms; rejected values can carry credentials
- Add an AST-based audit test asserting parser reads, migration writes
  and help entries stay within the registered key set for all ten notify
  subsystems, with floor assertions so collector drift fails loudly
- Document (unchanged) FetchEnabledTargets fail-fast and pin it with a
  characterization test

Known same-class gap left in place and pinned by the audit's allowlist:
SetNotifyPostgres/SetNotifyMySQL write five unregistered DSN-era keys;
tracked for a follow-up issue.

Closes #39

Co-authored-by: ChatGPT <noreply@openai.com>
Co-authored-by: Claude <noreply@anthropic.com>
2026-08-04 23:00:31 +08:00

116 lines
3.7 KiB
Go

// Copyright (c) 2015-2026 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package notify
import (
"testing"
"github.com/minio/minio/internal/config"
"github.com/minio/minio/internal/event/target"
xnet "github.com/minio/pkg/v3/net"
"github.com/rabbitmq/amqp091-go"
)
// T5 (NATS): a config produced by the legacy migration must survive validation
// and round-trip back through the parser unchanged. Before the fix the
// migration wrote an env var name as a config key, so every migrated NATS
// target failed CheckValidKeys on the next config load.
func TestSetNotifyNATSRoundTrip(t *testing.T) {
addr, err := xnet.ParseHost(testNATSAddr)
if err != nil {
t.Fatalf("ParseHost: %v", err)
}
args := target.NATSArgs{
Enable: true,
Address: *addr,
Subject: testNATSSubj,
UserCredentials: testCredsPath,
NKeySeed: testNKeyPath,
TLSHandshakeFirst: true,
}
s := config.Config{config.NotifyNATSSubSys: map[string]config.KVS{}}
if err := SetNotifyNATS(s, testTargetName, args); err != nil {
t.Fatalf("SetNotifyNATS: %v", err)
}
if err := checkValidNotificationKeysForSubSys(config.NotifyNATSSubSys, s[config.NotifyNATSSubSys]); err != nil {
t.Fatalf("migrated NATS config must pass key validation, got: %v", err)
}
targets, err := GetNotifyNATS(s[config.NotifyNATSSubSys], nil)
if err != nil {
t.Fatalf("GetNotifyNATS: %v", err)
}
got, ok := targets[testTargetName]
if !ok {
t.Fatalf("target %q missing after round trip: %v", testTargetName, targets)
}
if got.UserCredentials != args.UserCredentials {
t.Errorf("UserCredentials = %q, want %q", got.UserCredentials, args.UserCredentials)
}
if got.NKeySeed != args.NKeySeed {
t.Errorf("NKeySeed = %q, want %q", got.NKeySeed, args.NKeySeed)
}
if got.TLSHandshakeFirst != args.TLSHandshakeFirst {
t.Errorf("TLSHandshakeFirst = %v, want %v", got.TLSHandshakeFirst, args.TLSHandshakeFirst)
}
}
// T5 (AMQP): the migration mapped cfg.Immediate onto the `internal` key and
// dropped cfg.Internal entirely, so a migrated target came back with both
// fields wrong.
func TestSetNotifyAMQPRoundTrip(t *testing.T) {
uri, err := amqp091.ParseURI(testAMQPURL)
if err != nil {
t.Fatalf("ParseURI: %v", err)
}
args := target.AMQPArgs{
Enable: true,
URL: uri,
Immediate: true,
Internal: false,
}
s := config.Config{config.NotifyAMQPSubSys: map[string]config.KVS{}}
if err := SetNotifyAMQP(s, testTargetName, args); err != nil {
t.Fatalf("SetNotifyAMQP: %v", err)
}
if err := checkValidNotificationKeysForSubSys(config.NotifyAMQPSubSys, s[config.NotifyAMQPSubSys]); err != nil {
t.Fatalf("migrated AMQP config must pass key validation, got: %v", err)
}
targets, err := GetNotifyAMQP(s[config.NotifyAMQPSubSys])
if err != nil {
t.Fatalf("GetNotifyAMQP: %v", err)
}
got, ok := targets[testTargetName]
if !ok {
t.Fatalf("target %q missing after round trip: %v", testTargetName, targets)
}
if !got.Immediate {
t.Errorf("Immediate = false, want true")
}
if got.Internal {
t.Errorf("Internal = true, want false (immediate must not be written to the internal key)")
}
}