Secrets: Add authz checks for the single-tenant SecureValue client (#108216)

This commit is contained in:
Matheus Macabu
2025-07-21 11:09:07 +02:00
committed by GitHub
parent 47abf5f811
commit f7e55f2c5d
4 changed files with 124 additions and 6 deletions
@@ -5,17 +5,21 @@ import (
"fmt"
claims "github.com/grafana/authlib/types"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/watch"
"k8s.io/apiserver/pkg/admission"
"k8s.io/apiserver/pkg/authorization/authorizer"
"k8s.io/client-go/dynamic"
secretv1beta1 "github.com/grafana/grafana/apps/secret/pkg/apis/secret/v1beta1"
"github.com/grafana/grafana/pkg/apimachinery/utils"
"github.com/grafana/grafana/pkg/registry/apis/secret/contracts"
"github.com/grafana/grafana/pkg/registry/apis/secret/xkube"
authsvc "github.com/grafana/grafana/pkg/services/apiserver/auth/authorizer"
)
// SecureValueClient is a CRUD client for the secure value API.
@@ -25,14 +29,16 @@ type secureValueClient struct {
namespace string
service contracts.SecureValueService
validator contracts.SecureValueValidator
access authorizer.Authorizer
}
var _ SecureValueClient = &secureValueClient{}
func ProvideSecureValueClient(service contracts.SecureValueService, validator contracts.SecureValueValidator) SecureValueClient {
func ProvideSecureValueClient(service contracts.SecureValueService, validator contracts.SecureValueValidator, access claims.AccessClient) SecureValueClient {
return &secureValueClient{
service: service,
validator: validator,
access: authsvc.NewResourceAuthorizer(access),
}
}
@@ -62,6 +68,10 @@ func (c *secureValueClient) Create(ctx context.Context, obj *unstructured.Unstru
return nil, fmt.Errorf("namespace is required")
}
if err := c.checkAccess(ctx, obj.GetName(), utils.VerbCreate); err != nil {
return nil, err
}
sv, err := fromUnstructured(obj)
if err != nil {
return nil, err
@@ -96,6 +106,10 @@ func (c *secureValueClient) Get(ctx context.Context, name string, _ metav1.GetOp
return nil, fmt.Errorf("name is required")
}
if err := c.checkAccess(ctx, name, utils.VerbGet); err != nil {
return nil, err
}
sv, err := c.service.Read(ctx, xkube.Namespace(c.namespace), name)
if err != nil {
return nil, err
@@ -110,6 +124,10 @@ func (c *secureValueClient) Update(ctx context.Context, obj *unstructured.Unstru
return nil, fmt.Errorf("namespace is required")
}
if err := c.checkAccess(ctx, obj.GetName(), utils.VerbUpdate); err != nil {
return nil, err
}
oldUnstructured, err := c.Get(ctx, obj.GetName(), metav1.GetOptions{})
if err != nil {
return nil, err
@@ -154,6 +172,10 @@ func (c *secureValueClient) Delete(ctx context.Context, name string, _ metav1.De
return fmt.Errorf("name is required")
}
if err := c.checkAccess(ctx, name, utils.VerbDelete); err != nil {
return err
}
_, err := c.service.Delete(ctx, xkube.Namespace(c.namespace), name)
return err
}
@@ -164,6 +186,10 @@ func (c *secureValueClient) List(ctx context.Context, _ metav1.ListOptions) (*un
return nil, fmt.Errorf("namespace is required")
}
if err := c.checkAccess(ctx, "", utils.VerbList); err != nil {
return nil, err
}
list, err := c.service.List(ctx, xkube.Namespace(c.namespace))
if err != nil {
return nil, err
@@ -214,6 +240,31 @@ func (c *secureValueClient) ApplyStatus(_ context.Context, _ string, _ *unstruct
return nil, fmt.Errorf("applyStatus is not supported")
}
func (c *secureValueClient) checkAccess(ctx context.Context, name, verb string) error {
gr := secretv1beta1.SecureValuesResourceInfo.GroupResource()
decision, reason, err := c.access.Authorize(ctx, authorizer.AttributesRecord{
Verb: verb,
Namespace: c.namespace,
APIGroup: secretv1beta1.APIGroup,
APIVersion: secretv1beta1.APIVersion,
Resource: gr.Resource,
Subresource: "",
Name: name,
ResourceRequest: true,
})
if err != nil {
return apierrors.NewForbidden(gr, name, fmt.Errorf("failed to check access: %w", err))
}
if decision != authorizer.DecisionAllow {
return apierrors.NewForbidden(gr, name, fmt.Errorf("no access to %s: %s", verb, reason))
}
return nil
}
func toUnstructured(sv *secretv1beta1.SecureValue) (*unstructured.Unstructured, error) {
unstructuredObj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(sv)
if err != nil {