-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathproxy.go
153 lines (138 loc) · 4.22 KB
/
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
package main
import (
"bytes"
"compress/gzip"
"crypto/tls"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net"
"net/http"
"net/http/httputil"
"net/url"
"strings"
"time"
log "github.com/Sirupsen/logrus"
"github.com/uninett/goidc-proxy/conf"
"golang.org/x/oauth2"
)
type transport struct {
http.RoundTripper
authenticators map[string]*Authenticator
}
type ACRValues struct {
Values string `json:"required_acr_values"`
}
type UpstreamProxy struct {
upstream *url.URL
handler http.Handler
}
func singleJoiningSlash(a, b string) string {
aslash := strings.HasSuffix(a, "/")
bslash := strings.HasPrefix(b, "/")
switch {
case aslash && bslash:
return a + b[1:]
case !aslash && !bslash:
return a + "/" + b
}
return a + b
}
func (t *transport) RoundTrip(req *http.Request) (resp *http.Response, err error) {
resp, err = t.RoundTripper.RoundTrip(req)
if err != nil {
return nil, err
}
if resp.StatusCode == http.StatusForbidden &&
conf.GetBoolValue("engine.twofactor.rediect_on_response") {
// Check that the server actually sent compressed data
var reader io.ReadCloser
switch resp.Header.Get("Content-Encoding") {
case "gzip":
// Remove header, as we will send reply as simple json
resp.Header.Del("Content-Encoding")
reader, err = gzip.NewReader(resp.Body)
default:
reader = resp.Body
}
defer resp.Body.Close()
b, err := ioutil.ReadAll(reader)
if err != nil {
log.Warn("Failed in reading response body ", err)
return resp, err
}
defer reader.Close()
var acr ACRValues
err = json.Unmarshal(b, &acr)
if err != nil {
log.Warn("Failed in parsing response body ", err)
}
if acr.Values != "" {
log.Info("Endpoint require two factor authentication")
var state string
for _, c := range req.Cookies() {
if strings.HasPrefix(c.Name, "state.") {
state = strings.Split(c.Name, ".")[1]
}
}
acrVal := oauth2.SetAuthURLParam("acr_values", acr.Values)
var bodyData []byte
authn, found := t.authenticators[req.Host]
if !found {
return resp, fmt.Errorf("No oauth config found for host: %s", req.Host)
}
if isXHR(req.URL.Path) {
bodyData = append(append([]byte(`{"two_factor": true, "redirect_url": "`+authn.clientConfig.AuthCodeURL(state, acrVal)+`", "body":`), b...), []byte("}")...)
log.Info("Got 403 with non empty ACR Values, redirecting for XHR ", acrVal)
} else {
resp.StatusCode = http.StatusFound
bodyData = []byte("{}")
resp.Header.Add("Location", authn.clientConfig.AuthCodeURL(state, acrVal))
log.Info("Got 403 with non empty ACR Values, redirecting ", acrVal)
}
resp.Body = ioutil.NopCloser(bytes.NewReader(bodyData))
return resp, nil
}
resp.Body = ioutil.NopCloser(bytes.NewReader(b))
}
return resp, nil
}
func NewUpstreamProxy(target *url.URL, authenticators map[string]*Authenticator, useReqHost bool) *UpstreamProxy {
proxy := newReverseProxy(target, authenticators, useReqHost)
return &UpstreamProxy{target, proxy}
}
func (u *UpstreamProxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if isWebsocketRequest(r) {
u.handleWebsocket(w, r)
} else {
u.handler.ServeHTTP(w, r)
}
}
// NewReverseProxy prvoides reverse proxy functionality towards target
func newReverseProxy(target *url.URL, authenticators map[string]*Authenticator, useReqHost bool) *httputil.ReverseProxy {
director := func(req *http.Request) {
req.URL.Scheme = target.Scheme
req.URL.Host = target.Host
if !useReqHost {
req.Host = target.Host
}
req.URL.Path = singleJoiningSlash(target.Path, req.URL.Path)
}
// These are copied from the DefaultTrasport RoundTripper of Go 1.7.1
// The only change is the Dialer Timeout and KeepAlive that have been
// upped from 30 to 120 seconds.
proxyTransport := &http.Transport{
Proxy: http.ProxyFromEnvironment,
DialContext: (&net.Dialer{
Timeout: 120 * time.Second,
KeepAlive: 120 * time.Second,
}).DialContext,
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
TLSClientConfig: &tls.Config{InsecureSkipVerify: conf.GetBoolValue("proxy.insecure_skip_verify")},
}
return &httputil.ReverseProxy{Director: director, Transport: &transport{proxyTransport, authenticators}}
}