forked from oauth2-proxy/oauth2-proxy
-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
f7de8bb
commit 85b4dca
Showing
6 changed files
with
350 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,160 @@ | ||
package audit | ||
|
||
import ( | ||
"encoding/json" | ||
"errors" | ||
"fmt" | ||
"log" | ||
"strings" | ||
"time" | ||
|
||
"github.com/go-resty/resty/v2" | ||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions" | ||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger" | ||
) | ||
|
||
type AuditClientOpts struct { | ||
Enabled bool | ||
Url string | ||
ProductKey string | ||
ProductName string | ||
SharedKey string | ||
SecretKey string | ||
} | ||
|
||
// AuditClient interface for communicating with audit system | ||
type AuditClient struct { | ||
enabled bool | ||
apiSignature APISignature | ||
opts *AuditClientOpts | ||
client *resty.Client | ||
} | ||
|
||
func NewAuditClient(opts *AuditClientOpts) (*AuditClient, error) { | ||
if opts.Enabled { | ||
log.Print("Audit entries will be created since OAUTH2_PROXY_ENABLE_AUDIT is true") | ||
err := opts.Validate() | ||
if err != nil { | ||
return nil, err | ||
} | ||
} else { | ||
log.Print("Audit entries will NOT be created since OAUTH2_PROXY_ENABLE_AUDIT is false") | ||
} | ||
apiSignature := NewAPISignature(opts.SecretKey, opts.SharedKey) | ||
client := resty.New() | ||
client.SetRetryCount(3). | ||
SetRetryWaitTime(5 * time.Second). | ||
SetRetryMaxWaitTime(20 * time.Second). | ||
SetContentLength(true). | ||
SetRetryAfter(func(client *resty.Client, resp *resty.Response) (time.Duration, error) { | ||
return 0, fmt.Errorf("%w: retry quota exceeded", ErrPersitAuditEvent) | ||
}) | ||
return &AuditClient{enabled: opts.Enabled, apiSignature: apiSignature, client: client, opts: opts}, nil | ||
} | ||
|
||
func (a *AuditClient) CreateSuccessfulLoginAuditEntry(ss *sessions.SessionState, appUrl string, tenantId string) { | ||
a.createAuditEntry(ss, appUrl, tenantId, "0", "Success") | ||
} | ||
|
||
func (a *AuditClient) CreateFailedLoginAuditEntry(ss *sessions.SessionState, appUrl string, tenantId string, errorDesc string) { | ||
a.createAuditEntry(ss, appUrl, tenantId, "1", errorDesc) | ||
} | ||
|
||
func (a *AuditClient) createAuditEntry(ss *sessions.SessionState, appUrl string, tenantId string, outcomeCode string, outcomeDesc string) { | ||
if !a.enabled { | ||
return | ||
} | ||
auditObject := AuditEvent{ | ||
ResourceType: "AuditEvent", | ||
Event: &Event{ | ||
Type: &Coding{ | ||
System: "http://hl7.org/fhir/ValueSet/audit-event-type", Version: "1", Code: "110114", Display: "User Authentication"}, | ||
Action: "E", | ||
DateTime: time.Now().UTC().Format(time.RFC3339), | ||
Outcome: outcomeCode, | ||
OutcomeDesc: outcomeDesc}, | ||
|
||
Participant: []*Participant{ | ||
{AltID: ss.User, UserID: UserID{Value: ss.Email}, Name: ss.PreferredUsername, Requestor: true}}, | ||
Source: Source{ | ||
Identifier: Identifier{ | ||
Type: &Coding{ | ||
System: "http://hl7.org/fhir/ValueSet/identifier-type", | ||
Code: "4", | ||
Display: "Application Server", | ||
}, | ||
Value: ss.Email, | ||
}, | ||
Type: []*Coding{{System: "http://hl7.org/fhir/security-source-type", Code: "1", Display: "End-user display device, diagnostic device."}}, | ||
Extension: []*Extension{ | ||
{ | ||
URL: appUrl, | ||
Extension: []*ExtensionContent{ | ||
{ | ||
URL: "applicationName", | ||
ValueString: a.opts.ProductName, | ||
}, | ||
{ | ||
URL: "applicationVersion", | ||
ValueString: "1", | ||
}, | ||
{ | ||
URL: "serverName", | ||
ValueString: "oauth2proxy", | ||
}, | ||
{ | ||
URL: "componentName", | ||
ValueString: "oauth2proxy", | ||
}, | ||
{ | ||
URL: "productKey", | ||
ValueString: a.opts.ProductKey, | ||
}, | ||
{ | ||
URL: "tenant", | ||
ValueString: tenantId, | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
} | ||
|
||
auditMessage, err := json.Marshal(auditObject) | ||
if err != nil { | ||
logger.Errorf("%s: could not marshal the audit object: %v", ErrPersitAuditEvent.Error(), err) | ||
return | ||
} | ||
err = a.send(string(auditMessage)) | ||
if err != nil { | ||
logger.Errorf("%s: could not send the audit message to the url '%s': %v", ErrPersitAuditEvent.Error(), a.opts.Url, err) | ||
return | ||
} | ||
} | ||
|
||
func (c *AuditClient) send(msg string) error { | ||
signedDate := time.Now().UTC().Format(time.RFC3339) | ||
signature := c.apiSignature.GetSignature(signedDate) | ||
resp, err := c.client.R(). | ||
SetHeader("Content-Type", "application/json"). | ||
SetHeader("api-version", "2"). | ||
SetHeader("HSDP-API-Signature", signature). | ||
SetHeader("SignedDate", signedDate). | ||
SetBody(msg). | ||
Post(c.opts.Url) | ||
if err != nil { | ||
return err | ||
} | ||
if resp.StatusCode() != 201 { | ||
log.Println("Not able to send the audit message ", resp) | ||
return fmt.Errorf("not able to persist audit, audit server returned %v", resp.StatusCode()) | ||
} | ||
return nil | ||
} | ||
|
||
func (a *AuditClientOpts) Validate() error { | ||
if strings.TrimSpace(a.ProductName) == "" || strings.TrimSpace(a.ProductKey) == "" || strings.TrimSpace(a.SecretKey) == "" || strings.TrimSpace(a.SharedKey) == "" { | ||
return errors.New("the audit is enabled and therefore the audit product name, audit key, audit secret key or audit shared key are required (however found empty)") | ||
} | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
package audit | ||
|
||
import "errors" | ||
|
||
var ( | ||
ErrPersitAuditEvent = errors.New("could not persist the audit event") | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
package audit | ||
|
||
type Coding struct { | ||
System string `json:"system,omitempty"` | ||
Version string `json:"version,omitempty"` | ||
Code string `json:"code,omitempty"` | ||
Display string `json:"display,omitempty"` | ||
UserSelected string `json:"userSelected,omitempty"` | ||
} | ||
|
||
type Identifier struct { | ||
Use string `json:"use,omitempty"` | ||
Type *Coding `json:"type,omitempty"` | ||
Value string `json:"value,omitempty"` | ||
System string `json:"system,omitempty"` | ||
} | ||
|
||
type Reference struct { | ||
Reference string `json:"reference,omitempty"` | ||
Display string `json:"display,omitempty"` | ||
} | ||
|
||
type Detail struct { | ||
Type string `json:"type,omitempty"` | ||
Value string `json:"value,omitempty"` | ||
} | ||
|
||
type Object struct { | ||
Identifier *Identifier `json:"identifier,omitempty"` | ||
Reference *Reference `json:"reference,omitempty"` | ||
Type *Coding `json:"type,omitempty"` | ||
Role *Coding `json:"role,omitempty"` | ||
Lifecycle *Coding `json:"lifecycle,omitempty"` | ||
SecurityLabel []Coding `json:"securityLabel,omitempty"` | ||
Description string `json:"description,omitempty"` | ||
Query interface{} `json:"query,omitempty"` | ||
Name string `json:"name,omitempty"` | ||
Detail []Detail `json:"detail,omitempty"` | ||
} | ||
|
||
type Type struct { | ||
Coding `json:"coding,omitempty"` | ||
Text string `json:"text,omitempty"` | ||
} | ||
|
||
type UserID struct { | ||
Use string `json:"use,omitempty"` | ||
Type *Type `json:"type,omitempty"` | ||
System string `json:"system,omitempty"` | ||
Value string `json:"value,omitempty"` | ||
} | ||
|
||
type Network struct { | ||
Address string `json:"address,omitempty"` | ||
Type string `json:"type,omitempty"` | ||
} | ||
|
||
type Media struct { | ||
*Coding `json:"coding,omitempty"` | ||
} | ||
|
||
type PurposeOfUse struct { | ||
*Coding `json:"coding,omitempty"` | ||
} | ||
|
||
type Participant struct { | ||
Role []Type `json:"role,omitempty"` | ||
Reference *Reference `json:"reference,omitempty"` | ||
UserID UserID `json:"userId,omitempty"` | ||
AltID string `json:"altId,omitempty"` | ||
Name string `json:"name,omitempty"` | ||
Requestor bool `json:"requestor,omitempty"` | ||
Location *Reference `json:"location,omitempty"` | ||
Policy []string `json:"policy,omitempty"` | ||
Media *Media `json:"media,omitempty"` | ||
Network *Network `json:"network,omitempty"` | ||
PurposeOfUse []PurposeOfUse `json:"purposeOfUse,omitempty"` | ||
} | ||
|
||
type ExtensionContent struct { | ||
URL string `json:"url,omitempty"` | ||
ValueString string `json:"valueString,omitempty"` | ||
} | ||
type Extension struct { | ||
URL string `json:"url,omitempty"` | ||
Extension []*ExtensionContent `json:"extension,omitempty"` | ||
} | ||
|
||
type Source struct { | ||
Site string `json:"site,omitempty"` | ||
Identifier Identifier `json:"identifier,omitempty"` | ||
Type []*Coding `json:"type,omitempty"` | ||
Extension []*Extension `json:"extension,omitempty"` | ||
} | ||
|
||
type PurposeOfEvent struct { | ||
Coding `json:"coding,omitempty"` | ||
} | ||
|
||
type Event struct { | ||
Type *Coding `json:"type,omitempty"` | ||
Subtype []*Coding `json:"subtype,omitempty"` | ||
Action string `json:"action,omitempty"` | ||
DateTime string `json:"dateTime,omitempty"` | ||
Outcome string `json:"outcome,omitempty"` | ||
OutcomeDesc string `json:"outcomeDesc,omitempty"` | ||
PurposeOfEvent []*PurposeOfEvent `json:"purposeOfEvent,omitempty"` | ||
} | ||
|
||
type AuditEvent struct { | ||
ResourceType string `json:"resourceType,omitempty"` | ||
Event *Event `json:"event,omitempty"` | ||
Participant []*Participant `json:"participant,omitempty"` | ||
Source Source `json:"source,omitempty"` | ||
Object []*Object `json:"object,omitempty"` | ||
} |
Oops, something went wrong.