mirror of
https://github.com/grafana/grafana.git
synced 2025-12-20 16:54:59 +08:00
Compare commits
2 Commits
docs/add-t
...
macabu/poc
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
faf366b2fa | ||
|
|
6bce5f2d73 |
89
pkg/apiserver/auditing/event.go
Normal file
89
pkg/apiserver/auditing/event.go
Normal file
@@ -0,0 +1,89 @@
|
||||
package auditing
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Event struct {
|
||||
// The namespace the action was performed in.
|
||||
Namespace string `json:"namespace"`
|
||||
|
||||
// When it happened.
|
||||
ObservedAt time.Time `json:"observedAt"`
|
||||
|
||||
// Who/what performed the action.
|
||||
SubjectName string `json:"subjectName"`
|
||||
SubjectUID string `json:"subjectUID"`
|
||||
|
||||
// What was performed.
|
||||
Verb string `json:"verb"`
|
||||
|
||||
// The object the action was performed on. For verbs like "list" this will be empty.
|
||||
Object string `json:"object,omitempty"`
|
||||
|
||||
// API information.
|
||||
APIGroup string `json:"apiGroup,omitempty"`
|
||||
APIVersion string `json:"apiVersion,omitempty"`
|
||||
Kind string `json:"kind,omitempty"`
|
||||
|
||||
// Outcome of the action.
|
||||
Outcome EventOutcome `json:"outcome"`
|
||||
|
||||
// Extra fields to add more context to the event.
|
||||
Extra map[string]string `json:"extra,omitempty"`
|
||||
}
|
||||
|
||||
var _ Sinkable = &Event{}
|
||||
|
||||
func (e Event) Time() time.Time {
|
||||
return e.ObservedAt
|
||||
}
|
||||
|
||||
func (e Event) MarshalJSON() ([]byte, error) {
|
||||
type Alias Event
|
||||
return json.Marshal(&struct {
|
||||
FormattedTimestamp string `json:"timestamp"`
|
||||
Alias
|
||||
}{
|
||||
FormattedTimestamp: e.ObservedAt.UTC().Format(time.RFC3339Nano),
|
||||
Alias: (Alias)(e),
|
||||
})
|
||||
}
|
||||
|
||||
func (e Event) KVPairs() []any {
|
||||
args := []any{
|
||||
"audit", true,
|
||||
"namespace", e.Namespace,
|
||||
"observedAt", e.ObservedAt.UTC().Format(time.RFC3339Nano),
|
||||
"subjectName", e.SubjectName,
|
||||
"subjectUID", e.SubjectUID,
|
||||
"verb", e.Verb,
|
||||
"object", e.Object,
|
||||
"apiGroup", e.APIGroup,
|
||||
"apiVersion", e.APIVersion,
|
||||
"kind", e.Kind,
|
||||
"outcome", e.Outcome,
|
||||
}
|
||||
|
||||
if len(e.Extra) > 0 {
|
||||
extraArgs := make([]any, 0, len(e.Extra)*2)
|
||||
for k, v := range e.Extra {
|
||||
extraArgs = append(extraArgs, "extra_"+k, v)
|
||||
}
|
||||
|
||||
args = append(args, extraArgs...)
|
||||
}
|
||||
|
||||
return args
|
||||
}
|
||||
|
||||
type EventOutcome string
|
||||
|
||||
const (
|
||||
EventOutcomeUnknown EventOutcome = "unknown"
|
||||
EventOutcomeSuccess EventOutcome = "success"
|
||||
EventOutcomeFailureUnauthorized EventOutcome = "failure_unauthorized"
|
||||
EventOutcomeFailureNotFound EventOutcome = "failure_not_found"
|
||||
EventOutcomeFailureGeneric EventOutcome = "failure_generic"
|
||||
)
|
||||
45
pkg/apiserver/auditing/logger.go
Normal file
45
pkg/apiserver/auditing/logger.go
Normal file
@@ -0,0 +1,45 @@
|
||||
package auditing
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"time"
|
||||
)
|
||||
|
||||
type loggerContextKey struct{}
|
||||
|
||||
var (
|
||||
DefaultLogger Logger
|
||||
|
||||
contextKey = loggerContextKey{}
|
||||
)
|
||||
|
||||
type Logger interface {
|
||||
Log(event Sinkable) error
|
||||
Type() string
|
||||
Close() error
|
||||
}
|
||||
|
||||
type Sinkable interface {
|
||||
json.Marshaler
|
||||
KVPairs() []any
|
||||
Time() time.Time
|
||||
}
|
||||
|
||||
func FromContext(ctx context.Context) Logger {
|
||||
if l := ctx.Value(contextKey); l != nil {
|
||||
if logger, ok := l.(Logger); ok {
|
||||
return logger
|
||||
}
|
||||
}
|
||||
|
||||
if DefaultLogger != nil {
|
||||
return DefaultLogger
|
||||
}
|
||||
|
||||
return &NoopLogger{}
|
||||
}
|
||||
|
||||
func Context(ctx context.Context, logger Logger) context.Context {
|
||||
return context.WithValue(ctx, contextKey, logger)
|
||||
}
|
||||
18
pkg/apiserver/auditing/noop_backend.go
Normal file
18
pkg/apiserver/auditing/noop_backend.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package auditing
|
||||
|
||||
import (
|
||||
auditinternal "k8s.io/apiserver/pkg/apis/audit"
|
||||
"k8s.io/apiserver/pkg/audit"
|
||||
)
|
||||
|
||||
type NoopBackend struct{}
|
||||
|
||||
func ProvideNoopBackend() audit.Backend { return &NoopBackend{} }
|
||||
|
||||
func (b *NoopBackend) ProcessEvents(k8sEvents ...*auditinternal.Event) bool { return false }
|
||||
|
||||
func (NoopBackend) Run(stopCh <-chan struct{}) error { return nil }
|
||||
|
||||
func (NoopBackend) Shutdown() {}
|
||||
|
||||
func (NoopBackend) String() string { return "" }
|
||||
9
pkg/apiserver/auditing/noop_logger.go
Normal file
9
pkg/apiserver/auditing/noop_logger.go
Normal file
@@ -0,0 +1,9 @@
|
||||
package auditing
|
||||
|
||||
type NoopLogger struct{}
|
||||
|
||||
func (*NoopLogger) Log(Sinkable) error { return nil }
|
||||
|
||||
func (*NoopLogger) Type() string { return "noop" }
|
||||
|
||||
func (*NoopLogger) Close() error { return nil }
|
||||
99
pkg/apiserver/auditing/policy_rule_evaluator.go
Normal file
99
pkg/apiserver/auditing/policy_rule_evaluator.go
Normal file
@@ -0,0 +1,99 @@
|
||||
package auditing
|
||||
|
||||
import (
|
||||
"slices"
|
||||
|
||||
"github.com/grafana/grafana/pkg/apimachinery/utils"
|
||||
"github.com/grafana/grafana/pkg/services/apiserver/builder"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
auditinternal "k8s.io/apiserver/pkg/apis/audit"
|
||||
"k8s.io/apiserver/pkg/audit"
|
||||
"k8s.io/apiserver/pkg/authentication/user"
|
||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||
)
|
||||
|
||||
// PolicyRuleEvaluator alias for easier imports.
|
||||
type PolicyRuleEvaluator = audit.PolicyRuleEvaluator
|
||||
|
||||
// UnionPolicyRuleEvaluator dispatches to the specific PolicyRuleEvaluator depending on the API group+version in the request.
|
||||
type UnionPolicyRuleEvaluator struct {
|
||||
evaluators map[schema.GroupVersion]PolicyRuleEvaluator
|
||||
}
|
||||
|
||||
var _ PolicyRuleEvaluator = &UnionPolicyRuleEvaluator{}
|
||||
|
||||
func NewUnionPolicyRuleEvaluator(builders []builder.APIGroupBuilder) *UnionPolicyRuleEvaluator {
|
||||
policyRuleEvaluators := make(map[schema.GroupVersion]audit.PolicyRuleEvaluator, 0)
|
||||
|
||||
for _, b := range builders {
|
||||
auditor, ok := b.(builder.APIGroupAuditor)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
policyRuleEvaluator := auditor.GetPolicyRuleEvaluator()
|
||||
if policyRuleEvaluator == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, gv := range builder.GetGroupVersions(b) {
|
||||
if gv.Empty() {
|
||||
continue
|
||||
}
|
||||
|
||||
policyRuleEvaluators[gv] = policyRuleEvaluator
|
||||
}
|
||||
}
|
||||
|
||||
return &UnionPolicyRuleEvaluator{policyRuleEvaluators}
|
||||
}
|
||||
|
||||
func (e *UnionPolicyRuleEvaluator) EvaluatePolicyRule(attrs authorizer.Attributes) audit.RequestAuditConfig {
|
||||
evaluator, ok := e.evaluators[schema.GroupVersion{Group: attrs.GetAPIGroup(), Version: attrs.GetAPIVersion()}]
|
||||
if !ok {
|
||||
return audit.RequestAuditConfig{
|
||||
Level: auditinternal.LevelNone,
|
||||
}
|
||||
}
|
||||
|
||||
return evaluator.EvaluatePolicyRule(attrs)
|
||||
}
|
||||
|
||||
// DefaultGrafanaPolicyRuleEvaluator provides a sane default configuration for audit logging for API group+versions.
|
||||
// It logs all resource requests (at the `ResponseComplete` stage) except for watch requests.
|
||||
type defaultGrafanaPolicyRuleEvaluator struct{}
|
||||
|
||||
var _ PolicyRuleEvaluator = &defaultGrafanaPolicyRuleEvaluator{}
|
||||
|
||||
func NewDefaultGrafanaPolicyRuleEvaluator() defaultGrafanaPolicyRuleEvaluator {
|
||||
return defaultGrafanaPolicyRuleEvaluator{}
|
||||
}
|
||||
|
||||
func (defaultGrafanaPolicyRuleEvaluator) EvaluatePolicyRule(attrs authorizer.Attributes) audit.RequestAuditConfig {
|
||||
// Skip non-resource and watch requests otherwise it is too noisy.
|
||||
if !attrs.IsResourceRequest() || attrs.GetVerb() == utils.VerbWatch {
|
||||
return audit.RequestAuditConfig{
|
||||
Level: auditinternal.LevelNone,
|
||||
}
|
||||
}
|
||||
|
||||
// Skip auditing if the user is part of the privileged group.
|
||||
// The loopback client uses this group, so requests initiated in `/api/` would be duplicated.
|
||||
if u := attrs.GetUser(); u != nil && slices.Contains(u.GetGroups(), user.SystemPrivilegedGroup) {
|
||||
return audit.RequestAuditConfig{
|
||||
Level: auditinternal.LevelNone,
|
||||
}
|
||||
}
|
||||
|
||||
return audit.RequestAuditConfig{
|
||||
Level: auditinternal.LevelMetadata,
|
||||
OmitStages: []auditinternal.Stage{
|
||||
// Only log on StageResponseComplete
|
||||
auditinternal.StageRequestReceived,
|
||||
auditinternal.StageResponseStarted,
|
||||
auditinternal.StagePanic,
|
||||
},
|
||||
// Keep this not because we use it but because it avoids extra copying/unmarshalling.
|
||||
OmitManagedFields: false,
|
||||
}
|
||||
}
|
||||
@@ -37,6 +37,7 @@ import (
|
||||
folders "github.com/grafana/grafana/apps/folder/pkg/apis/folder/v1beta1"
|
||||
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
||||
"github.com/grafana/grafana/pkg/apimachinery/utils"
|
||||
"github.com/grafana/grafana/pkg/apiserver/auditing"
|
||||
grafanaregistry "github.com/grafana/grafana/pkg/apiserver/registry/generic"
|
||||
"github.com/grafana/grafana/pkg/infra/db"
|
||||
"github.com/grafana/grafana/pkg/infra/tracing"
|
||||
@@ -1011,3 +1012,7 @@ func (b *DashboardsAPIBuilder) verifyFolderAccessPermissions(ctx context.Context
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *DashboardsAPIBuilder) GetPolicyRuleEvaluator() auditing.PolicyRuleEvaluator {
|
||||
return auditing.NewDefaultGrafanaPolicyRuleEvaluator()
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ import (
|
||||
folders "github.com/grafana/grafana/apps/folder/pkg/apis/folder/v1beta1"
|
||||
"github.com/grafana/grafana/apps/iam/pkg/reconcilers"
|
||||
"github.com/grafana/grafana/pkg/apimachinery/utils"
|
||||
"github.com/grafana/grafana/pkg/apiserver/auditing"
|
||||
grafanaregistry "github.com/grafana/grafana/pkg/apiserver/registry/generic"
|
||||
grafanarest "github.com/grafana/grafana/pkg/apiserver/rest"
|
||||
"github.com/grafana/grafana/pkg/cmd/grafana-cli/logger"
|
||||
@@ -43,6 +44,7 @@ import (
|
||||
|
||||
var _ builder.APIGroupBuilder = (*FolderAPIBuilder)(nil)
|
||||
var _ builder.APIGroupValidation = (*FolderAPIBuilder)(nil)
|
||||
var _ builder.APIGroupAuditor = (*FolderAPIBuilder)(nil)
|
||||
|
||||
var resourceInfo = folders.FolderResourceInfo
|
||||
|
||||
@@ -361,3 +363,7 @@ func (b *FolderAPIBuilder) Validate(ctx context.Context, a admission.Attributes,
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (b *FolderAPIBuilder) GetPolicyRuleEvaluator() auditing.PolicyRuleEvaluator {
|
||||
return auditing.NewDefaultGrafanaPolicyRuleEvaluator()
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package apiregistry
|
||||
import (
|
||||
"github.com/google/wire"
|
||||
|
||||
"github.com/grafana/grafana/pkg/apiserver/auditing"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/collections"
|
||||
dashboardinternal "github.com/grafana/grafana/pkg/registry/apis/dashboard"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/datasource"
|
||||
@@ -33,6 +34,8 @@ var WireSetExts = wire.NewSet(
|
||||
|
||||
externalgroupmapping.ProvideNoopTeamGroupsREST,
|
||||
wire.Bind(new(externalgroupmapping.TeamGroupsHandler), new(*externalgroupmapping.NoopTeamGroupsREST)),
|
||||
|
||||
auditing.ProvideNoopBackend,
|
||||
)
|
||||
|
||||
var provisioningExtras = wire.NewSet(
|
||||
|
||||
7
pkg/server/wire_gen.go
generated
7
pkg/server/wire_gen.go
generated
@@ -14,6 +14,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/api"
|
||||
"github.com/grafana/grafana/pkg/api/avatar"
|
||||
"github.com/grafana/grafana/pkg/api/routing"
|
||||
"github.com/grafana/grafana/pkg/apiserver/auditing"
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/configprovider"
|
||||
"github.com/grafana/grafana/pkg/expr"
|
||||
@@ -831,7 +832,8 @@ func Initialize(ctx context.Context, cfg *setting.Cfg, opts Options, apiOpts api
|
||||
}
|
||||
v2 := appregistry.ProvideAppInstallers(featureToggles, playlistAppInstaller, appInstaller, shortURLAppInstaller, alertingRulesAppInstaller, correlationsAppInstaller, alertingNotificationsAppInstaller, logsDrilldownAppInstaller, annotationAppInstaller, exampleAppInstaller, advisorAppInstaller, alertingHistorianAppInstaller, quotasAppInstaller)
|
||||
builderMetrics := builder.ProvideBuilderMetrics(registerer)
|
||||
apiserverService, err := apiserver.ProvideService(cfg, featureToggles, routeRegisterImpl, tracingService, serverLockService, sqlStore, kvStore, middlewareHandler, scopedPluginDatasourceProvider, plugincontextProvider, pluginstoreService, dualwriteService, resourceClient, inlineSecureValueSupport, eventualRestConfigProvider, v, eventualRestConfigProvider, registerer, aggregatorRunner, v2, builderMetrics)
|
||||
backend := auditing.ProvideNoopBackend()
|
||||
apiserverService, err := apiserver.ProvideService(cfg, featureToggles, routeRegisterImpl, tracingService, serverLockService, sqlStore, kvStore, middlewareHandler, scopedPluginDatasourceProvider, plugincontextProvider, pluginstoreService, dualwriteService, resourceClient, inlineSecureValueSupport, eventualRestConfigProvider, v, eventualRestConfigProvider, registerer, aggregatorRunner, v2, builderMetrics, backend)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -1489,7 +1491,8 @@ func InitializeForTest(ctx context.Context, t sqlutil.ITestDB, testingT interfac
|
||||
}
|
||||
v2 := appregistry.ProvideAppInstallers(featureToggles, playlistAppInstaller, appInstaller, shortURLAppInstaller, alertingRulesAppInstaller, correlationsAppInstaller, alertingNotificationsAppInstaller, logsDrilldownAppInstaller, annotationAppInstaller, exampleAppInstaller, advisorAppInstaller, alertingHistorianAppInstaller, quotasAppInstaller)
|
||||
builderMetrics := builder.ProvideBuilderMetrics(registerer)
|
||||
apiserverService, err := apiserver.ProvideService(cfg, featureToggles, routeRegisterImpl, tracingService, serverLockService, sqlStore, kvStore, middlewareHandler, scopedPluginDatasourceProvider, plugincontextProvider, pluginstoreService, dualwriteService, resourceClient, inlineSecureValueSupport, eventualRestConfigProvider, v, eventualRestConfigProvider, registerer, aggregatorRunner, v2, builderMetrics)
|
||||
backend := auditing.ProvideNoopBackend()
|
||||
apiserverService, err := apiserver.ProvideService(cfg, featureToggles, routeRegisterImpl, tracingService, serverLockService, sqlStore, kvStore, middlewareHandler, scopedPluginDatasourceProvider, plugincontextProvider, pluginstoreService, dualwriteService, resourceClient, inlineSecureValueSupport, eventualRestConfigProvider, v, eventualRestConfigProvider, registerer, aggregatorRunner, v2, builderMetrics, backend)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
"k8s.io/apiserver/pkg/audit"
|
||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||
"k8s.io/apiserver/pkg/registry/generic"
|
||||
genericapiserver "k8s.io/apiserver/pkg/server"
|
||||
@@ -59,6 +60,10 @@ type APIGroupAuthorizer interface {
|
||||
GetAuthorizer() authorizer.Authorizer
|
||||
}
|
||||
|
||||
type APIGroupAuditor interface {
|
||||
GetPolicyRuleEvaluator() audit.PolicyRuleEvaluator
|
||||
}
|
||||
|
||||
type APIGroupMutation interface {
|
||||
// Mutate allows the builder to make changes to the object before it is persisted.
|
||||
// Context is used only for timeout/deadline/cancellation and tracing information.
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/runtime/serializer"
|
||||
"k8s.io/apiserver/pkg/audit"
|
||||
genericapifilters "k8s.io/apiserver/pkg/endpoints/filters"
|
||||
"k8s.io/apiserver/pkg/endpoints/responsewriter"
|
||||
genericapiserver "k8s.io/apiserver/pkg/server"
|
||||
@@ -27,6 +28,7 @@ import (
|
||||
dataplaneaggregator "github.com/grafana/grafana/pkg/aggregator/apiserver"
|
||||
"github.com/grafana/grafana/pkg/api/routing"
|
||||
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
||||
grafanaauditing "github.com/grafana/grafana/pkg/apiserver/auditing"
|
||||
grafanaresponsewriter "github.com/grafana/grafana/pkg/apiserver/endpoints/responsewriter"
|
||||
grafanarest "github.com/grafana/grafana/pkg/apiserver/rest"
|
||||
"github.com/grafana/grafana/pkg/infra/db"
|
||||
@@ -113,6 +115,7 @@ type service struct {
|
||||
appInstallers []appsdkapiserver.AppInstaller
|
||||
builderMetrics *builder.BuilderMetrics
|
||||
dualWriterMetrics *grafanarest.DualWriterMetrics
|
||||
auditBackend audit.Backend
|
||||
}
|
||||
|
||||
func ProvideService(
|
||||
@@ -137,6 +140,7 @@ func ProvideService(
|
||||
aggregatorRunner aggregatorrunner.AggregatorRunner,
|
||||
appInstallers []appsdkapiserver.AppInstaller,
|
||||
builderMetrics *builder.BuilderMetrics,
|
||||
auditBackend audit.Backend,
|
||||
) (*service, error) {
|
||||
scheme := builder.ProvideScheme()
|
||||
codecs := builder.ProvideCodecFactory(scheme)
|
||||
@@ -167,6 +171,7 @@ func ProvideService(
|
||||
appInstallers: appInstallers,
|
||||
builderMetrics: builderMetrics,
|
||||
dualWriterMetrics: grafanarest.NewDualWriterMetrics(reg),
|
||||
auditBackend: auditBackend,
|
||||
}
|
||||
// This will be used when running as a dskit service
|
||||
s.NamedService = services.NewBasicService(s.start, s.running, nil).WithName(modules.GrafanaAPIServer)
|
||||
@@ -355,6 +360,11 @@ func (s *service) start(ctx context.Context) error {
|
||||
appinstaller.BuildOpenAPIDefGetter(s.appInstallers),
|
||||
}
|
||||
|
||||
if s.auditBackend != nil {
|
||||
serverConfig.AuditBackend = s.auditBackend
|
||||
serverConfig.AuditPolicyRuleEvaluator = grafanaauditing.NewUnionPolicyRuleEvaluator(builders)
|
||||
}
|
||||
|
||||
// Add OpenAPI specs for each group+version (existing builders)
|
||||
err = builder.SetupConfig(
|
||||
s.scheme,
|
||||
|
||||
@@ -16,9 +16,9 @@ import (
|
||||
|
||||
"github.com/grafana/authlib/authn"
|
||||
claims "github.com/grafana/authlib/types"
|
||||
"github.com/grafana/grafana-app-sdk/logging"
|
||||
|
||||
secretv1beta1 "github.com/grafana/grafana/apps/secret/pkg/apis/secret/v1beta1"
|
||||
"github.com/grafana/grafana/pkg/apiserver/auditing"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/secret/contracts"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/secret/xkube"
|
||||
"github.com/grafana/grafana/pkg/storage/secret/metadata/metrics"
|
||||
@@ -79,11 +79,7 @@ func (s *decryptStorage) Decrypt(ctx context.Context, namespace xkube.Namespace,
|
||||
|
||||
span.SetAttributes(attribute.String("decrypter.identity", decrypterIdentity))
|
||||
|
||||
args := []any{
|
||||
"namespace", namespace.String(),
|
||||
"secret_name", name,
|
||||
"decrypter_identity", decrypterIdentity,
|
||||
}
|
||||
auditExtra := make(map[string]string)
|
||||
|
||||
// The service identity used for decryption is always what is from the signed token, but if the request is
|
||||
// coming from grafana, the service identity will be grafana, but the request metadata will contain
|
||||
@@ -91,23 +87,36 @@ func (s *decryptStorage) Decrypt(ctx context.Context, namespace xkube.Namespace,
|
||||
// we do this for auditing purposes.
|
||||
if md, ok := metadata.FromIncomingContext(ctx); ok {
|
||||
if svcIdentities := md.Get(contracts.HeaderGrafanaServiceIdentityName); len(svcIdentities) > 0 {
|
||||
args = append(args, "grafana_decrypter_identity", svcIdentities[0])
|
||||
auditExtra["grafana_decrypter_identity"] = svcIdentities[0]
|
||||
span.SetAttributes(attribute.String("grafana_decrypter.identity", svcIdentities[0]))
|
||||
}
|
||||
}
|
||||
|
||||
decryptResultLabel := metrics.DecryptResultLabel(decryptErr)
|
||||
|
||||
eventOutcome := auditing.EventOutcomeSuccess
|
||||
if decryptErr == nil {
|
||||
span.SetStatus(codes.Ok, "Decrypt succeeded")
|
||||
args = append(args, "operation", "decrypt_secret_success")
|
||||
} else {
|
||||
eventOutcome = auditing.EventOutcomeFailureGeneric
|
||||
auditExtra["decrypt_result"] = decryptResultLabel
|
||||
span.SetStatus(codes.Error, "Decrypt failed")
|
||||
span.RecordError(decryptErr)
|
||||
args = append(args, "operation", "decrypt_secret_error", "error", decryptErr.Error(), "result", decryptResultLabel)
|
||||
}
|
||||
|
||||
logging.FromContext(ctx).Info("Secrets Audit Log", args...)
|
||||
auditing.FromContext(ctx).Log(auditing.Event{
|
||||
Namespace: namespace.String(),
|
||||
ObservedAt: time.Now(),
|
||||
SubjectName: decrypterIdentity,
|
||||
SubjectUID: decrypterIdentity,
|
||||
Verb: "decrypt",
|
||||
Object: name,
|
||||
APIGroup: secretv1beta1.APIGroup,
|
||||
APIVersion: secretv1beta1.APIVersion,
|
||||
Kind: secretv1beta1.SecureValueKind().Kind(),
|
||||
Outcome: eventOutcome,
|
||||
Extra: auditExtra,
|
||||
})
|
||||
|
||||
s.metrics.DecryptDuration.WithLabelValues(decryptResultLabel, cmp.Or(decrypterIdentity, "unknown")).Observe(time.Since(start).Seconds())
|
||||
|
||||
|
||||
Reference in New Issue
Block a user