2023-07-14 14:22:10 -05:00
|
|
|
package grafanaapiserver
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"crypto/x509"
|
|
|
|
"net"
|
|
|
|
"path"
|
2023-09-25 17:31:58 -05:00
|
|
|
"strconv"
|
|
|
|
"strings"
|
2023-07-14 14:22:10 -05:00
|
|
|
|
|
|
|
"github.com/go-logr/logr"
|
|
|
|
"github.com/grafana/dskit/services"
|
2023-09-08 09:12:12 -05:00
|
|
|
"github.com/grafana/grafana-apiserver/pkg/certgenerator"
|
2023-09-22 13:17:53 -05:00
|
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
2023-09-08 09:12:12 -05:00
|
|
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
2023-09-22 13:17:53 -05:00
|
|
|
"k8s.io/apimachinery/pkg/runtime"
|
|
|
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
|
|
"k8s.io/apimachinery/pkg/runtime/serializer"
|
2023-07-14 14:22:10 -05:00
|
|
|
"k8s.io/apiserver/pkg/authentication/authenticator"
|
|
|
|
"k8s.io/apiserver/pkg/authentication/request/headerrequest"
|
2023-09-28 17:28:58 -05:00
|
|
|
"k8s.io/apiserver/pkg/authorization/authorizer"
|
2023-09-22 20:29:43 -05:00
|
|
|
openapinamer "k8s.io/apiserver/pkg/endpoints/openapi"
|
2023-09-25 17:31:58 -05:00
|
|
|
"k8s.io/apiserver/pkg/endpoints/responsewriter"
|
2023-07-14 14:22:10 -05:00
|
|
|
genericapiserver "k8s.io/apiserver/pkg/server"
|
|
|
|
"k8s.io/apiserver/pkg/server/options"
|
2023-09-22 20:29:43 -05:00
|
|
|
"k8s.io/apiserver/pkg/util/openapi"
|
|
|
|
"k8s.io/client-go/kubernetes/scheme"
|
2023-09-22 13:17:53 -05:00
|
|
|
clientrest "k8s.io/client-go/rest"
|
2023-07-14 14:22:10 -05:00
|
|
|
"k8s.io/client-go/tools/clientcmd"
|
|
|
|
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
|
|
|
|
"k8s.io/klog/v2"
|
|
|
|
|
2023-09-25 17:31:58 -05:00
|
|
|
"github.com/grafana/grafana/pkg/api/routing"
|
|
|
|
"github.com/grafana/grafana/pkg/infra/appcontext"
|
|
|
|
"github.com/grafana/grafana/pkg/middleware"
|
2023-09-08 09:12:12 -05:00
|
|
|
"github.com/grafana/grafana/pkg/modules"
|
2023-09-25 17:31:58 -05:00
|
|
|
"github.com/grafana/grafana/pkg/registry"
|
|
|
|
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
|
|
|
|
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
|
|
|
"github.com/grafana/grafana/pkg/setting"
|
|
|
|
"github.com/grafana/grafana/pkg/web"
|
2023-07-14 14:22:10 -05:00
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
DefaultAPIServerHost = "https://" + certgenerator.DefaultAPIServerIp + ":6443"
|
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
2023-09-25 17:31:58 -05:00
|
|
|
_ Service = (*service)(nil)
|
|
|
|
_ RestConfigProvider = (*service)(nil)
|
|
|
|
_ registry.BackgroundService = (*service)(nil)
|
|
|
|
_ registry.CanBeDisabled = (*service)(nil)
|
2023-07-14 14:22:10 -05:00
|
|
|
)
|
|
|
|
|
2023-09-22 13:17:53 -05:00
|
|
|
var (
|
|
|
|
Scheme = runtime.NewScheme()
|
|
|
|
Codecs = serializer.NewCodecFactory(Scheme)
|
|
|
|
|
|
|
|
unversionedVersion = schema.GroupVersion{Group: "", Version: "v1"}
|
|
|
|
unversionedTypes = []runtime.Object{
|
|
|
|
&metav1.Status{},
|
|
|
|
&metav1.WatchEvent{},
|
|
|
|
&metav1.APIVersions{},
|
|
|
|
&metav1.APIGroupList{},
|
|
|
|
&metav1.APIGroup{},
|
|
|
|
&metav1.APIResourceList{},
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
|
|
|
func init() {
|
|
|
|
// we need to add the options to empty v1
|
|
|
|
metav1.AddToGroupVersion(Scheme, schema.GroupVersion{Group: "", Version: "v1"})
|
|
|
|
Scheme.AddUnversionedTypes(unversionedVersion, unversionedTypes...)
|
|
|
|
}
|
|
|
|
|
2023-07-14 14:22:10 -05:00
|
|
|
type Service interface {
|
|
|
|
services.NamedService
|
2023-09-25 17:31:58 -05:00
|
|
|
registry.BackgroundService
|
|
|
|
registry.CanBeDisabled
|
|
|
|
}
|
|
|
|
|
|
|
|
type APIRegistrar interface {
|
|
|
|
RegisterAPI(builder APIGroupBuilder)
|
2023-07-14 14:22:10 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
type RestConfigProvider interface {
|
2023-09-22 13:17:53 -05:00
|
|
|
GetRestConfig() *clientrest.Config
|
2023-07-14 14:22:10 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
type service struct {
|
|
|
|
*services.BasicService
|
|
|
|
|
2023-09-26 16:15:15 -05:00
|
|
|
restConfig *clientrest.Config
|
|
|
|
etcd_servers []string
|
2023-07-14 14:22:10 -05:00
|
|
|
|
2023-09-25 17:31:58 -05:00
|
|
|
enabled bool
|
2023-07-14 14:22:10 -05:00
|
|
|
dataPath string
|
|
|
|
stopCh chan struct{}
|
|
|
|
stoppedCh chan error
|
2023-09-25 17:31:58 -05:00
|
|
|
|
|
|
|
rr routing.RouteRegister
|
|
|
|
handler web.Handler
|
|
|
|
builders []APIGroupBuilder
|
2023-09-28 17:28:58 -05:00
|
|
|
|
|
|
|
authorizer authorizer.Authorizer
|
2023-07-14 14:22:10 -05:00
|
|
|
}
|
|
|
|
|
2023-09-26 16:15:15 -05:00
|
|
|
func ProvideService(
|
|
|
|
cfg *setting.Cfg,
|
2023-09-25 17:31:58 -05:00
|
|
|
rr routing.RouteRegister,
|
2023-09-28 17:28:58 -05:00
|
|
|
authz authorizer.Authorizer,
|
2023-09-25 17:31:58 -05:00
|
|
|
) (*service, error) {
|
2023-07-14 14:22:10 -05:00
|
|
|
s := &service{
|
2023-09-26 16:15:15 -05:00
|
|
|
etcd_servers: cfg.SectionWithEnvOverrides("grafana-apiserver").Key("etcd_servers").Strings(","),
|
|
|
|
enabled: cfg.IsFeatureToggleEnabled(featuremgmt.FlagGrafanaAPIServer),
|
|
|
|
rr: rr,
|
|
|
|
dataPath: path.Join(cfg.DataPath, "k8s"),
|
|
|
|
stopCh: make(chan struct{}),
|
|
|
|
builders: []APIGroupBuilder{},
|
2023-09-28 17:28:58 -05:00
|
|
|
authorizer: authz,
|
2023-07-14 14:22:10 -05:00
|
|
|
}
|
|
|
|
|
2023-09-25 17:31:58 -05:00
|
|
|
// This will be used when running as a dskit service
|
2023-08-31 08:12:01 -05:00
|
|
|
s.BasicService = services.NewBasicService(s.start, s.running, nil).WithName(modules.GrafanaAPIServer)
|
2023-07-14 14:22:10 -05:00
|
|
|
|
2023-09-25 17:31:58 -05:00
|
|
|
// TODO: this is very hacky
|
|
|
|
// We need to register the routes in ProvideService to make sure
|
|
|
|
// the routes are registered before the Grafana HTTP server starts.
|
|
|
|
s.rr.Group("/k8s", func(k8sRoute routing.RouteRegister) {
|
|
|
|
handler := func(c *contextmodel.ReqContext) {
|
|
|
|
if s.handler == nil {
|
|
|
|
c.Resp.WriteHeader(404)
|
|
|
|
_, _ = c.Resp.Write([]byte("Not found"))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if handle, ok := s.handler.(func(c *contextmodel.ReqContext)); ok {
|
|
|
|
handle(c)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
k8sRoute.Any("/", middleware.ReqSignedIn, handler)
|
|
|
|
k8sRoute.Any("/*", middleware.ReqSignedIn, handler)
|
|
|
|
})
|
|
|
|
|
2023-07-14 14:22:10 -05:00
|
|
|
return s, nil
|
|
|
|
}
|
|
|
|
|
2023-09-22 13:17:53 -05:00
|
|
|
func (s *service) GetRestConfig() *clientrest.Config {
|
2023-07-14 14:22:10 -05:00
|
|
|
return s.restConfig
|
|
|
|
}
|
|
|
|
|
2023-09-25 17:31:58 -05:00
|
|
|
func (s *service) IsDisabled() bool {
|
|
|
|
return !s.enabled
|
|
|
|
}
|
|
|
|
|
|
|
|
// Run is an adapter for the BackgroundService interface.
|
|
|
|
func (s *service) Run(ctx context.Context) error {
|
|
|
|
if err := s.start(ctx); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return s.running(ctx)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *service) RegisterAPI(builder APIGroupBuilder) {
|
|
|
|
s.builders = append(s.builders, builder)
|
|
|
|
}
|
|
|
|
|
2023-07-14 14:22:10 -05:00
|
|
|
func (s *service) start(ctx context.Context) error {
|
|
|
|
logger := logr.New(newLogAdapter())
|
|
|
|
logger.V(9)
|
|
|
|
klog.SetLoggerWithOptions(logger, klog.ContextualLogger(true))
|
|
|
|
|
2023-09-22 13:17:53 -05:00
|
|
|
o := options.NewRecommendedOptions("", unstructured.UnstructuredJSONScheme)
|
|
|
|
o.SecureServing.BindPort = 6443
|
|
|
|
o.Authentication.RemoteKubeConfigFileOptional = true
|
|
|
|
o.Authorization.RemoteKubeConfigFileOptional = true
|
2023-09-26 16:15:15 -05:00
|
|
|
o.Etcd.StorageConfig.Transport.ServerList = s.etcd_servers
|
|
|
|
|
2023-09-22 13:17:53 -05:00
|
|
|
o.Admission = nil
|
|
|
|
o.CoreAPI = nil
|
2023-09-26 16:15:15 -05:00
|
|
|
if len(o.Etcd.StorageConfig.Transport.ServerList) == 0 {
|
|
|
|
o.Etcd = nil
|
|
|
|
}
|
2023-07-14 14:22:10 -05:00
|
|
|
|
|
|
|
// Get the util to get the paths to pre-generated certs
|
|
|
|
certUtil := certgenerator.CertUtil{
|
|
|
|
K8sDataPath: s.dataPath,
|
|
|
|
}
|
|
|
|
|
2023-09-01 14:31:51 -05:00
|
|
|
if err := certUtil.InitializeCACertPKI(); err != nil {
|
2023-08-31 08:12:01 -05:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2023-09-01 14:31:51 -05:00
|
|
|
if err := certUtil.EnsureApiServerPKI(certgenerator.DefaultAPIServerIp); err != nil {
|
2023-08-31 08:12:01 -05:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2023-09-22 13:17:53 -05:00
|
|
|
o.SecureServing.BindAddress = net.ParseIP(certgenerator.DefaultAPIServerIp)
|
|
|
|
o.SecureServing.ServerCert.CertKey = options.CertKey{
|
2023-07-14 14:22:10 -05:00
|
|
|
CertFile: certUtil.APIServerCertFile(),
|
|
|
|
KeyFile: certUtil.APIServerKeyFile(),
|
|
|
|
}
|
|
|
|
|
2023-09-22 13:17:53 -05:00
|
|
|
if err := o.Validate(); len(err) > 0 {
|
|
|
|
return err[0]
|
2023-07-14 14:22:10 -05:00
|
|
|
}
|
|
|
|
|
2023-09-22 13:17:53 -05:00
|
|
|
serverConfig := genericapiserver.NewRecommendedConfig(Codecs)
|
|
|
|
err := o.ApplyTo(serverConfig)
|
|
|
|
if err != nil {
|
2023-07-14 14:22:10 -05:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2023-09-22 13:17:53 -05:00
|
|
|
rootCert, err := certUtil.GetK8sCACert()
|
2023-07-14 14:22:10 -05:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2023-09-22 13:17:53 -05:00
|
|
|
authenticator, err := newAuthenticator(rootCert)
|
2023-07-14 14:22:10 -05:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2023-09-28 17:28:58 -05:00
|
|
|
serverConfig.Authorization.Authorizer = s.authorizer
|
2023-09-22 13:17:53 -05:00
|
|
|
serverConfig.Authentication.Authenticator = authenticator
|
2023-09-08 09:12:12 -05:00
|
|
|
|
2023-09-22 20:29:43 -05:00
|
|
|
// Get the list of groups the server will support
|
2023-09-25 17:31:58 -05:00
|
|
|
builders := s.builders
|
2023-09-22 20:29:43 -05:00
|
|
|
|
|
|
|
// Install schemas
|
|
|
|
for _, b := range builders {
|
|
|
|
err = b.InstallSchema(Scheme) // previously was in init
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Add OpenAPI specs for each group+version
|
|
|
|
defsGetter := getOpenAPIDefinitions(builders)
|
|
|
|
serverConfig.OpenAPIConfig = genericapiserver.DefaultOpenAPIConfig(
|
|
|
|
openapi.GetOpenAPIDefinitionsWithoutDisabledFeatures(defsGetter),
|
|
|
|
openapinamer.NewDefinitionNamer(Scheme, scheme.Scheme))
|
|
|
|
|
|
|
|
serverConfig.OpenAPIV3Config = genericapiserver.DefaultOpenAPIV3Config(
|
|
|
|
openapi.GetOpenAPIDefinitionsWithoutDisabledFeatures(defsGetter),
|
|
|
|
openapinamer.NewDefinitionNamer(Scheme, scheme.Scheme))
|
|
|
|
|
|
|
|
serverConfig.SkipOpenAPIInstallation = false
|
|
|
|
|
|
|
|
// Create the server
|
2023-09-22 13:17:53 -05:00
|
|
|
server, err := serverConfig.Complete().New("grafana-apiserver", genericapiserver.NewEmptyDelegate())
|
2023-07-14 14:22:10 -05:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2023-09-22 20:29:43 -05:00
|
|
|
// Install the API Group+version
|
|
|
|
for _, b := range builders {
|
2023-09-26 16:15:15 -05:00
|
|
|
g, err := b.GetAPIGroupInfo(Scheme, Codecs, serverConfig.RESTOptionsGetter)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
err = server.InstallAPIGroup(g)
|
2023-09-22 20:29:43 -05:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2023-07-14 14:22:10 -05:00
|
|
|
}
|
|
|
|
|
2023-09-22 13:17:53 -05:00
|
|
|
s.restConfig = server.LoopbackClientConfig
|
2023-07-14 14:22:10 -05:00
|
|
|
err = s.writeKubeConfiguration(s.restConfig)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2023-09-22 13:17:53 -05:00
|
|
|
prepared := server.PrepareRun()
|
2023-07-14 14:22:10 -05:00
|
|
|
|
2023-09-25 17:31:58 -05:00
|
|
|
// TODO: this is a hack. see note in ProvideService
|
|
|
|
s.handler = func(c *contextmodel.ReqContext) {
|
2023-09-01 14:31:51 -05:00
|
|
|
req := c.Req
|
|
|
|
req.URL.Path = strings.TrimPrefix(req.URL.Path, "/k8s")
|
|
|
|
if req.URL.Path == "" {
|
|
|
|
req.URL.Path = "/"
|
|
|
|
}
|
|
|
|
ctx := req.Context()
|
|
|
|
signedInUser := appcontext.MustUser(ctx)
|
|
|
|
|
|
|
|
req.Header.Set("X-Remote-User", strconv.FormatInt(signedInUser.UserID, 10))
|
|
|
|
req.Header.Set("X-Remote-Group", "grafana")
|
|
|
|
|
|
|
|
resp := responsewriter.WrapForHTTP1Or2(c.Resp)
|
|
|
|
prepared.GenericAPIServer.Handler.ServeHTTP(resp, req)
|
|
|
|
}
|
|
|
|
|
2023-07-14 14:22:10 -05:00
|
|
|
go func() {
|
|
|
|
s.stoppedCh <- prepared.Run(s.stopCh)
|
|
|
|
}()
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *service) running(ctx context.Context) error {
|
|
|
|
select {
|
|
|
|
case err := <-s.stoppedCh:
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
case <-ctx.Done():
|
|
|
|
close(s.stopCh)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2023-09-22 13:17:53 -05:00
|
|
|
func (s *service) writeKubeConfiguration(restConfig *clientrest.Config) error {
|
2023-07-14 14:22:10 -05:00
|
|
|
clusters := make(map[string]*clientcmdapi.Cluster)
|
|
|
|
clusters["default-cluster"] = &clientcmdapi.Cluster{
|
|
|
|
Server: restConfig.Host,
|
|
|
|
InsecureSkipTLSVerify: true,
|
|
|
|
}
|
|
|
|
|
|
|
|
contexts := make(map[string]*clientcmdapi.Context)
|
|
|
|
contexts["default-context"] = &clientcmdapi.Context{
|
|
|
|
Cluster: "default-cluster",
|
|
|
|
Namespace: "default",
|
|
|
|
AuthInfo: "default",
|
|
|
|
}
|
|
|
|
|
|
|
|
authinfos := make(map[string]*clientcmdapi.AuthInfo)
|
|
|
|
authinfos["default"] = &clientcmdapi.AuthInfo{
|
|
|
|
Token: restConfig.BearerToken,
|
|
|
|
}
|
|
|
|
|
|
|
|
clientConfig := clientcmdapi.Config{
|
|
|
|
Kind: "Config",
|
|
|
|
APIVersion: "v1",
|
|
|
|
Clusters: clusters,
|
|
|
|
Contexts: contexts,
|
|
|
|
CurrentContext: "default-context",
|
|
|
|
AuthInfos: authinfos,
|
|
|
|
}
|
|
|
|
return clientcmd.WriteToFile(clientConfig, path.Join(s.dataPath, "grafana.kubeconfig"))
|
|
|
|
}
|
|
|
|
|
|
|
|
func newAuthenticator(cert *x509.Certificate) (authenticator.Request, error) {
|
|
|
|
reqHeaderOptions := options.RequestHeaderAuthenticationOptions{
|
|
|
|
UsernameHeaders: []string{"X-Remote-User"},
|
|
|
|
GroupHeaders: []string{"X-Remote-Group"},
|
|
|
|
ExtraHeaderPrefixes: []string{"X-Remote-Extra-"},
|
|
|
|
}
|
|
|
|
|
|
|
|
requestHeaderAuthenticator, err := headerrequest.New(
|
|
|
|
reqHeaderOptions.UsernameHeaders,
|
|
|
|
reqHeaderOptions.GroupHeaders,
|
|
|
|
reqHeaderOptions.ExtraHeaderPrefixes,
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return requestHeaderAuthenticator, nil
|
|
|
|
}
|