diff --git a/Makefile b/Makefile
index e617ef88272..fdee5626b33 100644
--- a/Makefile
+++ b/Makefile
@@ -468,7 +468,7 @@ misspell:
@echo checking misspell...
@find . -type d \( -path ./tests \) -prune -o -name '*.go' -print | xargs misspell -error
-# golangci-lint binary installation or refer to https://golangci-lint.run/usage/install/#local-installation
+# golangci-lint binary installation or refer to https://golangci-lint.run/usage/install/#local-installation
# curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.55.2
GOLANGCI_LINT := $(shell go env GOPATH)/bin/golangci-lint
lint:
diff --git a/api/v2.0/swagger.yaml b/api/v2.0/swagger.yaml
index c0f3bf59641..9186208028e 100644
--- a/api/v2.0/swagger.yaml
+++ b/api/v2.0/swagger.yaml
@@ -6833,6 +6833,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:
@@ -7871,6 +7877,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.
oidc_provider_name:
type: string
x-nullable: true
@@ -8931,6 +8947,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
@@ -9211,6 +9233,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.
diff --git a/make/migrations/postgresql/0140_2.11.0_schema.up.sql b/make/migrations/postgresql/0140_2.11.0_schema.up.sql
index b43f6072fce..d070097c913 100644
--- a/make/migrations/postgresql/0140_2.11.0_schema.up.sql
+++ b/make/migrations/postgresql/0140_2.11.0_schema.up.sql
@@ -1,3 +1,6 @@
+ALTER TABLE audit_log ADD user_agent VARCHAR(255);
+ALTER TABLE audit_log ADD client_ip inet;
+
/*
table artifact:
id SERIAL PRIMARY KEY NOT NULL,
@@ -28,4 +31,4 @@ then set column artifact_type as not null
*/
UPDATE artifact SET artifact_type = media_type;
-ALTER TABLE artifact ALTER COLUMN artifact_type SET NOT NULL;
\ No newline at end of file
+ALTER TABLE artifact ALTER COLUMN artifact_type SET NOT NULL;
diff --git a/src/common/const.go b/src/common/const.go
index aaa3c3fbe0d..6f0de7e645b 100644
--- a/src/common/const.go
+++ b/src/common/const.go
@@ -214,6 +214,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
diff --git a/src/controller/event/handler/auditlog/auditlog.go b/src/controller/event/handler/auditlog/auditlog.go
index 6c6b5c3e96d..64145c3d283 100644
--- a/src/controller/event/handler/auditlog/auditlog.go
+++ b/src/controller/event/handler/auditlog/auditlog.go
@@ -18,6 +18,7 @@ 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"
@@ -40,7 +41,6 @@ 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,
@@ -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)
}
diff --git a/src/controller/systeminfo/controller.go b/src/controller/systeminfo/controller.go
index 60118676a3d..35569d50406 100644
--- a/src/controller/systeminfo/controller.go
+++ b/src/controller/systeminfo/controller.go
@@ -49,10 +49,16 @@ type Data struct {
HarborVersion string
BannerMessage string
AuthProxySettings *models.HTTPAuthProxy
+ AuditLogs AuditLogSettings
Protected *protectedData
OIDCProviderName string
}
+type AuditLogSettings struct {
+ TrackIPAddress bool
+ TrackUserAgent bool
+}
+
type protectedData struct {
CurrentTime time.Time
RegistryURL string
@@ -105,6 +111,10 @@ func (c *controller) GetInfo(ctx context.Context, opt Options) (*Data, error) {
HarborVersion: fmt.Sprintf("%s-%s", version.ReleaseVersion, version.GitCommit),
BannerMessage: utils.SafeCastString(mgr.Get(ctx, common.BannerMessage).GetString()),
OIDCProviderName: OIDCProviderName(cfg),
+ 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 {
diff --git a/src/core/middlewares/middlewares.go b/src/core/middlewares/middlewares.go
index 555ca56748a..7fea862d8b6 100644
--- a/src/core/middlewares/middlewares.go
+++ b/src/core/middlewares/middlewares.go
@@ -18,6 +18,8 @@ import (
"net/http"
"regexp"
+ "github.com/goharbor/harbor/src/server/middleware/clientinfo"
+
"github.com/beego/beego/v2/server/web"
"github.com/goharbor/harbor/src/pkg/distribution"
@@ -94,6 +96,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(),
diff --git a/src/lib/config/config.go b/src/lib/config/config.go
index 0755b597392..7820b58077b 100644
--- a/src/lib/config/config.go
+++ b/src/lib/config/config.go
@@ -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
}
diff --git a/src/lib/config/metadata/metadatalist.go b/src/lib/config/metadata/metadatalist.go
index dd2a7f67cde..2cae275e37c 100644
--- a/src/lib/config/metadata/metadatalist.go
+++ b/src/lib/config/metadata/metadatalist.go
@@ -189,6 +189,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`},
diff --git a/src/lib/config/userconfig.go b/src/lib/config/userconfig.go
index a0fd5f90aee..be4bdfb7750 100644
--- a/src/lib/config/userconfig.go
+++ b/src/lib/config/userconfig.go
@@ -251,6 +251,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 {
return DefaultMgr().Get(ctx, common.ScannerSkipUpdatePullTime).GetBool()
diff --git a/src/lib/context.go b/src/lib/context.go
index 24ef00451c6..ba793251162 100644
--- a/src/lib/context.go
+++ b/src/lib/context.go
@@ -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/"
@@ -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
+}
diff --git a/src/pkg/audit/manager.go b/src/pkg/audit/manager.go
index 136d23ff77a..39b4d2437a7 100644
--- a/src/pkg/audit/manager.go
+++ b/src/pkg/audit/manager.go
@@ -77,9 +77,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
diff --git a/src/pkg/audit/model/model.go b/src/pkg/audit/model/model.go
index 7258473a109..a8291c0d292 100644
--- a/src/pkg/audit/model/model.go
+++ b/src/pkg/audit/model/model.go
@@ -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
diff --git a/src/portal/src/app/base/left-side-nav/config/config.ts b/src/portal/src/app/base/left-side-nav/config/config.ts
index 0fedfca3007..2185edd1dfb 100644
--- a/src/portal/src/app/base/left-side-nav/config/config.ts
+++ b/src/portal/src/app/base/left-side-nav/config/config.ts
@@ -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;
@@ -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(
diff --git a/src/portal/src/app/base/left-side-nav/config/system/system-settings.component.html b/src/portal/src/app/base/left-side-nav/config/system/system-settings.component.html
index 485c31ec711..bb0d039e8ae 100644
--- a/src/portal/src/app/base/left-side-nav/config/system/system-settings.component.html
+++ b/src/portal/src/app/base/left-side-nav/config/system/system-settings.component.html
@@ -346,6 +346,64 @@
" />
+
+
+
+
+
+
+
+
+
+
+
+