forked from Jipok/Jauth
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathssl-proxy.go
216 lines (203 loc) · 6.97 KB
/
ssl-proxy.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
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
package main
import (
"crypto/tls"
"fmt"
"log"
"net/http"
"net/http/httputil"
"net/url"
"os"
"strings"
"time"
"github.com/klauspost/compress/gzhttp"
"github.com/suyashkumar/ssl-proxy/gen"
"golang.org/x/crypto/acme/autocert"
)
const (
SELF_SIGNED_CERT_FILE = "self-signed.crt"
SELF_SIGNED_KEY_FILE = "self-signed.key"
AUTOCERT_DIR_CACHE = "jauth-autocert"
)
func handleCerts() {
if (cfg.Certificate.Cert != "") && (cfg.Certificate.Type != "manual") {
log.Print("Certificate.Cert and Certificate.Key are ignored since not a manual mode")
}
switch cfg.Certificate.Type {
case "autocert":
log.Printf("Using LetsEncrypt to autogenerate and serve certs for all domains")
case "manual":
_, err := os.Stat(cfg.Certificate.Cert)
if err != nil {
log.Fatalf("Problem wit Certificate.Cert file: %s", err)
}
_, err = os.Stat(cfg.Certificate.Key)
if err != nil {
log.Fatalf("Problem wit Certificate.Key file: %s", err)
}
log.Printf("Using provided Cert and Key for all domains")
case "self-signed":
cfg.Certificate.Cert = SELF_SIGNED_CERT_FILE
cfg.Certificate.Key = SELF_SIGNED_KEY_FILE
// Checking for a previously generated certificate
_, err := os.Stat(cfg.Certificate.Cert)
if err == nil {
log.Printf("Using the previously generated self-signed certificate")
break
}
// Generate new self-signed tls cert via original ssl-proxy library
log.Print("Generating a new self-signed certificate")
certBuf, keyBuf, fingerprint, err := gen.Keys(365 * 24 * time.Hour)
if err != nil {
log.Fatal("Error generating default keys", err)
}
// Save Cert
certOut, err := os.Create(cfg.Certificate.Cert)
if err != nil {
log.Fatalf("Unable to create %s file: %s", SELF_SIGNED_CERT_FILE, err)
}
certOut.Write(certBuf.Bytes())
// Save Key
keyOut, err := os.OpenFile(SELF_SIGNED_KEY_FILE, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
if err != nil {
log.Fatalf("Unable to create %s file: %s", SELF_SIGNED_KEY_FILE, err)
}
keyOut.Write(keyBuf.Bytes())
log.Printf("SHA256 Fingerprint: % X", fingerprint)
default:
log.Printf("Wrong value for Certificate.Type: %s", cfg.Certificate.Type)
log.Fatalf("Must be one of: autocert, self-signed, manual")
}
}
// Convert string with port or address:port ot URL type
func targetToURL(target string) (*url.URL, error) {
// Allow to specify only port
if !strings.Contains(target, ":") {
target = "http://127.0.0.1:" + target
}
// Ensure the to URL start from http://
if !strings.HasPrefix(target, "http://") && !strings.HasPrefix(target, "https://") {
target = "http://" + target
}
// Parse as a URL
return url.Parse(target)
}
func startWebServer() {
// Parse target of user's domains
toURLs := map[string]*url.URL{}
var domains []string
for _, d := range cfg.Domains {
toURL, err := targetToURL(d.Target)
if err != nil {
log.Fatalf("Unable to parse target url `%s` for domain `%s`: %s", d.Target, d.Domain, err)
}
toURLs[d.Domain] = toURL
// Domain_Info used for DefaultTarget(non specified domains, ip address)
if d.Domain == "" {
// With autocert we using HostWhitelist
// Hiding the default target since it can't be used in that case
if cfg.Certificate.Type != "autocert" {
log.Print(green("Default target for unspecified domains: "), toURL)
}
continue
} else {
domains = append(domains, d.Domain)
}
// TODO Подсвечивать дубликаты портов
log.Printf(green("Proxying from https://%s to %s"), d.Domain, toURL)
}
if cfg.Certificate.Type == "autocert" {
log.Print("With autocert using HostWhitelist: ", strings.Join(domains, ", "))
}
// TODO Is possible add http/2 support?
// transport := &http.Transport{
// ForceAttemptHTTP2: true,
// }
// http2.ConfigureTransport(transport)
// Setup reverse proxy
var proxy http.Handler = &httputil.ReverseProxy{
// Transport: transport,
Rewrite: func(r *httputil.ProxyRequest) {
// Use default target(with empty domain) for everything we don't know how to redirect
target := toURLs[r.In.Host]
if target == nil {
target = toURLs[""]
}
// Based on doc for SetURL
r.SetURL(target)
r.SetXForwarded()
r.Out.Host = r.In.Host
},
ErrorHandler: func(w http.ResponseWriter, r *http.Request, err error) {
log.Printf(yellow("User can't access https://%s - ")+"%s", r.Host, err)
w.WriteHeader(http.StatusBadGateway)
w.Write(embed_502_html)
},
}
if cfg.Gzip {
wrapper, err := gzhttp.NewWrapper(gzhttp.KeepAcceptRanges())
if err != nil {
log.Fatalf("Unable to create gzip wrapper: %s", err)
}
proxy = wrapper(proxy)
}
// See auth-handler.go
mux := newAuthMux(proxy)
// Listen http on 80 port and redirect all incoming to https
if cfg.RedirectHTTP {
log.Printf("Redirecting http(%s:80) requests to https \n", cfg.Listen)
redirectTLS := func(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "https://"+r.Host+r.RequestURI, http.StatusMovedPermanently)
}
go func() {
// TODO MaxHeaderBytes and timeouts to read/write/idle
httpServer := http.Server{
Addr: cfg.Listen + ":80",
Handler: http.HandlerFunc(redirectTLS),
ErrorLog: newServerErrorLog(),
}
err := httpServer.ListenAndServe()
if err != nil {
log.Fatal("HTTP redirection server failure", err)
}
}()
}
var err error
address := cfg.Listen + ":" + cfg.HttpsPort
log.Printf("Web server is listening: %s", address)
if cfg.Certificate.Type == "autocert" {
// For some reason LetsEncrypt seems to only work on :443
m := &autocert.Manager{
Cache: autocert.DirCache(AUTOCERT_DIR_CACHE),
Prompt: autocert.AcceptTOS,
HostPolicy: autocert.HostWhitelist(domains...),
Email: cfg.Certificate.Email,
}
c := m.TLSConfig()
c.MinVersion = tls.VersionTLS12
s := &http.Server{
Addr: address,
TLSConfig: c,
Handler: mux,
ErrorLog: newServerErrorLog(),
}
err = s.ListenAndServeTLS("", "")
} else {
// manual or self-signed mode. Serve TLS using provided/generated certificate files
err = http.ListenAndServeTLS(address, cfg.Certificate.Cert, cfg.Certificate.Key, mux)
}
log.Print(red(err.Error()))
if strings.HasSuffix(err.Error(), "bind: permission denied") {
fmt.Print("You can't bind to a privileged port (ports less than 1024) as a non-root user." + "\n" +
"There are various solutions:" + "\n" +
"1) Give the application the ability to bind privileged ports:" + "\n" +
green(" sudo setcap 'cap_net_bind_service=+ep' /path/to/jauth") + "\n" +
"2) Temporarily change the list of privileged ports(to 0-80) for the entire system:" + "\n" +
green(" sudo sysctl -w net.ipv4.ip_unprivileged_port_start=80") + "\n" +
"3) Permanently change the list of privileged ports(to 0-80) for the entire system" + "\n" +
green(" sudo echo 'net.ipv4.ip_unprivileged_port_start=0' > /etc/sysctl.d/50-unprivileged-ports.conf") + "\n" +
green(" sudo sysctl --system # To apply the setting without rebooting") + "\n" +
"4) Run as root" + "\n" +
"5) Use an autobind: https://en.wikipedia.org/wiki/Authbind" + "\n")
}
os.Exit(1)
}