/
secProxy.go
160 lines (127 loc) · 3.74 KB
/
secProxy.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
package frontend
import (
"context"
"fmt"
"net/http"
"regexp"
"time"
"google.golang.org/grpc"
log "github.com/Sirupsen/logrus"
"github.com/afex/hystrix-go/hystrix"
pb "github.com/mchudgins/certMgr/pkg/service"
"github.com/patrickmn/go-cache"
)
const (
remoteUserHeader = "X-RemoteUser"
grpcMetadataHeaderPrefix = "Grpc-Metadata-"
)
var (
bearerRegex = regexp.MustCompile(`^\s*bearer\s+([[:alnum:]]+)`)
authVerifier pb.AuthVerifierServiceClient
)
type securityProxy struct {
url string
auth pb.AuthVerifierServiceClient
logonURL string
logoutURL string
cache *cache.Cache
}
func init() {
}
func NewSecurityProxy(idp string) (*securityProxy, error) {
conn, err := grpc.Dial(idp, grpc.WithInsecure())
if err != nil {
return nil, err
}
client := pb.NewAuthVerifierServiceClient(conn)
server := &securityProxy{url: idp, auth: client}
hystrix.ConfigureCommand(server.url, hystrix.CommandConfig{
Timeout: 250, // 250 ms
MaxConcurrentRequests: 100,
})
err = hystrix.Do(server.url, func() (err error) {
resp, err := client.Configuration(context.Background(),
&pb.ConfigurationRequest{})
if err != nil {
newerr := fmt.Errorf("Unable to retrieve configuration URLs from the authentication service (%s) -- %s", idp, err)
return newerr
}
server.logonURL = resp.LogonURL
server.logoutURL = resp.LogoutURL
return nil
}, nil)
// set up the cache
server.cache = cache.New(30*time.Minute, 30*time.Second)
return server, err
}
// DRY: make sure we always redirect to LogonURL in the same way
func (s *securityProxy) redirectToLogon(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r,
s.logonURL, http.StatusTemporaryRedirect)
}
func (s *securityProxy) verifyToken(token string) (*pb.VerificationResponse, error) {
var resp *pb.VerificationResponse
err := hystrix.Do(s.url, func() (err error) {
request := &pb.VerificationRequest{Token: token}
resp, err = s.auth.VerifyToken(context.Background(), request)
if err != nil {
log.WithError(err).WithField("token", token).Warn("VerifyToken returned an error")
}
return err
}, nil)
return resp, err
}
func (s *securityProxy) Handler(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var token string
auth := r.Header.Get("Authorization")
if len(auth) == 0 {
s.redirectToLogon(w, r)
return
}
if bearerRegex.MatchString(auth) {
result := bearerRegex.FindStringSubmatch(auth)
if len(result) == 2 {
token = result[1]
}
}
// Todo: if the authorization header is present, then determine the
// user's ID and pass it along to the backend via the grpc per call credentials
if len(token) == 0 {
s.redirectToLogon(w, r)
return
}
var resp *pb.VerificationResponse
// check the process cache to see if the token is valid
now := time.Now()
cacheHit, found := s.cache.Get(token)
if !found { // not in the cache, go get it
var err error
resp, err = s.verifyToken(token)
if err != nil {
w.WriteHeader(http.StatusServiceUnavailable)
return
}
expires := time.Unix(resp.CacheExpiration, 0)
if resp.Valid && expires.After(now) {
s.cache.Set(token, resp, expires.Sub(now))
}
} else { // in the cache, double check it
resp = cacheHit.(*pb.VerificationResponse)
expires := time.Unix(resp.CacheExpiration, 0)
if now.After(expires) {
resp.Valid = false
s.cache.Delete(token)
}
}
// if the token's invalid, send 'em to the logon URL
if !resp.Valid {
s.redirectToLogon(w, r)
return
}
// resp.UserID needs to be passed along to the backend
r.Header.Set(grpcMetadataHeaderPrefix+remoteUserHeader, resp.UserID)
// finally, pass the request along the processing chain
h.ServeHTTP(w, r)
})
}