Compare commits
2 Commits
af562e17bc
...
f536496069
| Author | SHA1 | Date | |
|---|---|---|---|
| f536496069 | |||
| 467b4b4452 |
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user