Skip to content

Commit

Permalink
Update kindsys, add validating admission controller
Browse files Browse the repository at this point in the history
  • Loading branch information
sdboyer committed Jul 10, 2023
1 parent 7af9a35 commit d6a0b40
Show file tree
Hide file tree
Showing 5 changed files with 108 additions and 8 deletions.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,7 @@ require (
github.com/grafana/dataplane/sdata v0.0.6
github.com/grafana/go-mssqldb v0.9.1
github.com/grafana/grafana-apiserver v0.0.0-20230708003113-8a0ae77345b1
github.com/grafana/kindsys v0.0.0-20230508162304-452481b63482
github.com/grafana/kindsys v0.0.0-20230710031138-57f73e1488a7
github.com/grafana/thema v0.0.0-20230615161902-b6e21996aef8
github.com/ory/fosite v0.44.1-0.20230317114349-45a6785cc54f
github.com/redis/go-redis/v9 v9.0.2
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -1327,8 +1327,8 @@ github.com/grafana/grafana-plugin-sdk-go v0.94.0/go.mod h1:3VXz4nCv6wH5SfgB3mlW3
github.com/grafana/grafana-plugin-sdk-go v0.114.0/go.mod h1:D7x3ah+1d4phNXpbnOaxa/osSaZlwh9/ZUnGGzegRbk=
github.com/grafana/grafana-plugin-sdk-go v0.162.0 h1:ij2ARWohf0IoK9yCVC1Wup4Gp6zwBq2AueVXRYsv/to=
github.com/grafana/grafana-plugin-sdk-go v0.162.0/go.mod h1:dPhljkVno3Bg/ZYafMrR/BfYjtCRJD2hU2719Nl3QzM=
github.com/grafana/kindsys v0.0.0-20230508162304-452481b63482 h1:1YNoeIhii4UIIQpCPU+EXidnqf449d0C3ZntAEt4KSo=
github.com/grafana/kindsys v0.0.0-20230508162304-452481b63482/go.mod h1:GNcfpy5+SY6RVbNGQW264gC0r336Dm+0zgQ5vt6+M8Y=
github.com/grafana/kindsys v0.0.0-20230710031138-57f73e1488a7 h1:UzmfEDqmHGeqiCxKnxr0aPsFoJjkLFlHtH0WtV1YbLg=
github.com/grafana/kindsys v0.0.0-20230710031138-57f73e1488a7/go.mod h1:tReyLqR9xponnYrhIpA/eShnEkAixy6b2U/TSz5PZN0=
github.com/grafana/phlare/api v0.1.4-0.20230426005640-f90edba05413 h1:bBzCezZNRyYlJpXTkyZdY4fpPxHZUdyeyRWzhtw/P6I=
github.com/grafana/phlare/api v0.1.4-0.20230426005640-f90edba05413/go.mod h1:IvwuGG9xa/h96UH/exgvsfy3zE+ZpctkNT9o5aaGdrU=
github.com/grafana/prometheus-alertmanager v0.25.1-0.20230508090422-7d5630522a53 h1:X3Jl4PBIGCtlPSMa6Uiu2+3FDNWmddSjivp+1DDznQs=
Expand Down
14 changes: 14 additions & 0 deletions pkg/registry/corekind/base.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package corekind

import (
"sort"
"sync"

"github.com/google/wire"
Expand Down Expand Up @@ -46,3 +47,16 @@ func (b *Base) All() []kindsys.Core {
copy(ret, b.all)
return ret
}

// ByName looks up a kind in the registry by name. If no kind exists for the
// given name, nil is returned.
func (b *Base) ByName(name string) kindsys.Core {
i := sort.Search(len(b.all), func(i int) bool {
return b.all[i].Name() >= name
})

if b.all[i].Name() == name {
return b.all[i]
}
return nil
}
81 changes: 81 additions & 0 deletions pkg/services/k8s/apiserver/admission/schema_validate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package admission

import (
"context"
"encoding/json"
"fmt"
"io"

"github.com/grafana/grafana/pkg/registry/corekind"
"github.com/grafana/kindsys/encoding"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apiserver/pkg/admission"

"github.com/grafana/grafana/pkg/infra/log"
)

const PluginNameSchemaValidate = "SchemaValidate"

// Register registers a plugin
func RegisterSchemaValidate(plugins *admission.Plugins, reg *corekind.Base) {
plugins.Register(PluginNameSchemaValidate, func(config io.Reader) (admission.Interface, error) {
return NewSchemaValidate(reg), nil
})
}

type schemaValidate struct {
log log.Logger
reg *corekind.Base
}

var _ admission.ValidationInterface = schemaValidate{}

// Validate makes an admission decision based on the request attributes. It is NOT allowed to mutate.
func (sv schemaValidate) Validate(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces) (err error) {
obj := a.GetObject()
sv.log.Info(fmt.Sprintf("validating resource of kind %s", a.GetKind().Kind))
ck := sv.reg.ByName(obj.GetObjectKind().GroupVersionKind().Kind)
if ck == nil {
sv.log.Info(fmt.Sprintf("did not match %s", obj.GetObjectKind().GroupVersionKind().Kind))
return nil
}

j, err := json.Marshal(obj.(*unstructured.Unstructured))
if err != nil {
return fmt.Errorf("failed to marshal unstructured to json: %w", err)
}

switch a.GetOperation() { //nolint:exhaustive
case admission.Create, admission.Update:
// This logic will accept any known version of the kind, not just the
// current/latest one that is known to the server. Errors may occur when
// translating to the current/latest version, but that's not this admission
// handler's responsibility.
//
// TODO vanilla k8s CRDs allow specifying a subset of that kind's versions as acceptable on that server. Do we want to do that?
// TODO this triggers all translation/migrations and throws away the result, which is wasteful. If this ends up being how we want to do this, add a helper or a narrower method in kindsys
if err := ck.Validate(j, &encoding.KubernetesJSONDecoder{}); err != nil {
return err
}
}
return nil
}

// Handles returns true if this admission controller can handle the given operation
// where operation can be one of CREATE, UPDATE, DELETE, or CONNECT
func (schemaValidate) Handles(operation admission.Operation) bool {
switch operation {
case admission.Connect, admission.Delete:
return false
default:
return true
}
}

// NewSchemaValidate creates a NewSchemaValidate admission handler
func NewSchemaValidate(reg *corekind.Base) admission.Interface {
return schemaValidate{
log: log.New("admission.schema-validate"),
reg: reg,
}
}
15 changes: 10 additions & 5 deletions pkg/services/k8s/apiserver/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,13 @@ import (
"os"
"path"
"strconv"
"strings"

"cuelang.org/go/pkg/strings"
"github.com/go-logr/logr"
"github.com/grafana/dskit/services"
"github.com/grafana/grafana-apiserver/pkg/apis/kinds/install"
kindsv1 "github.com/grafana/grafana-apiserver/pkg/apis/kinds/v1"
"github.com/grafana/grafana-apiserver/pkg/certgenerator"
grafanaapiserveroptions "github.com/grafana/grafana-apiserver/pkg/cmd/server/options"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
Expand All @@ -33,11 +34,11 @@ import (
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
"k8s.io/klog/v2"

"github.com/grafana/grafana-apiserver/pkg/certgenerator"
"github.com/grafana/grafana/pkg/api/routing"
"github.com/grafana/grafana/pkg/infra/appcontext"
"github.com/grafana/grafana/pkg/middleware"
"github.com/grafana/grafana/pkg/modules"
"github.com/grafana/grafana/pkg/registry/corekind"
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
grafanaAdmission "github.com/grafana/grafana/pkg/services/k8s/apiserver/admission"
"github.com/grafana/grafana/pkg/services/k8s/apiserver/authorization"
Expand Down Expand Up @@ -68,6 +69,8 @@ type service struct {
restConfig *rest.Config
rr routing.RouteRegister

corereg *corekind.Base

restOptionsGetter func(runtime.Codec) genericregistry.RESTOptionsGetter

handler web.Handler
Expand All @@ -76,12 +79,13 @@ type service struct {
stoppedCh chan error
}

func ProvideService(cfg *setting.Cfg, rr routing.RouteRegister, restOptionsGetter func(runtime.Codec) genericregistry.RESTOptionsGetter) (*service, error) {
func ProvideService(cfg *setting.Cfg, rr routing.RouteRegister, restOptionsGetter func(runtime.Codec) genericregistry.RESTOptionsGetter, reg *corekind.Base) (*service, error) {
s := &service{
rr: rr,
dataPath: path.Join(cfg.DataPath, "k8s"),
stopCh: make(chan struct{}),
restOptionsGetter: restOptionsGetter,
corereg: reg,
}

s.BasicService = services.NewBasicService(s.start, s.running, nil).WithName(modules.KubernetesAPIServer)
Expand Down Expand Up @@ -164,10 +168,11 @@ func (s *service) start(ctx context.Context) error {
// plugins that depend on the Core V1 APIs and informers.
o.RecommendedOptions.Admission.Plugins = admission.NewPlugins()
grafanaAdmission.RegisterDenyByName(o.RecommendedOptions.Admission.Plugins)
grafanaAdmission.RegisterSchemaValidate(o.RecommendedOptions.Admission.Plugins, s.corereg)
grafanaAdmission.RegisterAddDefaultFields(o.RecommendedOptions.Admission.Plugins)
o.RecommendedOptions.Admission.RecommendedPluginOrder = []string{grafanaAdmission.PluginNameDenyByName, grafanaAdmission.PluginNameAddDefaultFields}
o.RecommendedOptions.Admission.RecommendedPluginOrder = []string{grafanaAdmission.PluginNameDenyByName, grafanaAdmission.PluginNameSchemaValidate, grafanaAdmission.PluginNameAddDefaultFields}
o.RecommendedOptions.Admission.DisablePlugins = append([]string{}, o.RecommendedOptions.Admission.EnablePlugins...)
o.RecommendedOptions.Admission.EnablePlugins = []string{grafanaAdmission.PluginNameDenyByName, grafanaAdmission.PluginNameAddDefaultFields}
o.RecommendedOptions.Admission.EnablePlugins = []string{grafanaAdmission.PluginNameDenyByName, grafanaAdmission.PluginNameSchemaValidate, grafanaAdmission.PluginNameAddDefaultFields}

// Get the util to get the paths to pre-generated certs
certUtil := certgenerator.CertUtil{
Expand Down

0 comments on commit d6a0b40

Please sign in to comment.