diff --git a/go.mod b/go.mod index 5997a21d..9351d800 100644 --- a/go.mod +++ b/go.mod @@ -33,6 +33,7 @@ require ( github.com/spf13/afero v1.7.1 // indirect github.com/spf13/viper v1.10.1 github.com/urfave/cli v1.22.5 + github.com/urfave/cli/v2 v2.3.0 golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 // indirect golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e // indirect ) diff --git a/go.sum b/go.sum index c3c23e78..9cecf0a8 100644 --- a/go.sum +++ b/go.sum @@ -439,6 +439,8 @@ github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69 github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= github.com/urfave/cli v1.22.5 h1:lNq9sAHXK2qfdI8W+GRItjCEkI+2oR4d+MEHy1CKXoU= github.com/urfave/cli v1.22.5/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M= +github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= diff --git a/tls/main.go b/tls/main.go index 31d97689..c9dd23a0 100644 --- a/tls/main.go +++ b/tls/main.go @@ -2,10 +2,10 @@ package main import ( "crypto/tls" - "flag" "fmt" "log" "net/http" + "os" "time" "github.com/jmpsec/osctrl/backend" @@ -20,6 +20,7 @@ import ( thandlers "github.com/jmpsec/osctrl/tls/handlers" "github.com/jmpsec/osctrl/types" "github.com/jmpsec/osctrl/version" + "github.com/urfave/cli/v2" "github.com/gorilla/mux" "github.com/jinzhu/gorm" @@ -42,13 +43,13 @@ const ( // Default endpoint to handle HTTP errors errorPath string = "/error" // Default service configuration file - configurationFile string = "config/" + settings.ServiceTLS + ".json" + defConfigurationFile string = "config/" + settings.ServiceTLS + ".json" // Default DB configuration file - dbConfigurationFile string = "config/db.json" + defDBConfigurationFile string = "config/db.json" // Default TLS certificate file - tlsCertificateFile string = "config/tls.crt" + defTLSCertificateFile string = "config/tls.crt" // Default TLS private key file - tlsKeyFile string = "config/tls.key" + defTLSKeyFile string = "config/tls.key" // Default refreshing interval in seconds defaultRefresh int = 300 // Default accelerate interval in seconds @@ -64,6 +65,7 @@ var ( var ( err error tlsConfig types.JSONConfigurationService + dbConfig backend.JSONConfigurationDB db *gorm.DB settingsmgr *settings.Settings envs *environments.Environment @@ -76,16 +78,32 @@ var ( loggerTLS *logging.LoggerTLS handlersTLS *thandlers.HandlersTLS tagsmgr *tags.TagManager + app *cli.App + flags []cli.Flag ) // Variables for flags var ( - versionFlag *bool - configFlag *string - dbFlag *string - tlsServer *bool - tlsCert *string - tlsKey *string + configFlag bool + configFile string + dbFlag bool + dbConfigFile string + tlsServer bool + tlsCertFile string + tlsKeyFile string + cfgListener string + cfgPort string + cfgHost string + cfgAuth string + cfgLogging string + dbHost string + dbPort string + dbName string + dbUsername string + dbPassword string + dbMaxIdleConns int + dbMaxOpenConns int + dbConnMaxLifetime int ) // Valid values for auth and logging in configuration @@ -127,39 +145,169 @@ func loadConfiguration(file string) (types.JSONConfigurationService, error) { // Initialization code func init() { - log.Printf("==================== Initializing %s v%s", serviceName, serviceVersion) - // Command line flags - flag.Usage = tlsUsage - // Define flags - versionFlag = flag.Bool("v", false, "Displays the binary version.") - configFlag = flag.String("c", configurationFile, "Service configuration JSON file to use.") - dbFlag = flag.String("D", dbConfigurationFile, "DB configuration JSON file to use.") - tlsServer = flag.Bool("tls", false, "TLS termination is enabled. It requires certificate and key.") - tlsCert = flag.String("cert", tlsCertificateFile, "Certificate file to be used for TLS.") - tlsKey = flag.String("key", tlsKeyFile, "Private key file to be used for TLS.") - // Parse all flags - flag.Parse() - if *versionFlag { - tlsVersion() + // Initialize CLI flags + flags = []cli.Flag{ + &cli.BoolFlag{ + Name: "db", + Aliases: []string{"d"}, + Value: false, + Usage: "Provide DB configuration via JSON file", + EnvVars: []string{"DB_CONFIG"}, + Destination: &dbFlag, + }, + &cli.StringFlag{ + Name: "db-file", + Aliases: []string{"D"}, + Value: defConfigurationFile, + Usage: "Load DB configuration from `FILE`", + EnvVars: []string{"DB_CONFIG_FILE"}, + Destination: &configFile, + }, + &cli.BoolFlag{ + Name: "config", + Aliases: []string{"c"}, + Value: false, + Usage: "Provide service configuration via JSON file", + EnvVars: []string{"SERVICE_CONFIG"}, + Destination: &configFlag, + }, + &cli.StringFlag{ + Name: "config-file", + Aliases: []string{"C"}, + Value: defDBConfigurationFile, + Usage: "Load service configuration from `FILE`", + EnvVars: []string{"SERVICE_CONFIG_FILE"}, + Destination: &dbConfigFile, + }, + &cli.BoolFlag{ + Name: "tls", + Aliases: []string{"t"}, + Value: false, + Usage: "Enable TLS termination. It requires certificate and key", + EnvVars: []string{"TLS_SERVER"}, + Destination: &tlsServer, + }, + &cli.StringFlag{ + Name: "cert", + Aliases: []string{"T"}, + Value: defTLSCertificateFile, + Usage: "TLS termination certificate from `FILE`", + EnvVars: []string{"TLS_CERTIFICATE"}, + Destination: &tlsCertFile, + }, + &cli.StringFlag{ + Name: "key", + Aliases: []string{"K"}, + Value: defTLSKeyFile, + Usage: "TLS termination private key from `FILE`", + EnvVars: []string{"TLS_KEY"}, + Destination: &tlsKeyFile, + }, + &cli.StringFlag{ + Name: "listener", + Aliases: []string{"l"}, + Value: "0.0.0.0", + Usage: "Listener for the service", + EnvVars: []string{"SERVICE_LISTENER"}, + Destination: &cfgListener, + }, + &cli.StringFlag{ + Name: "port", + Aliases: []string{"p"}, + Value: "9000", + Usage: "TCP port for the service", + EnvVars: []string{"SERVICE_PORT"}, + Destination: &cfgPort, + }, + &cli.StringFlag{ + Name: "auth", + Aliases: []string{"A"}, + Value: settings.AuthNone, + Usage: "Authentication mechanism for the service", + EnvVars: []string{"SERVICE_AUTH"}, + Destination: &cfgAuth, + }, + &cli.StringFlag{ + Name: "host", + Aliases: []string{"H"}, + Value: "0.0.0.0", + Usage: "Exposed hostname the service uses", + EnvVars: []string{"SERVICE_HOST"}, + Destination: &cfgHost, + }, + &cli.StringFlag{ + Name: "logging", + Aliases: []string{"L"}, + Value: settings.LoggingDB, + Usage: "Logging mechanism to handle logs from nodes", + EnvVars: []string{"SERVICE_LOGGING"}, + Destination: &cfgLogging, + }, + &cli.StringFlag{ + Name: "db-host", + Value: "127.0.0.1", + Usage: "Backend host to be connected to", + EnvVars: []string{"DB_HOST"}, + Destination: &dbHost, + }, + &cli.StringFlag{ + Name: "db-port", + Value: "5432", + Usage: "Backend port to be connected to", + EnvVars: []string{"DB_PORT"}, + Destination: &dbPort, + }, + &cli.StringFlag{ + Name: "db-name", + Value: "postgres", + Usage: "Backend port to be connected to", + EnvVars: []string{"DB_NAME"}, + Destination: &dbName, + }, + &cli.StringFlag{ + Name: "db-user", + Value: "postgres", + Usage: "Username to be used for the backend", + EnvVars: []string{"DB_USER"}, + Destination: &dbUsername, + }, + &cli.StringFlag{ + Name: "db-pass", + Value: "postgres", + Usage: "Password to be used for the backend", + EnvVars: []string{"DB_PASS"}, + Destination: &dbPassword, + }, + &cli.IntFlag{ + Name: "db-max-idle-conns", + Value: 20, + Usage: "Maximum number of connections in the idle connection pool", + EnvVars: []string{"DB_MAX_IDLE_CONNS"}, + Destination: &dbMaxIdleConns, + }, + &cli.IntFlag{ + Name: "db-max-open-conns", + Value: 100, + Usage: "Maximum number of open connections to the database", + EnvVars: []string{"DB_MAX_OPEN_CONNS"}, + Destination: &dbMaxOpenConns, + }, + &cli.IntFlag{ + Name: "db-conn-max-lifetime", + Value: 30, + Usage: "Maximum amount of time a connection may be reused", + EnvVars: []string{"DB_CONN_MAX_LIFETIME"}, + Destination: &dbConnMaxLifetime, + }, } // Logging format flags log.SetFlags(log.Lshortfile) - // Load TLS configuration - tlsConfig, err = loadConfiguration(*configFlag) - if err != nil { - log.Fatalf("Error loading %s - %s", *configFlag, err) - } } // Go go! -func main() { - log.Printf("==================== Starting %s v%s", serviceName, serviceVersion) - // Backend configuration - dbConfig, err := backend.LoadConfiguration(*dbFlag, backend.DBKey) - if err != nil { - log.Fatalf("Failed to load DB configuration - %v", err) - } - // Connect to backend waiting until is ready +func osctrlService() { + log.Println("Initializing backend...") + // Attempt to connect to backend waiting until is ready for { db, err = backend.GetDB(dbConfig) if db != nil { @@ -178,19 +326,18 @@ func main() { log.Fatalf("Failed to close Database handler - %v", err) } }() - // Initialize environment + log.Println("Initialize environment") envs = environments.CreateEnvironment(db) - // Initialize settings + log.Println("Initialize settings") settingsmgr = settings.NewSettings(db) - // Initialize nodes + log.Println("Initialize nodes") nodesmgr = nodes.CreateNodes(db) - // Initialize tags + log.Println("Initialize tags") tagsmgr = tags.CreateTagManager(db) - // Initialize queries + log.Println("Initialize queries") queriesmgr = queries.CreateQueries(db) - // Initialize carves + log.Println("Initialize carves") filecarves = carves.CreateFileCarves(db) - // Initialize service settings log.Println("Loading service settings") if err := loadingSettings(settingsmgr); err != nil { log.Fatalf("Error loading settings - %s: %v", tlsConfig.Logging, err) @@ -211,6 +358,7 @@ func main() { // Sleep to reload environments // FIXME Implement Redis cache // FIXME splay this? + log.Println("Preparing pseudo-cache for environments") go func() { _t := settingsmgr.RefreshEnvs(settings.ServiceTLS) if _t == 0 { @@ -227,6 +375,7 @@ func main() { // Sleep to reload settings // FIXME Implement Redis cache // FIXME splay this? + log.Println("Preparing pseudo-cache for settings") go func() { _t := settingsmgr.RefreshSettings(settings.ServiceTLS) if _t == 0 { @@ -240,7 +389,6 @@ func main() { time.Sleep(time.Duration(_t) * time.Second) } }() - // Initialize TLS handlers before router handlersTLS = thandlers.CreateHandlersTLS( thandlers.WithEnvs(envs), @@ -281,7 +429,8 @@ func main() { // ////////////////////////////// Everything is ready at this point! serviceListener := tlsConfig.Listener + ":" + tlsConfig.Port - if *tlsServer { + if tlsServer { + log.Println("TLS Termination is enabled") cfg := &tls.Config{ MinVersion: tls.VersionTLS12, CurvePreferences: []tls.CurveID{tls.CurveP521, tls.CurveP384, tls.CurveP256}, @@ -300,9 +449,55 @@ func main() { TLSNextProto: make(map[string]func(*http.Server, *tls.Conn, http.Handler), 0), } log.Printf("%s v%s - HTTPS listening %s", serviceName, serviceVersion, serviceListener) - log.Fatal(srv.ListenAndServeTLS(*tlsCert, *tlsKey)) + log.Fatal(srv.ListenAndServeTLS(tlsCertFile, tlsKeyFile)) } else { log.Printf("%s v%s - HTTP listening %s", serviceName, serviceVersion, serviceListener) log.Fatal(http.ListenAndServe(serviceListener, routerTLS)) } } + +// Action to run when no flags are provided to run checks and prepare data +func cliAction(c *cli.Context) error { + // Load configuration if external service JSON config file is used + if configFlag { + tlsConfig, err = loadConfiguration(configFile) + if err != nil { + return fmt.Errorf("Error loading %s - %s", configFile, err) + } + } + // Load db configuration if external db JSON config file is used + if dbFlag { + dbConfig, err = backend.LoadConfiguration(dbConfigFile, backend.DBKey) + if err != nil { + return fmt.Errorf("Failed to load DB configuration - %v", err) + } + } + return nil +} + +func main() { + // Initiate CLI and parse arguments + app = cli.NewApp() + app.Name = serviceName + app.Usage = appDescription + app.Version = serviceVersion + app.Description = appDescription + app.Flags = flags + // Define this command for help to exit when help flag is passed + app.Commands = []*cli.Command{ + { + Name: "help", + Action: func(c *cli.Context) error { + cli.ShowAppHelpAndExit(c, 0) + return nil + }, + }, + } + app.Action = cliAction + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } + // Service starts! + osctrlService() +} diff --git a/tls/utils.go b/tls/utils.go index b5ed022f..d9edd281 100644 --- a/tls/utils.go +++ b/tls/utils.go @@ -1,10 +1,7 @@ package main import ( - "flag" - "fmt" "log" - "os" "github.com/jmpsec/osctrl/environments" "github.com/jmpsec/osctrl/settings" @@ -61,20 +58,3 @@ func refreshSettings() settings.MapSettings { } return _settingsmap } - -// Usage for service binary -func tlsUsage() { - fmt.Printf("NAME:\n %s - %s\n\n", serviceName, serviceDescription) - fmt.Printf("USAGE: %s [global options] [arguments...]\n\n", serviceName) - fmt.Printf("VERSION:\n %s\n\n", serviceVersion) - fmt.Printf("DESCRIPTION:\n %s\n\n", appDescription) - fmt.Printf("GLOBAL OPTIONS:\n") - flag.PrintDefaults() - fmt.Printf("\n") -} - -// Display binary version -func tlsVersion() { - fmt.Printf("%s v%s\n", serviceName, serviceVersion) - os.Exit(0) -}