diff --git a/authenticate/handlers.go b/authenticate/handlers.go index 5ea9c5c07..9b0aba49c 100644 --- a/authenticate/handlers.go +++ b/authenticate/handlers.go @@ -216,6 +216,8 @@ func (a *Authenticate) SignIn(w http.ResponseWriter, r *http.Request) error { a.cfg.profileTrimFn(profile) } + a.logAuthenticateEvent(r, profile) + redirectTo, err := urlutil.CallbackURL(state.hpkePrivateKey, proxyPublicKey, requestParams, profile) if err != nil { return httputil.NewError(http.StatusInternalServerError, err) @@ -315,6 +317,8 @@ func (a *Authenticate) reauthenticateOrFail(w http.ResponseWriter, r *http.Reque return err } + a.logAuthenticateEvent(r, nil) + state.sessionStore.ClearSession(w, r) redirectURL := state.redirectURL.ResolveReference(r.URL) nonce := csrf.Token(r) diff --git a/authenticate/middleware.go b/authenticate/middleware.go index 86cc3cbdb..e7e4c95b8 100644 --- a/authenticate/middleware.go +++ b/authenticate/middleware.go @@ -2,10 +2,14 @@ package authenticate import ( "net/http" + "net/url" "github.com/pomerium/pomerium/internal/httputil" + "github.com/pomerium/pomerium/internal/log" "github.com/pomerium/pomerium/internal/middleware" "github.com/pomerium/pomerium/internal/urlutil" + "github.com/pomerium/pomerium/pkg/grpc/identity" + "github.com/pomerium/pomerium/pkg/hpke" ) // requireValidSignatureOnRedirect validates the pomerium_signature if a redirect_uri or pomerium_signature @@ -48,3 +52,40 @@ func (a *Authenticate) getExternalRequest(r *http.Request) *http.Request { return urlutil.GetExternalRequest(internalURL, externalURL, r) } + +func (a *Authenticate) logAuthenticateEvent(r *http.Request, profile *identity.Profile) { + 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 := log.Info(ctx). + Str("pomerium_version", params.Get(urlutil.QueryVersion)). + Str("pomerium_request_uuid", params.Get(urlutil.QueryRequestUUID)). + Str("pomerium_pub", pub.String()) + + if uid := getUserID(profile); uid != "" { + evt = evt.Str("authenticate_event", "sign_in_completed"). + Str("pomerium_idp_user", getUserID(profile)) + } else { + evt = evt.Str("authenticate_event", "sign_in") + } + + if redirectURL, err := url.Parse(params.Get(urlutil.QueryRedirectURI)); err == nil { + evt = evt.Str("domain", redirectURL.Hostname()) + } + + evt.Msg("authenticate: event") +} + +func getUserID(profile *identity.Profile) string { + if profile == nil { + return "" + } + if profile.Claims == nil { + return "" + } + return profile.Claims.Fields["sub"].GetStringValue() +} diff --git a/internal/urlutil/known.go b/internal/urlutil/known.go index e7833e098..20d1cd03d 100644 --- a/internal/urlutil/known.go +++ b/internal/urlutil/known.go @@ -4,8 +4,12 @@ import ( "fmt" "net/http" "net/url" + "os" + "runtime" + "strings" "time" + "github.com/google/uuid" "google.golang.org/protobuf/encoding/protojson" "github.com/pomerium/pomerium/internal/version" @@ -21,6 +25,15 @@ const DefaultDeviceType = "any" 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. func CallbackURL( authenticatePrivateKey *hpke.PrivateKey, @@ -59,7 +72,7 @@ func CallbackURL( return "", fmt.Errorf("error marshaling identity profile: %w", err) } callbackParams.Set(QueryIdentityProfile, string(rawProfile)) - callbackParams.Set(QueryVersion, version.FullVersion()) + callbackParams.Set(QueryVersion, versionStr()) BuildTimeParameters(callbackParams, signInExpiry) @@ -99,7 +112,8 @@ func SignInURL( q := signInURL.Query() q.Set(QueryRedirectURI, redirectURL.String()) q.Set(QueryIdentityProviderID, idpID) - q.Set(QueryVersion, version.FullVersion()) + q.Set(QueryVersion, versionStr()) + q.Set(QueryRequestUUID, uuid.NewString()) BuildTimeParameters(q, signInExpiry) q, err := hpke.EncryptURLValues(senderPrivateKey, authenticatePublicKey, q) if err != nil { @@ -119,7 +133,7 @@ func SignOutURL(r *http.Request, authenticateURL *url.URL, key []byte) string { if redirectURI, ok := RedirectURL(r); ok { q.Set(QueryRedirectURI, redirectURI) } - q.Set(QueryVersion, version.FullVersion()) + q.Set(QueryVersion, versionStr()) u.RawQuery = q.Encode() return NewSignedURL(key, u).Sign().String() } diff --git a/internal/urlutil/query_params.go b/internal/urlutil/query_params.go index 15a0c850a..ac5a127bc 100644 --- a/internal/urlutil/query_params.go +++ b/internal/urlutil/query_params.go @@ -19,6 +19,7 @@ const ( QuerySessionEncrypted = "pomerium_session_encrypted" QuerySessionState = "pomerium_session_state" QueryVersion = "pomerium_version" + QueryRequestUUID = "pomerium_request_uuid" ) // URL signature based query params used for verifying the authenticity of a URL.