add auth support
continuous-integration/drone/push Build is passing

This commit is contained in:
2026-04-17 13:19:08 +10:00
parent 9a561f3b07
commit ae3e2be89a
22 changed files with 2479 additions and 40 deletions
+128
View File
@@ -31,6 +31,134 @@ func TestReadYMLSettingsRejectsUnknownField(t *testing.T) {
}
}
func TestReadYMLSettingsAppliesAuthDefaults(t *testing.T) {
tmpDir := t.TempDir()
settingsPath := filepath.Join(tmpDir, "vctp.yml")
content := `settings:
log_level: "info"
`
if err := os.WriteFile(settingsPath, []byte(content), 0o600); err != nil {
t.Fatalf("failed to write settings file: %v", err)
}
logger := slog.New(slog.NewTextHandler(io.Discard, nil))
s := New(logger, settingsPath)
if err := s.ReadYMLSettings(); err != nil {
t.Fatalf("expected settings to load, got error: %v", err)
}
got := s.Values.Settings
if got.AuthMode != authModeDisabled {
t.Fatalf("expected default auth_mode=%q, got %q", authModeDisabled, got.AuthMode)
}
if got.AuthTokenLifespanMinutes != defaultAuthTokenLifespanMinutes {
t.Fatalf("expected default auth_token_lifespan_minutes=%d, got %d", defaultAuthTokenLifespanMinutes, got.AuthTokenLifespanMinutes)
}
if got.AuthJWTIssuer != defaultAuthJWTIssuer {
t.Fatalf("expected default auth_jwt_issuer=%q, got %q", defaultAuthJWTIssuer, got.AuthJWTIssuer)
}
if got.AuthJWTAudience != defaultAuthJWTAudience {
t.Fatalf("expected default auth_jwt_audience=%q, got %q", defaultAuthJWTAudience, got.AuthJWTAudience)
}
if got.AuthClockSkewSeconds != defaultAuthClockSkewSeconds {
t.Fatalf("expected default auth_clock_skew_seconds=%d, got %d", defaultAuthClockSkewSeconds, got.AuthClockSkewSeconds)
}
}
func TestReadYMLSettingsRejectsInvalidAuthMode(t *testing.T) {
tmpDir := t.TempDir()
settingsPath := filepath.Join(tmpDir, "vctp.yml")
content := `settings:
auth_mode: "sometimes"
`
if err := os.WriteFile(settingsPath, []byte(content), 0o600); err != nil {
t.Fatalf("failed to write settings file: %v", err)
}
logger := slog.New(slog.NewTextHandler(io.Discard, nil))
s := New(logger, settingsPath)
err := s.ReadYMLSettings()
if err == nil {
t.Fatal("expected invalid auth_mode to fail")
}
if !strings.Contains(strings.ToLower(err.Error()), "auth_mode") {
t.Fatalf("expected error to mention auth_mode, got: %v", err)
}
}
func TestReadYMLSettingsRejectsAuthEnabledWithoutSigningKey(t *testing.T) {
tmpDir := t.TempDir()
settingsPath := filepath.Join(tmpDir, "vctp.yml")
content := `settings:
auth_enabled: true
auth_mode: "required"
ldap_bind_address: "ldaps://ldap.example.com:636"
ldap_base_dn: "dc=example,dc=com"
auth_group_role_mappings:
"cn=vctp-admin,ou=groups,dc=example,dc=com": "admin"
`
if err := os.WriteFile(settingsPath, []byte(content), 0o600); err != nil {
t.Fatalf("failed to write settings file: %v", err)
}
logger := slog.New(slog.NewTextHandler(io.Discard, nil))
s := New(logger, settingsPath)
err := s.ReadYMLSettings()
if err == nil {
t.Fatal("expected auth_enabled=true without signing key to fail")
}
if !strings.Contains(strings.ToLower(err.Error()), "auth_jwt_signing_key") {
t.Fatalf("expected error to mention auth_jwt_signing_key, got: %v", err)
}
}
func TestReadYMLSettingsAcceptsValidAuthConfigAndNormalizesMappings(t *testing.T) {
tmpDir := t.TempDir()
settingsPath := filepath.Join(tmpDir, "vctp.yml")
content := `settings:
auth_enabled: true
auth_mode: "REQUIRED"
auth_jwt_signing_key: "c2VjcmV0"
auth_token_lifespan_minutes: 90
auth_jwt_issuer: " custom-issuer "
auth_jwt_audience: " custom-audience "
auth_clock_skew_seconds: 15
ldap_bind_address: "ldaps://ldap.example.com:636"
ldap_base_dn: "dc=example,dc=com"
ldap_groups:
- " cn=vctp-viewers,ou=groups,dc=example,dc=com "
auth_group_role_mappings:
" cn=vctp-admins,ou=groups,dc=example,dc=com ": " ADMIN "
"cn=vctp-viewers,ou=groups,dc=example,dc=com": "viewer"
`
if err := os.WriteFile(settingsPath, []byte(content), 0o600); err != nil {
t.Fatalf("failed to write settings file: %v", err)
}
logger := slog.New(slog.NewTextHandler(io.Discard, nil))
s := New(logger, settingsPath)
if err := s.ReadYMLSettings(); err != nil {
t.Fatalf("expected valid auth config, got error: %v", err)
}
got := s.Values.Settings
if got.AuthMode != authModeRequired {
t.Fatalf("expected normalized auth_mode=%q, got %q", authModeRequired, got.AuthMode)
}
if got.AuthJWTIssuer != "custom-issuer" {
t.Fatalf("expected trimmed auth_jwt_issuer, got %q", got.AuthJWTIssuer)
}
if got.AuthJWTAudience != "custom-audience" {
t.Fatalf("expected trimmed auth_jwt_audience, got %q", got.AuthJWTAudience)
}
if len(got.LDAPGroups) != 1 || got.LDAPGroups[0] != "cn=vctp-viewers,ou=groups,dc=example,dc=com" {
t.Fatalf("expected ldap_groups to be compacted+trimmed, got %#v", got.LDAPGroups)
}
if got.AuthGroupRoleMappings["cn=vctp-admins,ou=groups,dc=example,dc=com"] != authRoleAdmin {
t.Fatalf("expected admin mapping to normalize role to %q, got %#v", authRoleAdmin, got.AuthGroupRoleMappings)
}
}
func TestSecureSettingsFileMode(t *testing.T) {
cases := []struct {
name string