diff --git a/cluster-manager/cmd/cluster-manager/main.go b/cluster-manager/cmd/cluster-manager/main.go index d1012713..f944dae2 100644 --- a/cluster-manager/cmd/cluster-manager/main.go +++ b/cluster-manager/cmd/cluster-manager/main.go @@ -166,7 +166,7 @@ func main() { }() log.Info().Msgf("gRPC server listening at %v", lis.Addr()) - httpSrv, err := server.New(cfg.ListenHTTPAddr, cfg.ListenGRPCAddr, tlsConfig) + httpSrv, err := server.New(cfg.ListenHTTPAddr, cfg.ListenGRPCAddr, tlsConfig, cfg.CORSAllowedOrigins) if err != nil { log.Fatal().Msgf("### Can't setup HTTP server: %v", err) } diff --git a/cluster-manager/pkg/config/config.go b/cluster-manager/pkg/config/config.go index ff077ebf..280ce7ef 100644 --- a/cluster-manager/pkg/config/config.go +++ b/cluster-manager/pkg/config/config.go @@ -8,28 +8,29 @@ import ( // Config represents system configuration. type Config struct { - NewDB bool // forces recreation of DB - PostgresAddr string // Postgres address in host[:port] format - PostgresDB string // Postgres db name - PostgresUser string // Postgres user - PostgresPassword string // Postgres password - PostgresSSLMode bool // Postgres SSL mode - PostgresSSLCheckCert bool // Check postgres SSL cert - AdministratorUsername string // Administrator user - AdministratorPassword string // Administrator password - LogLevel string // log level can be INFO, WARN, ERROR, FATAL, DEBUG or ALL - LogFile string // path to log file - ListenGRPCAddr string // address "[host]:port" that server should be listening on - ListenHTTPAddr string // address "[host]:port" that server should be listening for health checks - InstrumentationAddr string // address "[host]:port" that instrumentation server should be listening for health checks and metrics - TLS bool // is TLS enabled? - TokenKey string // key for jwt token - EncryptionKey string // encryption key for child clusters - PublicAccessTokenSaltKey string // publicAccessTokenSaltKey key for public api service - Auth bool // is auth enabled? - OwnCSURL string // URL of current CS (http(s)://host[:port]). - GopsAddr string // gops listen address - CSVersion string // CS version + NewDB bool // forces recreation of DB + PostgresAddr string // Postgres address in host[:port] format + PostgresDB string // Postgres db name + PostgresUser string // Postgres user + PostgresPassword string // Postgres password + PostgresSSLMode bool // Postgres SSL mode + PostgresSSLCheckCert bool // Check postgres SSL cert + AdministratorUsername string // Administrator user + AdministratorPassword string // Administrator password + LogLevel string // log level can be INFO, WARN, ERROR, FATAL, DEBUG or ALL + CORSAllowedOrigins config.StringList // origins allowed to call the API cross-origin + LogFile string // path to log file + ListenGRPCAddr string // address "[host]:port" that server should be listening on + ListenHTTPAddr string // address "[host]:port" that server should be listening for health checks + InstrumentationAddr string // address "[host]:port" that instrumentation server should be listening for health checks and metrics + TLS bool // is TLS enabled? + TokenKey string // key for jwt token + EncryptionKey string // encryption key for child clusters + PublicAccessTokenSaltKey string // publicAccessTokenSaltKey key for public api service + Auth bool // is auth enabled? + OwnCSURL string // URL of current CS (http(s)://host[:port]). + GopsAddr string // gops listen address + CSVersion string // CS version } // New reads config from environment and returns pointer to a new Config. @@ -59,6 +60,8 @@ func New() *Config { flag.StringVar(&c.OwnCSURL, "ownCSURL", config.LookupEnvString("OWN_CS_URL", ""), "URL of current CS (http(s)://host[:port]).") flag.StringVar(&c.InstrumentationAddr, "listenInstrumentationAddr", config.LookupEnvString("LISTEN_INSTRUMENTATION_ADDR", ":9090"), `Address in form of "[host]:port" that instrumentation HTTP server should be listening on.`) + config.StringListVar(&c.CORSAllowedOrigins, "corsAllowedOrigins", "CORS_ALLOWED_ORIGINS", "", "Comma separated list of origins allowed to call the API cross-origin. Empty value allows same-origin requests only.") + flag.Parse() return c diff --git a/cluster-manager/pkg/server/server.go b/cluster-manager/pkg/server/server.go index 25feaaae..6b096d56 100644 --- a/cluster-manager/pkg/server/server.go +++ b/cluster-manager/pkg/server/server.go @@ -10,7 +10,6 @@ import ( "github.com/justinas/alice" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promhttp" - "github.com/rs/cors" "github.com/runtime-radar/runtime-radar/cluster-manager/api" "github.com/runtime-radar/runtime-radar/lib/server/healthcheck" "github.com/runtime-radar/runtime-radar/lib/server/middleware" @@ -26,7 +25,7 @@ const ( ) // New constructs and configures new *http.Server capable of serving application and gRPC gateway endpoints. -func New(httpAddr, grpcAddr string, tlsConfig *tls.Config) (*http.Server, error) { +func New(httpAddr, grpcAddr string, tlsConfig *tls.Config, corsAllowedOrigins []string) (*http.Server, error) { mux := http.NewServeMux() gwMux, err := newGWMux(context.Background(), grpcAddr, tlsConfig) @@ -34,7 +33,7 @@ func New(httpAddr, grpcAddr string, tlsConfig *tls.Config) (*http.Server, error) return nil, err } - h := setupRouter(mux, gwMux) + h := setupRouter(mux, gwMux, corsAllowedOrigins) s := &http.Server{ ReadTimeout: readTimeout, @@ -65,19 +64,13 @@ func NewInstrumentation(listenAddress string, gatherer prometheus.Gatherer) *htt } } -func setupRouter(mux *http.ServeMux, gwMux *runtime.ServeMux) http.Handler { +func setupRouter(mux *http.ServeMux, gwMux *runtime.ServeMux, corsAllowedOrigins []string) http.Handler { mux.Handle("/", gwMux) - corsOpts := cors.Options{ - AllowedOrigins: []string{"*"}, - AllowedMethods: []string{"GET", "POST", "PUT", "PATCH", "DELETE"}, - AllowedHeaders: []string{"Origin", "Accept", "Content-Type", "X-Requested-With", "Authorization"}, - } - h := alice.New( middleware.Log, middleware.Recovery, - cors.New(corsOpts).Handler, + middleware.CORS(corsAllowedOrigins, middleware.DefaultCORSHeaders), ).Then(mux) return h diff --git a/cs-manager/cmd/cs-manager/main.go b/cs-manager/cmd/cs-manager/main.go index d2411b99..999b6879 100644 --- a/cs-manager/cmd/cs-manager/main.go +++ b/cs-manager/cmd/cs-manager/main.go @@ -198,7 +198,7 @@ func main() { }() log.Info().Msgf("gRPC server listening at %v", lis.Addr()) - httpSrv, err := server.New(cfg.ListenHTTPAddr, cfg.ListenGRPCAddr, tlsConfig) + httpSrv, err := server.New(cfg.ListenHTTPAddr, cfg.ListenGRPCAddr, tlsConfig, cfg.CORSAllowedOrigins) if err != nil { log.Fatal().Msgf("### Can't setup HTTP server: %v", err) } diff --git a/cs-manager/pkg/config/config.go b/cs-manager/pkg/config/config.go index 5be9fb6a..55754214 100644 --- a/cs-manager/pkg/config/config.go +++ b/cs-manager/pkg/config/config.go @@ -9,30 +9,31 @@ import ( // Config represents system configuration. type Config struct { - NewDB bool // forces recreation of DB - PostgresAddr string // Postgres address in host[:port] format - PostgresDB string // Postgres db name - PostgresUser string // Postgres user - PostgresPassword string // Postgres password - PostgresSSLMode bool // Postgres SSL mode - PostgresSSLCheckCert bool // Check postgres SSL cert - LogLevel string // log level can be INFO, WARN, ERROR, FATAL, DEBUG or ALL - LogFile string // path to log file - ListenGRPCAddr string // address "[host]:port" that server should be listening on - ListenHTTPAddr string // address "[host]:port" that server should be listening for health checks - InstrumentationAddr string // address "[host]:port" that instrumentation server should be listening for health checks and metrics - TLS bool // is TLS enabled? - CSVersion string // CS version - TokenKey string // key for jwt token - Auth bool // is auth enabled? - GopsAddr string // gops listen address - IsChildCluster bool // is CS in child cluster - OwnCSURL string // URL of current CS - CentralCSURL string // URL of central CS - GrafanaURL string // URL to Grafana with CS metrics - CentralCSTLSCheckCert bool // Check central CS TLS certificate - RegistrationToken string // token of current cluster to register in central CS - RegistrationInterval time.Duration // interval for registration in central CS + NewDB bool // forces recreation of DB + PostgresAddr string // Postgres address in host[:port] format + PostgresDB string // Postgres db name + PostgresUser string // Postgres user + PostgresPassword string // Postgres password + PostgresSSLMode bool // Postgres SSL mode + PostgresSSLCheckCert bool // Check postgres SSL cert + LogLevel string // log level can be INFO, WARN, ERROR, FATAL, DEBUG or ALL + CORSAllowedOrigins config.StringList // origins allowed to call the API cross-origin + LogFile string // path to log file + ListenGRPCAddr string // address "[host]:port" that server should be listening on + ListenHTTPAddr string // address "[host]:port" that server should be listening for health checks + InstrumentationAddr string // address "[host]:port" that instrumentation server should be listening for health checks and metrics + TLS bool // is TLS enabled? + CSVersion string // CS version + TokenKey string // key for jwt token + Auth bool // is auth enabled? + GopsAddr string // gops listen address + IsChildCluster bool // is CS in child cluster + OwnCSURL string // URL of current CS + CentralCSURL string // URL of central CS + GrafanaURL string // URL to Grafana with CS metrics + CentralCSTLSCheckCert bool // Check central CS TLS certificate + RegistrationToken string // token of current cluster to register in central CS + RegistrationInterval time.Duration // interval for registration in central CS } // New reads config from environment and returns pointer to a new Config. @@ -64,6 +65,8 @@ func New() *Config { flag.DurationVar(&c.RegistrationInterval, "registrationInterval", config.LookupEnvDuration("REGISTRATION_INTERVAL", time.Minute*5), "Interval for registration in central CS.") flag.StringVar(&c.InstrumentationAddr, "listenInstrumentationAddr", config.LookupEnvString("LISTEN_INSTRUMENTATION_ADDR", ":9090"), `Address in form of "[host]:port" that instrumentation HTTP server should be listening on.`) + config.StringListVar(&c.CORSAllowedOrigins, "corsAllowedOrigins", "CORS_ALLOWED_ORIGINS", "", "Comma separated list of origins allowed to call the API cross-origin. Empty value allows same-origin requests only.") + flag.Parse() return c diff --git a/cs-manager/pkg/server/server.go b/cs-manager/pkg/server/server.go index e3122c3e..bdae819d 100644 --- a/cs-manager/pkg/server/server.go +++ b/cs-manager/pkg/server/server.go @@ -10,7 +10,6 @@ import ( "github.com/justinas/alice" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promhttp" - "github.com/rs/cors" "github.com/runtime-radar/runtime-radar/cs-manager/api" "github.com/runtime-radar/runtime-radar/lib/server/healthcheck" "github.com/runtime-radar/runtime-radar/lib/server/middleware" @@ -28,14 +27,14 @@ const ( ) // New constructs and configures new *http.Server capable of serving application and gRPC gateway endpoints. -func New(httpAddr, grpcAddr string, tlsConfig *tls.Config) (*http.Server, error) { +func New(httpAddr, grpcAddr string, tlsConfig *tls.Config, corsAllowedOrigins []string) (*http.Server, error) { mux := http.NewServeMux() gwMux, err := newGWMux(context.Background(), grpcAddr, tlsConfig) if err != nil { return nil, err } - h := setupRouter(mux, gwMux) + h := setupRouter(mux, gwMux, corsAllowedOrigins) s := &http.Server{ ReadTimeout: readTimeout, @@ -68,19 +67,13 @@ func NewInstrumentation(listenAddress string, gatherer prometheus.Gatherer) *htt } } -func setupRouter(mux *http.ServeMux, gwMux *runtime.ServeMux) http.Handler { +func setupRouter(mux *http.ServeMux, gwMux *runtime.ServeMux, corsAllowedOrigins []string) http.Handler { mux.Handle("/", gwMux) - corsOpts := cors.Options{ - AllowedOrigins: []string{"*"}, - AllowedMethods: []string{"GET", "POST", "PUT", "PATCH", "DELETE"}, - AllowedHeaders: []string{"Origin", "Accept", "Content-Type", "X-Requested-With", "Authorization"}, - } - h := alice.New( middleware.Log, middleware.Recovery, - cors.New(corsOpts).Handler, + middleware.CORS(corsAllowedOrigins, middleware.DefaultCORSHeaders), ).Then(mux) return h diff --git a/event-processor/cmd/event-processor/main.go b/event-processor/cmd/event-processor/main.go index 4590a785..7e4e8d9b 100644 --- a/event-processor/cmd/event-processor/main.go +++ b/event-processor/cmd/event-processor/main.go @@ -225,7 +225,7 @@ func main() { }() log.Info().Msgf("gRPC server listening at %v", lis.Addr()) - httpSrv, err := server.New(cfg.ListenHTTPAddr, cfg.ListenGRPCAddr, tlsConfig) + httpSrv, err := server.New(cfg.ListenHTTPAddr, cfg.ListenGRPCAddr, tlsConfig, cfg.CORSAllowedOrigins) if err != nil { log.Fatal().Msgf("### Can't setup HTTP server: %v", err) } diff --git a/event-processor/pkg/config/config.go b/event-processor/pkg/config/config.go index d9e9debd..5a5d5165 100644 --- a/event-processor/pkg/config/config.go +++ b/event-processor/pkg/config/config.go @@ -9,36 +9,37 @@ import ( // Config represents system configuration. type Config struct { - NewDB bool // forces recreation of DB - PostgresAddr string // Postgres address in host[:port] format - PostgresDB string // Postgres db name - PostgresUser string // Postgres user - PostgresPassword string // Postgres password - PostgresSSLMode bool // Postgres SSL mode - PostgresSSLCheckCert bool // Check postgres SSL cert - LogLevel string // log level can be INFO, WARN, ERROR, FATAL, DEBUG or ALL - LogFile string // path to log file - ListenGRPCAddr string // address "[host]:port" that server should be listening on - ListenHTTPAddr string // address "[host]:port" that server should be listening on - InstrumentationAddr string // address "[host]:port" that instrumentation server should be listening for health checks and metrics - TLS bool // is TLS enabled? - TokenKey string // key for jwt token - Auth bool // is auth enabled? - ConfigUpdateInterval time.Duration // interval for config periodic update check - RabbitAddr string // RabbitMQ address in host[:port] format - RabbitUser string // RabbitMQ user - RabbitPassword string // RabbitMQ password - RabbitRuntimeEventsQueue string // RabbitMQ queue name for consuming runtime events - RabbitRuntimeEventsQueuePrefetchCount int // RabbitMQ prefetch count when consuming runtime events - RabbitHistoryEventsQueue string // RabbitMQ queue name for publishing processed events - GopsAddr string // gops listen address - WorkersPoolSize int // how many workers will process the events in parallel, 0 means auto-adjust to available cpu cores (minimum 2) - JobsBufferSize int // how many events will be read and kept in memory when all workers are busy - DeployDir string // directory to load detectors from - PolicyEnforcerGRPCAddr string // Policy Enforcer address in host[:port] format - NotifierGRPCAddr string // Notifier address in host[:port] format - KubeManagerGRPCAddr string // Kube Manager address in host[:port] format - OwnCSURL string // URL of current CS (http(s)://host[:port]). + NewDB bool // forces recreation of DB + PostgresAddr string // Postgres address in host[:port] format + PostgresDB string // Postgres db name + PostgresUser string // Postgres user + PostgresPassword string // Postgres password + PostgresSSLMode bool // Postgres SSL mode + PostgresSSLCheckCert bool // Check postgres SSL cert + LogLevel string // log level can be INFO, WARN, ERROR, FATAL, DEBUG or ALL + CORSAllowedOrigins config.StringList // origins allowed to call the API cross-origin + LogFile string // path to log file + ListenGRPCAddr string // address "[host]:port" that server should be listening on + ListenHTTPAddr string // address "[host]:port" that server should be listening on + InstrumentationAddr string // address "[host]:port" that instrumentation server should be listening for health checks and metrics + TLS bool // is TLS enabled? + TokenKey string // key for jwt token + Auth bool // is auth enabled? + ConfigUpdateInterval time.Duration // interval for config periodic update check + RabbitAddr string // RabbitMQ address in host[:port] format + RabbitUser string // RabbitMQ user + RabbitPassword string // RabbitMQ password + RabbitRuntimeEventsQueue string // RabbitMQ queue name for consuming runtime events + RabbitRuntimeEventsQueuePrefetchCount int // RabbitMQ prefetch count when consuming runtime events + RabbitHistoryEventsQueue string // RabbitMQ queue name for publishing processed events + GopsAddr string // gops listen address + WorkersPoolSize int // how many workers will process the events in parallel, 0 means auto-adjust to available cpu cores (minimum 2) + JobsBufferSize int // how many events will be read and kept in memory when all workers are busy + DeployDir string // directory to load detectors from + PolicyEnforcerGRPCAddr string // Policy Enforcer address in host[:port] format + NotifierGRPCAddr string // Notifier address in host[:port] format + KubeManagerGRPCAddr string // Kube Manager address in host[:port] format + OwnCSURL string // URL of current CS (http(s)://host[:port]). } // New reads config from environment and returns pointer to a new Config. @@ -76,6 +77,8 @@ func New() *Config { flag.StringVar(&c.OwnCSURL, "ownCSURL", config.LookupEnvString("OWN_CS_URL", ""), "URL of current CS (http(s)://host[:port]).") flag.StringVar(&c.InstrumentationAddr, "listenInstrumentationAddr", config.LookupEnvString("LISTEN_INSTRUMENTATION_ADDR", ":9090"), `Address in form of "[host]:port" that instrumentation HTTP server should be listening on.`) + config.StringListVar(&c.CORSAllowedOrigins, "corsAllowedOrigins", "CORS_ALLOWED_ORIGINS", "", "Comma separated list of origins allowed to call the API cross-origin. Empty value allows same-origin requests only.") + flag.Parse() return c diff --git a/event-processor/pkg/server/server.go b/event-processor/pkg/server/server.go index 5cd40435..e6e7ae00 100644 --- a/event-processor/pkg/server/server.go +++ b/event-processor/pkg/server/server.go @@ -10,7 +10,6 @@ import ( "github.com/justinas/alice" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promhttp" - "github.com/rs/cors" "github.com/runtime-radar/runtime-radar/event-processor/api" "github.com/runtime-radar/runtime-radar/lib/server/healthcheck" "github.com/runtime-radar/runtime-radar/lib/server/middleware" @@ -28,14 +27,14 @@ const ( ) // New constructs and configures new *http.Server capable of serving application and gRPC gateway endpoints. -func New(httpAddr, grpcAddr string, tlsConfig *tls.Config) (*http.Server, error) { +func New(httpAddr, grpcAddr string, tlsConfig *tls.Config, corsAllowedOrigins []string) (*http.Server, error) { mux := http.NewServeMux() gwMux, err := newGWMux(context.Background(), grpcAddr, tlsConfig) if err != nil { return nil, err } - h := setupRouter(mux, gwMux) + h := setupRouter(mux, gwMux, corsAllowedOrigins) s := &http.Server{ ReadTimeout: readTimeout, @@ -68,19 +67,13 @@ func NewInstrumentation(listenAddress string, gatherer prometheus.Gatherer) *htt } } -func setupRouter(mux *http.ServeMux, gwMux *runtime.ServeMux) http.Handler { +func setupRouter(mux *http.ServeMux, gwMux *runtime.ServeMux, corsAllowedOrigins []string) http.Handler { mux.Handle("/", gwMux) - corsOpts := cors.Options{ - AllowedOrigins: []string{"*"}, - AllowedMethods: []string{"GET", "POST", "PUT", "PATCH", "DELETE"}, - AllowedHeaders: []string{"Origin", "Accept", "Content-Type", "X-Requested-With", "Authorization"}, - } - h := alice.New( middleware.Log, middleware.Recovery, - cors.New(corsOpts).Handler, + middleware.CORS(corsAllowedOrigins, middleware.DefaultCORSHeaders), ).Then(mux) return h diff --git a/history-api/cmd/history-api/main.go b/history-api/cmd/history-api/main.go index 5e9ee22b..8629bddd 100644 --- a/history-api/cmd/history-api/main.go +++ b/history-api/cmd/history-api/main.go @@ -169,7 +169,7 @@ func main() { }() log.Info().Msgf("gRPC server listening at %v", lis.Addr()) - httpSrv, err := server.New(cfg.ListenHTTPAddr, cfg.ListenGRPCAddr, tlsConfig) + httpSrv, err := server.New(cfg.ListenHTTPAddr, cfg.ListenGRPCAddr, tlsConfig, cfg.CORSAllowedOrigins) if err != nil { log.Fatal().Msgf("### Can't setup HTTP server: %v", err) } diff --git a/history-api/pkg/config/config.go b/history-api/pkg/config/config.go index a312cf06..cfc10631 100644 --- a/history-api/pkg/config/config.go +++ b/history-api/pkg/config/config.go @@ -9,40 +9,41 @@ import ( // Config represents system configuration. type Config struct { - NewDB bool // forces recreation of DB - PopulateNum int // populates DB with some test data according to given number - PostgresAddr string // Postgres address in host[:port] format - PostgresDB string // Postgres db name - PostgresUser string // Postgres user - PostgresPassword string // Postgres password - PostgresSSLMode bool // Postgres SSL mode - PostgresSSLCheckCert bool // Check postgres SSL cert - ClickhouseAddr string // Clickhouse address in host[:port] format - ClickhouseDB string // Clickhouse db name - ClickhouseUser string // Clickhouse user - ClickhousePassword string // Clickhouse password - ClickhouseSSLMode bool // Clickhouse SSL mode - ClickhouseSSLCheckCert bool // Check clickhouse SSL cert - RabbitAddr string // RabbitMQ address in host[:port] format - RabbitUser string // RabbitMQ user - RabbitPassword string // RabbitMQ password - RabbitQueue string // RabbitMQ queue name to consume events from - RabbitQueuePrefetchCount int // RabbitMQ prefetch count for queue to consume events from - RuntimeEventsBatchSize int // Size of runtime events buffer - RuntimeEventsSaveInterval time.Duration // Interval between savings of runtime buffer - RuntimeEventsLimit int // Max number of runtime events to be stored in database - RuntimeEventsCleanInterval time.Duration // Interval between cleans of runtime events table - LogLevel string // log level can be INFO, WARN, ERROR, FATAL, DEBUG or ALL - LogFile string // path to log file - ListenGRPCAddr string // address "[host]:port" that server should be listening on - ListenHTTPAddr string // address "[host]:port" that server should be listening for health checks - InstrumentationAddr string // address "[host]:port" that instrumentation server should be listening on - TLS bool // is TLS enabled? - RetentionInterval time.Duration // Interval during which events are kept in DB - TokenKey string // key for jwt token - Auth bool // is auth enabled? - OwnCSURL string // URL of current CS (http(s)://host[:port]). - GopsAddr string // gops listen address + NewDB bool // forces recreation of DB + PopulateNum int // populates DB with some test data according to given number + PostgresAddr string // Postgres address in host[:port] format + PostgresDB string // Postgres db name + PostgresUser string // Postgres user + PostgresPassword string // Postgres password + PostgresSSLMode bool // Postgres SSL mode + PostgresSSLCheckCert bool // Check postgres SSL cert + ClickhouseAddr string // Clickhouse address in host[:port] format + ClickhouseDB string // Clickhouse db name + ClickhouseUser string // Clickhouse user + ClickhousePassword string // Clickhouse password + ClickhouseSSLMode bool // Clickhouse SSL mode + ClickhouseSSLCheckCert bool // Check clickhouse SSL cert + RabbitAddr string // RabbitMQ address in host[:port] format + RabbitUser string // RabbitMQ user + RabbitPassword string // RabbitMQ password + RabbitQueue string // RabbitMQ queue name to consume events from + RabbitQueuePrefetchCount int // RabbitMQ prefetch count for queue to consume events from + RuntimeEventsBatchSize int // Size of runtime events buffer + RuntimeEventsSaveInterval time.Duration // Interval between savings of runtime buffer + RuntimeEventsLimit int // Max number of runtime events to be stored in database + RuntimeEventsCleanInterval time.Duration // Interval between cleans of runtime events table + LogLevel string // log level can be INFO, WARN, ERROR, FATAL, DEBUG or ALL + CORSAllowedOrigins config.StringList // origins allowed to call the API cross-origin + LogFile string // path to log file + ListenGRPCAddr string // address "[host]:port" that server should be listening on + ListenHTTPAddr string // address "[host]:port" that server should be listening for health checks + InstrumentationAddr string // address "[host]:port" that instrumentation server should be listening on + TLS bool // is TLS enabled? + RetentionInterval time.Duration // Interval during which events are kept in DB + TokenKey string // key for jwt token + Auth bool // is auth enabled? + OwnCSURL string // URL of current CS (http(s)://host[:port]). + GopsAddr string // gops listen address } // New reads config from environment and returns pointer to a new Config. @@ -84,6 +85,8 @@ func New() *Config { flag.StringVar(&c.OwnCSURL, "ownCSURL", config.LookupEnvString("OWN_CS_URL", ""), "URL of current CS (http(s)://host[:port]).") flag.StringVar(&c.InstrumentationAddr, "listenInstrumentationAddr", config.LookupEnvString("LISTEN_INSTRUMENTATION_ADDR", ":9090"), `Address in form of "[host]:port" that instrumentation (metrics, probes...) HTTP server should be listening on.`) + config.StringListVar(&c.CORSAllowedOrigins, "corsAllowedOrigins", "CORS_ALLOWED_ORIGINS", "", "Comma separated list of origins allowed to call the API cross-origin. Empty value allows same-origin requests only.") + flag.Parse() return c diff --git a/history-api/pkg/server/server.go b/history-api/pkg/server/server.go index 3496d9e9..e658adb6 100644 --- a/history-api/pkg/server/server.go +++ b/history-api/pkg/server/server.go @@ -11,7 +11,6 @@ import ( "github.com/justinas/alice" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promhttp" - "github.com/rs/cors" "github.com/runtime-radar/runtime-radar/history-api/api" "github.com/runtime-radar/runtime-radar/lib/server/healthcheck" "github.com/runtime-radar/runtime-radar/lib/server/middleware" @@ -29,14 +28,14 @@ const ( ) // New constructs and configures new *http.Server capable of serving application and gRPC gateway endpoints. -func New(httpAddr, grpcAddr string, tlsConfig *tls.Config) (*http.Server, error) { +func New(httpAddr, grpcAddr string, tlsConfig *tls.Config, corsAllowedOrigins []string) (*http.Server, error) { mux := http.NewServeMux() gwMux, err := newGWMux(context.Background(), grpcAddr, tlsConfig) if err != nil { return nil, err } - h := setupRouter(mux, gwMux) + h := setupRouter(mux, gwMux, corsAllowedOrigins) s := &http.Server{ ReadTimeout: readTimeout, @@ -69,19 +68,13 @@ func NewInstrumentation(listenAddress string, gatherer prometheus.Gatherer) *htt } } -func setupRouter(mux *http.ServeMux, gwMux *runtime.ServeMux) http.Handler { +func setupRouter(mux *http.ServeMux, gwMux *runtime.ServeMux, corsAllowedOrigins []string) http.Handler { mux.Handle("/", gwMux) - corsOpts := cors.Options{ - AllowedOrigins: []string{"*"}, - AllowedMethods: []string{"GET", "POST", "PUT", "PATCH", "DELETE"}, - AllowedHeaders: []string{"Origin", "Accept", "Content-Type", "X-Requested-With", "Authorization"}, - } - h := alice.New( middleware.Log, middleware.Recovery, - cors.New(corsOpts).Handler, + middleware.CORS(corsAllowedOrigins, middleware.DefaultCORSHeaders), ).Then(mux) return h diff --git a/install/helm/templates/configmap.yaml b/install/helm/templates/configmap.yaml index 174dc162..cfa5d32a 100644 --- a/install/helm/templates/configmap.yaml +++ b/install/helm/templates/configmap.yaml @@ -20,6 +20,8 @@ data: CENTRAL_CS_URL: {{ $centralCsUrl | quote }} CENTRAL_CS_TLS_CHECK_CERT: {{ .Values.tls.verify | quote }} CENTRAL_CS_HOSTNAME: {{ $centralCsUrl | urlParse | pluck "hostname" | first | quote }} + {{- /* Only a child cluster is called cross-origin, by the UI of the central CS. */}} + CORS_ALLOWED_ORIGINS: {{ default (ternary $centralCsUrl "" (eq (include "common.cs.isChildCluster" .) "true")) (.Values.global).corsAllowedOrigins | quote }} GOPS_CONFIG_DIR: "/tmp" {{- if ((.Values.global).logger).enabled }} LOGGER_ENABLED: "true" diff --git a/install/helm/values.yaml b/install/helm/values.yaml index 0854fd0e..58e6162e 100644 --- a/install/helm/values.yaml +++ b/install/helm/values.yaml @@ -106,6 +106,8 @@ global: centralCsUrl: "" # -- Is this a child cluster isChildCluster: false + # -- Comma separated origins allowed cross-origin. Empty means same-origin only; child clusters fall back to `centralCsUrl` + corsAllowedOrigins: "" # @skip global.csVersion csVersion: "" diff --git a/lib/config/util.go b/lib/config/util.go index 414d4fc4..44ac9268 100644 --- a/lib/config/util.go +++ b/lib/config/util.go @@ -1,8 +1,10 @@ package config import ( + "flag" "os" "strconv" + "strings" "time" "github.com/rs/zerolog/log" @@ -57,3 +59,59 @@ func LookupEnvDuration(key string, defaultVal time.Duration) time.Duration { } return defaultVal } + +// StringList is a comma separated flag value, e.g. "https://a.example,https://b.example". +type StringList []string + +func (l *StringList) String() string { + if l == nil { + return "" + } + return strings.Join(*l, ",") +} + +// Set appends to the current value, so a repeated flag accumulates: +// -flag=a,b -flag=c gives [a b c]. +func (l *StringList) Set(value string) error { + *l = append(*l, splitList(value)...) + return nil +} + +// stringListValue drops the env default the first time the flag appears on the command line. +// Without it the env value and the command line one would merge into a list wider than either. +type stringListValue struct { + list *StringList + seen bool +} + +func (v *stringListValue) String() string { + return v.list.String() +} + +func (v *stringListValue) Set(value string) error { + if !v.seen { + *v.list, v.seen = nil, true + } + + return v.list.Set(value) +} + +// StringListVar defines a comma separated list flag with a default taken from env. +// flag.Var accepts no default of its own, hence the helper. +func StringListVar(p *StringList, name, key, defaultVal, usage string) { + *p = splitList(LookupEnvString(key, defaultVal)) + flag.Var(&stringListValue{list: p}, name, usage) +} + +// splitList turns a comma separated setting into a list, dropping empty entries and whitespace. +func splitList(value string) []string { + res := []string{} + + for _, item := range strings.Split(value, ",") { + if item = strings.TrimSpace(item); item != "" { + res = append(res, item) + } + } + + return res +} diff --git a/lib/config/util_test.go b/lib/config/util_test.go new file mode 100644 index 00000000..6a961115 --- /dev/null +++ b/lib/config/util_test.go @@ -0,0 +1,98 @@ +package config + +import ( + "flag" + "slices" + "testing" +) + +// withCommandLine swaps the global flag set, so a test can declare and parse flags in isolation. +func withCommandLine(t *testing.T) { + t.Helper() + + orig := flag.CommandLine + flag.CommandLine = flag.NewFlagSet(t.Name(), flag.ContinueOnError) + t.Cleanup(func() { flag.CommandLine = orig }) +} + +func TestStringListVarUsesEnvDefault(t *testing.T) { + withCommandLine(t) + t.Setenv("CORS_ALLOWED_ORIGINS", " https://a.example , ,https://b.example ") + + var origins StringList + StringListVar(&origins, "corsAllowedOrigins", "CORS_ALLOWED_ORIGINS", "", "") + + if err := flag.CommandLine.Parse(nil); err != nil { + t.Fatalf("Can't parse flags: %v", err) + } + + want := []string{"https://a.example", "https://b.example"} + if !slices.Equal(origins, want) { + t.Fatalf("Expected %q, got %q", want, origins) + } +} + +func TestStringListVarFlagOverridesEnv(t *testing.T) { + withCommandLine(t) + t.Setenv("CORS_ALLOWED_ORIGINS", "https://from-env.example") + + var origins StringList + StringListVar(&origins, "corsAllowedOrigins", "CORS_ALLOWED_ORIGINS", "", "") + + // The env default must be replaced, not extended: an allow list merged from two sources is + // wider than either of them. + if err := flag.CommandLine.Parse([]string{"-corsAllowedOrigins", "https://from-flag.example"}); err != nil { + t.Fatalf("Can't parse flags: %v", err) + } + + want := []string{"https://from-flag.example"} + if !slices.Equal(origins, want) { + t.Fatalf("Expected %q, got %q", want, origins) + } +} + +func TestStringListVarRepeatedFlagAppends(t *testing.T) { + withCommandLine(t) + t.Setenv("CORS_ALLOWED_ORIGINS", "https://from-env.example") + + var origins StringList + StringListVar(&origins, "corsAllowedOrigins", "CORS_ALLOWED_ORIGINS", "", "") + + args := []string{"-corsAllowedOrigins", "https://a.example,https://b.example", "-corsAllowedOrigins", "https://c.example"} + if err := flag.CommandLine.Parse(args); err != nil { + t.Fatalf("Can't parse flags: %v", err) + } + + // Repeated occurrences accumulate, but the env default is still dropped by the first one. + want := []string{"https://a.example", "https://b.example", "https://c.example"} + if !slices.Equal(origins, want) { + t.Fatalf("Expected %q, got %q", want, origins) + } +} + +func TestStringListVarEmptyValue(t *testing.T) { + withCommandLine(t) + + var origins StringList + StringListVar(&origins, "corsAllowedOrigins", "CORS_ALLOWED_ORIGINS", "", "") + + if err := flag.CommandLine.Parse(nil); err != nil { + t.Fatalf("Can't parse flags: %v", err) + } + + if len(origins) != 0 { + t.Fatalf("Expected no origins, got %q", origins) + } +} + +func TestStringListString(t *testing.T) { + var empty *StringList + if got := empty.String(); got != "" { + t.Fatalf("Expected an empty string for a nil list, got %q", got) + } + + origins := StringList{"https://a.example", "https://b.example"} + if got := origins.String(); got != "https://a.example,https://b.example" { + t.Fatalf("Unexpected string representation %q", got) + } +} diff --git a/lib/server/middleware/cors.go b/lib/server/middleware/cors.go new file mode 100644 index 00000000..7aa29ec8 --- /dev/null +++ b/lib/server/middleware/cors.go @@ -0,0 +1,26 @@ +package middleware + +import ( + "net/http" + + "github.com/rs/cors" +) + +// DefaultCORSHeaders are the headers the UI sends to every service. +var DefaultCORSHeaders = []string{"Origin", "Accept", "Content-Type", "X-Requested-With", "Authorization"} + +var corsMethods = []string{http.MethodGet, http.MethodPost, http.MethodPut, http.MethodPatch, http.MethodDelete} + +// CORS allows cross-origin API calls from allowedOrigins, which is how a central CS UI reaches a +// child cluster. An empty list is skipped, since rs/cors would read it as "allow any". +func CORS(allowedOrigins, allowedHeaders []string) func(http.Handler) http.Handler { + if len(allowedOrigins) == 0 { + return func(next http.Handler) http.Handler { return next } + } + + return cors.New(cors.Options{ + AllowedOrigins: allowedOrigins, + AllowedMethods: corsMethods, + AllowedHeaders: allowedHeaders, + }).Handler +} diff --git a/lib/server/middleware/cors_test.go b/lib/server/middleware/cors_test.go new file mode 100644 index 00000000..de891ced --- /dev/null +++ b/lib/server/middleware/cors_test.go @@ -0,0 +1,59 @@ +package middleware + +import ( + "net/http" + "net/http/httptest" + "testing" +) + +func handlerWithCORS(allowedOrigins []string) http.Handler { + next := http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusOK) + }) + + return CORS(allowedOrigins, DefaultCORSHeaders)(next) +} + +func originHeaderFor(t *testing.T, allowedOrigins []string, origin string) string { + t.Helper() + + req := httptest.NewRequest(http.MethodGet, "/api/v1/rule", nil) + req.Header.Set("Origin", origin) + + rec := httptest.NewRecorder() + handlerWithCORS(allowedOrigins).ServeHTTP(rec, req) + + return rec.Header().Get("Access-Control-Allow-Origin") +} + +func TestCORSEmptyOriginsAllowsNobody(t *testing.T) { + // rs/cors reads an empty origin list as "allow any", which is exactly what must not happen here + if got := originHeaderFor(t, nil, "https://attacker.tld"); got != "" { + t.Fatalf("Expected no allow-origin header, got %q", got) + } + if got := originHeaderFor(t, []string{}, "https://attacker.tld"); got != "" { + t.Fatalf("Expected no allow-origin header, got %q", got) + } +} + +func TestCORSAllowsConfiguredOrigin(t *testing.T) { + allowed := []string{"https://central.example"} + + if got := originHeaderFor(t, allowed, "https://central.example"); got != "https://central.example" { + t.Fatalf("Expected the configured origin to be allowed, got %q", got) + } + if got := originHeaderFor(t, allowed, "https://attacker.tld"); got != "" { + t.Fatalf("Expected an unknown origin to be rejected, got %q", got) + } +} + +func TestCORSPassesRequestThroughWhenDisabled(t *testing.T) { + req := httptest.NewRequest(http.MethodGet, "/api/v1/rule", nil) + rec := httptest.NewRecorder() + + handlerWithCORS(nil).ServeHTTP(rec, req) + + if rec.Code != http.StatusOK { + t.Fatalf("Expected same-origin requests to still be served, got %d", rec.Code) + } +} diff --git a/notifier/cmd/notifier/main.go b/notifier/cmd/notifier/main.go index a9b3c7da..6f4d3120 100644 --- a/notifier/cmd/notifier/main.go +++ b/notifier/cmd/notifier/main.go @@ -169,7 +169,7 @@ func main() { }() log.Info().Msgf("gRPC server listening at %v", lis.Addr()) - httpSrv, err := server.New(cfg.ListenHTTPAddr, cfg.ListenGRPCAddr, tlsConfig) + httpSrv, err := server.New(cfg.ListenHTTPAddr, cfg.ListenGRPCAddr, tlsConfig, cfg.CORSAllowedOrigins) if err != nil { log.Fatal().Msgf("### Can't setup HTTP server: %v", err) } diff --git a/notifier/pkg/config/config.go b/notifier/pkg/config/config.go index 51823f55..691eda89 100644 --- a/notifier/pkg/config/config.go +++ b/notifier/pkg/config/config.go @@ -8,27 +8,28 @@ import ( // Config represents system configuration. type Config struct { - NewDB bool // forces recreation of DB - PostgresAddr string // Postgres address in host[:port] format - PostgresDB string // Postgres db name - PostgresUser string // Postgres user - PostgresPassword string // Postgres password - PostgresSSLMode bool // Postgres SSL mode - PostgresSSLCheckCert bool // Check postgres SSL cert - LogLevel string // log level can be INFO, WARN, ERROR, FATAL, DEBUG or ALL - LogFile string // path to log file - ListenGRPCAddr string // address "[host]:port" that server should be listening on - ListenHTTPAddr string // address "[host]:port" that server should be listening for health checks - InstrumentationAddr string // address "[host]:port" that instrumentation server should be listening for health checks and metrics - TLS bool // is TLS enabled? - PolicyEnforcerGRPCAddr string // Policy Enforcer address in host[:port] format - EncryptionKey string // key for encryption - TokenKey string // key for jwt token - Auth bool // is auth enabled? - CSVersion string // CS version - TemplatesFolder string // Relative path to the templates root folder - GopsAddr string // gops listen address - OwnCSURL string // URL of current CS (http(s)://host[:port]). + NewDB bool // forces recreation of DB + PostgresAddr string // Postgres address in host[:port] format + PostgresDB string // Postgres db name + PostgresUser string // Postgres user + PostgresPassword string // Postgres password + PostgresSSLMode bool // Postgres SSL mode + PostgresSSLCheckCert bool // Check postgres SSL cert + LogLevel string // log level can be INFO, WARN, ERROR, FATAL, DEBUG or ALL + CORSAllowedOrigins config.StringList // origins allowed to call the API cross-origin + LogFile string // path to log file + ListenGRPCAddr string // address "[host]:port" that server should be listening on + ListenHTTPAddr string // address "[host]:port" that server should be listening for health checks + InstrumentationAddr string // address "[host]:port" that instrumentation server should be listening for health checks and metrics + TLS bool // is TLS enabled? + PolicyEnforcerGRPCAddr string // Policy Enforcer address in host[:port] format + EncryptionKey string // key for encryption + TokenKey string // key for jwt token + Auth bool // is auth enabled? + CSVersion string // CS version + TemplatesFolder string // Relative path to the templates root folder + GopsAddr string // gops listen address + OwnCSURL string // URL of current CS (http(s)://host[:port]). // For tests only TestMailpitHTTPAddr string // Mailpit HTTP API address @@ -69,6 +70,8 @@ func New() *Config { flag.StringVar(&c.TestSyslogUDPAddr, "testSyslogUDPAddr", config.LookupEnvString("TEST_SYSLOG_UDP_ADDR", "udp://127.0.0.1:6514"), `Address in form of "scheme://host:port" of Syslog UDP`) flag.StringVar(&c.TestSyslogTCPAddr, "testSyslogTCPAddr", config.LookupEnvString("TEST_SYSLOG_TCP_ADDR", "tcp://127.0.0.1:6601"), `Address in form of "scheme://host:port" of Syslog TCP`) + config.StringListVar(&c.CORSAllowedOrigins, "corsAllowedOrigins", "CORS_ALLOWED_ORIGINS", "", "Comma separated list of origins allowed to call the API cross-origin. Empty value allows same-origin requests only.") + flag.Parse() return c diff --git a/notifier/pkg/server/server.go b/notifier/pkg/server/server.go index 5544e4e9..f6f4a457 100644 --- a/notifier/pkg/server/server.go +++ b/notifier/pkg/server/server.go @@ -10,7 +10,6 @@ import ( "github.com/justinas/alice" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promhttp" - "github.com/rs/cors" "github.com/runtime-radar/runtime-radar/lib/server/healthcheck" "github.com/runtime-radar/runtime-radar/lib/server/middleware" "github.com/runtime-radar/runtime-radar/notifier/api" @@ -28,14 +27,14 @@ const ( ) // New constructs and configures new *http.Server capable of serving application and gRPC gateway endpoints. -func New(httpAddr, grpcAddr string, tlsConfig *tls.Config) (*http.Server, error) { +func New(httpAddr, grpcAddr string, tlsConfig *tls.Config, corsAllowedOrigins []string) (*http.Server, error) { mux := http.NewServeMux() gwMux, err := newGWMux(context.Background(), grpcAddr, tlsConfig) if err != nil { return nil, err } - h := setupRouter(mux, gwMux) + h := setupRouter(mux, gwMux, corsAllowedOrigins) s := &http.Server{ ReadTimeout: readTimeout, @@ -68,19 +67,13 @@ func NewInstrumentation(listenAddress string, gatherer prometheus.Gatherer) *htt } } -func setupRouter(mux *http.ServeMux, gwMux *runtime.ServeMux) http.Handler { +func setupRouter(mux *http.ServeMux, gwMux *runtime.ServeMux, corsAllowedOrigins []string) http.Handler { mux.Handle("/", gwMux) - corsOpts := cors.Options{ - AllowedOrigins: []string{"*"}, - AllowedMethods: []string{"GET", "POST", "PUT", "PATCH", "DELETE"}, - AllowedHeaders: []string{"Origin", "Accept", "Content-Type", "X-Requested-With", "Authorization"}, - } - h := alice.New( middleware.Log, middleware.Recovery, - cors.New(corsOpts).Handler, + middleware.CORS(corsAllowedOrigins, middleware.DefaultCORSHeaders), ).Then(mux) return h diff --git a/policy-enforcer/cmd/policy-enforcer/main.go b/policy-enforcer/cmd/policy-enforcer/main.go index 0e3822aa..c34ea5bf 100644 --- a/policy-enforcer/cmd/policy-enforcer/main.go +++ b/policy-enforcer/cmd/policy-enforcer/main.go @@ -157,7 +157,7 @@ func main() { }() log.Info().Msgf("gRPC server listening at %v", lis.Addr()) - httpSrv, err := server.New(cfg.ListenHTTPAddr, cfg.ListenGRPCAddr, tlsConfig) + httpSrv, err := server.New(cfg.ListenHTTPAddr, cfg.ListenGRPCAddr, tlsConfig, cfg.CORSAllowedOrigins) if err != nil { log.Fatal().Msgf("### Can't setup HTTP server: %v", err) } diff --git a/policy-enforcer/pkg/config/config.go b/policy-enforcer/pkg/config/config.go index 4d6fe73b..471bcc05 100644 --- a/policy-enforcer/pkg/config/config.go +++ b/policy-enforcer/pkg/config/config.go @@ -8,28 +8,29 @@ import ( // Config represents system configuration. type Config struct { - NewDB bool // forces recreation of DB - PostgresAddr string // Postgres address in host[:port] format - PostgresDB string // Postgres db name - PostgresUser string // Postgres user - PostgresPassword string // Postgres password - PostgresSSLMode bool // Postgres SSL mode - PostgresSSLCheckCert bool // Check postgres SSL cert - LogLevel string // log level can be INFO, WARN, ERROR, FATAL, DEBUG or ALL - LogFile string // path to log file - ListenGRPCAddr string // address "[host]:port" that server should be listening on - ListenHTTPAddr string // address "[host]:port" that server should be listening on - InstrumentationAddr string // address "[host]:port" that instrumentation server should be listening for health checks and metrics - TLS bool // is TLS enabled? - TokenKey string // key for jwt token - Auth bool // is auth enabled? - RedisAddr string // Redis address in host[:port] format - RedisUser string // Redis user - RedisPassword string // Redis password - RedisTLSMode bool // Redis TLS mode - RedisTLSCheckCert bool // Check redis TLS cert - OwnCSURL string // URL of current CS (http(s)://host[:port]). - GopsAddr string // gops listen address + NewDB bool // forces recreation of DB + PostgresAddr string // Postgres address in host[:port] format + PostgresDB string // Postgres db name + PostgresUser string // Postgres user + PostgresPassword string // Postgres password + PostgresSSLMode bool // Postgres SSL mode + PostgresSSLCheckCert bool // Check postgres SSL cert + LogLevel string // log level can be INFO, WARN, ERROR, FATAL, DEBUG or ALL + CORSAllowedOrigins config.StringList // origins allowed to call the API cross-origin + LogFile string // path to log file + ListenGRPCAddr string // address "[host]:port" that server should be listening on + ListenHTTPAddr string // address "[host]:port" that server should be listening on + InstrumentationAddr string // address "[host]:port" that instrumentation server should be listening for health checks and metrics + TLS bool // is TLS enabled? + TokenKey string // key for jwt token + Auth bool // is auth enabled? + RedisAddr string // Redis address in host[:port] format + RedisUser string // Redis user + RedisPassword string // Redis password + RedisTLSMode bool // Redis TLS mode + RedisTLSCheckCert bool // Check redis TLS cert + OwnCSURL string // URL of current CS (http(s)://host[:port]). + GopsAddr string // gops listen address } // New reads config from environment and returns pointer to a new Config. @@ -59,6 +60,8 @@ func New() *Config { flag.StringVar(&c.OwnCSURL, "ownCSURL", config.LookupEnvString("OWN_CS_URL", ""), "URL of current CS (http(s)://host[:port]).") flag.StringVar(&c.InstrumentationAddr, "listenInstrumentationAddr", config.LookupEnvString("LISTEN_INSTRUMENTATION_ADDR", ":9090"), `Address in form of "[host]:port" that instrumentation HTTP server should be listening on.`) + config.StringListVar(&c.CORSAllowedOrigins, "corsAllowedOrigins", "CORS_ALLOWED_ORIGINS", "", "Comma separated list of origins allowed to call the API cross-origin. Empty value allows same-origin requests only.") + flag.Parse() return c diff --git a/policy-enforcer/pkg/server/server.go b/policy-enforcer/pkg/server/server.go index d1a9ee2a..9f7c4341 100644 --- a/policy-enforcer/pkg/server/server.go +++ b/policy-enforcer/pkg/server/server.go @@ -10,7 +10,6 @@ import ( "github.com/justinas/alice" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promhttp" - "github.com/rs/cors" "github.com/runtime-radar/runtime-radar/lib/server/healthcheck" "github.com/runtime-radar/runtime-radar/lib/server/middleware" "github.com/runtime-radar/runtime-radar/policy-enforcer/api" @@ -28,14 +27,14 @@ const ( ) // New constructs and configures new *http.Server capable of serving application and gRPC gateway endpoints. -func New(httpAddr, grpcAddr string, tlsConfig *tls.Config) (*http.Server, error) { +func New(httpAddr, grpcAddr string, tlsConfig *tls.Config, corsAllowedOrigins []string) (*http.Server, error) { mux := http.NewServeMux() gwMux, err := newGWMux(context.Background(), grpcAddr, tlsConfig) if err != nil { return nil, err } - h := setupRouter(mux, gwMux) + h := setupRouter(mux, gwMux, corsAllowedOrigins) s := &http.Server{ ReadTimeout: readTimeout, @@ -68,19 +67,13 @@ func NewInstrumentation(listenAddress string, gatherer prometheus.Gatherer) *htt } } -func setupRouter(mux *http.ServeMux, gwMux *runtime.ServeMux) http.Handler { +func setupRouter(mux *http.ServeMux, gwMux *runtime.ServeMux, corsAllowedOrigins []string) http.Handler { mux.Handle("/", gwMux) - corsOpts := cors.Options{ - AllowedOrigins: []string{"*"}, - AllowedMethods: []string{"GET", "POST", "PUT", "PATCH", "DELETE"}, - AllowedHeaders: []string{"Origin", "Accept", "Content-Type", "X-Requested-With", "Authorization"}, - } - h := alice.New( middleware.Log, middleware.Recovery, - cors.New(corsOpts).Handler, + middleware.CORS(corsAllowedOrigins, middleware.DefaultCORSHeaders), ).Then(mux) return h diff --git a/public-api/cmd/public-api/main.go b/public-api/cmd/public-api/main.go index 2c5731df..f31586f3 100755 --- a/public-api/cmd/public-api/main.go +++ b/public-api/cmd/public-api/main.go @@ -169,6 +169,7 @@ func main() { services.configSvc, services.nodeSvc, services.podSvc, + cfg.CORSAllowedOrigins, ) go func() { diff --git a/public-api/pkg/config/config.go b/public-api/pkg/config/config.go index ed9bd2e5..f07cfb5e 100644 --- a/public-api/pkg/config/config.go +++ b/public-api/pkg/config/config.go @@ -8,27 +8,28 @@ import ( // Config represents system configuration. type Config struct { - NewDB bool // forces recreation of DB - PostgresAddr string // Postgres address in host[:port] format - PostgresDB string // Postgres db name - PostgresUser string // Postgres user - PostgresPassword string // Postgres password - PostgresSSLMode bool // Postgres SSL mode - PostgresSSLCheckCert bool // Check postgres SSL cert - LogLevel string // log level can be INFO, WARN, ERROR, FATAL, DEBUG or ALL - LogFile string // path to log file - ListenHTTPAddr string // address "[host]:port" that server should be listening for health checks - InstrumentationAddr string // address "[host]:port" that instrumentation server should be listening for health checks and metrics - TLS bool // is TLS enabled? - AuthAPIURL string // Auth API URL in schema://host[:port] format - PolicyEnforcerGRPCAddr string // Policy Enforcer gRPC address in host[:port] format - HistoryAPIGRPCAddr string // History API gRPC address in host[:port] format - KubeManagerGRPCAddr string // Kube Manager gRPC address in host[:port] format - TokenKey string // key for jwt token - AccessTokenSalt string // salt for access token - Auth bool // is auth enabled? - GopsAddr string // gops listen address - OwnCSURL string // URL of current CS (http(s)://host[:port]). + NewDB bool // forces recreation of DB + PostgresAddr string // Postgres address in host[:port] format + PostgresDB string // Postgres db name + PostgresUser string // Postgres user + PostgresPassword string // Postgres password + PostgresSSLMode bool // Postgres SSL mode + PostgresSSLCheckCert bool // Check postgres SSL cert + LogLevel string // log level can be INFO, WARN, ERROR, FATAL, DEBUG or ALL + CORSAllowedOrigins config.StringList // origins allowed to call the API cross-origin + LogFile string // path to log file + ListenHTTPAddr string // address "[host]:port" that server should be listening for health checks + InstrumentationAddr string // address "[host]:port" that instrumentation server should be listening for health checks and metrics + TLS bool // is TLS enabled? + AuthAPIURL string // Auth API URL in schema://host[:port] format + PolicyEnforcerGRPCAddr string // Policy Enforcer gRPC address in host[:port] format + HistoryAPIGRPCAddr string // History API gRPC address in host[:port] format + KubeManagerGRPCAddr string // Kube Manager gRPC address in host[:port] format + TokenKey string // key for jwt token + AccessTokenSalt string // salt for access token + Auth bool // is auth enabled? + GopsAddr string // gops listen address + OwnCSURL string // URL of current CS (http(s)://host[:port]). } // New reads config from environment and returns pointer to a new Config. @@ -57,6 +58,8 @@ func New() *Config { flag.StringVar(&c.OwnCSURL, "ownCSURL", config.LookupEnvString("OWN_CS_URL", ""), "URL of current CS (http(s)://host[:port]).") flag.StringVar(&c.InstrumentationAddr, "listenInstrumentationAddr", config.LookupEnvString("LISTEN_INSTRUMENTATION_ADDR", ":9090"), `Address in form of "[host]:port" that instrumentation HTTP server should be listening on.`) + config.StringListVar(&c.CORSAllowedOrigins, "corsAllowedOrigins", "CORS_ALLOWED_ORIGINS", "", "Comma separated list of origins allowed to call the API cross-origin. Empty value allows same-origin requests only.") + flag.Parse() return c diff --git a/public-api/pkg/server/server.go b/public-api/pkg/server/server.go index b46279d6..ca63b61c 100644 --- a/public-api/pkg/server/server.go +++ b/public-api/pkg/server/server.go @@ -3,13 +3,13 @@ package server import ( "crypto/tls" "net/http" + "slices" "time" "github.com/gorilla/mux" "github.com/justinas/alice" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promhttp" - "github.com/rs/cors" "github.com/runtime-radar/runtime-radar/lib/server/healthcheck" "github.com/runtime-radar/runtime-radar/lib/server/middleware" local_middleware "github.com/runtime-radar/runtime-radar/public-api/pkg/server/middleware" @@ -22,6 +22,9 @@ const ( writeTimeout = 5 * time.Second ) +// corsAllowedHeaders adds X-Auth-Key, which public-api accepts on top of the shared headers. +var corsAllowedHeaders = append(slices.Clone(middleware.DefaultCORSHeaders), "X-Auth-Key") + // New constructs and configures new *http.Server capable of serving application endpoints. func New( httpAddr string, @@ -32,6 +35,7 @@ func New( configSvc service.Config, nodeSvc service.Node, podSvc service.Pod, + corsAllowedOrigins []string, ) *http.Server { r := mux.NewRouter() @@ -39,7 +43,7 @@ func New( ReadTimeout: readTimeout, WriteTimeout: writeTimeout, Addr: httpAddr, - Handler: setupRouter(r, accessTokenSvc, ruleSvc, runtimeHistorySvc, configSvc, nodeSvc, podSvc), + Handler: setupRouter(r, accessTokenSvc, ruleSvc, runtimeHistorySvc, configSvc, nodeSvc, podSvc, corsAllowedOrigins), TLSConfig: tlsConfig, } } @@ -73,19 +77,14 @@ func setupRouter( configSvc service.Config, nodeSvc service.Node, podSvc service.Pod, + corsAllowedOrigins []string, ) http.Handler { r.StrictSlash(true) - corsOpts := cors.Options{ - AllowedOrigins: []string{"*"}, - AllowedMethods: []string{"GET", "POST", "PUT", "PATCH", "DELETE"}, - AllowedHeaders: []string{"Origin", "Accept", "Content-Type", "X-Requested-With", "Authorization", "X-Auth-Key"}, - } - h := alice.New( middleware.Log, middleware.Recovery, - cors.New(corsOpts).Handler, + middleware.CORS(corsAllowedOrigins, corsAllowedHeaders), local_middleware.JWT, local_middleware.AccessToken, local_middleware.Correlation,