From 432b94239d6096e75b413de5a79c6707ab808bc9 Mon Sep 17 00:00:00 2001 From: Andreas Schneider Date: Wed, 30 Oct 2019 22:12:42 +0100 Subject: [PATCH] admin listener as opt-in for initial config (#2834) * Always cleanup admin endpoint first * Error out if no config has been set (#2833) * Ignore explicitly missing admin config (#2833) * Separate config loading from admin initialization (#2833) * Add admin option to specify admin listener address (#2833) * Use zap for reporting admin endpoint status --- admin.go | 40 +++++++++++++++------------ caddyconfig/httpcaddyfile/httptype.go | 5 ++++ caddyconfig/httpcaddyfile/options.go | 14 ++++++++++ cmd/commandfuncs.go | 16 +++++++++-- 4 files changed, 55 insertions(+), 20 deletions(-) diff --git a/admin.go b/admin.go index 0b4affde4..e48a4ca6c 100644 --- a/admin.go +++ b/admin.go @@ -18,6 +18,7 @@ import ( "bytes" "context" "encoding/json" + "errors" "fmt" "io" "io/ioutil" @@ -42,6 +43,8 @@ var ( cfgEndptSrvMu sync.Mutex ) +var ErrAdminInterfaceNotConfigured = errors.New("no admin configuration has been set") + // AdminConfig configures the admin endpoint. type AdminConfig struct { Listen string `json:"listen,omitempty"` @@ -60,10 +63,24 @@ var DefaultAdminConfig = &AdminConfig{ // in the format of JSON bytes. It opens a listener // resource. When no longer needed, StopAdmin should // be called. +// If no configuration is given, a default listener is +// started. If a configuration is given that does NOT +// specifically configure the admin interface, +// `ErrAdminInterfaceNotConfigured` is returned and no +// listener is initialized. func StartAdmin(initialConfigJSON []byte) error { cfgEndptSrvMu.Lock() defer cfgEndptSrvMu.Unlock() + if cfgEndptSrv != nil { + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + err := cfgEndptSrv.Shutdown(ctx) + if err != nil { + return fmt.Errorf("shutting down old admin endpoint: %v", err) + } + } + adminConfig := DefaultAdminConfig if len(initialConfigJSON) > 0 { var config *Config @@ -71,16 +88,11 @@ func StartAdmin(initialConfigJSON []byte) error { if err != nil { return fmt.Errorf("unmarshaling bootstrap config: %v", err) } - if config != nil && config.Admin != nil { - adminConfig = config.Admin - } - if cfgEndptSrv != nil { - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() - err := cfgEndptSrv.Shutdown(ctx) - if err != nil { - return fmt.Errorf("shutting down old admin endpoint: %v", err) + if config != nil { + if config.Admin == nil { + return ErrAdminInterfaceNotConfigured } + adminConfig = config.Admin } } @@ -136,15 +148,7 @@ func StartAdmin(initialConfigJSON []byte) error { go cfgEndptSrv.Serve(ln) - fmt.Println("Caddy 2 admin endpoint listening on", adminConfig.Listen) - - if len(initialConfigJSON) > 0 { - err := Load(bytes.NewReader(initialConfigJSON)) - if err != nil { - return fmt.Errorf("loading initial config: %v", err) - } - fmt.Println("Caddy 2 serving initial configuration") - } + Log().Named("admin").Info("Caddy 2 admin endpoint started.", zap.String("listenAddress", adminConfig.Listen)) return nil } diff --git a/caddyconfig/httpcaddyfile/httptype.go b/caddyconfig/httpcaddyfile/httptype.go index bc546b4bf..05022d12c 100644 --- a/caddyconfig/httpcaddyfile/httptype.go +++ b/caddyconfig/httpcaddyfile/httptype.go @@ -74,6 +74,8 @@ func (st ServerType) Setup(originalServerBlocks []caddyfile.ServerBlock, val, err = parseOptACMECA(disp) case "email": val, err = parseOptEmail(disp) + case "admin": + val, err = parseOptAdmin(disp) default: return nil, warnings, fmt.Errorf("unrecognized parameter name: %s", dir) } @@ -254,6 +256,9 @@ func (st ServerType) Setup(originalServerBlocks []caddyfile.ServerBlock, storageCvtr.(caddy.Module).CaddyModule().ID(), &warnings) } + if adminConfig, ok := options["admin"].(string); ok && adminConfig != "" { + cfg.Admin = &caddy.AdminConfig{Listen: adminConfig} + } return cfg, warnings, nil } diff --git a/caddyconfig/httpcaddyfile/options.go b/caddyconfig/httpcaddyfile/options.go index a60d060a8..74ec5076c 100644 --- a/caddyconfig/httpcaddyfile/options.go +++ b/caddyconfig/httpcaddyfile/options.go @@ -132,3 +132,17 @@ func parseOptEmail(d *caddyfile.Dispenser) (string, error) { } return val, nil } + +func parseOptAdmin(d *caddyfile.Dispenser) (string, error) { + if d.Next() { + var listenAddress string + d.AllArgs(&listenAddress) + + if listenAddress == "" { + listenAddress = caddy.DefaultAdminListen + } + + return listenAddress, nil + } + return "", nil +} diff --git a/cmd/commandfuncs.go b/cmd/commandfuncs.go index 7c08c2ef6..d73644c28 100644 --- a/cmd/commandfuncs.go +++ b/cmd/commandfuncs.go @@ -161,12 +161,24 @@ func cmdRun(fl Flags) (int, error) { certmagic.UserAgent = "Caddy/" + cleanModVersion // start the admin endpoint along with any initial config + // a configuration without admin config is considered fine + // but does not enable the admin endpoint at all err = caddy.StartAdmin(config) - if err != nil { + if err == nil { + defer caddy.StopAdmin() + } else if err != caddy.ErrAdminInterfaceNotConfigured { return caddy.ExitCodeFailedStartup, fmt.Errorf("starting caddy administration endpoint: %v", err) } - defer caddy.StopAdmin() + + // if a config has been supplied, load it as initial config + if len(config) > 0 { + err := caddy.Load(bytes.NewReader(config)) + if err != nil { + return caddy.ExitCodeFailedStartup, fmt.Errorf("loading initial config: %v", err) + } + caddy.Log().Named("admin").Info("Caddy 2 serving initial configuration") + } // if we are to report to another process the successful start // of the server, do so now by echoing back contents of stdin