forked from cactus/go-camo
/
go-camo.go
186 lines (160 loc) · 5.44 KB
/
go-camo.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
// go-camo daemon (go-camod)
package main
import (
"fmt"
"io/ioutil"
"log"
"net/http"
"os"
"runtime"
"strconv"
"strings"
"syscall"
"time"
"github.com/cactus/go-camo/camo"
"github.com/cactus/go-camo/router"
"github.com/cactus/go-camo/stats"
"github.com/cactus/gologit"
flags "github.com/jessevdk/go-flags"
)
var (
ServerName = "go-camo"
ServerVersion = "no-version"
)
func main() {
var gmx int
if gmxEnv := os.Getenv("GOMAXPROCS"); gmxEnv != "" {
gmx, _ = strconv.Atoi(gmxEnv)
} else {
gmx = runtime.NumCPU()
}
runtime.GOMAXPROCS(gmx)
// command line flags
var opts struct {
HMACKey string `short:"k" long:"key" description:"HMAC key"`
AddHeaders []string `short:"H" long:"header" description:"Extra header to return for each response. This option can be used multiple times to add multiple headers"`
Stats bool `long:"stats" description:"Enable Stats"`
AllowList string `long:"allow-list" description:"Text file of hostname allow regexes (one per line)"`
MaxSize int64 `long:"max-size" default:"5120" description:"Max response image size (KB)"`
ReqTimeout time.Duration `long:"timeout" default:"4s" description:"Upstream request timeout"`
MaxRedirects int `long:"max-redirects" default:"3" description:"Maximum number of redirects to follow"`
DisableKeepAlivesFE bool `long:"no-fk" description:"Disable frontend http keep-alive support"`
DisableKeepAlivesBE bool `long:"no-bk" description:"Disable backend http keep-alive support"`
BindAddress string `long:"listen" default:"0.0.0.0:8080" description:"Address:Port to bind to for HTTP"`
BindAddressSSL string `long:"ssl-listen" description:"Address:Port to bind to for HTTPS/SSL/TLS"`
SSLKey string `long:"ssl-key" description:"ssl private key (key.pem) path"`
SSLCert string `long:"ssl-cert" description:"ssl cert (cert.pem) path"`
Verbose bool `short:"v" long:"verbose" description:"Show verbose (debug) log level output"`
Version bool `short:"V" long:"version" description:"print version and exit"`
}
// parse said flags
_, err := flags.Parse(&opts)
if err != nil {
if e, ok := err.(*flags.Error); ok {
if e.Type == flags.ErrHelp {
os.Exit(0)
}
}
os.Exit(1)
}
if opts.Version {
fmt.Printf("%s %s (%s,%s-%s)\n", ServerName, ServerVersion, runtime.Version(), runtime.Compiler, runtime.GOARCH)
os.Exit(0)
}
config := camo.Config{}
if hmacKey := os.Getenv("GOCAMO_HMAC"); hmacKey != "" {
config.HMACKey = []byte(hmacKey)
}
// flags override env var
if opts.HMACKey != "" {
config.HMACKey = []byte(opts.HMACKey)
}
if len(config.HMACKey) == 0 {
log.Fatal("HMAC key required")
}
if opts.BindAddress == "" && opts.BindAddressSSL == "" {
log.Fatal("One of bind-address or bind-ssl-address required")
}
if opts.BindAddressSSL != "" && opts.SSLKey == "" {
log.Fatal("ssl-key is required when specifying bind-ssl-address")
}
if opts.BindAddressSSL != "" && opts.SSLCert == "" {
log.Fatal("ssl-cert is required when specifying bind-ssl-address")
}
// set keepalive options
opts.DisableKeepAlivesBE = config.DisableKeepAlivesBE
opts.DisableKeepAlivesFE = config.DisableKeepAlivesFE
if opts.AllowList != "" {
b, err := ioutil.ReadFile(opts.AllowList)
if err != nil {
log.Fatal("Could not read alllow-list. ", err)
}
config.AllowList = strings.Split(string(b), "\n")
}
AddHeaders := map[string]string{
"X-Content-Type-Options": "nosniff",
"X-XSS-Protection": "1; mode=block",
"Content-Security-Policy": "default-src 'none'",
}
for _, v := range opts.AddHeaders {
s := strings.SplitN(v, ":", 2)
if len(s) != 2 {
log.Printf("ignoring bad header: '%s'\n", v)
continue
}
s0 := strings.TrimSpace(s[0])
s1 := strings.TrimSpace(s[1])
if len(s0) == 0 || len(s1) == 0 {
log.Printf("ignoring bad header: '%s'\n", v)
continue
}
AddHeaders[s[0]] = s[1]
}
// convert from KB to Bytes
config.MaxSize = opts.MaxSize * 1024
config.RequestTimeout = opts.ReqTimeout
config.MaxRedirects = opts.MaxRedirects
config.ServerName = ServerName
// set logger debug level and start toggle on signal handler
logger := gologit.Logger
logger.Set(opts.Verbose)
logger.Debugln("Debug logging enabled")
logger.ToggleOnSignal(syscall.SIGUSR1)
proxy, err := camo.New(config)
if err != nil {
log.Fatal(err)
}
dumbrouter := &router.DumbRouter{
ServerName: config.ServerName,
AddHeaders: AddHeaders,
CamoHandler: proxy,
}
if opts.Stats {
ps := &stats.ProxyStats{}
proxy.SetMetricsCollector(ps)
log.Println("Enabling stats at /status")
dumbrouter.StatsHandler = stats.StatsHandler(ps)
}
http.Handle("/", dumbrouter)
if opts.BindAddress != "" {
log.Println("Starting server on", opts.BindAddress)
go func() {
srv := &http.Server{
Addr: opts.BindAddress,
ReadTimeout: 30 * time.Second}
log.Fatal(srv.ListenAndServe())
}()
}
if opts.BindAddressSSL != "" {
log.Println("Starting TLS server on", opts.BindAddressSSL)
go func() {
srv := &http.Server{
Addr: opts.BindAddressSSL,
ReadTimeout: 30 * time.Second}
log.Fatal(srv.ListenAndServeTLS(opts.SSLCert, opts.SSLKey))
}()
}
// just block. listen and serve will exit the program if they fail/return
// so we just need to block to prevent main from exiting.
select {}
}