Enhance audit logging with extra data (#70)

<!--- Provide a general summary of your changes in the Title above -->

## Description

Enhance audit logging by including request details and IP addresses in
audit entries

## Motivation and Context

<!--- Why is this change required? What problem does it solve? -->
<!--- If it fixes an open issue, please link to the issue here. -->

## How Has This Been Tested?

<!--- Please describe in detail how you tested your changes. -->
<!--- Include details of your testing environment, and the tests you ran
to -->
<!--- see how your change affects other areas of the code, etc. -->

## Checklist:

<!--- Go over all the following points, and put an `x` in all the boxes
that apply. -->
<!--- If you're unsure about any of these, don't hesitate to ask. We're
here to help! -->

- [ ] My change requires a change to the documentation or CHANGELOG.
- [ ] I have updated the documentation/CHANGELOG accordingly.
- [ ] I have created a feature (non-master) branch for my PR.
This commit is contained in:
Danilo Mendes Rocha 2025-07-07 18:13:23 -03:00 committed by GitHub
commit 6f468da9e7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 52 additions and 18 deletions

View File

@ -827,14 +827,14 @@ func (p *OAuthProxy) backendLogout(rw http.ResponseWriter, req *http.Request, si
if resp.StatusCode() != 200 { if resp.StatusCode() != 200 {
logger.Errorf("error while calling backend logout url, returned error code %v", resp.StatusCode()) logger.Errorf("error while calling backend logout url, returned error code %v", resp.StatusCode())
} }
p.picsAuditClient.CreateSuccessfulLogoutAuditEntry(session, req.RequestURI, req.Header.Get("edisp-org-id")) p.picsAuditClient.CreateSuccessfulLogoutAuditEntry(session, req.RequestURI, req.Header.Get("edisp-org-id"), req)
} else { } else {
if providerData.BackendRevokeAccessTokenURL != "" { if providerData.BackendRevokeAccessTokenURL != "" {
err := PicsRevokeAccessToken(providerData.BackendRevokeAccessTokenURL, session.AccessToken, providerData.ClientID, providerData.ClientSecret) err := PicsRevokeAccessToken(providerData.BackendRevokeAccessTokenURL, session.AccessToken, providerData.ClientID, providerData.ClientSecret)
if err != nil { if err != nil {
logger.Errorf("error while calling backend revoke access token: %v", err) logger.Errorf("error while calling backend revoke access token: %v", err)
} else { } else {
p.picsAuditClient.CreateSuccessfulRevokeAccessTokenAuditEntry(session, req.RequestURI, req.Header.Get("edisp-org-id")) p.picsAuditClient.CreateSuccessfulRevokeAccessTokenAuditEntry(session, req.RequestURI, req.Header.Get("edisp-org-id"), req)
} }
} }
@ -1010,7 +1010,7 @@ func (p *OAuthProxy) OAuthCallback(rw http.ResponseWriter, req *http.Request) {
p.ErrorPage(rw, req, http.StatusInternalServerError, err.Error()) p.ErrorPage(rw, req, http.StatusInternalServerError, err.Error())
return return
} }
p.picsAuditClient.CreateSuccessfulLoginAuditEntry(session, appRedirect, req.Header.Get("edisp-org-id")) p.picsAuditClient.CreateSuccessfulLoginAuditEntry(session, appRedirect, req.Header.Get("edisp-org-id"), req)
http.Redirect(rw, req, appRedirect, http.StatusFound) http.Redirect(rw, req, appRedirect, http.StatusFound)
} else { } else {
logger.PrintAuthf(session.Email, req, logger.AuthFailure, "Invalid authentication via OAuth2: unauthorized") logger.PrintAuthf(session.Email, req, logger.AuthFailure, "Invalid authentication via OAuth2: unauthorized")

View File

@ -5,6 +5,9 @@ import (
"errors" "errors"
"fmt" "fmt"
"log" "log"
"net"
"net/http"
"os"
"strings" "strings"
"time" "time"
@ -52,34 +55,60 @@ func NewAuditClient(opts *ClientOpts) (*Client, error) {
return &Client{enabled: opts.Enabled, apiSignature: apiSignature, client: client, opts: opts}, nil return &Client{enabled: opts.Enabled, apiSignature: apiSignature, client: client, opts: opts}, nil
} }
func (c *Client) CreateSuccessfulLoginAuditEntry(ss *sessions.SessionState, appURL string, tenantID string) { func (c *Client) CreateSuccessfulLoginAuditEntry(ss *sessions.SessionState, appURL string, tenantID string, req *http.Request) {
coding := Coding{ coding := Coding{
System: "http://hl7.org/fhir/ValueSet/audit-event-type", Version: "1", Code: "110114", Display: "User Authentication"} System: "http://hl7.org/fhir/ValueSet/audit-event-type", Version: "1", Code: "110114", Display: "User Authentication"}
c.createAuditEntry(ss, appURL, tenantID, "0", "Success", &coding) c.createAuditEntry(ss, appURL, tenantID, "0", "Success", &coding, req)
} }
func (c *Client) CreateFailedLoginAuditEntry(ss *sessions.SessionState, appURL string, tenantID string, errorDesc string) { func (c *Client) CreateFailedLoginAuditEntry(ss *sessions.SessionState, appURL string, tenantID string, errorDesc string, req *http.Request) {
coding := Coding{ coding := Coding{
System: "http://hl7.org/fhir/ValueSet/audit-event-type", Version: "1", Code: "110114", Display: "User Authentication"} System: "http://hl7.org/fhir/ValueSet/audit-event-type", Version: "1", Code: "110114", Display: "User Authentication"}
c.createAuditEntry(ss, appURL, tenantID, "1", errorDesc, &coding) c.createAuditEntry(ss, appURL, tenantID, "1", errorDesc, &coding, req)
} }
func (c *Client) CreateSuccessfulLogoutAuditEntry(ss *sessions.SessionState, appURL string, tenantID string) { func (c *Client) CreateSuccessfulLogoutAuditEntry(ss *sessions.SessionState, appURL string, tenantID string, req *http.Request) {
coding := Coding{ coding := Coding{
System: "http://hl7.org/fhir/ValueSet/audit-event-type", Version: "1", Code: "110123", Display: "User Logout All Sessions"} System: "http://hl7.org/fhir/ValueSet/audit-event-type", Version: "1", Code: "110123", Display: "User Logout All Sessions"}
c.createAuditEntry(ss, appURL, tenantID, "0", "Success", &coding) c.createAuditEntry(ss, appURL, tenantID, "0", "Success", &coding, req)
} }
func (c *Client) CreateSuccessfulRevokeAccessTokenAuditEntry(ss *sessions.SessionState, appURL string, tenantID string) { func (c *Client) CreateSuccessfulRevokeAccessTokenAuditEntry(ss *sessions.SessionState, appURL string, tenantID string, req *http.Request) {
coding := Coding{ coding := Coding{
System: "http://hl7.org/fhir/ValueSet/audit-event-type", Version: "1", Code: "110123", Display: "User revoked access token"} System: "http://hl7.org/fhir/ValueSet/audit-event-type", Version: "1", Code: "110123", Display: "User revoked access token"}
c.createAuditEntry(ss, appURL, tenantID, "0", "Success", &coding) c.createAuditEntry(ss, appURL, tenantID, "0", "Success", &coding, req)
} }
func (c *Client) createAuditEntry(ss *sessions.SessionState, appURL string, tenantID string, outcomeCode string, outcomeDesc string, coding *Coding) { func (c *Client) createAuditEntry(ss *sessions.SessionState, appURL string, tenantID string, outcomeCode string, outcomeDesc string, coding *Coding, req *http.Request) {
if !c.enabled { if !c.enabled {
return return
} }
// Service/component identifier
serviceIDs := []string{"oauth2proxy"}
var targetIP string
if hn, err := os.Hostname(); err == nil {
serviceIDs = append(serviceIDs, hn)
addrs, err := net.LookupHost(hn)
if err == nil && len(addrs) > 0 {
targetIP = addrs[0]
} else {
targetIP = hn
}
} else {
targetIP = "unknown"
}
// Extract source IP address
sourceIP := ""
if req != nil {
sourceIP = req.RemoteAddr
if xff := req.Header.Get("X-Forwarded-For"); xff != "" {
ips := strings.Split(xff, ",")
sourceIP = strings.TrimSpace(ips[0])
}
}
auditObject := RootEvent{ auditObject := RootEvent{
ResourceType: "AuditEvent", ResourceType: "AuditEvent",
Event: &Event{ Event: &Event{
@ -88,7 +117,6 @@ func (c *Client) createAuditEntry(ss *sessions.SessionState, appURL string, tena
DateTime: time.Now().UTC().Format(time.RFC3339), DateTime: time.Now().UTC().Format(time.RFC3339),
Outcome: outcomeCode, Outcome: outcomeCode,
OutcomeDesc: outcomeDesc}, OutcomeDesc: outcomeDesc},
Participant: []*Participant{ Participant: []*Participant{
{AltID: ss.User, UserID: UserID{Value: ss.Email}, Name: ss.PreferredUsername, Requestor: true}}, {AltID: ss.User, UserID: UserID{Value: ss.Email}, Name: ss.PreferredUsername, Requestor: true}},
Source: Source{ Source: Source{
@ -133,6 +161,9 @@ func (c *Client) createAuditEntry(ss *sessions.SessionState, appURL string, tena
}, },
}, },
}, },
ServiceIDs: serviceIDs,
SourceIPAddress: sourceIP,
TargetIPAddress: targetIP,
} }
auditMessage, err := json.Marshal(auditObject) auditMessage, err := json.Marshal(auditObject)

View File

@ -115,4 +115,7 @@ type RootEvent struct {
Participant []*Participant `json:"participant,omitempty"` Participant []*Participant `json:"participant,omitempty"`
Source Source `json:"source,omitempty"` Source Source `json:"source,omitempty"`
Object []*Object `json:"object,omitempty"` Object []*Object `json:"object,omitempty"`
ServiceIDs []string `json:"service_ids,omitempty"`
SourceIPAddress string `json:"source_ip_address,omitempty"`
TargetIPAddress string `json:"target_ip_address,omitempty"`
} }