mirror of
https://github.com/goharbor/harbor
synced 2025-04-12 22:50:55 +00:00
Add user login event to audit log (#21415)
Add common event handler Register login event Update previous audit log event redirect to auditlogext table Signed-off-by: stonezdj <stone.zhang@broadcom.com>
This commit is contained in:
parent
39b2898e18
commit
f808f33cca
|
@ -16,12 +16,14 @@ package auditlog
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/goharbor/harbor/src/controller/event"
|
||||
evtModel "github.com/goharbor/harbor/src/controller/event/model"
|
||||
"github.com/goharbor/harbor/src/lib/config"
|
||||
"github.com/goharbor/harbor/src/lib/log"
|
||||
"github.com/goharbor/harbor/src/pkg/audit"
|
||||
am "github.com/goharbor/harbor/src/pkg/audit/model"
|
||||
"github.com/goharbor/harbor/src/pkg/auditext"
|
||||
am "github.com/goharbor/harbor/src/pkg/auditext/model"
|
||||
)
|
||||
|
||||
// Handler - audit log handler
|
||||
|
@ -30,7 +32,7 @@ type Handler struct {
|
|||
|
||||
// AuditResolver - interface to resolve to AuditLog
|
||||
type AuditResolver interface {
|
||||
ResolveToAuditLog() (*am.AuditLog, error)
|
||||
ResolveToAuditLog() (*am.AuditLogExt, error)
|
||||
}
|
||||
|
||||
// Name ...
|
||||
|
@ -40,13 +42,12 @@ 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,
|
||||
*event.DeleteRepositoryEvent, *event.CreateProjectEvent, *event.DeleteProjectEvent,
|
||||
*event.DeleteTagEvent, *event.CreateTagEvent,
|
||||
*event.CreateRobotEvent, *event.DeleteRobotEvent:
|
||||
*event.CreateRobotEvent, *event.DeleteRobotEvent, *evtModel.CommonEvent:
|
||||
addAuditLog = true
|
||||
case *event.PullArtifactEvent:
|
||||
addAuditLog = !config.PullAuditLogDisable(ctx)
|
||||
|
@ -56,14 +57,13 @@ func (h *Handler) Handle(ctx context.Context, value interface{}) error {
|
|||
|
||||
if addAuditLog {
|
||||
resolver := value.(AuditResolver)
|
||||
al, err := resolver.ResolveToAuditLog()
|
||||
auditLog, err := resolver.ResolveToAuditLog()
|
||||
if err != nil {
|
||||
log.Errorf("failed to handler event %v", err)
|
||||
return err
|
||||
}
|
||||
auditLog = al
|
||||
if auditLog != nil {
|
||||
_, err := audit.Mgr.Create(ctx, auditLog)
|
||||
if auditLog != nil && config.AuditLogEventEnabled(ctx, fmt.Sprintf("%v_%v", auditLog.Operation, auditLog.ResourceType)) {
|
||||
_, err := auditext.Mgr.Create(ctx, auditLog)
|
||||
if err != nil {
|
||||
log.Debugf("add audit log err: %v", err)
|
||||
}
|
||||
|
|
|
@ -67,6 +67,7 @@ func init() {
|
|||
_ = notifier.Subscribe(event.TopicDeleteTag, &auditlog.Handler{})
|
||||
_ = notifier.Subscribe(event.TopicCreateRobot, &auditlog.Handler{})
|
||||
_ = notifier.Subscribe(event.TopicDeleteRobot, &auditlog.Handler{})
|
||||
_ = notifier.Subscribe(event.TopicCommonEvent, &auditlog.Handler{})
|
||||
|
||||
// internal
|
||||
_ = notifier.Subscribe(event.TopicPullArtifact, &internal.ArtifactEventHandler{})
|
||||
|
|
|
@ -62,8 +62,10 @@ type Metadata struct {
|
|||
IPAddress string
|
||||
// ResponseLocation response location
|
||||
ResponseLocation string
|
||||
// ResourceName
|
||||
// ResourceName resource name
|
||||
ResourceName string
|
||||
// Payload request payload
|
||||
Payload string
|
||||
}
|
||||
|
||||
// Resolve parse the audit information from CommonEventMetadata
|
||||
|
|
|
@ -14,7 +14,12 @@
|
|||
|
||||
package model
|
||||
|
||||
import "github.com/goharbor/harbor/src/pkg/retention/policy/rule"
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/goharbor/harbor/src/pkg/auditext/model"
|
||||
"github.com/goharbor/harbor/src/pkg/retention/policy/rule"
|
||||
)
|
||||
|
||||
// Replication describes replication infos
|
||||
type Replication struct {
|
||||
|
@ -80,3 +85,32 @@ type Scan struct {
|
|||
// ScanType the scan type
|
||||
ScanType string `json:"scan_type,omitempty"`
|
||||
}
|
||||
|
||||
// CommonEvent ...
|
||||
type CommonEvent struct {
|
||||
Operator string
|
||||
ProjectID int64
|
||||
OcurrAt time.Time
|
||||
Operation string
|
||||
Payload string
|
||||
SourceIP string
|
||||
ResourceType string
|
||||
ResourceName string
|
||||
OperationDescription string
|
||||
IsSuccessful bool
|
||||
}
|
||||
|
||||
// ResolveToAuditLog ...
|
||||
func (c *CommonEvent) ResolveToAuditLog() (*model.AuditLogExt, error) {
|
||||
auditLog := &model.AuditLogExt{
|
||||
ProjectID: c.ProjectID,
|
||||
OpTime: c.OcurrAt,
|
||||
Operation: c.Operation,
|
||||
Username: c.Operator,
|
||||
ResourceType: c.ResourceType,
|
||||
Resource: c.ResourceName,
|
||||
OperationDescription: c.OperationDescription,
|
||||
IsSuccessful: c.IsSuccessful,
|
||||
}
|
||||
return auditLog, nil
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@ import (
|
|||
"github.com/goharbor/harbor/src/common/rbac"
|
||||
"github.com/goharbor/harbor/src/lib/selector"
|
||||
"github.com/goharbor/harbor/src/pkg/artifact"
|
||||
"github.com/goharbor/harbor/src/pkg/audit/model"
|
||||
"github.com/goharbor/harbor/src/pkg/auditext/model"
|
||||
proModels "github.com/goharbor/harbor/src/pkg/project/models"
|
||||
robotModel "github.com/goharbor/harbor/src/pkg/robot/model"
|
||||
v1 "github.com/goharbor/harbor/src/pkg/scan/rest/v1"
|
||||
|
@ -43,13 +43,19 @@ const (
|
|||
TopicScanningStopped = "SCANNING_STOPPED"
|
||||
TopicScanningCompleted = "SCANNING_COMPLETED"
|
||||
// QuotaExceedTopic is topic for quota warning event, the usage reaches the warning bar of limitation, like 85%
|
||||
TopicQuotaWarning = "QUOTA_WARNING"
|
||||
TopicQuotaExceed = "QUOTA_EXCEED"
|
||||
TopicReplication = "REPLICATION"
|
||||
TopicArtifactLabeled = "ARTIFACT_LABELED"
|
||||
TopicTagRetention = "TAG_RETENTION"
|
||||
TopicCreateRobot = "CREATE_ROBOT"
|
||||
TopicDeleteRobot = "DELETE_ROBOT"
|
||||
TopicQuotaWarning = "QUOTA_WARNING"
|
||||
TopicQuotaExceed = "QUOTA_EXCEED"
|
||||
TopicReplication = "REPLICATION"
|
||||
TopicArtifactLabeled = "ARTIFACT_LABELED"
|
||||
TopicTagRetention = "TAG_RETENTION"
|
||||
TopicCreateRobot = "CREATE_ROBOT"
|
||||
TopicDeleteRobot = "DELETE_ROBOT"
|
||||
TopicCommonEvent = "COMMON_API"
|
||||
ResourceTypeProject = "project"
|
||||
ResourceTypeArtifact = "artifact"
|
||||
ResourceTypeRepository = "repository"
|
||||
ResourceTypeRobot = "robot"
|
||||
ResourceTypeTag = "tag"
|
||||
)
|
||||
|
||||
// CreateProjectEvent is the creating project event
|
||||
|
@ -62,14 +68,16 @@ type CreateProjectEvent struct {
|
|||
}
|
||||
|
||||
// ResolveToAuditLog ...
|
||||
func (c *CreateProjectEvent) ResolveToAuditLog() (*model.AuditLog, error) {
|
||||
auditLog := &model.AuditLog{
|
||||
ProjectID: c.ProjectID,
|
||||
OpTime: c.OccurAt,
|
||||
Operation: rbac.ActionCreate.String(),
|
||||
Username: c.Operator,
|
||||
ResourceType: "project",
|
||||
Resource: c.Project}
|
||||
func (c *CreateProjectEvent) ResolveToAuditLog() (*model.AuditLogExt, error) {
|
||||
auditLog := &model.AuditLogExt{
|
||||
ProjectID: c.ProjectID,
|
||||
OpTime: c.OccurAt,
|
||||
Operation: rbac.ActionCreate.String(),
|
||||
Username: c.Operator,
|
||||
ResourceType: ResourceTypeProject,
|
||||
IsSuccessful: true,
|
||||
OperationDescription: fmt.Sprintf("create project: %s", c.Project),
|
||||
Resource: c.Project}
|
||||
return auditLog, nil
|
||||
}
|
||||
|
||||
|
@ -88,14 +96,16 @@ type DeleteProjectEvent struct {
|
|||
}
|
||||
|
||||
// ResolveToAuditLog ...
|
||||
func (d *DeleteProjectEvent) ResolveToAuditLog() (*model.AuditLog, error) {
|
||||
auditLog := &model.AuditLog{
|
||||
ProjectID: d.ProjectID,
|
||||
OpTime: d.OccurAt,
|
||||
Operation: rbac.ActionDelete.String(),
|
||||
Username: d.Operator,
|
||||
ResourceType: "project",
|
||||
Resource: d.Project}
|
||||
func (d *DeleteProjectEvent) ResolveToAuditLog() (*model.AuditLogExt, error) {
|
||||
auditLog := &model.AuditLogExt{
|
||||
ProjectID: d.ProjectID,
|
||||
OpTime: d.OccurAt,
|
||||
Operation: rbac.ActionDelete.String(),
|
||||
Username: d.Operator,
|
||||
ResourceType: ResourceTypeProject,
|
||||
IsSuccessful: true,
|
||||
OperationDescription: fmt.Sprintf("delete project: %s", d.Project),
|
||||
Resource: d.Project}
|
||||
return auditLog, nil
|
||||
}
|
||||
|
||||
|
@ -114,14 +124,16 @@ type DeleteRepositoryEvent struct {
|
|||
}
|
||||
|
||||
// ResolveToAuditLog ...
|
||||
func (d *DeleteRepositoryEvent) ResolveToAuditLog() (*model.AuditLog, error) {
|
||||
auditLog := &model.AuditLog{
|
||||
ProjectID: d.ProjectID,
|
||||
OpTime: d.OccurAt,
|
||||
Operation: rbac.ActionDelete.String(),
|
||||
Username: d.Operator,
|
||||
ResourceType: "repository",
|
||||
Resource: d.Repository,
|
||||
func (d *DeleteRepositoryEvent) ResolveToAuditLog() (*model.AuditLogExt, error) {
|
||||
auditLog := &model.AuditLogExt{
|
||||
ProjectID: d.ProjectID,
|
||||
OpTime: d.OccurAt,
|
||||
Operation: rbac.ActionDelete.String(),
|
||||
Username: d.Operator,
|
||||
ResourceType: ResourceTypeRepository,
|
||||
IsSuccessful: true,
|
||||
OperationDescription: fmt.Sprintf("delete repository: %s", d.Repository),
|
||||
Resource: d.Repository,
|
||||
}
|
||||
return auditLog, nil
|
||||
}
|
||||
|
@ -154,13 +166,15 @@ type PushArtifactEvent struct {
|
|||
}
|
||||
|
||||
// ResolveToAuditLog ...
|
||||
func (p *PushArtifactEvent) ResolveToAuditLog() (*model.AuditLog, error) {
|
||||
auditLog := &model.AuditLog{
|
||||
ProjectID: p.Artifact.ProjectID,
|
||||
OpTime: p.OccurAt,
|
||||
Operation: rbac.ActionCreate.String(),
|
||||
Username: p.Operator,
|
||||
ResourceType: "artifact"}
|
||||
func (p *PushArtifactEvent) ResolveToAuditLog() (*model.AuditLogExt, error) {
|
||||
auditLog := &model.AuditLogExt{
|
||||
ProjectID: p.Artifact.ProjectID,
|
||||
OpTime: p.OccurAt,
|
||||
Operation: rbac.ActionCreate.String(),
|
||||
Username: p.Operator,
|
||||
IsSuccessful: true,
|
||||
OperationDescription: fmt.Sprintf("push artifact: %s@%s", p.Artifact.RepositoryName, p.Artifact.Digest),
|
||||
ResourceType: ResourceTypeArtifact}
|
||||
|
||||
if len(p.Tags) == 0 {
|
||||
auditLog.Resource = fmt.Sprintf("%s@%s",
|
||||
|
@ -183,13 +197,15 @@ type PullArtifactEvent struct {
|
|||
}
|
||||
|
||||
// ResolveToAuditLog ...
|
||||
func (p *PullArtifactEvent) ResolveToAuditLog() (*model.AuditLog, error) {
|
||||
auditLog := &model.AuditLog{
|
||||
ProjectID: p.Artifact.ProjectID,
|
||||
OpTime: p.OccurAt,
|
||||
Operation: rbac.ActionPull.String(),
|
||||
Username: p.Operator,
|
||||
ResourceType: "artifact"}
|
||||
func (p *PullArtifactEvent) ResolveToAuditLog() (*model.AuditLogExt, error) {
|
||||
auditLog := &model.AuditLogExt{
|
||||
ProjectID: p.Artifact.ProjectID,
|
||||
OpTime: p.OccurAt,
|
||||
Operation: rbac.ActionPull.String(),
|
||||
Username: p.Operator,
|
||||
IsSuccessful: true,
|
||||
OperationDescription: fmt.Sprintf("pull artifact: %s@%s", p.Artifact.RepositoryName, p.Artifact.Digest),
|
||||
ResourceType: ResourceTypeArtifact}
|
||||
|
||||
if len(p.Tags) == 0 {
|
||||
auditLog.Resource = fmt.Sprintf("%s@%s",
|
||||
|
@ -219,14 +235,16 @@ type DeleteArtifactEvent struct {
|
|||
}
|
||||
|
||||
// ResolveToAuditLog ...
|
||||
func (d *DeleteArtifactEvent) ResolveToAuditLog() (*model.AuditLog, error) {
|
||||
auditLog := &model.AuditLog{
|
||||
ProjectID: d.Artifact.ProjectID,
|
||||
OpTime: d.OccurAt,
|
||||
Operation: rbac.ActionDelete.String(),
|
||||
Username: d.Operator,
|
||||
ResourceType: "artifact",
|
||||
Resource: fmt.Sprintf("%s@%s", d.Artifact.RepositoryName, d.Artifact.Digest)}
|
||||
func (d *DeleteArtifactEvent) ResolveToAuditLog() (*model.AuditLogExt, error) {
|
||||
auditLog := &model.AuditLogExt{
|
||||
ProjectID: d.Artifact.ProjectID,
|
||||
OpTime: d.OccurAt,
|
||||
Operation: rbac.ActionDelete.String(),
|
||||
Username: d.Operator,
|
||||
ResourceType: ResourceTypeArtifact,
|
||||
IsSuccessful: true,
|
||||
OperationDescription: fmt.Sprintf("delete artifact: %s@%s", d.Artifact.RepositoryName, d.Artifact.Digest),
|
||||
Resource: fmt.Sprintf("%s@%s", d.Artifact.RepositoryName, d.Artifact.Digest)}
|
||||
return auditLog, nil
|
||||
}
|
||||
|
||||
|
@ -246,14 +264,16 @@ type CreateTagEvent struct {
|
|||
}
|
||||
|
||||
// ResolveToAuditLog ...
|
||||
func (c *CreateTagEvent) ResolveToAuditLog() (*model.AuditLog, error) {
|
||||
auditLog := &model.AuditLog{
|
||||
ProjectID: c.AttachedArtifact.ProjectID,
|
||||
OpTime: c.OccurAt,
|
||||
Operation: rbac.ActionCreate.String(),
|
||||
Username: c.Operator,
|
||||
ResourceType: "tag",
|
||||
Resource: fmt.Sprintf("%s:%s", c.Repository, c.Tag)}
|
||||
func (c *CreateTagEvent) ResolveToAuditLog() (*model.AuditLogExt, error) {
|
||||
auditLog := &model.AuditLogExt{
|
||||
ProjectID: c.AttachedArtifact.ProjectID,
|
||||
OpTime: c.OccurAt,
|
||||
Operation: rbac.ActionCreate.String(),
|
||||
Username: c.Operator,
|
||||
ResourceType: ResourceTypeTag,
|
||||
IsSuccessful: true,
|
||||
OperationDescription: fmt.Sprintf("create tag: %s:%s", c.Repository, c.Tag),
|
||||
Resource: fmt.Sprintf("%s:%s", c.Repository, c.Tag)}
|
||||
return auditLog, nil
|
||||
}
|
||||
|
||||
|
@ -275,13 +295,14 @@ type DeleteTagEvent struct {
|
|||
}
|
||||
|
||||
// ResolveToAuditLog ...
|
||||
func (d *DeleteTagEvent) ResolveToAuditLog() (*model.AuditLog, error) {
|
||||
auditLog := &model.AuditLog{
|
||||
func (d *DeleteTagEvent) ResolveToAuditLog() (*model.AuditLogExt, error) {
|
||||
auditLog := &model.AuditLogExt{
|
||||
ProjectID: d.AttachedArtifact.ProjectID,
|
||||
OpTime: d.OccurAt,
|
||||
Operation: rbac.ActionDelete.String(),
|
||||
Username: d.Operator,
|
||||
ResourceType: "tag",
|
||||
ResourceType: ResourceTypeTag,
|
||||
IsSuccessful: true,
|
||||
Resource: fmt.Sprintf("%s:%s", d.Repository, d.Tag)}
|
||||
return auditLog, nil
|
||||
}
|
||||
|
@ -385,14 +406,16 @@ type CreateRobotEvent struct {
|
|||
}
|
||||
|
||||
// ResolveToAuditLog ...
|
||||
func (c *CreateRobotEvent) ResolveToAuditLog() (*model.AuditLog, error) {
|
||||
auditLog := &model.AuditLog{
|
||||
ProjectID: c.Robot.ProjectID,
|
||||
OpTime: c.OccurAt,
|
||||
Operation: rbac.ActionCreate.String(),
|
||||
Username: c.Operator,
|
||||
ResourceType: "robot",
|
||||
Resource: c.Robot.Name}
|
||||
func (c *CreateRobotEvent) ResolveToAuditLog() (*model.AuditLogExt, error) {
|
||||
auditLog := &model.AuditLogExt{
|
||||
ProjectID: c.Robot.ProjectID,
|
||||
OpTime: c.OccurAt,
|
||||
Operation: rbac.ActionCreate.String(),
|
||||
Username: c.Operator,
|
||||
ResourceType: ResourceTypeRobot,
|
||||
IsSuccessful: true,
|
||||
OperationDescription: fmt.Sprintf("create robot: %s", c.Robot.Name),
|
||||
Resource: c.Robot.Name}
|
||||
return auditLog, nil
|
||||
}
|
||||
|
||||
|
@ -410,14 +433,16 @@ type DeleteRobotEvent struct {
|
|||
}
|
||||
|
||||
// ResolveToAuditLog ...
|
||||
func (c *DeleteRobotEvent) ResolveToAuditLog() (*model.AuditLog, error) {
|
||||
auditLog := &model.AuditLog{
|
||||
ProjectID: c.Robot.ProjectID,
|
||||
OpTime: c.OccurAt,
|
||||
Operation: rbac.ActionDelete.String(),
|
||||
Username: c.Operator,
|
||||
ResourceType: "robot",
|
||||
Resource: c.Robot.Name}
|
||||
func (c *DeleteRobotEvent) ResolveToAuditLog() (*model.AuditLogExt, error) {
|
||||
auditLog := &model.AuditLogExt{
|
||||
ProjectID: c.Robot.ProjectID,
|
||||
OpTime: c.OccurAt,
|
||||
Operation: rbac.ActionDelete.String(),
|
||||
Username: c.Operator,
|
||||
ResourceType: ResourceTypeRobot,
|
||||
IsSuccessful: true,
|
||||
OperationDescription: fmt.Sprintf("delete robot: %s", c.Robot.Name),
|
||||
Resource: c.Robot.Name}
|
||||
return auditLog, nil
|
||||
}
|
||||
|
||||
|
|
|
@ -63,6 +63,7 @@ import (
|
|||
_ "github.com/goharbor/harbor/src/pkg/accessory/model/sbom"
|
||||
_ "github.com/goharbor/harbor/src/pkg/accessory/model/subject"
|
||||
"github.com/goharbor/harbor/src/pkg/audit"
|
||||
_ "github.com/goharbor/harbor/src/pkg/auditext/event/login"
|
||||
dbCfg "github.com/goharbor/harbor/src/pkg/config/db"
|
||||
_ "github.com/goharbor/harbor/src/pkg/config/inmemory"
|
||||
"github.com/goharbor/harbor/src/pkg/notification"
|
||||
|
|
|
@ -264,6 +264,9 @@ func BannerMessage(ctx context.Context) string {
|
|||
|
||||
// AuditLogEventEnabled returns the audit log enabled setting for a specific event_type, such as delete_user, create_user
|
||||
func AuditLogEventEnabled(ctx context.Context, eventType string) bool {
|
||||
if DefaultMgr() == nil || DefaultMgr().Get(ctx, common.AuditLogEventsDisabled) == nil {
|
||||
return true
|
||||
}
|
||||
disableListStr := DefaultMgr().Get(ctx, common.AuditLogEventsDisabled).GetString()
|
||||
disableList := strings.Split(disableListStr, ",")
|
||||
for _, t := range disableList {
|
||||
|
|
|
@ -46,7 +46,7 @@ func (d *daoTestSuite) SetupSuite() {
|
|||
Resource: "user01",
|
||||
Username: "admin",
|
||||
OperationDescription: "Create user",
|
||||
OperationResult: true,
|
||||
IsSuccessful: true,
|
||||
OpTime: time.Now().AddDate(0, 0, -8),
|
||||
})
|
||||
d.Require().Nil(err)
|
||||
|
@ -148,11 +148,11 @@ func (d *daoTestSuite) TestListPIDs() {
|
|||
|
||||
func (d *daoTestSuite) TestCreate() {
|
||||
audit := &model.AuditLogExt{
|
||||
Operation: "create",
|
||||
ResourceType: "user",
|
||||
Resource: "user02",
|
||||
OperationResult: true,
|
||||
Username: "admin",
|
||||
Operation: "create",
|
||||
ResourceType: "user",
|
||||
Resource: "user02",
|
||||
IsSuccessful: true,
|
||||
Username: "admin",
|
||||
}
|
||||
_, err := d.dao.Create(d.ctx, audit)
|
||||
d.Require().Nil(err)
|
||||
|
|
86
src/pkg/auditext/event/login/login.go
Normal file
86
src/pkg/auditext/event/login/login.go
Normal file
|
@ -0,0 +1,86 @@
|
|||
// Copyright Project Harbor Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package login
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"time"
|
||||
|
||||
"github.com/goharbor/harbor/src/common/rbac"
|
||||
ctlEvent "github.com/goharbor/harbor/src/controller/event"
|
||||
"github.com/goharbor/harbor/src/controller/event/metadata/commonevent"
|
||||
"github.com/goharbor/harbor/src/controller/event/model"
|
||||
"github.com/goharbor/harbor/src/lib/config"
|
||||
"github.com/goharbor/harbor/src/pkg/notifier/event"
|
||||
)
|
||||
|
||||
func init() {
|
||||
var login = &loginResolver{}
|
||||
var logout = &logoutResolver{}
|
||||
commonevent.RegisterResolver(`/c/login$`, login)
|
||||
commonevent.RegisterResolver(`/c/log_out$`, logout)
|
||||
}
|
||||
|
||||
const (
|
||||
opLogout = "logout"
|
||||
opLogin = "login"
|
||||
logoutSuffix = "log_out"
|
||||
payloadPattern = `principal=(.*?)&password`
|
||||
)
|
||||
|
||||
type loginResolver struct {
|
||||
}
|
||||
|
||||
func (l *loginResolver) Resolve(ce *commonevent.Metadata, event *event.Event) error {
|
||||
e := &model.CommonEvent{
|
||||
Operator: ce.Username,
|
||||
ResourceType: rbac.ResourceUser.String(),
|
||||
ResourceName: ce.Username,
|
||||
OcurrAt: time.Now(),
|
||||
Operation: opLogin,
|
||||
OperationDescription: opLogin,
|
||||
IsSuccessful: true,
|
||||
}
|
||||
|
||||
// Extract the username from payload
|
||||
re := regexp.MustCompile(payloadPattern)
|
||||
if len(ce.RequestPayload) > 0 {
|
||||
match := re.FindStringSubmatch(ce.RequestPayload)
|
||||
if len(match) > 1 {
|
||||
e.ResourceName = match[1]
|
||||
e.Operator = match[1]
|
||||
}
|
||||
}
|
||||
if ce.ResponseCode != http.StatusOK {
|
||||
e.IsSuccessful = false
|
||||
}
|
||||
event.Topic = ctlEvent.TopicCommonEvent
|
||||
event.Data = e
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *loginResolver) PreCheck(ctx context.Context, _ string, method string) (bool, string) {
|
||||
operation := ""
|
||||
if method == http.MethodPost {
|
||||
operation = opLogin
|
||||
}
|
||||
if len(operation) == 0 {
|
||||
return false, ""
|
||||
}
|
||||
return config.AuditLogEventEnabled(ctx, fmt.Sprintf("%v_%v", operation, rbac.ResourceUser.String())), ""
|
||||
}
|
109
src/pkg/auditext/event/login/login_test.go
Normal file
109
src/pkg/auditext/event/login/login_test.go
Normal file
|
@ -0,0 +1,109 @@
|
|||
// Copyright Project Harbor Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package login
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/goharbor/harbor/src/controller/event/metadata/commonevent"
|
||||
"github.com/goharbor/harbor/src/controller/event/model"
|
||||
"github.com/goharbor/harbor/src/pkg/notifier/event"
|
||||
)
|
||||
|
||||
func Test_login_Resolve(t *testing.T) {
|
||||
type args struct {
|
||||
ce *commonevent.Metadata
|
||||
event *event.Event
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
l *loginResolver
|
||||
args args
|
||||
wantErr bool
|
||||
wantUsername string
|
||||
wantOperation string
|
||||
wantOperationDescription string
|
||||
wantIsSuccessful bool
|
||||
}{
|
||||
|
||||
{"test normal", &loginResolver{}, args{
|
||||
ce: &commonevent.Metadata{
|
||||
Username: "test",
|
||||
RequestURL: "/c/login",
|
||||
RequestMethod: "POST",
|
||||
Payload: "principal=test&password=123456",
|
||||
ResponseCode: 200,
|
||||
}, event: &event.Event{}}, false, "test", "login", "login", true},
|
||||
{"test fail", &loginResolver{}, args{
|
||||
ce: &commonevent.Metadata{
|
||||
Username: "test",
|
||||
RequestURL: "/c/login",
|
||||
RequestMethod: "POST",
|
||||
Payload: "principal=test&password=123456",
|
||||
ResponseCode: 401,
|
||||
}, event: &event.Event{}}, false, "test", "login", "login", false},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
l := &loginResolver{}
|
||||
if err := l.Resolve(tt.args.ce, tt.args.event); (err != nil) != tt.wantErr {
|
||||
t.Errorf("resolver.Resolve() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
if tt.args.event.Data.(*model.CommonEvent).Operator != tt.wantUsername {
|
||||
t.Errorf("resolver.Resolve() got = %v, want %v", tt.args.event.Data.(*model.CommonEvent).Operator, tt.wantUsername)
|
||||
}
|
||||
if tt.args.event.Data.(*model.CommonEvent).Operation != tt.wantOperation {
|
||||
t.Errorf("resolver.Resolve() got = %v, want %v", tt.args.event.Data.(*model.CommonEvent).Operation, tt.wantOperation)
|
||||
}
|
||||
if tt.args.event.Data.(*model.CommonEvent).OperationDescription != tt.wantOperationDescription {
|
||||
t.Errorf("resolver.Resolve() got = %v, want %v", tt.args.event.Data.(*model.CommonEvent).OperationDescription, tt.wantOperationDescription)
|
||||
}
|
||||
if tt.args.event.Data.(*model.CommonEvent).IsSuccessful != tt.wantIsSuccessful {
|
||||
t.Errorf("resolver.Resolve() got = %v, want %v", tt.args.event.Data.(*model.CommonEvent).IsSuccessful, tt.wantIsSuccessful)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_login_PreCheck(t *testing.T) {
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
url string
|
||||
method string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
e *loginResolver
|
||||
args args
|
||||
wantMatched bool
|
||||
wantResourceName string
|
||||
}{
|
||||
{"test normal", &loginResolver{}, args{context.Background(), "/c/login", "POST"}, true, ""},
|
||||
{"test fail method", &loginResolver{}, args{context.Background(), "/c/login", "PUT"}, false, ""},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
e := &loginResolver{}
|
||||
got, got1 := e.PreCheck(tt.args.ctx, tt.args.url, tt.args.method)
|
||||
if got != tt.wantMatched {
|
||||
t.Errorf("resolver.PreCheck() got = %v, want %v", got, tt.wantMatched)
|
||||
}
|
||||
if got1 != tt.wantResourceName {
|
||||
t.Errorf("resolver.PreCheck() got1 = %v, want %v", got1, tt.wantResourceName)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
61
src/pkg/auditext/event/login/logout.go
Normal file
61
src/pkg/auditext/event/login/logout.go
Normal file
|
@ -0,0 +1,61 @@
|
|||
// Copyright Project Harbor Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package login
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/goharbor/harbor/src/common/rbac"
|
||||
ctlEvent "github.com/goharbor/harbor/src/controller/event"
|
||||
"github.com/goharbor/harbor/src/controller/event/metadata/commonevent"
|
||||
"github.com/goharbor/harbor/src/controller/event/model"
|
||||
"github.com/goharbor/harbor/src/lib/config"
|
||||
"github.com/goharbor/harbor/src/pkg/notifier/event"
|
||||
)
|
||||
|
||||
type logoutResolver struct {
|
||||
}
|
||||
|
||||
func (l *logoutResolver) Resolve(ce *commonevent.Metadata, event *event.Event) error {
|
||||
e := &model.CommonEvent{
|
||||
Operator: ce.Username,
|
||||
ResourceType: rbac.ResourceUser.String(),
|
||||
ResourceName: ce.Username,
|
||||
OcurrAt: time.Now(),
|
||||
Operation: opLogout,
|
||||
OperationDescription: opLogout,
|
||||
IsSuccessful: true,
|
||||
}
|
||||
if ce.ResponseCode != http.StatusOK {
|
||||
e.IsSuccessful = false
|
||||
}
|
||||
event.Topic = ctlEvent.TopicCommonEvent
|
||||
event.Data = e
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *logoutResolver) PreCheck(ctx context.Context, _ string, method string) (bool, string) {
|
||||
operation := ""
|
||||
if method == http.MethodGet {
|
||||
operation = opLogout
|
||||
}
|
||||
if len(operation) == 0 {
|
||||
return false, ""
|
||||
}
|
||||
return config.AuditLogEventEnabled(ctx, fmt.Sprintf("%v_%v", operation, rbac.ResourceUser.String())), ""
|
||||
}
|
100
src/pkg/auditext/event/login/logout_test.go
Normal file
100
src/pkg/auditext/event/login/logout_test.go
Normal file
|
@ -0,0 +1,100 @@
|
|||
// Copyright Project Harbor Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package login
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/goharbor/harbor/src/controller/event/metadata/commonevent"
|
||||
"github.com/goharbor/harbor/src/controller/event/model"
|
||||
"github.com/goharbor/harbor/src/pkg/notifier/event"
|
||||
)
|
||||
|
||||
func Test_logout_Resolve(t *testing.T) {
|
||||
type args struct {
|
||||
ce *commonevent.Metadata
|
||||
event *event.Event
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
l *logoutResolver
|
||||
args args
|
||||
wantErr bool
|
||||
wantUsername string
|
||||
wantOperation string
|
||||
wantOperationDescription string
|
||||
wantIsSuccessful bool
|
||||
}{
|
||||
{"test logout", &logoutResolver{}, args{
|
||||
ce: &commonevent.Metadata{
|
||||
Username: "test",
|
||||
RequestURL: "/c/log_out",
|
||||
RequestMethod: "GET",
|
||||
Payload: "",
|
||||
ResponseCode: 200,
|
||||
}, event: &event.Event{}}, false, "test", "logout", "logout", true},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
l := &logoutResolver{}
|
||||
if err := l.Resolve(tt.args.ce, tt.args.event); (err != nil) != tt.wantErr {
|
||||
t.Errorf("resolver.Resolve() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
if tt.args.event.Data.(*model.CommonEvent).Operator != tt.wantUsername {
|
||||
t.Errorf("resolver.Resolve() got = %v, want %v", tt.args.event.Data.(*model.CommonEvent).Operator, tt.wantUsername)
|
||||
}
|
||||
if tt.args.event.Data.(*model.CommonEvent).Operation != tt.wantOperation {
|
||||
t.Errorf("resolver.Resolve() got = %v, want %v", tt.args.event.Data.(*model.CommonEvent).Operation, tt.wantOperation)
|
||||
}
|
||||
if tt.args.event.Data.(*model.CommonEvent).OperationDescription != tt.wantOperationDescription {
|
||||
t.Errorf("resolver.Resolve() got = %v, want %v", tt.args.event.Data.(*model.CommonEvent).OperationDescription, tt.wantOperationDescription)
|
||||
}
|
||||
if tt.args.event.Data.(*model.CommonEvent).IsSuccessful != tt.wantIsSuccessful {
|
||||
t.Errorf("resolver.Resolve() got = %v, want %v", tt.args.event.Data.(*model.CommonEvent).IsSuccessful, tt.wantIsSuccessful)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_logout_PreCheck(t *testing.T) {
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
url string
|
||||
method string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
e *logoutResolver
|
||||
args args
|
||||
wantMatched bool
|
||||
wantResourceName string
|
||||
}{
|
||||
{"test normal", &logoutResolver{}, args{context.Background(), "/c/log_out", "GET"}, true, ""},
|
||||
{"test fail wrong url", &logoutResolver{}, args{context.Background(), "/c/logout", "DELETE"}, false, ""},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
e := &logoutResolver{}
|
||||
got, got1 := e.PreCheck(tt.args.ctx, tt.args.url, tt.args.method)
|
||||
if got != tt.wantMatched {
|
||||
t.Errorf("resolver.PreCheck() got = %v, want %v", got, tt.wantMatched)
|
||||
}
|
||||
if got1 != tt.wantResourceName {
|
||||
t.Errorf("resolver.PreCheck() got1 = %v, want %v", got1, tt.wantResourceName)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -30,7 +30,7 @@ type AuditLogExt struct {
|
|||
ProjectID int64 `orm:"column(project_id)" json:"project_id"`
|
||||
Operation string `orm:"column(operation)" json:"operation"`
|
||||
OperationDescription string `orm:"column(op_desc)" json:"operation_description"`
|
||||
OperationResult bool `orm:"column(op_result)" json:"operation_result"`
|
||||
IsSuccessful bool `orm:"column(op_result)" json:"is_successful"`
|
||||
ResourceType string `orm:"column(resource_type)" json:"resource_type"`
|
||||
Resource string `orm:"column(resource)" json:"resource"`
|
||||
Username string `orm:"column(username)" json:"username"`
|
||||
|
|
|
@ -191,7 +191,7 @@ func convertToModelAuditLogExt(logs []*model.AuditLogExt) []*models.AuditLogExt
|
|||
Username: log.Username,
|
||||
Operation: log.Operation,
|
||||
OperationDescription: log.OperationDescription,
|
||||
OperationResult: log.OperationResult,
|
||||
OperationResult: log.IsSuccessful,
|
||||
OpTime: strfmt.DateTime(log.OpTime),
|
||||
})
|
||||
}
|
||||
|
|
|
@ -111,7 +111,7 @@ class Project(base.Base):
|
|||
base._assert_status_code(expect_status_code, status_code)
|
||||
|
||||
def get_project_log(self, project_name, expect_status_code = 200, **kwargs):
|
||||
body, status_code, _ = self._get_client(**kwargs).get_logs_with_http_info(project_name)
|
||||
body, status_code, _ = self._get_client(**kwargs).get_log_exts_with_http_info(project_name)
|
||||
base._assert_status_code(expect_status_code, status_code)
|
||||
return body
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ class ProjectV2(base.Base, object):
|
|||
super(ProjectV2,self).__init__(api_type = "projectv2")
|
||||
|
||||
def get_project_log(self, project_name, expect_status_code = 200, **kwargs):
|
||||
body, status_code, _ = self._get_client(**kwargs).get_logs_with_http_info(project_name)
|
||||
body, status_code, _ = self._get_client(**kwargs).get_log_exts_with_http_info(project_name)
|
||||
base._assert_status_code(expect_status_code, status_code)
|
||||
return body
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user