mirror of
https://github.com/pomerium/pomerium.git
synced 2025-08-06 10:21:05 +02:00
authenticate: add events (#4051)
This commit is contained in:
parent
b936b3653b
commit
0ab2057714
6 changed files with 145 additions and 3 deletions
|
@ -9,6 +9,7 @@ import (
|
||||||
type authenticateConfig struct {
|
type authenticateConfig struct {
|
||||||
getIdentityProvider func(options *config.Options, idpID string) (identity.Authenticator, error)
|
getIdentityProvider func(options *config.Options, idpID string) (identity.Authenticator, error)
|
||||||
profileTrimFn func(*identitypb.Profile)
|
profileTrimFn func(*identitypb.Profile)
|
||||||
|
authEventFn AuthEventFn
|
||||||
}
|
}
|
||||||
|
|
||||||
// An Option customizes the Authenticate config.
|
// An Option customizes the Authenticate config.
|
||||||
|
@ -36,3 +37,10 @@ func WithProfileTrimFn(profileTrimFn func(*identitypb.Profile)) Option {
|
||||||
cfg.profileTrimFn = profileTrimFn
|
cfg.profileTrimFn = profileTrimFn
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithOnAuthenticationEventHook sets the authEventFn function in the config
|
||||||
|
func WithOnAuthenticationEventHook(fn AuthEventFn) Option {
|
||||||
|
return func(cfg *authenticateConfig) {
|
||||||
|
cfg.authEventFn = fn
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
101
authenticate/events.go
Normal file
101
authenticate/events.go
Normal file
|
@ -0,0 +1,101 @@
|
||||||
|
package authenticate
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
|
"github.com/pomerium/pomerium/internal/httputil"
|
||||||
|
"github.com/pomerium/pomerium/internal/log"
|
||||||
|
"github.com/pomerium/pomerium/internal/urlutil"
|
||||||
|
"github.com/pomerium/pomerium/pkg/grpc/identity"
|
||||||
|
"github.com/pomerium/pomerium/pkg/hpke"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AuthEventKind is the type of an authentication event
|
||||||
|
type AuthEventKind string
|
||||||
|
|
||||||
|
const (
|
||||||
|
// AuthEventSignInRequest is an authentication event for a sign in request before IdP redirect
|
||||||
|
AuthEventSignInRequest AuthEventKind = "sign_in_request"
|
||||||
|
// AuthEventSignInComplete is an authentication event for a sign in request after IdP redirect
|
||||||
|
AuthEventSignInComplete AuthEventKind = "sign_in_complete"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AuthEvent is a log event for an authentication event
|
||||||
|
type AuthEvent struct {
|
||||||
|
// Event is the type of authentication event
|
||||||
|
Event AuthEventKind
|
||||||
|
// IP is the IP address of the client
|
||||||
|
IP string
|
||||||
|
// Version is the version of the Pomerium client
|
||||||
|
Version string
|
||||||
|
// RequestUUID is the UUID of the request
|
||||||
|
RequestUUID string
|
||||||
|
// PubKey is the public key of the client
|
||||||
|
PubKey string
|
||||||
|
// UID is the IdP user ID of the user
|
||||||
|
UID *string
|
||||||
|
// Email is the email of the user
|
||||||
|
Email *string
|
||||||
|
// Domain is the domain of the request (for sign in complete events)
|
||||||
|
Domain *string
|
||||||
|
}
|
||||||
|
|
||||||
|
// AuthEventFn is a function that handles an authentication event
|
||||||
|
type AuthEventFn func(context.Context, AuthEvent)
|
||||||
|
|
||||||
|
func (a *Authenticate) logAuthenticateEvent(r *http.Request, profile *identity.Profile) {
|
||||||
|
if a.cfg.authEventFn == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
state := a.state.Load()
|
||||||
|
ctx := r.Context()
|
||||||
|
pub, params, err := hpke.DecryptURLValues(state.hpkePrivateKey, r.Form)
|
||||||
|
if err != nil {
|
||||||
|
log.Warn(ctx).Err(err).Msg("log authenticate event: failed to decrypt request params")
|
||||||
|
}
|
||||||
|
|
||||||
|
evt := AuthEvent{
|
||||||
|
IP: httputil.GetClientIP(r),
|
||||||
|
Version: params.Get(urlutil.QueryVersion),
|
||||||
|
RequestUUID: params.Get(urlutil.QueryRequestUUID),
|
||||||
|
PubKey: pub.String(),
|
||||||
|
}
|
||||||
|
|
||||||
|
if uid := getUserClaim(profile, "sub"); uid != nil {
|
||||||
|
evt.UID = uid
|
||||||
|
}
|
||||||
|
if email := getUserClaim(profile, "email"); email != nil {
|
||||||
|
evt.Email = email
|
||||||
|
}
|
||||||
|
|
||||||
|
if evt.UID != nil {
|
||||||
|
evt.Event = AuthEventSignInComplete
|
||||||
|
} else {
|
||||||
|
evt.Event = AuthEventSignInRequest
|
||||||
|
}
|
||||||
|
|
||||||
|
if redirectURL, err := url.Parse(params.Get(urlutil.QueryRedirectURI)); err == nil {
|
||||||
|
domain := redirectURL.Hostname()
|
||||||
|
evt.Domain = &domain
|
||||||
|
}
|
||||||
|
|
||||||
|
a.cfg.authEventFn(ctx, evt)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getUserClaim(profile *identity.Profile, field string) *string {
|
||||||
|
if profile == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if profile.Claims == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
val, ok := profile.Claims.Fields[field]
|
||||||
|
if !ok || val == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
txt := val.GetStringValue()
|
||||||
|
return &txt
|
||||||
|
}
|
|
@ -216,6 +216,8 @@ func (a *Authenticate) SignIn(w http.ResponseWriter, r *http.Request) error {
|
||||||
a.cfg.profileTrimFn(profile)
|
a.cfg.profileTrimFn(profile)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
a.logAuthenticateEvent(r, profile)
|
||||||
|
|
||||||
redirectTo, err := urlutil.CallbackURL(state.hpkePrivateKey, proxyPublicKey, requestParams, profile)
|
redirectTo, err := urlutil.CallbackURL(state.hpkePrivateKey, proxyPublicKey, requestParams, profile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return httputil.NewError(http.StatusInternalServerError, err)
|
return httputil.NewError(http.StatusInternalServerError, err)
|
||||||
|
@ -315,6 +317,8 @@ func (a *Authenticate) reauthenticateOrFail(w http.ResponseWriter, r *http.Reque
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
a.logAuthenticateEvent(r, nil)
|
||||||
|
|
||||||
state.sessionStore.ClearSession(w, r)
|
state.sessionStore.ClearSession(w, r)
|
||||||
redirectURL := state.redirectURL.ResolveReference(r.URL)
|
redirectURL := state.redirectURL.ResolveReference(r.URL)
|
||||||
nonce := csrf.Token(r)
|
nonce := csrf.Token(r)
|
||||||
|
|
14
internal/httputil/ip.go
Normal file
14
internal/httputil/ip.go
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
package httputil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetClientIP returns the client IP address from the request.
|
||||||
|
func GetClientIP(r *http.Request) string {
|
||||||
|
if clientIP := r.Header.Get("X-Forwarded-For"); clientIP != "" {
|
||||||
|
return strings.Split(clientIP, ",")[0]
|
||||||
|
}
|
||||||
|
return strings.Split(r.RemoteAddr, ":")[0]
|
||||||
|
}
|
|
@ -4,8 +4,12 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
"google.golang.org/protobuf/encoding/protojson"
|
"google.golang.org/protobuf/encoding/protojson"
|
||||||
|
|
||||||
"github.com/pomerium/pomerium/internal/version"
|
"github.com/pomerium/pomerium/internal/version"
|
||||||
|
@ -21,6 +25,15 @@ const DefaultDeviceType = "any"
|
||||||
|
|
||||||
const signInExpiry = time.Minute * 5
|
const signInExpiry = time.Minute * 5
|
||||||
|
|
||||||
|
var (
|
||||||
|
pomeriumRuntime = os.Getenv("POMERIUM_RUNTIME")
|
||||||
|
pomeriumArch = fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH)
|
||||||
|
)
|
||||||
|
|
||||||
|
func versionStr() string {
|
||||||
|
return strings.Join([]string{version.FullVersion(), pomeriumArch, pomeriumRuntime}, " ")
|
||||||
|
}
|
||||||
|
|
||||||
// CallbackURL builds the callback URL using an HPKE encrypted query string.
|
// CallbackURL builds the callback URL using an HPKE encrypted query string.
|
||||||
func CallbackURL(
|
func CallbackURL(
|
||||||
authenticatePrivateKey *hpke.PrivateKey,
|
authenticatePrivateKey *hpke.PrivateKey,
|
||||||
|
@ -59,7 +72,7 @@ func CallbackURL(
|
||||||
return "", fmt.Errorf("error marshaling identity profile: %w", err)
|
return "", fmt.Errorf("error marshaling identity profile: %w", err)
|
||||||
}
|
}
|
||||||
callbackParams.Set(QueryIdentityProfile, string(rawProfile))
|
callbackParams.Set(QueryIdentityProfile, string(rawProfile))
|
||||||
callbackParams.Set(QueryVersion, version.FullVersion())
|
callbackParams.Set(QueryVersion, versionStr())
|
||||||
|
|
||||||
BuildTimeParameters(callbackParams, signInExpiry)
|
BuildTimeParameters(callbackParams, signInExpiry)
|
||||||
|
|
||||||
|
@ -99,7 +112,8 @@ func SignInURL(
|
||||||
q := signInURL.Query()
|
q := signInURL.Query()
|
||||||
q.Set(QueryRedirectURI, redirectURL.String())
|
q.Set(QueryRedirectURI, redirectURL.String())
|
||||||
q.Set(QueryIdentityProviderID, idpID)
|
q.Set(QueryIdentityProviderID, idpID)
|
||||||
q.Set(QueryVersion, version.FullVersion())
|
q.Set(QueryVersion, versionStr())
|
||||||
|
q.Set(QueryRequestUUID, uuid.NewString())
|
||||||
BuildTimeParameters(q, signInExpiry)
|
BuildTimeParameters(q, signInExpiry)
|
||||||
q, err := hpke.EncryptURLValues(senderPrivateKey, authenticatePublicKey, q)
|
q, err := hpke.EncryptURLValues(senderPrivateKey, authenticatePublicKey, q)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -119,7 +133,7 @@ func SignOutURL(r *http.Request, authenticateURL *url.URL, key []byte) string {
|
||||||
if redirectURI, ok := RedirectURL(r); ok {
|
if redirectURI, ok := RedirectURL(r); ok {
|
||||||
q.Set(QueryRedirectURI, redirectURI)
|
q.Set(QueryRedirectURI, redirectURI)
|
||||||
}
|
}
|
||||||
q.Set(QueryVersion, version.FullVersion())
|
q.Set(QueryVersion, versionStr())
|
||||||
u.RawQuery = q.Encode()
|
u.RawQuery = q.Encode()
|
||||||
return NewSignedURL(key, u).Sign().String()
|
return NewSignedURL(key, u).Sign().String()
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,7 @@ const (
|
||||||
QuerySessionEncrypted = "pomerium_session_encrypted"
|
QuerySessionEncrypted = "pomerium_session_encrypted"
|
||||||
QuerySessionState = "pomerium_session_state"
|
QuerySessionState = "pomerium_session_state"
|
||||||
QueryVersion = "pomerium_version"
|
QueryVersion = "pomerium_version"
|
||||||
|
QueryRequestUUID = "pomerium_request_uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
// URL signature based query params used for verifying the authenticity of a URL.
|
// URL signature based query params used for verifying the authenticity of a URL.
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue