package main import ( "context" "encoding/json" "flag" "fmt" "os" "os/signal" "time" "git.faercol.me/faercol/polyculeconnect/polyculeconnect/config" "git.faercol.me/faercol/polyculeconnect/polyculeconnect/connector" "git.faercol.me/faercol/polyculeconnect/polyculeconnect/logger" "git.faercol.me/faercol/polyculeconnect/polyculeconnect/server" "github.com/dexidp/dex/connector/oidc" dex_server "github.com/dexidp/dex/server" "github.com/dexidp/dex/storage" "github.com/dexidp/dex/storage/memory" "github.com/dexidp/dex/storage/sql" "github.com/prometheus/client_golang/prometheus" ) const stopTimeout = 10 * time.Second type cliArgs struct { configPath string } func parseArgs() *cliArgs { configPath := flag.String("config", "", "Path to the JSON configuration file") flag.Parse() return &cliArgs{ configPath: *configPath, } } func initStorage(conf *config.AppConfig) (storage.Storage, error) { var storageType storage.Storage var err error switch conf.StorageType { case "memory": storageType = memory.New(logger.L) case "sqlite": sqlconfig := sql.SQLite3{ File: conf.StorageConfig.File, } storageType, err = sqlconfig.Open(logger.L) if err != nil { logger.L.Fatalf("Failed to initialize sqlite backend: %s", err.Error()) } default: return storageType, fmt.Errorf("unsupported storage backend type: %s", conf.StorageType) } return storageType, nil } func createConnector(backend *config.BackendConfig, dexConf *dex_server.Config, connectorIDs []string) error { for _, id := range connectorIDs { if id == backend.ID { return nil } } backendConfJson, err := json.Marshal(backend.Config) if err != nil { return fmt.Errorf("failed to serialize oidc config for backend %q: %s", backend.Name, err.Error()) } return dexConf.Storage.CreateConnector(storage.Connector{ ID: backend.ID, Name: backend.Name, Type: string(backend.Type), Config: backendConfJson, }) } func main() { args := parseArgs() mainCtx, cancel := context.WithCancel(context.Background()) conf, err := config.New(args.configPath) if err != nil { panic(err) } logger.Init(conf.LogLevel) logger.L.Infof("Initialized logger with level %v", conf.LogLevel) storageType, err := initStorage(conf) if err != nil { logger.L.Fatalf("Failed to initialize storage backend: %s", err.Error()) } logger.L.Infof("Initialized storage backend %q", conf.StorageType) dexConf := dex_server.Config{ Web: dex_server.WebConfig{ Dir: "./", Theme: "default", }, Storage: storageType, Issuer: conf.OpenConnectConfig.Issuer, SupportedResponseTypes: []string{"code"}, SkipApprovalScreen: false, AllowedOrigins: []string{"*"}, Logger: logger.L, PrometheusRegistry: prometheus.NewRegistry(), } logger.L.Info("Initializing authentication backends") dex_server.ConnectorsConfig["refuseAll"] = func() dex_server.ConnectorConfig { return new(connector.RefuseAllConfig) } connectors, err := dexConf.Storage.ListConnectors() if err != nil { logger.L.Fatalf("Failed to get existing connectors: %s", err.Error()) } var connectorIDs []string for _, conn := range connectors { connectorIDs = append(connectorIDs, conn.ID) } backend := config.BackendConfig{ Config: &oidc.Config{}, Name: "RefuseAll", ID: "null", Type: "refuseAll", } if err := createConnector(&backend, &dexConf, connectorIDs); err != nil { logger.L.Errorf("Failed to add connector for backend RefuseAll to stage: %s", err.Error()) } for _, backend := range conf.OpenConnectConfig.BackendConfigs { if err := createConnector(backend, &dexConf, connectorIDs); err != nil { logger.L.Errorf("Failed to add connector for backend %q to stage: %s", backend.Name, err.Error()) continue } } logger.L.Info("Initializing clients") for _, client := range conf.OpenConnectConfig.ClientConfigs { if err := dexConf.Storage.CreateClient(*client); err != nil { logger.L.Errorf("Failed to add client to storage: %s", err.Error()) } } dexSrv, err := dex_server.NewServer(mainCtx, dexConf) if err != nil { logger.L.Fatalf("Failed to init dex server: %s", err.Error()) } logger.L.Info("Initializing server") s, err := server.New(conf, dexSrv, logger.L) if err != nil { logger.L.Fatalf("Failed to initialize server: %s", err.Error()) } go s.Run(mainCtx) c := make(chan os.Signal, 1) signal.Notify(c, os.Interrupt) logger.L.Info("Application successfully started") logger.L.Debug("Waiting for stop signal") select { case <-s.Done(): logger.L.Fatal("Unexpected exit from server") case <-c: logger.L.Info("Stopping main application") cancel() } logger.L.Debugf("Waiting %v for all daemons to stop", stopTimeout) select { case <-time.After(stopTimeout): logger.L.Fatalf("Failed to stop all daemons in the expected time") case <-s.Done(): logger.L.Info("web server successfully stopped") } logger.L.Info("Application successfully stopped") os.Exit(0) }