types: omit secret fields from JSON marshalling

Add json:"-" to PostgresConfig.Pass, OIDCConfig.ClientSecret, and
CLIConfig.APIKey so they are excluded from json.Marshal output
(e.g. the /debug/config endpoint).
This commit is contained in:
Kristoffer Dalby
2026-04-09 17:54:46 +00:00
parent 0641771128
commit a3c4ad2ca3
2 changed files with 41 additions and 3 deletions

View File

@@ -148,7 +148,7 @@ type PostgresConfig struct {
Port int
Name string
User string
Pass string
Pass string `json:"-"` // never serialise the database password
Ssl string
MaxOpenConnections int
MaxIdleConnections int
@@ -198,7 +198,7 @@ type OIDCConfig struct {
OnlyStartIfOIDCIsAvailable bool
Issuer string
ClientID string
ClientSecret string
ClientSecret string `json:"-"` // never serialise the OIDC client secret
Scope []string
ExtraParams map[string]string
AllowedDomains []string
@@ -237,7 +237,7 @@ type TaildropConfig struct {
type CLIConfig struct {
Address string
APIKey string
APIKey string `json:"-"` // never serialise the headscale admin API key
Timeout time.Duration
Insecure bool
}

View File

@@ -1,6 +1,7 @@
package types
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
@@ -472,3 +473,40 @@ func TestSafeServerURL(t *testing.T) {
})
}
}
// TestConfigJSONOmitsSecrets verifies that marshalling a Config to JSON
// (as /debug/config does via state.DebugConfig) does not leak the
// Postgres password, the OIDC client secret, or the headscale admin
// API key. Operators who widen metrics_listen_addr to 0.0.0.0 should
// not be able to read these back via debug endpoints reachable over
// CGNAT/loopback.
func TestConfigJSONOmitsSecrets(t *testing.T) {
const (
secretPostgresPass = "p0stgres-secret-marker"
secretClientSecret = "oidc-client-secret-marker" //nolint:gosec // test marker, not a real credential
secretAPIKey = "headscale-cli-api-key-marker" //nolint:gosec // test marker, not a real credential
)
cfg := &Config{
Database: DatabaseConfig{
Postgres: PostgresConfig{
Pass: secretPostgresPass,
},
},
OIDC: OIDCConfig{
ClientSecret: secretClientSecret,
},
CLI: CLIConfig{
APIKey: secretAPIKey,
},
}
out, err := json.Marshal(cfg)
require.NoError(t, err)
body := string(out)
for _, secret := range []string{secretPostgresPass, secretClientSecret, secretAPIKey} {
assert.NotContains(t, body, secret,
"marshalled Config must not contain secret %q", secret)
}
}