package grafanaapiserver import ( "context" "crypto/x509" "net" "path" "github.com/go-logr/logr" "github.com/grafana/dskit/services" "github.com/grafana/grafana-apiserver/pkg/certgenerator" 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/runtime/schema" "k8s.io/apimachinery/pkg/runtime/serializer" "k8s.io/apiserver/pkg/authentication/authenticator" "k8s.io/apiserver/pkg/authentication/request/headerrequest" "k8s.io/apiserver/pkg/authentication/user" openapinamer "k8s.io/apiserver/pkg/endpoints/openapi" genericapiserver "k8s.io/apiserver/pkg/server" "k8s.io/apiserver/pkg/server/options" "k8s.io/apiserver/pkg/util/openapi" "k8s.io/client-go/kubernetes/scheme" clientrest "k8s.io/client-go/rest" "k8s.io/client-go/tools/clientcmd" clientcmdapi "k8s.io/client-go/tools/clientcmd/api" "k8s.io/klog/v2" "github.com/grafana/grafana/pkg/apis" playlistv1 "github.com/grafana/grafana/pkg/apis/playlist/v1" "github.com/grafana/grafana/pkg/modules" ) const ( DefaultAPIServerHost = "https://" + certgenerator.DefaultAPIServerIp + ":6443" ) var ( _ Service = (*service)(nil) _ RestConfigProvider = (*service)(nil) ) var ( Scheme = runtime.NewScheme() Codecs = serializer.NewCodecFactory(Scheme) // if you modify this, make sure you update the crEncoder 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...) } type Service interface { services.NamedService } type RestConfigProvider interface { GetRestConfig() *clientrest.Config } type service struct { *services.BasicService restConfig *clientrest.Config dataPath string stopCh chan struct{} stoppedCh chan error } func New(dataPath string) (*service, error) { s := &service{ dataPath: dataPath, stopCh: make(chan struct{}), } s.BasicService = services.NewBasicService(s.start, s.running, nil).WithName(modules.GrafanaAPIServer) return s, nil } func (s *service) GetRestConfig() *clientrest.Config { return s.restConfig } func (s *service) start(ctx context.Context) error { logger := logr.New(newLogAdapter()) logger.V(9) klog.SetLoggerWithOptions(logger, klog.ContextualLogger(true)) o := options.NewRecommendedOptions("", unstructured.UnstructuredJSONScheme) o.SecureServing.BindPort = 6443 o.Authentication.RemoteKubeConfigFileOptional = true o.Authorization.RemoteKubeConfigFileOptional = true o.Authorization.AlwaysAllowPaths = []string{"*"} o.Authorization.AlwaysAllowGroups = []string{user.SystemPrivilegedGroup, "grafana"} o.Etcd = nil o.Admission = nil o.CoreAPI = nil // Get the util to get the paths to pre-generated certs certUtil := certgenerator.CertUtil{ K8sDataPath: s.dataPath, } if err := certUtil.InitializeCACertPKI(); err != nil { return err } if err := certUtil.EnsureApiServerPKI(certgenerator.DefaultAPIServerIp); err != nil { return err } o.SecureServing.BindAddress = net.ParseIP(certgenerator.DefaultAPIServerIp) o.SecureServing.ServerCert.CertKey = options.CertKey{ CertFile: certUtil.APIServerCertFile(), KeyFile: certUtil.APIServerKeyFile(), } if err := o.Validate(); len(err) > 0 { return err[0] } serverConfig := genericapiserver.NewRecommendedConfig(Codecs) err := o.ApplyTo(serverConfig) if err != nil { return err } rootCert, err := certUtil.GetK8sCACert() if err != nil { return err } authenticator, err := newAuthenticator(rootCert) if err != nil { return err } serverConfig.Authentication.Authenticator = authenticator // Get the list of groups the server will support builders := []apis.APIGroupBuilder{ playlistv1.GetAPIGroupBuilder(), } // 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 server, err := serverConfig.Complete().New("grafana-apiserver", genericapiserver.NewEmptyDelegate()) if err != nil { return err } // Install the API Group+version for _, b := range builders { err = server.InstallAPIGroup(b.GetAPIGroupInfo(Scheme, Codecs)) if err != nil { return err } } s.restConfig = server.LoopbackClientConfig err = s.writeKubeConfiguration(s.restConfig) if err != nil { return err } prepared := server.PrepareRun() // TODO: not sure if we can still inject RouteRegister with the new module server setup // Disabling the /k8s endpoint until we have a solution /* handler := func(c *contextmodel.ReqContext) { 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") req.Header.Set("X-Remote-Extra-token-name", signedInUser.Name) req.Header.Set("X-Remote-Extra-org-role", string(signedInUser.OrgRole)) req.Header.Set("X-Remote-Extra-org-id", strconv.FormatInt(signedInUser.OrgID, 10)) req.Header.Set("X-Remote-Extra-user-id", strconv.FormatInt(signedInUser.UserID, 10)) resp := responsewriter.WrapForHTTP1Or2(c.Resp) prepared.GenericAPIServer.Handler.ServeHTTP(resp, req) } /* s.rr.Group("/k8s", func(k8sRoute routing.RouteRegister) { k8sRoute.Any("/", middleware.ReqSignedIn, handler) k8sRoute.Any("/*", middleware.ReqSignedIn, handler) }) */ 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 } func (s *service) writeKubeConfiguration(restConfig *clientrest.Config) error { 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 }