-
Notifications
You must be signed in to change notification settings - Fork 4.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Signed-off-by: stonezdj <[email protected]>
- Loading branch information
Showing
9 changed files
with
647 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,157 @@ | ||
// 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 event | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"net/http" | ||
"regexp" | ||
"time" | ||
|
||
"slices" | ||
|
||
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" | ||
) | ||
|
||
const ( | ||
create = "create" | ||
update = "update" | ||
delete = "delete" | ||
resourceIDPattern = `^%v/(\d+)$` | ||
) | ||
|
||
// ResolveIDToNameFunc is the function to resolve the resource name from resource id | ||
type ResolveIDToNameFunc func(string) string | ||
|
||
type EventResolver struct { | ||
BaseURLPattern string | ||
ResourceType string | ||
SucceedCodes []int | ||
// SensitiveAttributes is the attributes that need to be redacted | ||
SensitiveAttributes []string | ||
// HasResourceName indicates if the resource has name, if true, need to resolve the resource name before delete | ||
HasResourceName bool | ||
// IDToNameFunc is used to resolve the resource name from resource id | ||
IDToNameFunc ResolveIDToNameFunc | ||
} | ||
|
||
// PreCheck check if the event should be captured and resolve the resource name if needed, if need to resolve the resource name, return the resource name | ||
func (e *EventResolver) PreCheck(ctx context.Context, url string, method string) (capture bool, resourceName string) { | ||
capture = config.AuditLogEventEnabled(ctx, fmt.Sprintf("%v_%v", MethodToOperation(method), e.ResourceType)) | ||
if !capture { | ||
return false, "" | ||
} | ||
// for delete operation on a resource has name, need to resolve the resource id to resource name before delete | ||
resName := "" | ||
if capture && method == http.MethodDelete && e.HasResourceName { | ||
re := regexp.MustCompile(fmt.Sprintf(resourceIDPattern, e.BaseURLPattern)) | ||
m := re.FindStringSubmatch(url) | ||
if len(m) == 2 && e.IDToNameFunc != nil { | ||
resName = e.IDToNameFunc(m[1]) | ||
} | ||
} | ||
return true, resName | ||
} | ||
|
||
// Resolve ... | ||
func (e *EventResolver) Resolve(ce *commonevent.Metadata, event *event.Event) error { | ||
if ce.RequestMethod != http.MethodPost && ce.RequestMethod != http.MethodDelete && ce.RequestMethod != http.MethodPut { | ||
return nil | ||
} | ||
evt := &model.CommonEvent{ | ||
Operator: ce.Username, | ||
ResourceType: e.ResourceType, | ||
OcurrAt: time.Now(), | ||
IsSuccessful: true, | ||
} | ||
resourceName := "" | ||
operation := MethodToOperation(ce.RequestMethod) | ||
if len(operation) == 0 { | ||
return nil | ||
} | ||
evt.Operation = operation | ||
|
||
switch evt.Operation { | ||
case create: | ||
if len(ce.ResponseLocation) > 0 { | ||
// extract resource id from response location | ||
re := regexp.MustCompile(fmt.Sprintf(resourceIDPattern, e.BaseURLPattern)) | ||
m := re.FindStringSubmatch(ce.ResponseLocation) | ||
if len(m) != 2 { | ||
return nil | ||
} | ||
evt.ResourceName = m[1] | ||
if e.IDToNameFunc != nil { | ||
resourceName = e.IDToNameFunc(m[1]) | ||
} | ||
} | ||
if e.HasResourceName && resourceName != "" { | ||
evt.ResourceName = resourceName | ||
} | ||
|
||
case delete: | ||
re := regexp.MustCompile(fmt.Sprintf(resourceIDPattern, e.BaseURLPattern)) | ||
m := re.FindStringSubmatch(ce.RequestURL) | ||
if len(m) != 2 { | ||
return nil | ||
} | ||
evt.ResourceName = m[1] | ||
if e.HasResourceName && ce.ResourceName != "" { | ||
evt.ResourceName = ce.ResourceName | ||
} | ||
|
||
case update: | ||
re := regexp.MustCompile(fmt.Sprintf(resourceIDPattern, e.BaseURLPattern)) | ||
m := re.FindStringSubmatch(ce.RequestURL) | ||
if len(m) != 2 { | ||
return nil | ||
} | ||
evt.ResourceName = m[1] | ||
if e.IDToNameFunc != nil { | ||
resourceName = e.IDToNameFunc(m[1]) | ||
} | ||
if e.HasResourceName && resourceName != "" { | ||
evt.ResourceName = resourceName | ||
} | ||
} | ||
|
||
evt.OperationDescription = fmt.Sprintf("%s %s with name: %s", evt.Operation, e.ResourceType, evt.ResourceName) | ||
|
||
if !slices.Contains(e.SucceedCodes, ce.ResponseCode) { | ||
evt.IsSuccessful = false | ||
} | ||
|
||
event.Topic = ctlEvent.TopicCommonEvent | ||
event.Data = evt | ||
return nil | ||
} | ||
|
||
// MethodToOperation converts HTTP method to operation | ||
func MethodToOperation(method string) string { | ||
switch method { | ||
case http.MethodPost: | ||
return create | ||
case http.MethodDelete: | ||
return delete | ||
case http.MethodPut: | ||
return update | ||
} | ||
return "" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
// 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 event | ||
|
||
import ( | ||
"context" | ||
"testing" | ||
) | ||
|
||
func TestEventResolver_PreCheck(t *testing.T) { | ||
type fields struct { | ||
BaseURLPattern string | ||
ResourceType string | ||
SucceedCodes []int | ||
SensitiveAttributes []string | ||
HasResourceName bool | ||
IDToNameFunc ResolveIDToNameFunc | ||
} | ||
type args struct { | ||
ctx context.Context | ||
url string | ||
method string | ||
} | ||
tests := []struct { | ||
name string | ||
fields fields | ||
args args | ||
wantCapture bool | ||
wantResourceName string | ||
}{ | ||
{"test normal", fields{BaseURLPattern: `/api/v2.0/tests`, ResourceType: "test", SucceedCodes: []int{200}, HasResourceName: true, IDToNameFunc: func(string) string { return "test" }}, args{context.Background(), "/api/v2.0/tests/123", "DELETE"}, true, "test"}, | ||
{"test resource name", fields{BaseURLPattern: `/api/v2.0/tests`, ResourceType: "test", SucceedCodes: []int{200}, HasResourceName: true, IDToNameFunc: func(string) string { return "test_resource_name" }}, args{context.Background(), "/api/v2.0/tests/234", "DELETE"}, true, "test_resource_name"}, | ||
{"test no resource name", fields{BaseURLPattern: `/api/v2.0/tests`, ResourceType: "test", SucceedCodes: []int{200}, HasResourceName: true}, args{context.Background(), "/api/v2.0/tests/234", "GET"}, true, ""}, | ||
} | ||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
e := &EventResolver{ | ||
BaseURLPattern: tt.fields.BaseURLPattern, | ||
ResourceType: tt.fields.ResourceType, | ||
SucceedCodes: tt.fields.SucceedCodes, | ||
SensitiveAttributes: tt.fields.SensitiveAttributes, | ||
HasResourceName: tt.fields.HasResourceName, | ||
IDToNameFunc: tt.fields.IDToNameFunc, | ||
} | ||
gotCapture, gotResourceName := e.PreCheck(tt.args.ctx, tt.args.url, tt.args.method) | ||
if gotCapture != tt.wantCapture { | ||
t.Errorf("EventResolver.PreCheck() gotCapture = %v, want %v", gotCapture, tt.wantCapture) | ||
} | ||
if gotResourceName != tt.wantResourceName { | ||
t.Errorf("EventResolver.PreCheck() gotResourceName = %v, want %v", gotResourceName, tt.wantResourceName) | ||
} | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
// 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 config | ||
|
||
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" | ||
ext "github.com/goharbor/harbor/src/pkg/auditext/event" | ||
"github.com/goharbor/harbor/src/pkg/notifier/event" | ||
) | ||
|
||
func init() { | ||
var configureEventResolver = &resolver{ | ||
SensitiveAttributes: []string{"ldap_password", "oidc_client_secret"}, | ||
} | ||
commonevent.RegisterResolver(`/api/v2.0/configurations`, configureEventResolver) | ||
} | ||
|
||
const payloadSizeLimit = 450 | ||
|
||
// resolver used to resolve the configuration event | ||
type resolver struct { | ||
SensitiveAttributes []string | ||
} | ||
|
||
func (c *resolver) Resolve(ce *commonevent.Metadata, evt *event.Event) error { | ||
e := &model.CommonEvent{} | ||
e.Operation = "update" | ||
e.Operator = ce.Username | ||
e.ResourceType = rbac.ResourceConfiguration.String() | ||
e.ResourceName = rbac.ResourceConfiguration.String() | ||
e.Payload = ext.Redact(ce.RequestPayload, c.SensitiveAttributes) | ||
e.OcurrAt = time.Now() | ||
if len(ce.RequestPayload) > payloadSizeLimit { | ||
ce.RequestPayload = fmt.Sprintf("%v...", ce.RequestPayload[:payloadSizeLimit]) | ||
} | ||
e.OperationDescription = fmt.Sprintf("update configuration: %v", ce.RequestPayload) | ||
if ce.ResponseCode == http.StatusOK { | ||
e.IsSuccessful = true | ||
} | ||
evt.Topic = ctlEvent.TopicCommonEvent | ||
evt.Data = e | ||
return nil | ||
} | ||
|
||
func (c *resolver) PreCheck(ctx context.Context, url string, method string) (bool, string) { | ||
if method != http.MethodPut { | ||
return false, "" | ||
} | ||
return config.AuditLogEventEnabled(ctx, fmt.Sprintf("%v_%v", ext.MethodToOperation(method), rbac.ResourceConfiguration.String())), "" | ||
} |
Oops, something went wrong.