Skip to content

Commit

Permalink
add option to store ip addresses and/or user-agents in audit logs
Browse files Browse the repository at this point in the history
Signed-off-by: Maksym Trofimenko <[email protected]>
  • Loading branch information
Maksym Trofimenko committed Dec 17, 2023
1 parent f75a2f9 commit 18af23c
Show file tree
Hide file tree
Showing 25 changed files with 506 additions and 34 deletions.
60 changes: 46 additions & 14 deletions api/v2.0/swagger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4719,7 +4719,7 @@ paths:
summary: Get job log by job id
description: Get job log by job id, it is only used by administrator
produces:
- text/plain
- text/plain
tags:
- jobservice
parameters:
Expand Down Expand Up @@ -6072,7 +6072,7 @@ paths:
description: Specify whether the dangerous Artifact are included inside summary information
type: boolean
required: false
default: false
default: false
responses:
'200':
description: Success
Expand All @@ -6091,15 +6091,15 @@ paths:
get:
summary: Get the vulnerability list.
description: |
Get the vulnerability list. use q to pass the query condition,
Get the vulnerability list. use q to pass the query condition,
supported conditions:
cve_id(exact match)
cvss_score_v3(range condition)
severity(exact match)
repository_name(exact match)
project_id(exact match)
repository_name(exact match)
project_id(exact match)
package(exact match)
tag(exact match)
tag(exact match)
digest(exact match)
tags:
- securityhub
Expand Down Expand Up @@ -6140,7 +6140,7 @@ paths:
'401':
$ref: '#/responses/401'
'500':
$ref: '#/responses/500'
$ref: '#/responses/500'

parameters:
query:
Expand Down Expand Up @@ -6810,6 +6810,12 @@ definitions:
format: date-time
example: '2006-01-02T15:04:05Z'
description: The time when this operation is triggered.
client_ip:
type: string
description: Client IP address when this operation is triggered.
user_agent:
type: string
description: User agent during the operation.
Metadata:
type: object
properties:
Expand Down Expand Up @@ -7843,6 +7849,16 @@ definitions:
x-nullable: true
x-omitempty: true
$ref: '#/definitions/AuthproxySetting'
audit_log_track_ip_address:
type: boolean
x-nullable: true
x-omitempty: true
description: The flag to indicate whether IP address tracking is on in audit logs.
audit_log_track_user_agent:
type: boolean
x-nullable: true
x-omitempty: true
description: The flag to indicate whether user agent tracking is on in audit logs.
AuthproxySetting:
type: object
properties:
Expand Down Expand Up @@ -7966,7 +7982,7 @@ definitions:
type: string
description: |
The schedule type. The valid values are 'Hourly', 'Daily', 'Weekly', 'Custom', 'Manual', 'None' and 'Schedule'.
'Manual' means to trigger it right away, 'Schedule' means to trigger it by a specified cron schedule and
'Manual' means to trigger it right away, 'Schedule' means to trigger it by a specified cron schedule and
'None' means to cancel the schedule.
enum:
- Hourly
Expand Down Expand Up @@ -8898,6 +8914,12 @@ definitions:
skip_audit_log_database:
$ref: '#/definitions/BoolConfigItem'
description: Whether skip the audit log in database
audit_log_track_ip_address:
$ref: '#/definitions/BoolConfigItem'
description: Whether client ip address tracking is enabled in audit logs
audit_log_track_user_agent:
$ref: '#/definitions/BoolConfigItem'
description: Whether user agent tracking is enabled in audit logs
scanner_skip_update_pulltime:
$ref: '#/definitions/BoolConfigItem'
description: Whether or not to skip update the pull time for scanner
Expand Down Expand Up @@ -9178,6 +9200,16 @@ definitions:
description: Skip audit log database
x-omitempty: true
x-isnullable: true
audit_log_track_ip_address:
type: boolean
description: Track IP addresses in audit logs
x-omitempty: true
x-isnullable: true
audit_log_track_user_agent:
type: boolean
description: Track user agent in audit logs
x-omitempty: true
x-isnullable: true
session_timeout:
type: integer
description: The session timeout for harbor, in minutes.
Expand Down Expand Up @@ -9772,12 +9804,12 @@ definitions:
type: object
description: the dangerous CVE information
properties:
cve_id:
cve_id:
type: string
description: the cve id
severity:
type: string
description: the severity of the CVE
description: the severity of the CVE
cvss_score_v3:
type: number
format: float64
Expand All @@ -9787,22 +9819,22 @@ definitions:
description: the description of the CVE
package:
type: string
description: the package of the CVE
description: the package of the CVE
version:
type: string
description: the version of the package
DangerousArtifact:
type: object
description: the dangerous artifact information
properties:
project_id:
project_id:
type: integer
format: int64
description: the project id of the artifact
repository_name:
type: string
description: the repository name of the artifact
digest:
digest:
type: string
description: the digest of the artifact
critical_cnt:
Expand Down Expand Up @@ -9862,6 +9894,6 @@ definitions:
description: The description of the vulnerability
links:
type: array
items:
items:
type: string
description: Links of the vulnerability
2 changes: 2 additions & 0 deletions make/migrations/postgresql/0140_2.11.0_schema.up.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
ALTER TABLE audit_log ADD user_agent VARCHAR(255);
ALTER TABLE audit_log ADD client_ip inet;
6 changes: 6 additions & 0 deletions src/common/const.go
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,12 @@ const (
AuditLogForwardEndpoint = "audit_log_forward_endpoint"
// SkipAuditLogDatabase skip to log audit log in database
SkipAuditLogDatabase = "skip_audit_log_database"

// AuditLogTrackIpAddress track client ip address with audit_logs
AuditLogTrackIpAddress = "audit_log_track_ip_address"
// AuditLogTrackUserAgent track user agent with audit_logs
AuditLogTrackUserAgent = "audit_log_track_user_agent"

// MaxAuditRetentionHour allowed in audit log purge
MaxAuditRetentionHour = 240000
// ScannerSkipUpdatePullTime
Expand Down
18 changes: 13 additions & 5 deletions src/controller/event/handler/auditlog/auditlog.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ package auditlog

import (
"context"

"github.com/goharbor/harbor/src/controller/event"
"github.com/goharbor/harbor/src/lib"
"github.com/goharbor/harbor/src/lib/config"
"github.com/goharbor/harbor/src/lib/log"
"github.com/goharbor/harbor/src/pkg/audit"
Expand All @@ -40,7 +40,7 @@ func (h *Handler) Name() string {

// Handle ...
func (h *Handler) Handle(ctx context.Context, value interface{}) error {
var auditLog *am.AuditLog

var addAuditLog bool
switch v := value.(type) {
case *event.PushArtifactEvent, *event.DeleteArtifactEvent,
Expand All @@ -60,9 +60,17 @@ func (h *Handler) Handle(ctx context.Context, value interface{}) error {
log.Errorf("failed to handler event %v", err)
return err
}
auditLog = al
if auditLog != nil {
_, err := audit.Mgr.Create(ctx, auditLog)

if al != nil {
ip := lib.GetClientIpAddress(ctx)
if ip != "" {
al.ClientIP = &ip
}
ua := lib.GetUserAgent(ctx)
if ua != "" {
al.UserAgent = &ua
}
_, err := audit.Mgr.Create(ctx, al)
if err != nil {
log.Debugf("add audit log err: %v", err)
}
Expand Down
10 changes: 10 additions & 0 deletions src/controller/systeminfo/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,15 @@ type Data struct {
HarborVersion string
BannerMessage string
AuthProxySettings *models.HTTPAuthProxy
AuditLogs AuditLogSettings
Protected *protectedData
}

type AuditLogSettings struct {
TrackIPAddress bool
TrackUserAgent bool
}

type protectedData struct {
CurrentTime time.Time
RegistryURL string
Expand Down Expand Up @@ -103,6 +109,10 @@ func (c *controller) GetInfo(ctx context.Context, opt Options) (*Data, error) {
SelfRegistration: utils.SafeCastBool(cfg[common.SelfRegistration]),
HarborVersion: fmt.Sprintf("%s-%s", version.ReleaseVersion, version.GitCommit),
BannerMessage: utils.SafeCastString(mgr.Get(ctx, common.BannerMessage).GetString()),
AuditLogs: AuditLogSettings{
TrackIPAddress: utils.SafeCastBool(cfg[common.AuditLogTrackIpAddress]),
TrackUserAgent: utils.SafeCastBool(cfg[common.AuditLogTrackUserAgent]),
},
}
if res.AuthMode == common.HTTPAuth {
if s, err := config.HTTPAuthProxySetting(ctx); err == nil {
Expand Down
2 changes: 2 additions & 0 deletions src/core/middlewares/middlewares.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
package middlewares

import (
"github.com/goharbor/harbor/src/server/middleware/clientinfo"
"net/http"
"regexp"

Expand Down Expand Up @@ -88,6 +89,7 @@ func MiddleWares() []web.MiddleWare {
session.Middleware(),
csrf.Middleware(),
orm.Middleware(pingSkipper),
clientinfo.Middleware(pingSkipper),
notification.Middleware(pingSkipper), // notification must ahead of transaction ensure the DB transaction execution complete
transaction.Middleware(dbTxSkippers...),
artifactinfo.Middleware(),
Expand Down
2 changes: 1 addition & 1 deletion src/lib/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ func GetManager(name string) (Manager, error) {
func DefaultMgr() Manager {
manager, err := GetManager(DefaultCfgManager)
if err != nil {
log.Error("failed to get config manager")
log.Error("failed to get config manager", err)
}
return manager
}
Expand Down
3 changes: 3 additions & 0 deletions src/lib/config/metadata/metadatalist.go
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,9 @@ var (

{Name: common.AuditLogForwardEndpoint, Scope: UserScope, Group: BasicGroup, EnvKey: "AUDIT_LOG_FORWARD_ENDPOINT", DefaultValue: "", ItemType: &StringType{}, Editable: false, Description: `The endpoint to forward the audit log.`},
{Name: common.SkipAuditLogDatabase, Scope: UserScope, Group: BasicGroup, EnvKey: "SKIP_LOG_AUDIT_DATABASE", DefaultValue: "false", ItemType: &BoolType{}, Editable: false, Description: `The option to skip audit log in database`},
{Name: common.AuditLogTrackIpAddress, Scope: UserScope, Group: BasicGroup, EnvKey: "AUDIT_LOG_TRACK_IP_ADDRESS", DefaultValue: "false", ItemType: &BoolType{}, Editable: true, Description: `The flag to enable IP addresses tracking in audit logs.`},
{Name: common.AuditLogTrackUserAgent, Scope: UserScope, Group: BasicGroup, EnvKey: "AUDIT_LOG_TRACK_USER_AGENT", DefaultValue: "false", ItemType: &BoolType{}, Editable: true, Description: `The flag to enable user agent tracking in audit logs.`},

{Name: common.ScannerSkipUpdatePullTime, Scope: UserScope, Group: BasicGroup, EnvKey: "SCANNER_SKIP_UPDATE_PULL_TIME", DefaultValue: "false", ItemType: &BoolType{}, Editable: false, Description: `The option to skip update pull time for scanner`},

{Name: common.SessionTimeout, Scope: UserScope, Group: BasicGroup, EnvKey: "SESSION_TIMEOUT", DefaultValue: "60", ItemType: &Int64Type{}, Editable: true, Description: `The session timeout in minutes`},
Expand Down
10 changes: 10 additions & 0 deletions src/lib/config/userconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,16 @@ func SkipAuditLogDatabase(ctx context.Context) bool {
return DefaultMgr().Get(ctx, common.SkipAuditLogDatabase).GetBool()
}

// AuditLogTrackIpAddress enables ip address tracking
func AuditLogTrackIpAddress(ctx context.Context) bool {
return DefaultMgr().Get(ctx, common.AuditLogTrackIpAddress).GetBool()
}

// AuditLogTrackUserAgent enables user info tracking
func AuditLogTrackUserAgent(ctx context.Context) bool {
return DefaultMgr().Get(ctx, common.AuditLogTrackUserAgent).GetBool()
}

// ScannerSkipUpdatePullTime returns the scanner skip update pull time setting
func ScannerSkipUpdatePullTime(ctx context.Context) bool {
log.Infof("skip_update_pull_time:%v", DefaultMgr().Get(ctx, common.ScannerSkipUpdatePullTime).GetBool())
Expand Down
32 changes: 32 additions & 0 deletions src/lib/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ const (
contextKeyAuthMode contextKey = "authMode"
contextKeyCarrySession contextKey = "carrySession"
contextKeyRequestID contextKey = "X-Request-ID"
contextClientIpAddress contextKey = "clientIpAddress"
contextUserAgent contextKey = "userAgent"
)

// ArtifactInfo wraps the artifact info extracted from the request to "/v2/"
Expand Down Expand Up @@ -128,3 +130,33 @@ func GetXRequestID(ctx context.Context) string {
}
return id
}

// WithClientIpAddress returns a context with ipAddress set
func WithClientIpAddress(ctx context.Context, ipAddress string) context.Context {
return setToContext(ctx, contextClientIpAddress, ipAddress)
}

// WithUserAgent returns a context with user agent set
func WithUserAgent(ctx context.Context, userAgent string) context.Context {
return setToContext(ctx, contextUserAgent, userAgent)
}

// GetClientIpAddress gets the ip address from the context
func GetClientIpAddress(ctx context.Context) string {
var result string
value := getFromContext(ctx, contextClientIpAddress)
if value != nil {
result, _ = value.(string)
}
return result
}

// GetUserAgent gets the user agent from the context
func GetUserAgent(ctx context.Context) string {
var result string
value := getFromContext(ctx, contextUserAgent)
if value != nil {
result, _ = value.(string)
}
return result
}
12 changes: 9 additions & 3 deletions src/pkg/audit/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,15 @@ func (m *manager) Get(ctx context.Context, id int64) (*model.AuditLog, error) {
// Create ...
func (m *manager) Create(ctx context.Context, audit *model.AuditLog) (int64, error) {
if len(config.AuditLogForwardEndpoint(ctx)) > 0 {
LogMgr.DefaultLogger(ctx).WithField("operator", audit.Username).
WithField("time", audit.OpTime).WithField("resourceType", audit.ResourceType).
Infof("action:%s, resource:%s", audit.Operation, audit.Resource)
logger := LogMgr.DefaultLogger(ctx).WithField("operator", audit.Username).
WithField("time", audit.OpTime).WithField("resourceType", audit.ResourceType)
if config.AuditLogTrackIpAddress(ctx) && audit.ClientIP != nil {
logger.WithField("clientIP", *audit.ClientIP)
}
if config.AuditLogTrackUserAgent(ctx) && audit.UserAgent != nil {
logger.WithField("userAgent", *audit.UserAgent)
}
logger.Infof("action:%s, resource:%s", audit.Operation, audit.Resource)
}
if config.SkipAuditLogDatabase(ctx) {
return 0, nil
Expand Down
2 changes: 2 additions & 0 deletions src/pkg/audit/model/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ type AuditLog struct {
Resource string `orm:"column(resource)" json:"resource"`
Username string `orm:"column(username)" json:"username"`
OpTime time.Time `orm:"column(op_time)" json:"op_time" sort:"default:desc"`
UserAgent *string `orm:"column(user_agent)" json:"user_agent"`
ClientIP *string `orm:"column(client_ip)" json:"client_ip"`
}

// TableName for audit log
Expand Down
4 changes: 4 additions & 0 deletions src/portal/src/app/base/left-side-nav/config/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,8 @@ export class Configuration {
oidc_group_filter: StringValueItem;
audit_log_forward_endpoint: StringValueItem;
skip_audit_log_database: BoolValueItem;
audit_log_track_ip_address: BoolValueItem;
audit_log_track_user_agent: BoolValueItem;
session_timeout: NumberValueItem;
scanner_skip_update_pulltime: BoolValueItem;
banner_message: StringValueItem;
Expand Down Expand Up @@ -189,6 +191,8 @@ export class Configuration {
this.storage_per_project = new NumberValueItem(-1, true);
this.audit_log_forward_endpoint = new StringValueItem('', true);
this.skip_audit_log_database = new BoolValueItem(false, true);
this.audit_log_track_ip_address = new BoolValueItem(false, true);
this.audit_log_track_user_agent = new BoolValueItem(false, true);
this.session_timeout = new NumberValueItem(60, true);
this.scanner_skip_update_pulltime = new BoolValueItem(false, true);
this.banner_message = new StringValueItem(
Expand Down
Loading

0 comments on commit 18af23c

Please sign in to comment.