github.com/kyma-incubator/compass/components/director@v0.0.0-20230623144113-d764f56ff805/cmd/destinationfetcher-svc/main.go (about) 1 /* 2 * Copyright 2020 The Compass Authors 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package main 18 19 import ( 20 "context" 21 "net/http" 22 "os" 23 "time" 24 25 "github.com/kyma-incubator/compass/components/director/internal/authenticator/claims" 26 auth_middleware "github.com/kyma-incubator/compass/components/director/pkg/auth-middleware" 27 28 "github.com/gorilla/mux" 29 destinationfetcher "github.com/kyma-incubator/compass/components/director/internal/destinationfetchersvc" 30 "github.com/kyma-incubator/compass/components/director/internal/domain/api" 31 "github.com/kyma-incubator/compass/components/director/internal/domain/auth" 32 "github.com/kyma-incubator/compass/components/director/internal/domain/bundle" 33 "github.com/kyma-incubator/compass/components/director/internal/domain/destination" 34 "github.com/kyma-incubator/compass/components/director/internal/domain/document" 35 "github.com/kyma-incubator/compass/components/director/internal/domain/eventdef" 36 "github.com/kyma-incubator/compass/components/director/internal/domain/fetchrequest" 37 "github.com/kyma-incubator/compass/components/director/internal/domain/label" 38 "github.com/kyma-incubator/compass/components/director/internal/domain/spec" 39 "github.com/kyma-incubator/compass/components/director/internal/domain/tenant" 40 "github.com/kyma-incubator/compass/components/director/internal/domain/version" 41 configprovider "github.com/kyma-incubator/compass/components/director/pkg/config" 42 "github.com/kyma-incubator/compass/components/director/pkg/correlation" 43 "github.com/kyma-incubator/compass/components/director/pkg/cronjob" 44 "github.com/kyma-incubator/compass/components/director/pkg/executor" 45 timeouthandler "github.com/kyma-incubator/compass/components/director/pkg/handler" 46 httputil "github.com/kyma-incubator/compass/components/director/pkg/http" 47 "github.com/kyma-incubator/compass/components/director/pkg/log" 48 "github.com/kyma-incubator/compass/components/director/pkg/persistence" 49 "github.com/kyma-incubator/compass/components/director/pkg/signal" 50 "github.com/kyma-incubator/compass/components/system-broker/pkg/uuid" 51 "github.com/pkg/errors" 52 "github.com/vrischmann/envconfig" 53 ) 54 55 const envPrefix = "APP" 56 57 type config struct { 58 Address string `envconfig:"default=127.0.0.1:8080"` 59 60 ServerTimeout time.Duration `envconfig:"default=110s"` 61 ShutdownTimeout time.Duration `envconfig:"default=10s"` 62 DestinationFetcherSchedule time.Duration `envconfig:"APP_DESTINATION_FETCHER_SCHEDULE,default=10m"` 63 TenantSyncTimeout time.Duration `envconfig:"APP_DESTINATION_FETCHER_TENANT_SYNC_TIMEOUT,default=5m"` 64 ParallelTenantSyncs int `envconfig:"APP_DESTINATION_FETCHER_PARALLEL_TENANTS,default=10"` 65 DestinationsRootAPI string `envconfig:"APP_ROOT_API,default=/destinations"` 66 67 HandlerConfig destinationfetcher.HandlerConfig 68 DestinationServiceAPIConfig destinationfetcher.DestinationServiceAPIConfig 69 DestinationsConfig configprovider.DestinationsConfig 70 Database persistence.DatabaseConfig 71 Log log.Config 72 SecurityConfig securityConfig 73 ElectionConfig cronjob.ElectionConfig 74 } 75 76 type securityConfig struct { 77 JWKSSyncPeriod time.Duration `envconfig:"default=5m"` 78 AllowJWTSigningNone bool `envconfig:"APP_ALLOW_JWT_SIGNING_NONE,default=false"` 79 JwksEndpoint string `envconfig:"APP_JWKS_ENDPOINT,default=file://hack/default-jwks.json"` 80 DestinationsOnDemandScope string `envconfig:"APP_DESTINATIONS_SYNC_SCOPE,default=destinations:sync"` 81 DestinationsSensitiveDataScope string `envconfig:"APP_DESTINATIONS_SENSITIVE_DATA_SCOPE,default=destinations_sensitive_data:read"` 82 } 83 84 func main() { 85 ctx, cancel := context.WithCancel(context.Background()) 86 87 defer cancel() 88 89 term := make(chan os.Signal) 90 signal.HandleInterrupts(ctx, cancel, term) 91 92 cfg := config{} 93 err := envconfig.InitWithPrefix(&cfg, envPrefix) 94 exitOnError(err, "Error while loading app config") 95 96 ctx, err = log.Configure(ctx, &cfg.Log) 97 exitOnError(err, "Failed to configure Logger") 98 99 transactioner, closeFunc, err := persistence.Configure(ctx, cfg.Database) 100 exitOnError(err, "Error while establishing the connection to the database") 101 102 defer func() { 103 err := closeFunc() 104 exitOnError(err, "error while closing the connection to the database") 105 }() 106 107 httpClient := &http.Client{ 108 Transport: httputil.NewCorrelationIDTransport(httputil.NewHTTPTransportWrapper(http.DefaultTransport.(*http.Transport))), 109 CheckRedirect: func(req *http.Request, via []*http.Request) error { 110 return http.ErrUseLastResponse 111 }, 112 } 113 114 destinationService := getDestinationService(cfg, transactioner) 115 handler := initAPIHandler(ctx, httpClient, cfg, destinationService) 116 runMainSrv, shutdownMainSrv := createServer(ctx, cfg, handler, "main") 117 118 go func() { 119 <-ctx.Done() 120 // Interrupt signal received - shut down the servers 121 shutdownMainSrv() 122 }() 123 124 syncJobConfig := destinationfetcher.SyncJobConfig{ 125 ElectionCfg: cfg.ElectionConfig, 126 JobSchedulePeriod: cfg.DestinationFetcherSchedule, 127 ParallelTenants: cfg.ParallelTenantSyncs, 128 TenantSyncTimeout: cfg.TenantSyncTimeout, 129 } 130 go func() { 131 err := destinationfetcher.StartDestinationFetcherSyncJob(ctx, syncJobConfig, destinationService) 132 if err != nil { 133 log.C(ctx).WithError(err).Error("Failed to start destination fetcher cronjob. Stopping app...") 134 } 135 cancel() 136 }() 137 138 runMainSrv() 139 } 140 141 func configureAuthMiddleware(ctx context.Context, httpClient *http.Client, router *mux.Router, cfg securityConfig, requiredScopes ...string) { 142 scopeValidator := claims.NewScopesValidator(requiredScopes) 143 middleware := auth_middleware.New(httpClient, cfg.JwksEndpoint, cfg.AllowJWTSigningNone, "", scopeValidator) 144 router.Use(middleware.Handler()) 145 146 log.C(ctx).Infof("JWKS synchronization enabled. Sync period: %v", cfg.JWKSSyncPeriod) 147 periodicExecutor := executor.NewPeriodic(cfg.JWKSSyncPeriod, func(ctx context.Context) { 148 if err := middleware.SynchronizeJWKS(ctx); err != nil { 149 log.C(ctx).WithError(err).Errorf("An error has occurred while synchronizing JWKS: %v", err) 150 } 151 }) 152 go periodicExecutor.Run(ctx) 153 } 154 155 func exitOnError(err error, context string) { 156 if err != nil { 157 wrappedError := errors.Wrap(err, context) 158 log.D().Fatal(wrappedError) 159 } 160 } 161 162 func getDestinationService(cfg config, transact persistence.Transactioner) *destinationfetcher.DestinationService { 163 uuidSvc := uuid.NewService() 164 destConv := destination.NewConverter() 165 destRepo := destination.NewRepository(destConv) 166 bundleRepo := bundleRepo() 167 168 labelConverter := label.NewConverter() 169 labelRepo := label.NewRepository(labelConverter) 170 171 tenantConverter := tenant.NewConverter() 172 tenantRepo := tenant.NewRepository(tenantConverter) 173 174 err := cfg.DestinationsConfig.MapInstanceConfigs() 175 exitOnError(err, "error while loading destination instances config") 176 177 return &destinationfetcher.DestinationService{ 178 Transactioner: transact, 179 UUIDSvc: uuidSvc, 180 DestinationRepo: destRepo, 181 BundleRepo: bundleRepo, 182 LabelRepo: labelRepo, 183 DestinationsConfig: cfg.DestinationsConfig, 184 APIConfig: cfg.DestinationServiceAPIConfig, 185 TenantRepo: tenantRepo, 186 } 187 } 188 189 func initAPIHandler(ctx context.Context, httpClient *http.Client, 190 cfg config, destService *destinationfetcher.DestinationService) http.Handler { 191 const ( 192 healthzEndpoint = "/healthz" 193 readyzEndpoint = "/readyz" 194 ) 195 logger := log.C(ctx) 196 mainRouter := mux.NewRouter() 197 mainRouter.Use(correlation.AttachCorrelationIDToContext(), log.RequestLogger( 198 cfg.DestinationsRootAPI+healthzEndpoint, cfg.DestinationsRootAPI+readyzEndpoint)) 199 200 syncDestinationsAPIRouter := mainRouter.PathPrefix(cfg.DestinationsRootAPI).Subrouter() 201 destinationHandler := destinationfetcher.NewDestinationsHTTPHandler(destService, cfg.HandlerConfig) 202 sensitiveDataAPIRouter := syncDestinationsAPIRouter 203 204 log.C(ctx).Infof("Registering service destinations endpoint on %s...", cfg.HandlerConfig.SyncDestinationsEndpoint) 205 configureAuthMiddleware(ctx, httpClient, syncDestinationsAPIRouter, 206 cfg.SecurityConfig, cfg.SecurityConfig.DestinationsOnDemandScope) 207 208 syncDestinationsAPIRouter.HandleFunc(cfg.HandlerConfig.SyncDestinationsEndpoint, destinationHandler.SyncTenantDestinations). 209 Methods(http.MethodPut) 210 211 log.C(ctx).Infof("Registering service destinations endpoint on %s...", cfg.HandlerConfig.DestinationsSensitiveEndpoint) 212 configureAuthMiddleware(ctx, httpClient, sensitiveDataAPIRouter, 213 cfg.SecurityConfig, cfg.SecurityConfig.DestinationsSensitiveDataScope) 214 sensitiveDataAPIRouter.HandleFunc(cfg.HandlerConfig.DestinationsSensitiveEndpoint, destinationHandler.FetchDestinationsSensitiveData). 215 Methods(http.MethodGet) 216 217 healthCheckRouter := mainRouter.PathPrefix(cfg.DestinationsRootAPI).Subrouter() 218 logger.Infof("Registering readiness endpoint...") 219 healthCheckRouter.HandleFunc(readyzEndpoint, newReadinessHandler()) 220 logger.Infof("Registering liveness endpoint...") 221 healthCheckRouter.HandleFunc(healthzEndpoint, newReadinessHandler()) 222 223 return mainRouter 224 } 225 226 func bundleRepo() destinationfetcher.BundleRepo { 227 authConverter := auth.NewConverter() 228 frConverter := fetchrequest.NewConverter(authConverter) 229 versionConverter := version.NewConverter() 230 specConverter := spec.NewConverter(frConverter) 231 eventAPIConverter := eventdef.NewConverter(versionConverter, specConverter) 232 docConverter := document.NewConverter(frConverter) 233 apiConverter := api.NewConverter(versionConverter, specConverter) 234 235 return bundle.NewRepository(bundle.NewConverter(authConverter, apiConverter, eventAPIConverter, docConverter)) 236 } 237 238 func newReadinessHandler() func(writer http.ResponseWriter, request *http.Request) { 239 return func(writer http.ResponseWriter, request *http.Request) { 240 writer.WriteHeader(http.StatusOK) 241 } 242 } 243 244 func createServer(ctx context.Context, cfg config, handler http.Handler, name string) (func(), func()) { 245 logger := log.C(ctx) 246 247 handlerWithTimeout, err := timeouthandler.WithTimeout(handler, cfg.ServerTimeout) 248 exitOnError(err, "Error while configuring tenant mapping handler") 249 250 srv := &http.Server{ 251 Addr: cfg.Address, 252 Handler: handlerWithTimeout, 253 ReadHeaderTimeout: cfg.ServerTimeout, 254 } 255 256 runFn := func() { 257 logger.Infof("Running %s server on %s...", name, cfg.Address) 258 if err := srv.ListenAndServe(); err != http.ErrServerClosed { 259 logger.Errorf("%s HTTP server ListenAndServe: %v", name, err) 260 } 261 } 262 263 shutdownFn := func() { 264 ctx, cancel := context.WithTimeout(context.Background(), cfg.ShutdownTimeout) 265 defer cancel() 266 267 logger.Infof("Shutting down %s server...", name) 268 if err := srv.Shutdown(ctx); err != nil { 269 logger.Errorf("%s HTTP server Shutdown: %v", name, err) 270 } 271 } 272 273 return runFn, shutdownFn 274 }