Skip to content

Commit

Permalink
enable audit log for robot (#20843)
Browse files Browse the repository at this point in the history
1, add creation audit log for robot account
2, add deletion audit log for robot account

Signed-off-by: wang yan <wangyan@vmware.com>
  • Loading branch information
wy65701436 authored Aug 14, 2024
1 parent 8107f47 commit 8ad8827
Show file tree
Hide file tree
Showing 7 changed files with 238 additions and 3 deletions.
3 changes: 2 additions & 1 deletion src/controller/event/handler/auditlog/auditlog.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 2 additions & 0 deletions src/controller/event/handler/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{})
Expand Down
73 changes: 73 additions & 0 deletions src/controller/event/metadata/robot.go
Original file line number Diff line number Diff line change
@@ -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
}
83 changes: 83 additions & 0 deletions src/controller/event/metadata/robot_test.go
Original file line number Diff line number Diff line change
@@ -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{})
}
53 changes: 53 additions & 0 deletions src/controller/event/topic.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)

Expand All @@ -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
Expand Down Expand Up @@ -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"))
}
21 changes: 19 additions & 2 deletions src/controller/robot/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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,
Expand All @@ -130,25 +132,40 @@ 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
}
r.ID = robotID
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
}

Expand Down
6 changes: 6 additions & 0 deletions src/controller/robot/controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down

0 comments on commit 8ad8827

Please sign in to comment.