Skip to content

Commit

Permalink
Implemented acme http challenge support yyyar#138
Browse files Browse the repository at this point in the history
  • Loading branch information
illarion committed Aug 12, 2018
1 parent f095563 commit 80e5219
Show file tree
Hide file tree
Showing 8 changed files with 269 additions and 84 deletions.
15 changes: 12 additions & 3 deletions config/gobetween.toml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,17 @@ client_idle_timeout = "0" # Client inactivity duration before forced conn
backend_idle_timeout = "0" # Backend inactivity duration before forced connection drop
backend_connection_timeout = "0" # Backend connection timeout (ignored in udp)

#
## Acme (letsencrypt) configuration.
## Letsencrypt allows server obtain free TLS certificates automagically.
## See https://letsencrypt.org for details.
##
## Each server that requires acme certificates should have acme_hosts configured in tls section.
#
#[acme] # (optional)
#challenge = "http" # (optional) http | sni | dns
#http_bind = "0.0.0.0:80" # (optional) It is possible to bind to other port, but letsencrypt will send requests to http(80) anyway
#cache_dir = "/tmp" # (optional) directory to put acme certificates

#
# Servers contains as many [server.<name>] sections as needed.
Expand Down Expand Up @@ -127,7 +138,7 @@ protocol = "udp"
#
## ---------------------- tls properties --------------------- #
#
# # Either both cert_path and key_path or acme_enabled and acme_hosts should be specified
# # *Either both cert_path and key_path or acme_hosts should be specified (and configured global [acme] section
#
# [servers.default.tls] # (required) if protocol == "tls"
# cert_path = "/path/to/file.crt" # (*required) path to crt file
Expand All @@ -137,9 +148,7 @@ protocol = "udp"
# ciphers = [] # (optional) list of supported ciphers. Empty means all supported. For a list see https://golang.org/pkg/crypto/tls/#pkg-constants
# prefer_server_ciphers = false # (optional) if true server selects server's most preferred cipher
# session_tickets = true # (optional) if true enables session tickets
# acme_enabled = false # (*optional) if true enables acme/letsecrypt
# acme_hosts = [] # (*optional) list of acme hosts, to provide certificates for
# acme_cache_dir = "/tmp" # (optional) directory to put acme certificates
#
#
## ---------------------- udp properties --------------------- #
Expand Down
18 changes: 13 additions & 5 deletions src/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ type Config struct {
Logging LoggingConfig `toml:"logging" json:"logging"`
Api ApiConfig `toml:"api" json:"api"`
Defaults ConnectionOptions `toml:"defaults" json:"defaults"`
Acme *AcmeConfig `toml:"acme" json:"acme"`
Servers map[string]Server `toml:"servers" json:"servers"`
}

Expand Down Expand Up @@ -61,6 +62,15 @@ type ConnectionOptions struct {
BackendConnectionTimeout *string `toml:"backend_connection_timeout" json:"backend_connection_timeout"`
}

/**
* Acme config
*/
type AcmeConfig struct {
Challenge string `toml:"challenge" json:"challenge"`
HttpBind string `toml:"http_bind" json:"http_bind"`
CacheDir string `toml:"cache_dir" json:"cache_dir"`
}

/**
* Server section config
*/
Expand Down Expand Up @@ -133,11 +143,9 @@ type tlsCommon struct {
* for protocol = "tls"
*/
type Tls struct {
AcmeEnabled bool `toml:"acme_enabled" json:"acme_enabled"`
AcmeHosts []string `toml:"acme_hosts" json:"acme_hosts"`
AcmeCacheDir string `toml:"acme_cache_dir" json:"acme_cache_dir"`
CertPath string `toml:"cert_path" json:"cert_path"`
KeyPath string `toml:"key_path" json:"key_path"`
AcmeHosts []string `toml:"acme_hosts" json:"acme_hosts"`
CertPath string `toml:"cert_path" json:"cert_path"`
KeyPath string `toml:"key_path" json:"key_path"`
tlsCommon
}

Expand Down
18 changes: 18 additions & 0 deletions src/core/service.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package core

/**
* Service is a global facility that could be Enabled or Disabled for a number
* of core.Server instances, depending on their configration. See services/registry
* for exact examples.
*/
type Service interface {
/**
* Enable service for Server
*/
Enable(Server) error

/**
* Disable service for Server
*/
Disable(Server) error
}
97 changes: 65 additions & 32 deletions src/manager/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
"../core"
"../logging"
"../server"
"../service"
"../utils/codec"
)

Expand All @@ -28,6 +29,9 @@ var servers = struct {
/* default configuration for server */
var defaults config.ConnectionOptions

/* services */
var services []core.Service

/* original cfg read from the file */
var originalCfg config.Config

Expand All @@ -43,6 +47,13 @@ func Initialize(cfg config.Config) {

// save defaults for futher reuse
defaults = cfg.Defaults
initDefaults()

//Initialize global sections
initConfigGlobals(&cfg)

//create services
services = service.All(cfg)

// Go through config and start servers for each server
for name, serverCfg := range cfg.Servers {
Expand All @@ -55,6 +66,46 @@ func Initialize(cfg config.Config) {
log.Info("Initialized")
}

func initDefaults() {
//defaults
if defaults.MaxConnections == nil {
defaults.MaxConnections = new(int)
}

if defaults.ClientIdleTimeout == nil {
defaults.ClientIdleTimeout = new(string)
*defaults.ClientIdleTimeout = "0"
}

if defaults.BackendIdleTimeout == nil {
defaults.BackendIdleTimeout = new(string)
*defaults.BackendIdleTimeout = "0"
}

if defaults.BackendConnectionTimeout == nil {
defaults.BackendConnectionTimeout = new(string)
*defaults.BackendConnectionTimeout = "0"
}
}

func initConfigGlobals(cfg *config.Config) {

//acme
if cfg.Acme != nil {
if cfg.Acme.Challenge == "" {
cfg.Acme.Challenge = "http"
}

if cfg.Acme.HttpBind == "" {
cfg.Acme.HttpBind = "0.0.0.0:80"
}

if cfg.Acme.CacheDir == "" {
cfg.Acme.CacheDir = "/tmp"
}
}
}

/**
* Dumps current [servers] section to
* the config file
Expand Down Expand Up @@ -126,10 +177,18 @@ func Create(name string, cfg config.Server) error {
}

server, err := server.New(name, c)

if err != nil {
return err
}

for _, srv := range services {
err = srv.Enable(server)
if err != nil {
return err
}
}

if err = server.Start(); err != nil {
return err
}
Expand All @@ -155,6 +214,10 @@ func Delete(name string) error {
server.Stop()
delete(servers.m, name)

for _, s := range services {
s.Disable(server)
}

return nil
}

Expand Down Expand Up @@ -284,23 +347,8 @@ func prepareConfig(name string, server config.Server, defaults config.Connection

if server.Tls != nil {

if !server.Tls.AcmeEnabled && ((server.Tls.KeyPath == "") || (server.Tls.CertPath == "")) {
return config.Server{}, errors.New("tls requires specify either acme configuration or both key and cert paths")
}

if server.Tls.AcmeEnabled {

if server.Tls.AcmeCacheDir == "" {
server.Tls.AcmeCacheDir = os.TempDir()
}

if len(server.Tls.AcmeHosts) == 0 {
return config.Server{}, errors.New("Need to provide at least one host in acme_hosts")
}

if !strings.HasSuffix(server.Bind, ":443") {
return config.Server{}, errors.New("Enabled acme support requires to bind on default https port :443")
}
if (len(server.Tls.AcmeHosts) == 0) && ((server.Tls.KeyPath == "") || (server.Tls.CertPath == "")) {
return config.Server{}, errors.New("tls requires specify either acme hosts or both key and cert paths")
}

}
Expand Down Expand Up @@ -416,36 +464,21 @@ func prepareConfig(name string, server config.Server, defaults config.Connection

/* TODO: Still need to decide how to get rid of this */

if defaults.MaxConnections == nil {
defaults.MaxConnections = new(int)
}
if server.MaxConnections == nil {
server.MaxConnections = new(int)
*server.MaxConnections = *defaults.MaxConnections
}

if defaults.ClientIdleTimeout == nil {
defaults.ClientIdleTimeout = new(string)
*defaults.ClientIdleTimeout = "0"
}
if server.ClientIdleTimeout == nil {
server.ClientIdleTimeout = new(string)
*server.ClientIdleTimeout = *defaults.ClientIdleTimeout
}

if defaults.BackendIdleTimeout == nil {
defaults.BackendIdleTimeout = new(string)
*defaults.BackendIdleTimeout = "0"
}
if server.BackendIdleTimeout == nil {
server.BackendIdleTimeout = new(string)
*server.BackendIdleTimeout = *defaults.BackendIdleTimeout
}

if defaults.BackendConnectionTimeout == nil {
defaults.BackendConnectionTimeout = new(string)
*defaults.BackendConnectionTimeout = "0"
}
if server.BackendConnectionTimeout == nil {
server.BackendConnectionTimeout = new(string)
*server.BackendConnectionTimeout = *defaults.BackendConnectionTimeout
Expand Down
23 changes: 4 additions & 19 deletions src/server/tcp/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ package tcp
import (
"crypto/tls"
"net"
"strings"
"time"

"../../balance"
Expand Down Expand Up @@ -68,6 +67,9 @@ type Server struct {
/* Tls config used for incoming connections */
tlsConfig *tls.Config

/* Get certificate filled by external service */
GetCertificate func(*tls.ClientHelloInfo) (*tls.Certificate, error)

/* ----- modules ----- */

/* Access module checks if client is allowed to connect */
Expand Down Expand Up @@ -116,7 +118,7 @@ func New(name string, cfg config.Server) (*Server, error) {
return nil, err
}

server.tlsConfig, err = tlsutil.MakeTlsConfig(cfg.Tls)
server.tlsConfig, err = tlsutil.MakeTlsConfig(cfg.Tls, server.GetCertificate)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -235,23 +237,6 @@ func (this *Server) wrap(conn net.Conn, sniEnabled bool) {
return
}

// Acme and Sni support features overlap - sni middleware can drop connections to *.acme.invalid, but they
// should be processed in order to obtain/renew certificates
if this.cfg.Tls != nil && this.cfg.Tls.AcmeEnabled {
if strings.HasSuffix(hostname, ".acme.invalid") {
conn = tls.Server(sniConn, this.tlsConfig)

_, err := conn.Write([]byte{0})

if err != nil {
log.Error("Error while communicating with acme server", err)
}

conn.Close()
return
}
}

conn = sniConn
}

Expand Down
Loading

0 comments on commit 80e5219

Please sign in to comment.