...

Source file src/edge-infra.dev/pkg/edge/api/graph/setup/bff_setup.go

Documentation: edge-infra.dev/pkg/edge/api/graph/setup

     1  package setup
     2  
     3  import (
     4  	"context"
     5  	"database/sql"
     6  	"fmt"
     7  	"io/fs"
     8  	"net/http"
     9  	"os"
    10  	"strings"
    11  	"time"
    12  
    13  	"github.com/99designs/gqlgen/graphql/handler"
    14  	"github.com/99designs/gqlgen/graphql/handler/debug"
    15  	"github.com/99designs/gqlgen/graphql/handler/transport"
    16  	"github.com/gin-contrib/cors"
    17  	"github.com/gin-contrib/pprof"
    18  	"github.com/gin-contrib/requestid"
    19  	"github.com/gin-contrib/static"
    20  	"github.com/gin-gonic/gin"
    21  	"github.com/go-logr/logr"
    22  	"github.com/gorilla/websocket"
    23  	"github.com/rs/zerolog/log"
    24  
    25  	"edge-infra.dev/hack/graphiql"
    26  	"edge-infra.dev/pkg/edge/api/apierror"
    27  	"edge-infra.dev/pkg/edge/api/clients"
    28  	"edge-infra.dev/pkg/edge/api/graph/generated"
    29  	"edge-infra.dev/pkg/edge/api/graph/resolver"
    30  	"edge-infra.dev/pkg/edge/api/middleware"
    31  	"edge-infra.dev/pkg/edge/api/middleware/activityhistory"
    32  	"edge-infra.dev/pkg/edge/api/middleware/audit"
    33  	"edge-infra.dev/pkg/edge/api/middleware/environment"
    34  	"edge-infra.dev/pkg/edge/api/middleware/errorlog"
    35  	"edge-infra.dev/pkg/edge/api/middleware/request"
    36  	"edge-infra.dev/pkg/edge/api/services"
    37  	"edge-infra.dev/pkg/edge/api/services/artifacts"
    38  	cabundle "edge-infra.dev/pkg/edge/api/services/caBundle"
    39  	"edge-infra.dev/pkg/edge/api/services/channels"
    40  	clustersvc "edge-infra.dev/pkg/edge/api/services/cluster"
    41  	"edge-infra.dev/pkg/edge/api/services/clustersecrets"
    42  	"edge-infra.dev/pkg/edge/api/services/edgenode"
    43  	"edge-infra.dev/pkg/edge/api/services/kinform"
    44  	"edge-infra.dev/pkg/edge/api/services/virtualmachine"
    45  	"edge-infra.dev/pkg/edge/api/types"
    46  	"edge-infra.dev/pkg/edge/bsl"
    47  	edgeAgentClientApi "edge-infra.dev/pkg/edge/edgeagent/model"
    48  	"edge-infra.dev/pkg/edge/okta"
    49  	"edge-infra.dev/pkg/lib/gcp/cloudsql"
    50  	"edge-infra.dev/pkg/lib/runtime/version"
    51  )
    52  
    53  // Resolver initializes graphql resolver with all the necessary services that contains the business logic of the application.
    54  func Resolver(config *types.Config, sqlDB *sql.DB, bqClient clients.BQClient) *resolver.Resolver {
    55  	// Services setup
    56  	gkeClient, err := services.NewGkeClient(config.App.KubeConfig)
    57  	if err != nil {
    58  		log.Fatal().Err(err).Msg("could not create new gke service")
    59  	}
    60  	foremanSecretClient, err := clients.NewForemanSecretManagerClient(context.Background(), config.Bff.TopLevelProjectID)
    61  	if err != nil {
    62  		log.Fatal().Err(err).Msg("could not create new gke service")
    63  	}
    64  	clusterLabelSvc := clustersvc.NewLabelService(sqlDB)
    65  	gcpClientService := services.NewGcpClientService()
    66  	gcpService := services.NewGcpService(gcpClientService, config.Bff.TopLevelProjectID, bqClient)
    67  	chariotService := services.NewChariotService(config.Bff.TopLevelProjectID, config.Chariot.PubsubTopic)
    68  	secretService := services.NewSecretService(gkeClient, chariotService, gcpService, bqClient)
    69  	bslClient := bsl.NewBSLClient(config.BSP, foremanSecretClient.GetForemanSecret)
    70  	roleService := services.NewRoleService(config.BSP, bslClient)
    71  	oktaClient := okta.New(config.Okta)
    72  	bannerService := services.NewBannerService(gkeClient, chariotService, bslClient, config.Bff.TopLevelProjectID, sqlDB, config)
    73  	userManagementService := services.NewUserManagementService(*config, bslClient, oktaClient, roleService, bannerService)
    74  	iamService := services.NewIAMService()
    75  	artifactsService := artifacts.NewArtifactsService(sqlDB, clusterLabelSvc)
    76  	labelService := services.NewLabelService(artifactsService, sqlDB)
    77  	clusterConfigService := services.NewClusterConfigService(sqlDB)
    78  	terminalService := services.NewTerminalServiceBQ(sqlDB, bqClient, labelService)
    79  	compatibilityService := services.NewCompatibilityService(sqlDB)
    80  	storeClusterService := services.NewStoreClusterService(gkeClient, bqClient, sqlDB, chariotService, terminalService, compatibilityService)
    81  	terminalLabelService := services.NewTerminalLabelService(sqlDB, chariotService, terminalService, storeClusterService, labelService)
    82  	helmService := services.NewHelmService(config, chariotService, gcpService, sqlDB, bqClient, compatibilityService)
    83  	bslSiteService := services.NewBSLSiteService(bslClient, sqlDB)
    84  	activationCodeService := edgenode.NewActivationCodeService(sqlDB, terminalService, storeClusterService, clusterConfigService, secretService, gcpService, bannerService)
    85  	registrationService := services.NewRegistrationService(gkeClient, config.Bff.TopLevelProjectID, secretService, gcpService, bslSiteService, iamService, sqlDB, chariotService, terminalService, activationCodeService, clusterLabelSvc, labelService)
    86  	bootstrapService := services.NewBootstrapService(config.Bff.TopLevelProjectID, clients.NewArtifactRegistryClient(fmt.Sprintf("%s-docker.pkg.dev", config.Bff.GCPRegion)), sqlDB)
    87  	tenantService := services.NewTenantService(sqlDB, bslClient, &config.BSP)
    88  	capabilityService := services.NewCapabilityService(sqlDB, bannerService, chariotService, config.Bff.TopLevelProjectID)
    89  	activityService := services.NewActivityService(config, sqlDB)
    90  	artifactRegistryService := services.NewArtifactRegistryService(sqlDB)
    91  	edgeAgentService := services.NewEdgeAgentService(edgeAgentClientApi.EdgeAgentTopicAndOwner)
    92  	virtualMachineService := services.NewVirtualMachineService(sqlDB)
    93  	namespaceService := services.NewNamespaceService(sqlDB)
    94  	iamSettingsService := services.NewIAMSettingsService(sqlDB)
    95  	logClassificationService := services.NewLogClassificationService(sqlDB)           // TODO: TO BE DEPRECATED IN 0.25 @RS185722
    96  	logClassificationLabelService := services.NewLogClassificationLabelService(sqlDB) // TODO: TO BE DEPRECATED IN 0.25 @RS185722
    97  	logReplayService := services.NewLogReplayService(sqlDB)
    98  	operatorInterventionService := services.NewOperatorInterventionService(sqlDB)
    99  	clusterSecretService := clustersecrets.NewClusterSecretService(sqlDB, gcpService, config)
   100  	kinformService := kinform.New(sqlDB)
   101  	vmStatusService := virtualmachine.NewVirtualMachineStatusService(sqlDB, kinformService)
   102  	channelService := channels.NewChannelService(sqlDB, config.Bff.TopLevelProjectID, chariotService)
   103  	bannerConfigService := services.NewBannerConfigService(sqlDB)
   104  	caBundleService := cabundle.NewCABundleService(sqlDB, gcpClientService)
   105  
   106  	resolver := &resolver.Resolver{
   107  		GKEClient:                      gkeClient,
   108  		GCPClientService:               gcpClientService,
   109  		GCPService:                     gcpService,
   110  		StoreClusterService:            storeClusterService,
   111  		HelmService:                    helmService,
   112  		SecretService:                  secretService,
   113  		UserManagementService:          userManagementService,
   114  		BannerService:                  bannerService,
   115  		RoleService:                    roleService,
   116  		RegistrationService:            registrationService,
   117  		BootstrapService:               bootstrapService,
   118  		BSLSiteService:                 bslSiteService,
   119  		IAMService:                     iamService,
   120  		LabelService:                   labelService,
   121  		ChariotService:                 chariotService,
   122  		TenantService:                  tenantService,
   123  		CapabilityService:              capabilityService,
   124  		Config:                         config,
   125  		TerminalService:                terminalService,
   126  		ClusterConfigService:           clusterConfigService,
   127  		ActivityService:                activityService,
   128  		TerminalLabelService:           terminalLabelService,
   129  		EdgeAgentService:               edgeAgentService,
   130  		VirtualMachineService:          virtualMachineService,
   131  		ArtifactsService:               artifactsService,
   132  		NamespaceService:               namespaceService,
   133  		IAMSettingsService:             iamSettingsService,
   134  		LogClassificationService:       logClassificationService,      // TODO: TO BE DEPRECATED IN 0.25 @RS185722
   135  		LogClassificationLabelsService: logClassificationLabelService, // TODO: TO BE DEPRECATED IN 0.25 @RS185722
   136  		CompatibilityService:           compatibilityService,
   137  		ArtifactRegistryService:        artifactRegistryService,
   138  		LogReplayService:               logReplayService,
   139  		OperatorInterventionService:    operatorInterventionService,
   140  		ClusterSecretService:           clusterSecretService,
   141  		KinformService:                 kinformService,
   142  		VirtualMachineStatusService:    vmStatusService,
   143  		ActivationCodeService:          activationCodeService,
   144  		ChannelService:                 channelService,
   145  		BannerConfigService:            bannerConfigService,
   146  		CABundleService:                caBundleService,
   147  	}
   148  	return resolver
   149  }
   150  
   151  func GqlConfig(res *resolver.Resolver) generated.Config {
   152  	gqlConfig := generated.Config{Resolvers: res}
   153  	gqlConfig.Directives.HasRole = res.HasRole()
   154  	gqlConfig.Directives.CanAssignRole = res.CanAssignRole()
   155  	gqlConfig.Directives.HasBannerAccess = res.HasBannerAccess()
   156  	gqlConfig.Directives.HasBannerAccessInput = res.HasBannerAccess()
   157  	gqlConfig.Directives.HasClusterAccess = res.HasClusterAccess()
   158  	gqlConfig.Directives.HasHelmWorkloadAccess = res.HasHelmWorkloadAccess()
   159  	gqlConfig.Directives.HasHelmWorkloadAccessInput = res.HasHelmWorkloadAccess()
   160  	gqlConfig.Directives.HasLabelAccessInput = res.HasLabelAccess()
   161  	gqlConfig.Directives.HasLabelAccess = res.HasLabelAccess()
   162  	gqlConfig.Directives.HasClusterAccessInput = res.HasClusterAccess()
   163  	gqlConfig.Directives.HasTerminalAccess = res.HasTerminalAccess()
   164  	gqlConfig.Directives.HasTerminalAccessInput = res.HasTerminalAccess()
   165  	gqlConfig.Directives.HasValidProviderPinSetting = res.HasValidProviderPinSetting()
   166  	return gqlConfig
   167  }
   168  
   169  func DB(config *types.Config, log logr.Logger) (*sql.DB, error) {
   170  	var edgeDb *cloudsql.EdgePostgres
   171  
   172  	// Initialise the correct type of connection: CLoudSQL or local host, port pair,
   173  	// determined by the presence of the ConnectionName configuration param
   174  	switch {
   175  	case config.SQL.ConnectionName != "":
   176  		edgeDb = cloudsql.GCPPostgresConnection(config.SQL.ConnectionName)
   177  	case config.SQL.ConnectionName == "" && config.SQL.Host != "":
   178  		if config.SQL.Port == "" {
   179  			return nil, fmt.Errorf("database port is required")
   180  		}
   181  		edgeDb = cloudsql.PostgresConnection(config.SQL.Host, config.SQL.Port)
   182  	default:
   183  		return nil, fmt.Errorf("database-connection-name or database-host must be provided")
   184  	}
   185  
   186  	if config.SQL.SearchPath != "" {
   187  		edgeDb = edgeDb.SearchPath(config.SQL.SearchPath)
   188  	}
   189  
   190  	dbConnection, err := edgeDb.
   191  		DBName(config.SQL.DatabaseName).
   192  		Password(config.SQL.Password).
   193  		Username(config.SQL.User).
   194  		NewConnection()
   195  	if err != nil {
   196  		log.Error(err, "failed to create sql db connection")
   197  		os.Exit(1)
   198  	}
   199  	return dbConnection, err
   200  }
   201  
   202  func BQClient(config *types.Config) (clients.BQClient, error) {
   203  	return clients.New(context.Background(), config.Bff.TopLevelProjectID, config.BigQuery.PSTableName)
   204  }
   205  
   206  const (
   207  	//if changing this make sure to update the port in the deployment manifest
   208  	bffPort = "8080"
   209  )
   210  
   211  func GqlServer(gqlConfig generated.Config, config *types.Config, resolver *resolver.Resolver) *handler.Server {
   212  	srv := handler.NewDefaultServer(generated.NewExecutableSchema(gqlConfig))
   213  	srv.Use(&apierror.Extension{})
   214  	if environment.IsDevTracing(config.App.Environment) {
   215  		srv.Use(&debug.Tracer{})
   216  	} else {
   217  		srv.Use(&errorlog.GraphqlErrorLogger{})
   218  	}
   219  	srv.Use(&audit.Provider{Resolver: resolver})
   220  	srv.Use(&activityhistory.ActivityHistory{Resolver: resolver, RootOrg: config.BSP.Root})
   221  	srv.AddTransport(&transport.Websocket{
   222  		Upgrader: websocket.Upgrader{
   223  			CheckOrigin: func(_ *http.Request) bool {
   224  				// Check against your desired domains here
   225  				return true
   226  			},
   227  			ReadBufferSize:  1024,
   228  			WriteBufferSize: 1024,
   229  		},
   230  	})
   231  	return srv
   232  }
   233  
   234  func Server(log logr.Logger, config *types.Config, srv *handler.Server, db *sql.DB, r *resolver.Resolver) {
   235  	router := gin.New()
   236  
   237  	middleware.UseMetrics(router)
   238  
   239  	// Create contextual id at beginning of request
   240  	router.Use(middleware.SetRequestContext())
   241  
   242  	// Http logging
   243  	router.Use(requestid.New())
   244  	router.Use(middleware.RequestLogger(log))
   245  	router.Use(gin.Recovery())
   246  	router.Use(request.NewRequestMiddleware())
   247  
   248  	// Add CORS middleware around every request
   249  	corsConfig := cors.DefaultConfig()
   250  	corsConfig.AllowAllOrigins = true
   251  	corsConfig.AllowHeaders = []string{"*"}
   252  	router.Use(cors.New(corsConfig))
   253  
   254  	// Auth Middleware
   255  	router.Use(middleware.AuthMiddleware(config.App.AppSecret, config.App.TotpSecret, db))
   256  
   257  	// Check CLI Version
   258  	router.Use(middleware.CheckVersion())
   259  
   260  	router.Any("/health", func(c *gin.Context) {
   261  		c.String(http.StatusOK, "ok")
   262  	})
   263  
   264  	router.Any("/validate_token/*.", func(c *gin.Context) {
   265  		auth := c.GetHeader("Authorization")
   266  		banner := c.GetHeader("Banner")
   267  
   268  		if err := middleware.ValidateToken(c, auth, banner, config.App.AppSecret, r.BannerService.GetUserAssignedBanners); err != nil {
   269  			c.String(http.StatusBadRequest, "Not authorized: %s", err.Error())
   270  			return
   271  		}
   272  
   273  		c.String(http.StatusOK, "ok")
   274  	})
   275  
   276  	router.Any("/ready", func(c *gin.Context) {
   277  		if _, err := db.Exec("SELECT 1"); err != nil {
   278  			log.Error(err, "database not ready yet")
   279  			c.String(http.StatusServiceUnavailable, "database not yet ready: %w", err)
   280  			return
   281  		}
   282  		c.String(http.StatusOK, "ok")
   283  	})
   284  
   285  	router.POST("/api/v2", gin.WrapF(srv.ServeHTTP))
   286  
   287  	if !environment.IsProd(config.App.Environment) {
   288  		pprof.Register(router, "/debug/pprof")
   289  	}
   290  
   291  	var serverFileSystem static.ServeFileSystem
   292  	fSys, err := fs.Sub(graphiql.Graphiql, "react")
   293  	if err != nil {
   294  		serverFileSystem = static.LocalFile("./hack/graphiql/react", true)
   295  		log.Error(err, "failed to get graphiql embedded file system")
   296  	} else {
   297  		serverFileSystem = EmbeddedFileSystem(http.FS(fSys))
   298  	}
   299  	router.GET("/graphiql/*path", func(c *gin.Context) {
   300  		var req = c.Request.Clone(c.Request.Context())
   301  		fileserver := http.FileServer(serverFileSystem)
   302  		switch req.URL.Path {
   303  		case "/graphiql/login/callback":
   304  			req.URL.Path = "/"
   305  		default:
   306  			req.URL.Path = strings.Replace(req.URL.Path, "/graphiql", "", -1)
   307  		}
   308  		fileserver.ServeHTTP(c.Writer, req)
   309  	})
   310  	router.NoRoute(func(c *gin.Context) {
   311  		c.AbortWithStatus(404)
   312  	})
   313  	log.Info(fmt.Sprintf("connect to http://localhost:%s/graphiql/ for GraphiQL", bffPort), "version", version.New().SemVer)
   314  
   315  	server := &http.Server{
   316  		Addr:              ":" + bffPort,
   317  		Handler:           router,
   318  		ReadHeaderTimeout: time.Minute * 1,
   319  	}
   320  	log.Error(server.ListenAndServe(), fmt.Sprintf("could not ListenAndServe on port %s", bffPort))
   321  	os.Exit(1)
   322  }
   323  
   324  // embeddedFileSystem allows embedded file to be used by gin server using gin static middleware
   325  // https://github.com/gin-gonic/contrib/blob/master/static/example/bindata/example.go
   326  type embeddedFileSystem struct {
   327  	fs http.FileSystem
   328  }
   329  
   330  func (l *embeddedFileSystem) Open(name string) (http.File, error) {
   331  	return l.fs.Open(name)
   332  }
   333  
   334  func (l *embeddedFileSystem) Exists(_ string, _ string) bool {
   335  	return true
   336  }
   337  
   338  func EmbeddedFileSystem(fs http.FileSystem) *embeddedFileSystem { // nolint
   339  	return &embeddedFileSystem{fs}
   340  }
   341  

View as plain text