Compare commits

..

2 Commits

3 changed files with 123 additions and 14 deletions
+36
View File
@@ -1,6 +1,8 @@
package db package db
import "database/sql" import "database/sql"
import "errors"
import "fmt"
import "strings" import "strings"
import "time" import "time"
@@ -55,11 +57,41 @@ func (s *Store) GetTLSListener(id string) (models.TLSListener, error) {
return item, nil return item, nil
} }
func (s *Store) validateTLSListenerPKIRefs(item models.TLSListener) error {
var err error
if strings.TrimSpace(item.TLSPKIServerCertID) != "" {
_, err = s.GetPKICert(item.TLSPKIServerCertID)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return fmt.Errorf("selected TLS PKI server certificate not found")
} else {
return fmt.Errorf("error looking up TLS PKI server certificate - %s", err.Error())
}
}
}
if strings.TrimSpace(item.TLSPKIClientCAID) != "" {
_, err = s.GetPKICA(item.TLSPKIClientCAID)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return fmt.Errorf("selected TLS PKI client CA not found")
} else {
return fmt.Errorf("error looking up TLS PKI client CA - %s", err.Error())
}
}
}
return nil
}
func (s *Store) CreateTLSListener(item models.TLSListener) (models.TLSListener, error) { func (s *Store) CreateTLSListener(item models.TLSListener) (models.TLSListener, error) {
var id string var id string
var now int64 var now int64
var err error var err error
var endpointPoliciesJSON string var endpointPoliciesJSON string
err = s.validateTLSListenerPKIRefs(item)
if err != nil {
return item, err
}
if item.ID == "" { if item.ID == "" {
id, err = util.NewID() id, err = util.NewID()
if err != nil { if err != nil {
@@ -86,6 +118,10 @@ func (s *Store) UpdateTLSListener(item models.TLSListener) error {
var err error var err error
var now int64 var now int64
var endpointPoliciesJSON string var endpointPoliciesJSON string
err = s.validateTLSListenerPKIRefs(item)
if err != nil {
return err
}
now = time.Now().UTC().Unix() now = time.Now().UTC().Unix()
item.UpdatedAt = now item.UpdatedAt = now
endpointPoliciesJSON, err = encodeTLSEndpointPolicies(item.EndpointPolicies) endpointPoliciesJSON, err = encodeTLSEndpointPolicies(item.EndpointPolicies)
+38 -10
View File
@@ -1042,8 +1042,9 @@ func (api *API) UpdateTLSSettings(w http.ResponseWriter, r *http.Request, _ map[
WriteJSONWithErrorReason(w, r, http.StatusBadRequest, err.Error()) WriteJSONWithErrorReason(w, r, http.StatusBadRequest, err.Error())
return return
} }
if tlsClientAuthNeedsCA(settings.TLSClientAuth) && !tlsClientCAConfigured(settings.TLSPKIClientCAID) { err = validateTLSPKIClientCA(api.store(r), settings.TLSPKIClientCAID, tlsClientAuthNeedsCA(settings.TLSClientAuth))
WriteJSONWithErrorReason(w, r, http.StatusBadRequest, "tls_pki_client_ca_id is required for selected tls_client_auth") if err != nil {
WriteJSONWithErrorReason(w, r, http.StatusBadRequest, err.Error())
return return
} }
err = validateTLSEndpointPolicies(api.store(r), settings.EndpointPolicies) err = validateTLSEndpointPolicies(api.store(r), settings.EndpointPolicies)
@@ -1204,8 +1205,9 @@ func (api *API) CreateTLSListener(w http.ResponseWriter, r *http.Request, _ map[
WriteJSONWithErrorReason(w, r, http.StatusBadRequest, err.Error()) WriteJSONWithErrorReason(w, r, http.StatusBadRequest, err.Error())
return return
} }
if tlsClientAuthNeedsCA(item.TLSClientAuth) && !tlsClientCAConfigured(item.TLSPKIClientCAID) { err = validateTLSPKIClientCA(api.store(r), item.TLSPKIClientCAID, tlsClientAuthNeedsCA(item.TLSClientAuth))
WriteJSONWithErrorReason(w, r, http.StatusBadRequest, "tls_pki_client_ca_id is required for selected tls_client_auth") if err != nil {
WriteJSONWithErrorReason(w, r, http.StatusBadRequest, err.Error())
return return
} }
err = validateTLSEndpointPolicies(api.store(r), item.EndpointPolicies) err = validateTLSEndpointPolicies(api.store(r), item.EndpointPolicies)
@@ -1264,8 +1266,9 @@ func (api *API) UpdateTLSListener(w http.ResponseWriter, r *http.Request, params
WriteJSONWithErrorReason(w, r, http.StatusBadRequest, err.Error()) WriteJSONWithErrorReason(w, r, http.StatusBadRequest, err.Error())
return return
} }
if tlsClientAuthNeedsCA(item.TLSClientAuth) && !tlsClientCAConfigured(item.TLSPKIClientCAID) { err = validateTLSPKIClientCA(api.store(r), item.TLSPKIClientCAID, tlsClientAuthNeedsCA(item.TLSClientAuth))
WriteJSONWithErrorReason(w, r, http.StatusBadRequest, "tls_pki_client_ca_id is required for selected tls_client_auth") if err != nil {
WriteJSONWithErrorReason(w, r, http.StatusBadRequest, err.Error())
return return
} }
err = validateTLSEndpointPolicies(api.store(r), item.EndpointPolicies) err = validateTLSEndpointPolicies(api.store(r), item.EndpointPolicies)
@@ -5540,10 +5543,6 @@ func tlsClientAuthNeedsCA(value string) bool {
return v == "require_and_verify" || v == "verify_if_given" return v == "require_and_verify" || v == "verify_if_given"
} }
func tlsClientCAConfigured(pkiCAID string) bool {
return strings.TrimSpace(pkiCAID) != ""
}
func validateTLSServerCertificateFields(store *db.Store, source string, certFile string, keyFile string, certID string, required bool) error { func validateTLSServerCertificateFields(store *db.Store, source string, certFile string, keyFile string, certID string, required bool) error {
var err error var err error
source = normalizeTLSServerCertSource(source) source = normalizeTLSServerCertSource(source)
@@ -5596,6 +5595,35 @@ func validateTLSPKIServerCert(store *db.Store, certID string, required bool) err
return nil return nil
} }
func validateTLSPKIClientCA(store *db.Store, caID string, required bool) error {
var ca models.PKICA
var parsed *x509.Certificate
var err error
caID = strings.TrimSpace(caID)
if caID == "" {
if required {
return errors.New("tls_pki_client_ca_id is required for selected tls_client_auth")
}
return nil
}
ca, err = store.GetPKICA(caID)
if err != nil {
return errors.New("selected TLS PKI client CA not found")
}
if ca.Status != "active" {
return errors.New("selected TLS PKI client CA is not active")
}
parsed, err = parseCertificateFromPEM(ca.CertPEM)
if err != nil {
return errors.New("selected TLS PKI client CA certificate is invalid")
}
if !parsed.IsCA {
return errors.New("selected TLS PKI client CA certificate is not a CA")
}
return nil
}
func normalizeTLSListenerRequest(req tlsListenerRequest) models.TLSListener { func normalizeTLSListenerRequest(req tlsListenerRequest) models.TLSListener {
var item models.TLSListener var item models.TLSListener
var endpointPolicies []models.TLSEndpointPolicy var endpointPolicies []models.TLSEndpointPolicy
+49 -4
View File
@@ -37,6 +37,12 @@ type oidcErrorResponse struct {
ErrorDescription string `json:"error_description"` ErrorDescription string `json:"error_description"`
} }
type oidcIDTokenStandardClaims struct {
Iss string `json:"iss"`
Aud json.RawMessage `json:"aud"` // string or []string per OIDC spec
Exp int64 `json:"exp"`
}
func (api *API) ListPublicAuthProviders(w http.ResponseWriter, r *http.Request, _ map[string]string) { func (api *API) ListPublicAuthProviders(w http.ResponseWriter, r *http.Request, _ map[string]string) {
var providers []models.AuthProvider var providers []models.AuthProvider
var err error var err error
@@ -303,14 +309,17 @@ func (api *API) oidcResolveClaimsFromProvider(ctx context.Context, p models.Auth
var err error var err error
if strings.TrimSpace(p.OIDCUserInfoURL) != "" && strings.TrimSpace(token.AccessToken) != "" { if strings.TrimSpace(p.OIDCUserInfoURL) != "" && strings.TrimSpace(token.AccessToken) != "" {
claims, err = api.oidcUserInfoFromProvider(ctx, p, token.AccessToken) claims, err = api.oidcUserInfoFromProvider(ctx, p, token.AccessToken)
if err == nil { if err != nil {
return claims, nil // userinfo URL is configured but failed - hard error, do not fall back to
// the unverified ID token path.
return claims, fmt.Errorf("oidc userinfo failed: %v", err)
} }
return claims, nil
} }
if strings.TrimSpace(token.IDToken) == "" { if strings.TrimSpace(token.IDToken) == "" {
return claims, errors.New("missing id token and userinfo unavailable") return claims, errors.New("missing id token and userinfo unavailable")
} }
claims, err = oidcClaimsFromIDToken(token.IDToken) claims, err = oidcClaimsFromIDToken(token.IDToken, p.OIDCClientID)
if err != nil { if err != nil {
return claims, err return claims, err
} }
@@ -393,11 +402,31 @@ func (api *API) oidcGetOrCreateUser(ctx context.Context, claims oidcUserClaims,
return created, nil return created, nil
} }
func oidcClaimsFromIDToken(idToken string) (oidcUserClaims, error) { func oidcIDTokenAudienceContains(aud json.RawMessage, clientID string) bool {
var single string
var multiple []string
var i int
if json.Unmarshal(aud, &single) == nil {
return single == clientID
}
if json.Unmarshal(aud, &multiple) == nil {
for i = 0; i < len(multiple); i++ {
if multiple[i] == clientID {
return true
}
}
}
return false
}
func oidcClaimsFromIDToken(idToken string, clientID string) (oidcUserClaims, error) {
var claims oidcUserClaims var claims oidcUserClaims
var std oidcIDTokenStandardClaims
var parts []string var parts []string
var payload []byte var payload []byte
var err error var err error
parts = strings.Split(idToken, ".") parts = strings.Split(idToken, ".")
if len(parts) < 2 { if len(parts) < 2 {
return claims, errors.New("invalid id token") return claims, errors.New("invalid id token")
@@ -406,6 +435,22 @@ func oidcClaimsFromIDToken(idToken string) (oidcUserClaims, error) {
if err != nil { if err != nil {
return claims, err return claims, err
} }
err = json.Unmarshal(payload, &std)
if err != nil {
return claims, err
}
if strings.TrimSpace(std.Iss) == "" {
return claims, errors.New("id token missing iss")
}
if std.Exp == 0 {
return claims, errors.New("id token missing exp")
}
if time.Now().Unix() > std.Exp {
return claims, errors.New("id token expired")
}
if strings.TrimSpace(clientID) != "" && !oidcIDTokenAudienceContains(std.Aud, clientID) {
return claims, errors.New("id token audience mismatch")
}
err = json.Unmarshal(payload, &claims) err = json.Unmarshal(payload, &claims)
if err != nil { if err != nil {
return claims, err return claims, err