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
54 func Resolver(config *types.Config, sqlDB *sql.DB, bqClient clients.BQClient) *resolver.Resolver {
55
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)
96 logClassificationLabelService := services.NewLogClassificationLabelService(sqlDB)
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,
135 LogClassificationLabelsService: logClassificationLabelService,
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
173
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
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
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
240 router.Use(middleware.SetRequestContext())
241
242
243 router.Use(requestid.New())
244 router.Use(middleware.RequestLogger(log))
245 router.Use(gin.Recovery())
246 router.Use(request.NewRequestMiddleware())
247
248
249 corsConfig := cors.DefaultConfig()
250 corsConfig.AllowAllOrigins = true
251 corsConfig.AllowHeaders = []string{"*"}
252 router.Use(cors.New(corsConfig))
253
254
255 router.Use(middleware.AuthMiddleware(config.App.AppSecret, config.App.TotpSecret, db))
256
257
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
325
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 {
339 return &embeddedFileSystem{fs}
340 }
341
View as plain text