| /* |
| Copyright 2015 Cesanta Software Ltd. |
| |
| Licensed under the Apache License, Version 2.0 (the "License"); |
| you may not use this file except in compliance with the License. |
| You may obtain a copy of the License at |
| |
| https://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| */ |
| |
| //go:generate ./gen_version.py |
| |
| package main // import "github.com/cesanta/docker_auth/auth_server" |
| |
| import ( |
| "crypto/tls" |
| "flag" |
| "math/rand" |
| "net/http" |
| "os" |
| "os/signal" |
| "strconv" |
| "syscall" |
| "time" |
| |
| "github.com/cesanta/glog" |
| "github.com/facebookgo/httpdown" |
| "golang.org/x/crypto/acme/autocert" |
| fsnotify "gopkg.in/fsnotify.v1" |
| |
| "github.com/cesanta/docker_auth/auth_server/server" |
| ) |
| |
| type RestartableServer struct { |
| configFile string |
| hd *httpdown.HTTP |
| authServer *server.AuthServer |
| hs httpdown.Server |
| } |
| |
| func stringToUint16(s string) uint16 { |
| v, err := strconv.ParseUint(s, 0, 16) |
| if err != nil { |
| glog.Exitf("Failed to convert %s to uint16", s) |
| } |
| return uint16(v) |
| } |
| |
| func ServeOnce(c *server.Config, cf string, hd *httpdown.HTTP) (*server.AuthServer, httpdown.Server) { |
| glog.Infof("Config from %s (%d users, %d ACL static entries)", cf, len(c.Users), len(c.ACL)) |
| as, err := server.NewAuthServer(c) |
| if err != nil { |
| glog.Exitf("Failed to create auth server: %s", err) |
| } |
| |
| tlsConfig := &tls.Config{ |
| PreferServerCipherSuites: true, |
| } |
| if c.Server.HSTS { |
| glog.Info("HTTP Strict Transport Security enabled") |
| } |
| if c.Server.TLSMinVersion != "" { |
| value, found := server.TLSVersionValues[c.Server.TLSMinVersion] |
| if !found { |
| value = stringToUint16(c.Server.TLSMinVersion) |
| } |
| tlsConfig.MinVersion = value |
| glog.Infof("TLS MinVersion: %s", c.Server.TLSMinVersion) |
| } |
| if c.Server.TLSCurvePreferences != nil { |
| var values []tls.CurveID |
| for _, s := range c.Server.TLSCurvePreferences { |
| value, found := server.TLSCurveIDValues[s] |
| if !found { |
| value = tls.CurveID(stringToUint16(s)) |
| } |
| values = append(values, value) |
| } |
| tlsConfig.CurvePreferences = values |
| glog.Infof("TLS CurvePreferences: %s", c.Server.TLSCurvePreferences) |
| } |
| if c.Server.TLSCipherSuites != nil { |
| var values []uint16 |
| for _, s := range c.Server.TLSCipherSuites { |
| value, found := server.TLSCipherSuitesValues[s] |
| if !found { |
| value = stringToUint16(s) |
| } |
| values = append(values, value) |
| } |
| tlsConfig.CipherSuites = values |
| glog.Infof("TLS CipherSuites: %s", c.Server.TLSCipherSuites) |
| } |
| if c.Server.CertFile != "" || c.Server.KeyFile != "" { |
| // Check for partial configuration. |
| if c.Server.CertFile == "" || c.Server.KeyFile == "" { |
| glog.Exitf("Failed to load certificate and key: both were not provided") |
| } |
| glog.Infof("Cert file: %s", c.Server.CertFile) |
| glog.Infof("Key file : %s", c.Server.KeyFile) |
| tlsConfig.Certificates = make([]tls.Certificate, 1) |
| tlsConfig.Certificates[0], err = tls.LoadX509KeyPair(c.Server.CertFile, c.Server.KeyFile) |
| if err != nil { |
| glog.Exitf("Failed to load certificate and key: %s", err) |
| } |
| } else if c.Server.LetsEncrypt.Email != "" { |
| m := &autocert.Manager{ |
| Email: c.Server.LetsEncrypt.Email, |
| Cache: autocert.DirCache(c.Server.LetsEncrypt.CacheDir), |
| Prompt: autocert.AcceptTOS, |
| } |
| if c.Server.LetsEncrypt.Host != "" { |
| m.HostPolicy = autocert.HostWhitelist(c.Server.LetsEncrypt.Host) |
| } |
| glog.Infof("Using LetsEncrypt, host %q, email %q", c.Server.LetsEncrypt.Host, c.Server.LetsEncrypt.Email) |
| tlsConfig.GetCertificate = m.GetCertificate |
| } else { |
| glog.Warning("Running without TLS") |
| tlsConfig = nil |
| } |
| hs := &http.Server{ |
| Addr: c.Server.ListenAddress, |
| Handler: as, |
| TLSConfig: tlsConfig, |
| } |
| |
| s, err := hd.ListenAndServe(hs) |
| if err != nil { |
| glog.Exitf("Failed to set up listener: %s", err) |
| } |
| glog.Infof("Serving on %s", c.Server.ListenAddress) |
| return as, s |
| } |
| |
| func (rs *RestartableServer) Serve(c *server.Config) { |
| rs.authServer, rs.hs = ServeOnce(c, rs.configFile, rs.hd) |
| rs.WatchConfig() |
| } |
| |
| func (rs *RestartableServer) WatchConfig() { |
| w, err := fsnotify.NewWatcher() |
| if err != nil { |
| glog.Fatalf("Failed to create watcher: %s", err) |
| } |
| defer w.Close() |
| |
| stopSignals := make(chan os.Signal, 1) |
| signal.Notify(stopSignals, syscall.SIGTERM, syscall.SIGINT) |
| |
| err = w.Add(rs.configFile) |
| watching, needRestart := (err == nil), false |
| for { |
| select { |
| case <-time.After(1 * time.Second): |
| if !watching { |
| err = w.Add(rs.configFile) |
| if err != nil { |
| glog.Errorf("Failed to set up config watcher: %s", err) |
| } else { |
| watching, needRestart = true, true |
| } |
| } else if needRestart { |
| rs.MaybeRestart() |
| needRestart = false |
| } |
| case ev := <-w.Events: |
| if ev.Op == fsnotify.Remove { |
| glog.Warningf("Config file disappeared, serving continues") |
| w.Remove(rs.configFile) |
| watching, needRestart = false, false |
| } else if ev.Op == fsnotify.Write { |
| needRestart = true |
| } |
| case s := <-stopSignals: |
| signal.Stop(stopSignals) |
| glog.Infof("Signal: %s", s) |
| rs.hs.Stop() |
| rs.authServer.Stop() |
| glog.Exitf("Exiting") |
| } |
| } |
| } |
| |
| func (rs *RestartableServer) MaybeRestart() { |
| glog.Infof("Validating new config") |
| c, err := server.LoadConfig(rs.configFile) |
| if err != nil { |
| glog.Errorf("Failed to reload config (server not restarted): %s", err) |
| return |
| } |
| glog.Infof("Config ok, restarting server") |
| rs.hs.Stop() |
| rs.authServer.Stop() |
| rs.authServer, rs.hs = ServeOnce(c, rs.configFile, rs.hd) |
| } |
| |
| func main() { |
| flag.Parse() |
| rand.Seed(time.Now().UnixNano()) |
| glog.CopyStandardLogTo("INFO") |
| |
| glog.Infof("docker_auth %s build %s", Version, BuildId) |
| |
| cf := flag.Arg(0) |
| if cf == "" { |
| glog.Exitf("Config file not specified") |
| } |
| c, err := server.LoadConfig(cf) |
| if err != nil { |
| glog.Exitf("Failed to load config: %s", err) |
| } |
| rs := RestartableServer{ |
| configFile: cf, |
| hd: &httpdown.HTTP{}, |
| } |
| rs.Serve(c) |
| } |