enable audit log for robot (#20843)

1, add creation audit log for robot account
2, add deletion audit log for robot account

Signed-off-by: wang yan <wangyan@vmware.com>
This commit is contained in:
Wang Yan 2024-08-14 19:57:36 +08:00 committed by GitHub
parent 8107f47e12
commit 8ad8827e28
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 238 additions and 3 deletions

View File

@ -45,7 +45,8 @@ func (h *Handler) Handle(ctx context.Context, value interface{}) error {
switch v := value.(type) {
case *event.PushArtifactEvent, *event.DeleteArtifactEvent,
*event.DeleteRepositoryEvent, *event.CreateProjectEvent, *event.DeleteProjectEvent,
*event.DeleteTagEvent, *event.CreateTagEvent:
*event.DeleteTagEvent, *event.CreateTagEvent,
*event.CreateRobotEvent, *event.DeleteRobotEvent:
addAuditLog = true
case *event.PullArtifactEvent:
addAuditLog = !config.PullAuditLogDisable(ctx)

View File

@ -65,6 +65,8 @@ func init() {
_ = notifier.Subscribe(event.TopicDeleteRepository, &auditlog.Handler{})
_ = notifier.Subscribe(event.TopicCreateTag, &auditlog.Handler{})
_ = notifier.Subscribe(event.TopicDeleteTag, &auditlog.Handler{})
_ = notifier.Subscribe(event.TopicCreateRobot, &auditlog.Handler{})
_ = notifier.Subscribe(event.TopicDeleteRobot, &auditlog.Handler{})
// internal
_ = notifier.Subscribe(event.TopicPullArtifact, &internal.ArtifactEventHandler{})

View File

@ -0,0 +1,73 @@
// 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 metadata
import (
"context"
"fmt"
"time"
"github.com/goharbor/harbor/src/common/security"
event2 "github.com/goharbor/harbor/src/controller/event"
"github.com/goharbor/harbor/src/lib/config"
"github.com/goharbor/harbor/src/pkg/notifier/event"
"github.com/goharbor/harbor/src/pkg/robot/model"
)
// CreateRobotEventMetadata is the metadata from which the create robot event can be resolved
type CreateRobotEventMetadata struct {
Ctx context.Context
Robot *model.Robot
}
// Resolve to the event from the metadata
func (c *CreateRobotEventMetadata) Resolve(event *event.Event) error {
data := &event2.CreateRobotEvent{
EventType: event2.TopicCreateRobot,
Robot: c.Robot,
OccurAt: time.Now(),
}
cx, exist := security.FromContext(c.Ctx)
if exist {
data.Operator = cx.GetUsername()
}
data.Robot.Name = fmt.Sprintf("%s%s", config.RobotPrefix(c.Ctx), data.Robot.Name)
event.Topic = event2.TopicCreateRobot
event.Data = data
return nil
}
// DeleteRobotEventMetadata is the metadata from which the delete robot event can be resolved
type DeleteRobotEventMetadata struct {
Ctx context.Context
Robot *model.Robot
}
// Resolve to the event from the metadata
func (d *DeleteRobotEventMetadata) Resolve(event *event.Event) error {
data := &event2.DeleteRobotEvent{
EventType: event2.TopicDeleteRobot,
Robot: d.Robot,
OccurAt: time.Now(),
}
cx, exist := security.FromContext(d.Ctx)
if exist {
data.Operator = cx.GetUsername()
}
data.Robot.Name = fmt.Sprintf("%s%s", config.RobotPrefix(d.Ctx), data.Robot.Name)
event.Topic = event2.TopicDeleteRobot
event.Data = data
return nil
}

View File

@ -0,0 +1,83 @@
// 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 metadata
import (
"context"
"testing"
"github.com/stretchr/testify/suite"
"github.com/goharbor/harbor/src/common"
event2 "github.com/goharbor/harbor/src/controller/event"
"github.com/goharbor/harbor/src/lib/config"
_ "github.com/goharbor/harbor/src/pkg/config/inmemory"
"github.com/goharbor/harbor/src/pkg/notifier/event"
"github.com/goharbor/harbor/src/pkg/robot/model"
)
type robotEventTestSuite struct {
suite.Suite
}
func (t *tagEventTestSuite) TestResolveOfCreateRobotEventMetadata() {
cfg := map[string]interface{}{
common.RobotPrefix: "robot$",
}
config.InitWithSettings(cfg)
e := &event.Event{}
metadata := &CreateRobotEventMetadata{
Ctx: context.Background(),
Robot: &model.Robot{
ID: 1,
Name: "test",
},
}
err := metadata.Resolve(e)
t.Require().Nil(err)
t.Equal(event2.TopicCreateRobot, e.Topic)
t.Require().NotNil(e.Data)
data, ok := e.Data.(*event2.CreateRobotEvent)
t.Require().True(ok)
t.Equal(int64(1), data.Robot.ID)
t.Equal("robot$test", data.Robot.Name)
}
func (t *tagEventTestSuite) TestResolveOfDeleteRobotEventMetadata() {
cfg := map[string]interface{}{
common.RobotPrefix: "robot$",
}
config.InitWithSettings(cfg)
e := &event.Event{}
metadata := &DeleteRobotEventMetadata{
Ctx: context.Background(),
Robot: &model.Robot{
ID: 1,
},
}
err := metadata.Resolve(e)
t.Require().Nil(err)
t.Equal(event2.TopicDeleteRobot, e.Topic)
t.Require().NotNil(e.Data)
data, ok := e.Data.(*event2.DeleteRobotEvent)
t.Require().True(ok)
t.Equal(int64(1), data.Robot.ID)
}
func TestRobotEventTestSuite(t *testing.T) {
suite.Run(t, &robotEventTestSuite{})
}

View File

@ -23,6 +23,7 @@ import (
"github.com/goharbor/harbor/src/pkg/artifact"
"github.com/goharbor/harbor/src/pkg/audit/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"
)
@ -47,6 +48,8 @@ const (
TopicReplication = "REPLICATION"
TopicArtifactLabeled = "ARTIFACT_LABELED"
TopicTagRetention = "TAG_RETENTION"
TopicCreateRobot = "CREATE_ROBOT"
TopicDeleteRobot = "DELETE_ROBOT"
)
// CreateProjectEvent is the creating project event
@ -369,3 +372,53 @@ func (r *RetentionEvent) String() string {
return fmt.Sprintf("TaskID-%d Status-%s Deleted-%s OccurAt-%s",
r.TaskID, r.Status, candidates, r.OccurAt.Format("2006-01-02 15:04:05"))
}
// CreateRobotEvent is the creating robot event
type CreateRobotEvent struct {
EventType string
Robot *robotModel.Robot
Operator string
OccurAt time.Time
}
// 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}
return auditLog, nil
}
func (c *CreateRobotEvent) String() string {
return fmt.Sprintf("Name-%s Operator-%s OccurAt-%s",
c.Robot.Name, c.Operator, c.OccurAt.Format("2006-01-02 15:04:05"))
}
// DeleteRobotEvent is the deleting robot event
type DeleteRobotEvent struct {
EventType string
Robot *robotModel.Robot
Operator string
OccurAt time.Time
}
// 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}
return auditLog, nil
}
func (c *DeleteRobotEvent) String() string {
return fmt.Sprintf("Name-%s Operator-%s OccurAt-%s",
c.Robot.Name, c.Operator, c.OccurAt.Format("2006-01-02 15:04:05"))
}

View File

@ -23,12 +23,14 @@ import (
rbac_project "github.com/goharbor/harbor/src/common/rbac/project"
"github.com/goharbor/harbor/src/common/utils"
"github.com/goharbor/harbor/src/controller/event/metadata"
"github.com/goharbor/harbor/src/lib/config"
"github.com/goharbor/harbor/src/lib/errors"
"github.com/goharbor/harbor/src/lib/log"
"github.com/goharbor/harbor/src/lib/q"
"github.com/goharbor/harbor/src/lib/retry"
"github.com/goharbor/harbor/src/pkg"
"github.com/goharbor/harbor/src/pkg/notification"
"github.com/goharbor/harbor/src/pkg/permission/types"
"github.com/goharbor/harbor/src/pkg/project"
"github.com/goharbor/harbor/src/pkg/rbac"
@ -121,7 +123,7 @@ func (d *controller) Create(ctx context.Context, r *Robot) (int64, string, error
if r.Level == LEVELPROJECT {
name = fmt.Sprintf("%s+%s", r.ProjectName, r.Name)
}
robotID, err := d.robotMgr.Create(ctx, &model.Robot{
rCreate := &model.Robot{
Name: name,
Description: r.Description,
ProjectID: r.ProjectID,
@ -130,7 +132,8 @@ func (d *controller) Create(ctx context.Context, r *Robot) (int64, string, error
Duration: r.Duration,
Salt: salt,
Visible: r.Visible,
})
}
robotID, err := d.robotMgr.Create(ctx, rCreate)
if err != nil {
return 0, "", err
}
@ -138,17 +141,31 @@ func (d *controller) Create(ctx context.Context, r *Robot) (int64, string, error
if err := d.createPermission(ctx, r); err != nil {
return 0, "", err
}
// fire event
notification.AddEvent(ctx, &metadata.CreateRobotEventMetadata{
Ctx: ctx,
Robot: rCreate,
})
return robotID, pwd, nil
}
// Delete ...
func (d *controller) Delete(ctx context.Context, id int64) error {
rDelete, err := d.robotMgr.Get(ctx, id)
if err != nil {
return err
}
if err := d.robotMgr.Delete(ctx, id); err != nil {
return err
}
if err := d.rbacMgr.DeletePermissionsByRole(ctx, ROBOTTYPE, id); err != nil {
return err
}
// fire event
notification.AddEvent(ctx, &metadata.DeleteRobotEventMetadata{
Ctx: ctx,
Robot: rDelete,
})
return nil
}

View File

@ -145,6 +145,12 @@ func (suite *ControllerTestSuite) TestDelete() {
c := controller{robotMgr: robotMgr, rbacMgr: rbacMgr, proMgr: projectMgr}
ctx := context.TODO()
robotMgr.On("Get", mock.Anything, mock.Anything).Return(&model.Robot{
Name: "library+test",
Description: "test get method",
ProjectID: 1,
Secret: utils.RandStringBytes(10),
}, nil)
robotMgr.On("Delete", mock.Anything, mock.Anything).Return(nil)
rbacMgr.On("DeletePermissionsByRole", mock.Anything, mock.Anything, mock.Anything).Return(nil)