// NewTLSConfig returns an initialized TLS configuration suitable for client // authentication. If caFile is non-empty, it will be loaded. func NewTLSConfig(caFile string, mutualTLS bool) (*tls.Config, error) { var c tls.Config // TLS 1.0 at a minimum (for mysql) c.MinVersion = tls.VersionTLS12 // Remove insecure ciphers from the list c.CipherSuites = cipherSuites c.PreferServerCipherSuites = true if mutualTLS { log.Info("MutualTLS requested, client certificates will be verified") c.ClientAuth = tls.VerifyClientCertIfGiven } if caFile != "" { data, err := ioutil.ReadFile(caFile) if err != nil { return &c, err } c.ClientCAs = x509.NewCertPool() if !c.ClientCAs.AppendCertsFromPEM(data) { return &c, errors.New("No certificates parsed") } log.Info("Read in CA file:", caFile) } c.BuildNameToCertificate() return &c, nil }
// agentsHttp startes serving agents HTTP or HTTPS API requests func agentsHttp() { m := martini.Classic() m.Use(gzip.All()) m.Use(render.Renderer()) if config.Config.AgentsUseMutualTLS { m.Use(ssl.VerifyOUs(config.Config.AgentSSLValidOUs)) } log.Info("Starting agents listener") go logic.ContinuousAgentsPoll() http.AgentsAPI.RegisterRequests(m) // Serve if config.Config.AgentsUseSSL { log.Info("Starting agent HTTPS listener") tlsConfig, err := ssl.NewTLSConfig(config.Config.AgentSSLCAFile, config.Config.AgentsUseMutualTLS) if err != nil { log.Fatale(err) } tlsConfig.InsecureSkipVerify = config.Config.AgentSSLSkipVerify if err = ssl.AppendKeyPair(tlsConfig, config.Config.AgentSSLCertFile, config.Config.AgentSSLPrivateKeyFile); err != nil { log.Fatale(err) } if err = ssl.ListenAndServeTLS(config.Config.AgentsServerPort, m, tlsConfig); err != nil { log.Fatale(err) } } else { log.Info("Starting agent HTTP listener") if err := nethttp.ListenAndServe(config.Config.AgentsServerPort, m); err != nil { log.Fatale(err) } } log.Info("Agent server started") }
// standardHttp starts serving HTTP or HTTPS (api/web) requests, to be used by normal clients func standardHttp(discovery bool) { m := martini.Classic() switch strings.ToLower(config.Config.AuthenticationMethod) { case "basic": { if config.Config.HTTPAuthUser == "" { // Still allowed; may be disallowed in future versions log.Warning("AuthenticationMethod is configured as 'basic' but HTTPAuthUser undefined. Running without authentication.") } m.Use(auth.Basic(config.Config.HTTPAuthUser, config.Config.HTTPAuthPassword)) } case "multi": { if config.Config.HTTPAuthUser == "" { // Still allowed; may be disallowed in future versions log.Fatal("AuthenticationMethod is configured as 'multi' but HTTPAuthUser undefined") } m.Use(auth.BasicFunc(func(username, password string) bool { if username == "readonly" { // Will be treated as "read-only" return true } return auth.SecureCompare(username, config.Config.HTTPAuthUser) && auth.SecureCompare(password, config.Config.HTTPAuthPassword) })) } default: { // We inject a dummy User object because we have function signatures with User argument in api.go m.Map(auth.User("")) } } m.Use(gzip.All()) // Render html templates from templates directory m.Use(render.Renderer(render.Options{ Directory: "resources", Layout: "templates/layout", HTMLContentType: "text/html", })) m.Use(martini.Static("resources/public")) if config.Config.UseMutualTLS { m.Use(ssl.VerifyOUs(config.Config.SSLValidOUs)) } inst.SetMaintenanceOwner(process.ThisHostname) if discovery { log.Info("Starting Discovery") go logic.ContinuousDiscovery() } inst.ReadClusterAliases() log.Info("Registering endpoints") http.API.RegisterRequests(m) http.Web.RegisterRequests(m) // Serve if config.Config.UseSSL { log.Info("Starting HTTPS listener") tlsConfig, err := ssl.NewTLSConfig(config.Config.SSLCAFile, config.Config.UseMutualTLS) if err != nil { log.Fatale(err) } tlsConfig.InsecureSkipVerify = config.Config.SSLSkipVerify if err = ssl.AppendKeyPair(tlsConfig, config.Config.SSLCertFile, config.Config.SSLPrivateKeyFile); err != nil { log.Fatale(err) } if err = ssl.ListenAndServeTLS(config.Config.ListenAddress, m, tlsConfig); err != nil { log.Fatale(err) } } else { log.Info("Starting HTTP listener") if err := nethttp.ListenAndServe(config.Config.ListenAddress, m); err != nil { log.Fatale(err) } } log.Info("Web server started") }
// main is the application's entry point. It will either spawn a CLI or HTTP itnerfaces. func main() { configFile := flag.String("config", "", "config file name") command := flag.String("c", "", "command, required. See full list of commands via 'orchestrator -c help'") strict := flag.Bool("strict", false, "strict mode (more checks, slower)") instance := flag.String("i", "", "instance, host_fqdn[:port] (e.g. db.company.com:3306, db.company.com)") sibling := flag.String("s", "", "sibling instance, host_fqdn[:port]") destination := flag.String("d", "", "destination instance, host_fqdn[:port] (synonym to -s)") owner := flag.String("owner", "", "operation owner") reason := flag.String("reason", "", "operation reason") duration := flag.String("duration", "", "maintenance duration (format: 59s, 59m, 23h, 6d, 4w)") pattern := flag.String("pattern", "", "regular expression pattern") clusterAlias := flag.String("alias", "", "cluster alias") pool := flag.String("pool", "", "Pool logical name (applies for pool-related commands)") hostnameFlag := flag.String("hostname", "", "Hostname/fqdn/CNAME/VIP (applies for hostname/resolve related commands)") discovery := flag.Bool("discovery", true, "auto discovery mode") quiet := flag.Bool("quiet", false, "quiet") verbose := flag.Bool("verbose", false, "verbose") debug := flag.Bool("debug", false, "debug mode (very verbose)") stack := flag.Bool("stack", false, "add stack trace upon error") config.RuntimeCLIFlags.Databaseless = flag.Bool("databaseless", false, "EXPERIMENTAL! Work without backend database") config.RuntimeCLIFlags.SkipUnresolve = flag.Bool("skip-unresolve", false, "Do not unresolve a host name") config.RuntimeCLIFlags.SkipUnresolveCheck = flag.Bool("skip-unresolve-check", false, "Skip/ignore checking an unresolve mapping (via hostname_unresolve table) resolves back to same hostname") config.RuntimeCLIFlags.Noop = flag.Bool("noop", false, "Dry run; do not perform destructing operations") config.RuntimeCLIFlags.BinlogFile = flag.String("binlog", "", "Binary log file name") config.RuntimeCLIFlags.GrabElection = flag.Bool("grab-election", false, "Grab leadership (only applies to continuous mode)") flag.Parse() if *destination != "" && *sibling != "" { log.Fatalf("-s and -d are synonyms, yet both were specified. You're probably doing the wrong thing.") } if *destination == "" { *destination = *sibling } log.SetLevel(log.ERROR) if *verbose { log.SetLevel(log.INFO) } if *debug { log.SetLevel(log.DEBUG) } if *stack { log.SetPrintStackTrace(*stack) } log.Info("starting") runtime.GOMAXPROCS(math.MinInt(4, runtime.NumCPU())) if len(*configFile) > 0 { config.ForceRead(*configFile) } else { config.Read("/etc/orchestrator.conf.json", "conf/orchestrator.conf.json", "orchestrator.conf.json") } if *config.RuntimeCLIFlags.Databaseless { config.Config.DatabaselessMode__experimental = true } if config.Config.Debug { log.SetLevel(log.DEBUG) } if *quiet { // Override!! log.SetLevel(log.ERROR) } if config.Config.EnableSyslog { log.EnableSyslogWriter("orchestrator") log.SetSyslogLevel(log.INFO) } if config.Config.AuditToSyslog { inst.EnableAuditSyslog() } if len(flag.Args()) == 0 && *command == "" { // No command, no argument: just prompt fmt.Println(prompt) return } switch { case len(flag.Args()) == 0 || flag.Arg(0) == "cli": app.CliWrapper(*command, *strict, *instance, *destination, *owner, *reason, *duration, *pattern, *clusterAlias, *pool, *hostnameFlag) case flag.Arg(0) == "http": app.Http(*discovery) default: fmt.Fprintln(os.Stderr, `Usage: orchestrator --options... [cli|http] See complete list of commands: orchestrator -c help Full blown documentation: orchestrator `) os.Exit(1) } }