1
0
mirror of https://github.com/umputun/reproxy.git synced 2024-11-30 08:16:56 +02:00
reproxy/app/main.go

278 lines
10 KiB
Go
Raw Normal View History

2021-04-01 09:37:28 +02:00
package main
import (
"context"
"fmt"
2021-04-06 05:12:06 +02:00
"io"
2021-04-07 06:50:04 +02:00
"io/ioutil"
2021-04-01 09:37:28 +02:00
"os"
"os/signal"
"runtime"
2021-04-12 09:07:35 +02:00
"strings"
2021-04-01 09:37:28 +02:00
"syscall"
"time"
docker "github.com/fsouza/go-dockerclient"
2021-04-03 07:22:54 +02:00
log "github.com/go-pkgz/lgr"
2021-04-01 09:37:28 +02:00
"github.com/pkg/errors"
2021-04-02 07:07:36 +02:00
"github.com/umputun/go-flags"
2021-04-09 22:31:14 +02:00
"gopkg.in/natefinch/lumberjack.v2"
2021-04-02 07:07:36 +02:00
2021-04-03 21:23:23 +02:00
"github.com/umputun/reproxy/app/discovery"
"github.com/umputun/reproxy/app/discovery/provider"
"github.com/umputun/reproxy/app/proxy"
2021-04-01 09:37:28 +02:00
)
var opts struct {
2021-04-13 04:54:59 +02:00
Listen string `short:"l" long:"listen" env:"LISTEN" default:"127.0.0.1:8080" description:"listen on host:port"`
MaxSize int64 `short:"m" long:"max" env:"MAX_SIZE" default:"64000" description:"max response size"`
GzipEnabled bool `short:"g" long:"gzip" env:"GZIP" description:"enable gz compression"`
ProxyHeaders []string `short:"x" long:"header" env:"HEADER" description:"proxy headers" env-delim:","`
2021-04-01 09:37:28 +02:00
2021-04-03 07:22:54 +02:00
SSL struct {
Type string `long:"type" env:"TYPE" description:"ssl (auto) support" choice:"none" choice:"static" choice:"auto" default:"none"` //nolint
Cert string `long:"cert" env:"CERT" description:"path to cert.pem file"`
Key string `long:"key" env:"KEY" description:"path to key.pem file"`
ACMELocation string `long:"acme-location" env:"ACME_LOCATION" description:"dir where certificates will be stored by autocert manager" default:"./var/acme"`
ACMEEmail string `long:"acme-email" env:"ACME_EMAIL" description:"admin email for certificate notifications"`
2021-04-09 22:05:22 +02:00
RedirHTTPPort int `long:"http-port" env:"HTTP_PORT" default:"80" description:"http port for redirect to https and acme challenge test"`
FQDNs []string `long:"fqdn" env:"ACME_FQDN" env-delim:"," description:"FQDN(s) for ACME certificates"`
2021-04-03 07:22:54 +02:00
} `group:"ssl" namespace:"ssl" env-namespace:"SSL"`
2021-04-01 09:37:28 +02:00
Assets struct {
Location string `short:"a" long:"location" env:"LOCATION" default:"" description:"assets location"`
WebRoot string `long:"root" env:"ROOT" default:"/" description:"assets web root"`
} `group:"assets" namespace:"assets" env-namespace:"ASSETS"`
2021-04-06 05:12:06 +02:00
Logger struct {
2021-04-13 19:45:49 +02:00
StdOut bool `long:"stdout" env:"STDOUT" description:"enable stdout logging"`
2021-04-06 05:12:06 +02:00
Enabled bool `long:"enabled" env:"ENABLED" description:"enable access and error rotated logs"`
FileName string `long:"file" env:"FILE" default:"access.log" description:"location of access log"`
MaxSize int `long:"max-size" env:"MAX_SIZE" default:"100" description:"maximum size in megabytes before it gets rotated"`
MaxBackups int `long:"max-backups" env:"MAX_BACKUPS" default:"10" description:"maximum number of old log files to retain"`
} `group:"logger" namespace:"logger" env-namespace:"LOGGER"`
2021-04-01 09:37:28 +02:00
Docker struct {
Enabled bool `long:"enabled" env:"ENABLED" description:"enable docker provider"`
Host string `long:"host" env:"HOST" default:"unix:///var/run/docker.sock" description:"docker host"`
2021-04-10 09:07:42 +02:00
Network string `long:"network" env:"NETWORK" default:"" description:"docker network"`
2021-04-03 09:31:48 +02:00
Excluded []string `long:"exclude" env:"EXCLUDE" description:"excluded containers" env-delim:","`
2021-04-12 09:07:35 +02:00
AutoAPI bool `long:"auto" env:"AUTO" description:"enable automatic routing (without labels)"`
2021-04-01 09:37:28 +02:00
} `group:"docker" namespace:"docker" env-namespace:"DOCKER"`
File struct {
Enabled bool `long:"enabled" env:"ENABLED" description:"enable file provider"`
2021-04-07 06:18:27 +02:00
Name string `long:"name" env:"NAME" default:"reproxy.yml" description:"file name"`
2021-04-01 09:37:28 +02:00
CheckInterval time.Duration `long:"interval" env:"INTERVAL" default:"3s" description:"file check interval"`
Delay time.Duration `long:"delay" env:"DELAY" default:"500ms" description:"file event delay"`
} `group:"file" namespace:"file" env-namespace:"FILE"`
Static struct {
2021-04-01 09:53:42 +02:00
Enabled bool `long:"enabled" env:"ENABLED" description:"enable static provider"`
Rules []string `long:"rule" env:"RULES" description:"routing rules" env-delim:";"`
2021-04-01 09:37:28 +02:00
} `group:"static" namespace:"static" env-namespace:"STATIC"`
2021-04-13 04:54:59 +02:00
Timeouts struct {
ReadHeader time.Duration `long:"read-header" env:"READ_HEADER" default:"5s" description:"read header server timeout"`
Write time.Duration `long:"write" env:"WRITE" default:"30s" description:"write server timeout"`
Idle time.Duration `long:"idle" env:"IDLE" default:"30s" description:"idle server timeout"`
Dial time.Duration `long:"dial" env:"DIAL" default:"30s" description:"dial transport timeout"`
KeepAlive time.Duration `long:"keep-alive" env:"KEEP_ALIVE" default:"30s" description:"keep-alive transport timeout"`
ResponseHeader time.Duration `long:"resp-header" env:"RESP_HEADER" default:"5s" description:"response header transport timeout"`
IdleConn time.Duration `long:"idle-conn" env:"IDLE_CONN" default:"90s" description:"idle connection transport timeout"`
TLSHandshake time.Duration `long:"tls" env:"TLS" default:"10s" description:"TLS hanshake transport timeout"`
ExpectContinue time.Duration `long:"continue" env:"CONTINUE" default:"1s" description:"expect continue transport timeout"`
} `group:"timeout" namespace:"timeout" env-namespace:"TIMEOUT"`
2021-04-07 06:18:27 +02:00
NoSignature bool `long:"no-signature" env:"NO_SIGNATURE" description:"disable reproxy signature headers"`
Dbg bool `long:"dbg" env:"DEBUG" description:"debug mode"`
2021-04-01 09:37:28 +02:00
}
var revision = "unknown"
func main() {
2021-04-06 05:12:06 +02:00
fmt.Printf("reproxy %s\n", revision)
2021-04-01 09:37:28 +02:00
p := flags.NewParser(&opts, flags.PrintErrors|flags.PassDoubleDash|flags.HelpFlag)
p.SubcommandsOptional = true
if _, err := p.Parse(); err != nil {
if err.(*flags.Error).Type != flags.ErrHelp {
log.Printf("[ERROR] cli error: %v", err)
}
os.Exit(1)
}
setupLog(opts.Dbg)
catchSignal()
providers, err := makeProviders()
if err != nil {
log.Fatalf("[ERROR] failed to make providers, %v", err)
}
2021-04-12 09:29:17 +02:00
svc := discovery.NewService(providers, time.Second)
2021-04-11 21:21:58 +02:00
if len(providers) > 0 {
go func() {
if e := svc.Run(context.Background()); e != nil {
log.Fatalf("[ERROR] discovery failed, %v", e)
}
}()
}
2021-04-01 09:37:28 +02:00
2021-04-03 07:22:54 +02:00
sslConfig, err := makeSSLConfig()
if err != nil {
log.Fatalf("[ERROR] failed to make config of ssl server params, %v", err)
}
2021-04-09 22:05:22 +02:00
defer func() {
if x := recover(); x != nil {
log.Printf("[WARN] run time panic:\n%v", x)
panic(x)
}
}()
2021-04-07 06:18:27 +02:00
accessLog := makeAccessLogWriter()
2021-04-06 05:12:06 +02:00
defer func() {
if err := accessLog.Close(); err != nil {
log.Printf("[WARN] can't close access log, %v", err)
}
}()
2021-04-01 09:37:28 +02:00
px := &proxy.Http{
2021-04-07 06:18:27 +02:00
Version: revision,
Matcher: svc,
Address: opts.Listen,
MaxBodySize: opts.MaxSize,
AssetsLocation: opts.Assets.Location,
AssetsWebRoot: opts.Assets.WebRoot,
GzEnabled: opts.GzipEnabled,
SSLConfig: sslConfig,
ProxyHeaders: opts.ProxyHeaders,
AccessLog: accessLog,
2021-04-13 19:45:49 +02:00
StdOutEnabled: opts.Logger.StdOut,
2021-04-07 06:18:27 +02:00
DisableSignature: opts.NoSignature,
2021-04-13 04:54:59 +02:00
Timeouts: proxy.Timeouts{
ReadHeader: opts.Timeouts.ReadHeader,
Write: opts.Timeouts.Write,
Idle: opts.Timeouts.Idle,
Dial: opts.Timeouts.Dial,
KeepAlive: opts.Timeouts.KeepAlive,
IdleConn: opts.Timeouts.IdleConn,
TLSHandshake: opts.Timeouts.TLSHandshake,
ExpectContinue: opts.Timeouts.ExpectContinue,
ResponseHeader: opts.Timeouts.ResponseHeader,
},
2021-04-01 09:37:28 +02:00
}
2021-04-03 07:22:54 +02:00
if err := px.Run(context.Background()); err != nil {
2021-04-09 22:05:22 +02:00
log.Fatalf("[ERROR] proxy server failed, %v", err) //nolint gocritic
2021-04-01 09:37:28 +02:00
}
}
2021-04-12 09:07:35 +02:00
// make all providers. the order is matter, defines which provider will have priority in case of conflicting rules
// static first, file second and docker the last one
2021-04-01 09:37:28 +02:00
func makeProviders() ([]discovery.Provider, error) {
var res []discovery.Provider
2021-04-12 09:07:35 +02:00
if opts.Static.Enabled {
var msgs []string
for _, rule := range opts.Static.Rules {
msgs = append(msgs, "\""+rule+"\"")
}
log.Printf("[DEBUG] inject static rules: %s", strings.Join(msgs, " "))
res = append(res, &provider.Static{Rules: opts.Static.Rules})
}
2021-04-01 09:37:28 +02:00
if opts.File.Enabled {
res = append(res, &provider.File{
FileName: opts.File.Name,
CheckInterval: opts.File.CheckInterval,
Delay: opts.File.Delay,
})
}
if opts.Docker.Enabled {
client, err := docker.NewClient(opts.Docker.Host)
if err != nil {
return nil, errors.Wrapf(err, "failed to make docker client %s", err)
}
if opts.Docker.AutoAPI {
log.Printf("[INFO] auto-api enabled for docker")
}
res = append(res, &provider.Docker{DockerClient: client, Excludes: opts.Docker.Excluded,
Network: opts.Docker.Network, AutoAPI: opts.Docker.AutoAPI})
2021-04-01 09:37:28 +02:00
}
if len(res) == 0 && opts.Assets.Location == "" {
2021-04-01 09:37:28 +02:00
return nil, errors.Errorf("no providers enabled")
}
return res, nil
}
2021-04-03 07:22:54 +02:00
func makeSSLConfig() (config proxy.SSLConfig, err error) {
switch opts.SSL.Type {
case "none":
config.SSLMode = proxy.SSLNone
case "static":
if opts.SSL.Cert == "" {
return config, errors.New("path to cert.pem is required")
}
if opts.SSL.Key == "" {
return config, errors.New("path to key.pem is required")
}
config.SSLMode = proxy.SSLStatic
config.Cert = opts.SSL.Cert
config.Key = opts.SSL.Key
2021-04-09 22:05:22 +02:00
config.RedirHTTPPort = opts.SSL.RedirHTTPPort
2021-04-03 07:22:54 +02:00
case "auto":
config.SSLMode = proxy.SSLAuto
config.ACMELocation = opts.SSL.ACMELocation
config.ACMEEmail = opts.SSL.ACMEEmail
config.FQDNs = opts.SSL.FQDNs
2021-04-09 22:05:22 +02:00
config.RedirHTTPPort = opts.SSL.RedirHTTPPort
2021-04-03 07:22:54 +02:00
}
return config, err
}
2021-04-01 09:37:28 +02:00
2021-04-07 06:18:27 +02:00
func makeAccessLogWriter() (accessLog io.WriteCloser) {
2021-04-06 05:12:06 +02:00
if !opts.Logger.Enabled {
2021-04-07 06:50:04 +02:00
return nopWriteCloser{ioutil.Discard}
2021-04-06 05:12:06 +02:00
}
log.Printf("[INFO] logger enabled for %s", opts.Logger.FileName)
return &lumberjack.Logger{
Filename: opts.Logger.FileName,
MaxSize: opts.Logger.MaxSize,
2021-04-07 06:18:27 +02:00
MaxBackups: opts.Logger.MaxBackups,
2021-04-06 05:12:06 +02:00
Compress: true,
LocalTime: true,
}
}
type nopWriteCloser struct{ io.Writer }
func (n nopWriteCloser) Close() error { return nil }
2021-04-03 07:22:54 +02:00
func setupLog(dbg bool) {
2021-04-01 09:37:28 +02:00
if dbg {
2021-04-03 07:22:54 +02:00
log.Setup(log.Debug, log.CallerFile, log.CallerFunc, log.Msec, log.LevelBraces)
return
2021-04-01 09:37:28 +02:00
}
2021-04-03 07:22:54 +02:00
log.Setup(log.Msec, log.LevelBraces)
2021-04-01 09:37:28 +02:00
}
func catchSignal() {
// catch SIGQUIT and print stack traces
sigChan := make(chan os.Signal)
go func() {
for range sigChan {
log.Print("[INFO] SIGQUIT detected")
stacktrace := make([]byte, 8192)
length := runtime.Stack(stacktrace, true)
if length > 8192 {
length = 8192
}
fmt.Println(string(stacktrace[:length]))
}
}()
signal.Notify(sigChan, syscall.SIGQUIT)
}