Alte Schannel-Stacks brechen an einem TLS-1.3-ClientHello kommentarlos ab: sie nehmen die TCP-Verbindung an, lesen den ClientHello und setzen zurueck, ohne ein Zertifikat zu schicken. Herunterhandeln hilft dann nicht — TLS 1.3 darf gar nicht erst angeboten werden. max_tls_version (Default 1.3) deckelt die Hoechstversion; die Validierung lehnt eine Hoechstversion unterhalb der Mindestversion ab. check erkennt zusaetzlich zurueckgesetzte Verbindungen und nennt die beiden plausiblen Ursachen: fehlendes LDAPS-Zertifikat auf dem Server oder TLS-1.3-Inkompatibilitaet. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NBHF4R9EAejDJUMdwr6C68
200 lines
5.5 KiB
Go
200 lines
5.5 KiB
Go
package config
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func validCfg(t *testing.T) *Config {
|
|
t.Helper()
|
|
cfg, err := Parse(strings.NewReader(minimalYAML))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return cfg
|
|
}
|
|
|
|
func TestBaseDNFromDomain(t *testing.T) {
|
|
cases := map[string]string{
|
|
"firma.local": "DC=firma,DC=local",
|
|
"ad.firma.example": "DC=ad,DC=firma,DC=example",
|
|
"FIRMA.LOCAL": "DC=FIRMA,DC=LOCAL",
|
|
}
|
|
for in, want := range cases {
|
|
got, err := BaseDNFromDomain(in)
|
|
if err != nil {
|
|
t.Errorf("BaseDNFromDomain(%q): %v", in, err)
|
|
continue
|
|
}
|
|
if got != want {
|
|
t.Errorf("BaseDNFromDomain(%q) = %q, want %q", in, got, want)
|
|
}
|
|
}
|
|
if _, err := BaseDNFromDomain("firmalocal"); err == nil {
|
|
t.Error("Domain ohne Punkt muss abgelehnt werden")
|
|
}
|
|
if _, err := BaseDNFromDomain(""); err == nil {
|
|
t.Error("leere Domain muss abgelehnt werden")
|
|
}
|
|
}
|
|
|
|
func TestEffectiveBaseDNPrefersOverride(t *testing.T) {
|
|
cfg := validCfg(t)
|
|
cfg.AD.BaseDN = "OU=Users,DC=firma,DC=local"
|
|
if got := cfg.EffectiveBaseDN(); got != "OU=Users,DC=firma,DC=local" {
|
|
t.Errorf("Override muss gewinnen, got %q", got)
|
|
}
|
|
cfg.AD.BaseDN = ""
|
|
if got := cfg.EffectiveBaseDN(); got != "DC=firma,DC=local" {
|
|
t.Errorf("Ableitung falsch, got %q", got)
|
|
}
|
|
}
|
|
|
|
func TestValidateAcceptsMinimalConfig(t *testing.T) {
|
|
if err := validCfg(t).Validate(); err != nil {
|
|
t.Fatalf("minimale Config sollte gültig sein: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestValidateRejectsIPServers(t *testing.T) {
|
|
cfg := validCfg(t)
|
|
cfg.AD.Servers = []string{"10.1.1.10"}
|
|
err := cfg.Validate()
|
|
if err == nil || !strings.Contains(err.Error(), "10.1.1.10") {
|
|
t.Fatalf("IP-Adresse als DC muss abgelehnt werden, got: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestValidateRejectsHTTPOPNsense(t *testing.T) {
|
|
cfg := validCfg(t)
|
|
cfg.OPNsense.URL = "http://fw01.firma.local"
|
|
if err := cfg.Validate(); err == nil {
|
|
t.Fatal("http:// muss abgelehnt werden")
|
|
}
|
|
}
|
|
|
|
func TestValidateRejectsPatternAndRegexTogether(t *testing.T) {
|
|
cfg := validCfg(t)
|
|
cfg.Matching.CNRegex = "^.*$"
|
|
if err := cfg.Validate(); err == nil {
|
|
t.Fatal("cn_pattern und cn_regex gleichzeitig muss abgelehnt werden")
|
|
}
|
|
}
|
|
|
|
func TestValidateRejectsBadRegex(t *testing.T) {
|
|
cfg := validCfg(t)
|
|
cfg.Matching.CNPattern = ""
|
|
cfg.Matching.CNRegex = "([unbalanced"
|
|
if err := cfg.Validate(); err == nil {
|
|
t.Fatal("unkompilierbare Regex muss abgelehnt werden")
|
|
}
|
|
}
|
|
|
|
func TestValidateCollectsMultipleErrors(t *testing.T) {
|
|
cfg := validCfg(t)
|
|
cfg.Portal.TLSCert = ""
|
|
cfg.Portal.TLSKey = ""
|
|
cfg.AD.VPNGroup = ""
|
|
err := cfg.Validate()
|
|
if err == nil {
|
|
t.Fatal("mehrere Fehler erwartet")
|
|
}
|
|
for _, want := range []string{"tls_cert", "tls_key", "vpn_group"} {
|
|
if !strings.Contains(err.Error(), want) {
|
|
t.Errorf("Sammelfehler muss %q nennen, got: %v", want, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestMinTLSVersionDefaultsTo12(t *testing.T) {
|
|
cfg := validCfg(t)
|
|
if cfg.AD.MinTLSVersion != "1.2" {
|
|
t.Errorf("MinTLSVersion = %q, want Default 1.2", cfg.AD.MinTLSVersion)
|
|
}
|
|
if err := cfg.Validate(); err != nil {
|
|
t.Fatalf("Default muss gültig sein: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestValidateAcceptsLegacyMinTLSVersions(t *testing.T) {
|
|
// Alte Domain Controller (z. B. Windows Server 2012 R2 mit abgeschaltetem
|
|
// TLS 1.2) müssen erreichbar bleiben.
|
|
for _, v := range []string{"1.0", "1.1", "1.2", "1.3"} {
|
|
cfg := validCfg(t)
|
|
cfg.AD.MinTLSVersion = v
|
|
if err := cfg.Validate(); err != nil {
|
|
t.Errorf("min_tls_version %q muss zulässig sein: %v", v, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestValidateRejectsUnknownMinTLSVersion(t *testing.T) {
|
|
for _, v := range []string{"1.4", "tls1.2", "TLSv1", "", "12"} {
|
|
cfg := validCfg(t)
|
|
cfg.AD.MinTLSVersion = v
|
|
if err := cfg.Validate(); err == nil {
|
|
t.Errorf("min_tls_version %q muss abgelehnt werden", v)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestMaxTLSVersionDefaultsTo13(t *testing.T) {
|
|
cfg := validCfg(t)
|
|
if cfg.AD.MaxTLSVersion != "1.3" {
|
|
t.Errorf("MaxTLSVersion = %q, want Default 1.3", cfg.AD.MaxTLSVersion)
|
|
}
|
|
if err := cfg.Validate(); err != nil {
|
|
t.Fatalf("Default muss gültig sein: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestValidateAcceptsCappedMaxTLSVersion(t *testing.T) {
|
|
// Alte Schannel-Stacks brechen an einem TLS-1.3-ClientHello ab. Dann muss
|
|
// sich die Hoechstversion auf 1.2 deckeln lassen.
|
|
cfg := validCfg(t)
|
|
cfg.AD.MaxTLSVersion = "1.2"
|
|
if err := cfg.Validate(); err != nil {
|
|
t.Fatalf("max_tls_version 1.2 muss zulässig sein: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestValidateRejectsMaxBelowMin(t *testing.T) {
|
|
cfg := validCfg(t)
|
|
cfg.AD.MinTLSVersion = "1.2"
|
|
cfg.AD.MaxTLSVersion = "1.1"
|
|
err := cfg.Validate()
|
|
if err == nil {
|
|
t.Fatal("max_tls_version unter min_tls_version muss abgelehnt werden")
|
|
}
|
|
if !strings.Contains(err.Error(), "max_tls_version") {
|
|
t.Errorf("Fehler muss das Feld nennen: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestValidateRejectsUnknownMaxTLSVersion(t *testing.T) {
|
|
for _, v := range []string{"1.4", "tls1.3", "", "13"} {
|
|
cfg := validCfg(t)
|
|
cfg.AD.MaxTLSVersion = v
|
|
if err := cfg.Validate(); err == nil {
|
|
t.Errorf("max_tls_version %q muss abgelehnt werden", v)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestMinTLSVersionIsWeakBelow12(t *testing.T) {
|
|
cfg := validCfg(t)
|
|
for v, wantWeak := range map[string]bool{"1.0": true, "1.1": true, "1.2": false, "1.3": false} {
|
|
cfg.AD.MinTLSVersion = v
|
|
if got := cfg.AD.MinTLSVersionIsWeak(); got != wantWeak {
|
|
t.Errorf("MinTLSVersionIsWeak() für %q = %v, want %v", v, got, wantWeak)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestValidateRejectsPatternWithoutPlaceholder(t *testing.T) {
|
|
cfg := validCfg(t)
|
|
cfg.Matching.CNPattern = "fester-cn"
|
|
if err := cfg.Validate(); err == nil {
|
|
t.Fatal("cn_pattern ohne {username} muss abgelehnt werden")
|
|
}
|
|
}
|