Files
Matthew Jacobson ff6a20f54a Alerting: Include access control metadata in k8s receiver LIST & GET (#93013)
* Include access control metadata in k8s receiver List & Get

* Add tests for receiver access

* Simplify receiver access provisioning extension

- prevents edge case infinite recursion
- removes read requirement from create
2024-09-12 20:57:53 +03:00

133 lines
4.5 KiB
Go

package receiver
import (
"fmt"
"maps"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/fields"
"k8s.io/apimachinery/pkg/types"
common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1"
model "github.com/grafana/grafana/pkg/apis/alerting_notifications/v0alpha1"
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
"github.com/grafana/grafana/pkg/services/ngalert/notifier/legacy_storage"
)
func convertToK8sResources(orgID int64, receivers []*ngmodels.Receiver, accesses map[string]ngmodels.ReceiverPermissionSet, namespacer request.NamespaceMapper, selector fields.Selector) (*model.ReceiverList, error) {
result := &model.ReceiverList{
Items: make([]model.Receiver, 0, len(receivers)),
}
for _, receiver := range receivers {
var access *ngmodels.ReceiverPermissionSet
if accesses != nil {
if a, ok := accesses[receiver.GetUID()]; ok {
access = &a
}
}
k8sResource, err := convertToK8sResource(orgID, receiver, access, namespacer)
if err != nil {
return nil, err
}
if selector != nil && !selector.Empty() && !selector.Matches(model.SelectableReceiverFields(k8sResource)) {
continue
}
result.Items = append(result.Items, *k8sResource)
}
return result, nil
}
func convertToK8sResource(orgID int64, receiver *ngmodels.Receiver, access *ngmodels.ReceiverPermissionSet, namespacer request.NamespaceMapper) (*model.Receiver, error) {
spec := model.ReceiverSpec{
Title: receiver.Name,
}
for _, integration := range receiver.Integrations {
spec.Integrations = append(spec.Integrations, model.Integration{
Uid: &integration.UID,
Type: integration.Config.Type,
DisableResolveMessage: &integration.DisableResolveMessage,
Settings: common.Unstructured{Object: maps.Clone(integration.Settings)},
SecureFields: integration.SecureFields(),
})
}
r := &model.Receiver{
TypeMeta: resourceInfo.TypeMeta(),
ObjectMeta: metav1.ObjectMeta{
UID: types.UID(receiver.GetUID()), // This is needed to make PATCH work
Name: receiver.GetUID(),
Namespace: namespacer(orgID),
ResourceVersion: receiver.Version,
},
Spec: spec,
}
r.SetProvenanceStatus(string(receiver.Provenance))
if access != nil {
for _, action := range ngmodels.ReceiverPermissions() {
mappedAction, ok := permissionMapper[action]
if !ok {
return nil, fmt.Errorf("unknown action %v", action)
}
if can, _ := access.Has(action); can {
r.SetAccessControl(mappedAction)
}
}
}
return r, nil
}
var permissionMapper = map[ngmodels.ReceiverPermission]string{
ngmodels.ReceiverPermissionReadSecret: "canReadSecrets",
//ngmodels.ReceiverPermissionAdmin: "canAdmin", // TODO: Add when resource permissions are implemented.
ngmodels.ReceiverPermissionWrite: "canWrite",
ngmodels.ReceiverPermissionDelete: "canDelete",
}
func convertToDomainModel(receiver *model.Receiver) (*ngmodels.Receiver, map[string][]string, error) {
domain := &ngmodels.Receiver{
UID: legacy_storage.NameToUid(receiver.Spec.Title),
Name: receiver.Spec.Title,
Integrations: make([]*ngmodels.Integration, 0, len(receiver.Spec.Integrations)),
Version: receiver.ResourceVersion,
Provenance: ngmodels.ProvenanceNone,
}
storedSecureFields := make(map[string][]string, len(receiver.Spec.Integrations))
for _, integration := range receiver.Spec.Integrations {
config, err := ngmodels.IntegrationConfigFromType(integration.Type)
if err != nil {
return nil, nil, err
}
grafanaIntegration := ngmodels.Integration{
Name: receiver.Spec.Title,
Config: config,
Settings: maps.Clone(integration.Settings.UnstructuredContent()),
SecureSettings: make(map[string]string),
}
if integration.Uid != nil {
grafanaIntegration.UID = *integration.Uid
}
if integration.DisableResolveMessage != nil {
grafanaIntegration.DisableResolveMessage = *integration.DisableResolveMessage
}
domain.Integrations = append(domain.Integrations, &grafanaIntegration)
if grafanaIntegration.UID != "" {
// This is an existing integration, so we track the secure fields being requested to copy over from existing values.
secureFields := make([]string, 0, len(integration.SecureFields))
for k, isSecure := range integration.SecureFields {
if isSecure {
secureFields = append(secureFields, k)
}
}
storedSecureFields[grafanaIntegration.UID] = secureFields
}
}
return domain, storedSecureFields, nil
}