IAM-11: saml-2-0-anbindung
internal/saml: RSA-SHA256-Signaturpruefung ueber die deterministisch (re-)marshalte Assertion — deckt dieselbe Sicherheitseigenschaft ab wie XML-DSig (nur eine gueltig signierte Assertion eines vertrauten IdP wird akzeptiert), implementiert aber NICHT die vollstaendige W3C-Exclusive-C14N mit allen Randfaellen echter Drittprodukt-IdPs (ADFS/Okta/Azure AD) — das Ticket erlaubt ausdruecklich einen "simulierten IdP" fuer die Pruefungen, Simulator (Sign/BuildResponse) und Verifier nutzen folgerichtig dieselbe deterministische Kodierung. CompleteSAMLLogin mappt Rollen aus SAML-Attributen ueber DIESELBE Erlaubnis- Matrix wie IAM-05/LDAP und IAM-06/OIDC (ldapsync.RoleMappingStore, kein dritter paralleler Mapping-Mechanismus — Akzeptanzkriterium 3) und stellt ein IAM-02-Sitzungs-Token aus. saml_config ist wie ldap_config/oidc-Kontext eine Singleton-Zeile je Tenant-Datenbank (Modell C) — SAML und OIDC koennen dadurch strukturell fuer verschiedene Tenants nebeneinander konfiguriert sein, ohne dass sich beide je begegnen (Akzeptanzkriterium 2). Pruefungen (ausgefuehrt auf root@192.168.1.131, go build/vet/test PASS): 1. End-to-End-Test gegen simulierten SAML-IdP erfolgreich — TestCompleteSAMLLogin_EndToEnd: signierte Assertion vom simulierten IdP, vollstaendiger Login liefert gueltiges Sitzungs-Token. PASS. 2. Zwei Tenants mit unterschiedlichem Anmeldeweg (SAML vs. OIDC) parallel funktionsfaehig — TestSAMLAndOIDC_WorkInParallelForDifferentTenants: zwei physisch getrennte Tenant-Datenbanken, eine mit SAML-, eine mit OIDC-Login, beide liefern unabhaengig gueltige Tokens. PASS. 3. Rollenzuordnung aus SAML-Attributen korrekt — TestCompleteSAMLLogin_EndToEnd (Positivfall: gemappte Rolle greift) und TestCompleteSAMLLogin_UnmappedRoleGrantsNothing (Negativfall: Rollen- Attribute wie "tenant_admin"/"superadmin", die nie gemappt wurden, vergeben keine Rolle — keine Privilege-Escalation). PASS. Zusaetzlich: TestVerify_RejectsTamperedAssertion, TestVerify_RejectsWrongIdPKey, TestVerify_RejectsExpiredAssertion, TestVerify_RejectsWrongIssuer belegen die Kern-Sicherheitseigenschaften der Signaturpruefung. PASS. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
e4856dfc9d
commit
0ce29acf3e
@@ -0,0 +1,90 @@
|
||||
// Package saml implementiert Core IAM-11: SAML 2.0 als zusaetzlicher
|
||||
// Anmeldeweg neben OIDC (IAM-06).
|
||||
//
|
||||
// WICHTIGER HINWEIS: Die Signaturpruefung hier verifiziert eine RSA-SHA256-
|
||||
// Signatur ueber die deterministisch (re-)marshalte Assertion — das deckt
|
||||
// dieselbe Sicherheitseigenschaft ab wie XML-DSig (nur ein gueltig
|
||||
// signiertes Assertion eines vertrauten IdP wird akzeptiert), implementiert
|
||||
// aber NICHT die vollstaendige W3C-Exclusive-XML-Canonicalization (C14N) mit
|
||||
// all ihren Randfaellen (Namespace-Normalisierung, Attribut-Reihenfolge
|
||||
// etc.), die produktive SAML-Bibliotheken brauchen, um mit echten
|
||||
// Enterprise-IdPs (ADFS, Okta, Azure AD) byteidentisch zu sein. Das Ticket
|
||||
// erlaubt ausdruecklich einen "simulierten IdP" fuer die Pruefungen — dieser
|
||||
// Simulator und der Verifier hier verwenden folgerichtig dieselbe
|
||||
// deterministische Kodierung, echte Interop mit einem Drittprodukt-IdP ist
|
||||
// nicht Teil dieser Kachel.
|
||||
package saml
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
type attributeXML struct {
|
||||
Name string `xml:"Name,attr"`
|
||||
Values []string `xml:"AttributeValue"`
|
||||
}
|
||||
|
||||
type conditionsXML struct {
|
||||
NotBefore string `xml:"NotBefore,attr"`
|
||||
NotOnOrAfter string `xml:"NotOnOrAfter,attr"`
|
||||
}
|
||||
|
||||
// AssertionXML ist die (vereinfachte) SAML-Assertion.
|
||||
type AssertionXML struct {
|
||||
XMLName xml.Name `xml:"Assertion"`
|
||||
Issuer string `xml:"Issuer"`
|
||||
NameID string `xml:"Subject>NameID"`
|
||||
Conditions conditionsXML `xml:"Conditions"`
|
||||
Attributes []attributeXML `xml:"AttributeStatement>Attribute"`
|
||||
}
|
||||
|
||||
// ResponseXML umhuellt die Assertion mit der Signatur ueber deren kanonische
|
||||
// Bytes (siehe Paket-Dokumentation).
|
||||
type ResponseXML struct {
|
||||
XMLName xml.Name `xml:"Response"`
|
||||
Assertion AssertionXML `xml:"Assertion"`
|
||||
SignatureValue string `xml:"SignatureValue"` // base64
|
||||
}
|
||||
|
||||
// CanonicalAssertionBytes liefert die deterministischen Bytes, ueber die
|
||||
// signiert/verifiziert wird.
|
||||
func CanonicalAssertionBytes(a AssertionXML) ([]byte, error) {
|
||||
b, err := xml.Marshal(a)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("assertion serialisieren: %w", err)
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
|
||||
// Attribute liefert die Werte eines benannten SAML-Attributs (z.B. "roles"),
|
||||
// oder nil wenn nicht vorhanden.
|
||||
func (a AssertionXML) Attribute(name string) []string {
|
||||
for _, attr := range a.Attributes {
|
||||
if attr.Name == name {
|
||||
return attr.Values
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// WithinValidity prueft die Conditions/NotBefore/NotOnOrAfter der Assertion
|
||||
// gegen die aktuelle Zeit.
|
||||
func (a AssertionXML) WithinValidity(now time.Time) error {
|
||||
notBefore, err := time.Parse(time.RFC3339, a.Conditions.NotBefore)
|
||||
if err != nil {
|
||||
return fmt.Errorf("notbefore parsen: %w", err)
|
||||
}
|
||||
notOnOrAfter, err := time.Parse(time.RFC3339, a.Conditions.NotOnOrAfter)
|
||||
if err != nil {
|
||||
return fmt.Errorf("notonorafter parsen: %w", err)
|
||||
}
|
||||
if now.Before(notBefore) {
|
||||
return ErrNotYetValid
|
||||
}
|
||||
if !now.Before(notOnOrAfter) {
|
||||
return ErrExpired
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user