forked from TykTechnologies/tyk
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmain.go
353 lines (288 loc) · 10.1 KB
/
main.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
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
package main
import (
"fmt"
"github.com/RangelReale/osin"
"github.com/Sirupsen/logrus"
"github.com/buger/goterm"
"github.com/docopt/docopt.go"
"github.com/justinas/alice"
"github.com/rcrowley/goagain"
"html/template"
"net"
"net/http"
"net/url"
"os"
"strconv"
"time"
)
var log = logrus.New()
var authManager = AuthorisationManager{}
var config = Config{}
var templates = &template.Template{}
var analytics = RedisAnalyticsHandler{}
var profileFile = &os.File{}
var doMemoryProfile bool
var genericOsinStorage *RedisOsinStorageInterface
// Generic system error
const (
E_SYSTEM_ERROR string = "{\"status\": \"system error, please contact administrator\"}"
OAUTH_AUTH_CODE_TIMEOUT int = 60 * 60
OAUTH_PREFIX string = "oauth-data."
)
// Display introductory details
func intro() {
fmt.Print("\n\n")
fmt.Println(goterm.Bold(goterm.Color("Tyk.io Gateway API v1.0", goterm.GREEN)))
fmt.Println(goterm.Bold(goterm.Color("=======================", goterm.GREEN)))
fmt.Print("Copyright Jively Ltd. 2014")
fmt.Print("\nhttp://www.tyk.io\n\n")
}
// Display configuration options
func displayConfig() {
configTable := goterm.NewTable(0, 10, 5, ' ', 0)
fmt.Fprintf(configTable, "Listening on port:\t%d\n", config.ListenPort)
fmt.Println(configTable)
fmt.Println("")
}
// Create all globals and init connection handlers
func setupGlobals() {
if config.Storage.Type == "memory" {
log.Warning("Using in-memory storage. Warning: this is not scalable.")
authManager = AuthorisationManager{
&InMemoryStorageManager{
map[string]string{}}}
} else if config.Storage.Type == "redis" {
log.Info("Using Redis storage manager.")
authManager = AuthorisationManager{
&RedisStorageManager{KeyPrefix: "apikey-"}}
authManager.Store.Connect()
}
if (config.EnableAnalytics == true) && (config.Storage.Type != "redis") {
log.Panic("Analytics requires Redis Storage backend, please enable Redis in the tyk.conf file.")
}
if config.EnableAnalytics {
AnalyticsStore := RedisStorageManager{KeyPrefix: "analytics-"}
log.Info("Setting up analytics DB connection")
if config.AnalyticsConfig.Type == "csv" {
log.Info("Using CSV cache purge")
analytics = RedisAnalyticsHandler{
Store: &AnalyticsStore,
Clean: &CSVPurger{&AnalyticsStore}}
} else if config.AnalyticsConfig.Type == "mongo" {
log.Info("Using MongoDB cache purge")
analytics = RedisAnalyticsHandler{
Store: &AnalyticsStore,
Clean: &MongoPurger{&AnalyticsStore, nil}}
}
analytics.Store.Connect()
go analytics.Clean.StartPurgeLoop(config.AnalyticsConfig.PurgeDelay)
}
genericOsinStorage = MakeNewOsinServer()
templateFile := fmt.Sprintf("%s/error.json", config.TemplatePath)
templates = template.Must(template.ParseFiles(templateFile))
}
// Pull API Specs from configuration
func getAPISpecs() []APISpec {
var APISpecs []APISpec
thisAPILoader := APIDefinitionLoader{}
if config.UseDBAppConfigs {
log.Info("Using App Configuration from Mongo DB")
APISpecs = thisAPILoader.LoadDefinitionsFromMongo()
} else {
APISpecs = thisAPILoader.LoadDefinitions(config.AppPath)
}
return APISpecs
}
// Set up default Tyk control API endpoints - these are global, so need to be added first
func loadAPIEndpoints(Muxer *http.ServeMux) {
// set up main API handlers
Muxer.HandleFunc("/tyk/keys/create", CheckIsAPIOwner(createKeyHandler))
Muxer.HandleFunc("/tyk/keys/", CheckIsAPIOwner(keyHandler))
Muxer.HandleFunc("/tyk/reload/", CheckIsAPIOwner(resetHandler))
Muxer.HandleFunc("/tyk/oauth/clients/create", CheckIsAPIOwner(createOauthClient))
Muxer.HandleFunc("/tyk/oauth/clients/", CheckIsAPIOwner(oAuthClientHandler))
}
// Create API-specific OAuth handlers and respective auth servers
func addOAuthHandlers(spec APISpec, Muxer *http.ServeMux, test bool) {
apiAuthorizePath := spec.Proxy.ListenPath + "tyk/oauth/authorize-client/"
clientAuthPath := spec.Proxy.ListenPath + "oauth/authorize/"
clientAccessPath := spec.Proxy.ListenPath + "oauth/token/"
serverConfig := osin.NewServerConfig()
serverConfig.ErrorStatusCode = 403
serverConfig.AllowedAccessTypes = spec.Oauth2Meta.AllowedAccessTypes //osin.AllowedAccessType{osin.AUTHORIZATION_CODE, osin.REFRESH_TOKEN}
serverConfig.AllowedAuthorizeTypes = spec.Oauth2Meta.AllowedAuthorizeTypes // osin.AllowedAuthorizeType{osin.CODE, osin.TOKEN}
OAuthPrefix := OAUTH_PREFIX + spec.APIID + "."
storageManager := RedisStorageManager{KeyPrefix: OAuthPrefix}
storageManager.Connect()
osinStorage := RedisOsinStorageInterface{&storageManager}
if test {
log.Warning("Adding test client")
testClient := &osin.Client{
Id: "1234",
Secret: "aabbccdd",
RedirectUri: "http://client.oauth.com",
}
osinStorage.SetClient(testClient.Id, testClient, false)
log.Warning("Test client added")
}
osinServer := osin.NewServer(serverConfig, osinStorage)
osinServer.AccessTokenGen = &AccessTokenGenTyk{}
oauthManager := OAuthManager{spec, osinServer}
oauthHandlers := OAuthHandlers{oauthManager}
Muxer.HandleFunc(apiAuthorizePath, CheckIsAPIOwner(oauthHandlers.HandleGenerateAuthCodeData))
Muxer.HandleFunc(clientAuthPath, oauthHandlers.HandleAuthorizePassthrough)
Muxer.HandleFunc(clientAccessPath, oauthHandlers.HandleAccessRequest)
}
// Create the individual API (app) specs based on live configurations and assign middleware
func loadApps(APISpecs []APISpec, Muxer *http.ServeMux) {
// load the APi defs
log.Info("Loading API configurations.")
for _, spec := range APISpecs {
// Create a new handler for each API spec
remote, err := url.Parse(spec.APIDefinition.Proxy.TargetURL)
if err != nil {
log.Error("Culdn't parse target URL")
log.Error(err)
}
if spec.UseOauth2 {
addOAuthHandlers(spec, Muxer, false)
}
proxy := TykNewSingleHostReverseProxy(remote)
spec.target = remote
proxyHandler := http.HandlerFunc(ProxyHandler(proxy, spec))
tykMiddleware := TykMiddleware{spec, proxy}
if spec.APIDefinition.UseKeylessAccess {
// for KeyLessAccess we can't support rate limiting, versioning or access rules
chain := alice.New().Then(proxyHandler)
Muxer.Handle(spec.Proxy.ListenPath, chain)
} else {
// Select the keying method to use for setting session states
var keyCheck func(http.Handler) http.Handler
if spec.APIDefinition.UseOauth2 {
// Oauth2
keyCheck = Oauth2KeyExists{tykMiddleware}.New()
} else if spec.APIDefinition.UseBasicAuth {
// Basic Auth
keyCheck = BasicAuthKeyIsValid{tykMiddleware}.New()
} else if spec.EnableSignatureChecking {
// HMAC Auth
keyCheck = HMACMiddleware{tykMiddleware}.New()
} else {
// Auth key
keyCheck = KeyExists{tykMiddleware}.New()
}
chain := alice.New(
keyCheck,
KeyExpired{tykMiddleware}.New(),
VersionCheck{tykMiddleware}.New(),
AccessRightsCheck{tykMiddleware}.New(),
RateLimitAndQuotaCheck{tykMiddleware}.New()).Then(proxyHandler)
Muxer.Handle(spec.Proxy.ListenPath, chain)
}
}
}
// ReloadURLStructure will create a new muxer, reload all the app configs for an
// instance and then replace the DefaultServeMux with the new one, this enables a
// reconfiguration to take place without stopping any requests from being handled.
func ReloadURLStructure() {
newMuxes := http.NewServeMux()
loadAPIEndpoints(newMuxes)
specs := getAPISpecs()
loadApps(specs, newMuxes)
http.DefaultServeMux = newMuxes
log.Info("Reload complete")
}
func init() {
intro()
usage := `Tyk API Gateway.
Usage:
tyk [options]
Options:
-h --help Show this screen
--conf=FILE Load a named configuration file
--port=PORT Listen on PORT (overrides confg file)
--memprofile Generate a memory profile
--debug Enable Debug output
`
arguments, err := docopt.Parse(usage, nil, true, "Tyk v1.0", false)
if err != nil {
log.Println("Error while parsing arguments.")
log.Fatal(err)
}
filename := "/etc/tyk/tyk.conf"
value, _ := arguments["--conf"]
if value != nil {
log.Info(fmt.Sprintf("Using %s for configuration", value.(string)))
filename = arguments["--conf"].(string)
} else {
log.Info("No configuration file defined, will try to use default (./tyk.conf)")
}
loadConfig(filename, &config)
if config.Storage.Type != "redis" {
log.Fatal("Redis connection details not set, please ensure that the storage type is set to Redis and that the connection parameters are correct.")
}
setupGlobals()
port, _ := arguments["--port"]
if port != nil {
portNum, err := strconv.Atoi(port.(string))
if err != nil {
log.Error("Port specified in flags must be a number!")
log.Error(err)
} else {
config.ListenPort = portNum
}
}
doMemoryProfile, _ = arguments["--memprofile"].(bool)
doDebug, _ := arguments["--debug"]
log.Level = logrus.Info
if doDebug == true {
log.Level = logrus.Debug
log.Debug("Enabling debug-level output")
}
}
func main() {
displayConfig()
if doMemoryProfile {
log.Info("Memory profiling active")
profileFile, _ = os.Create("tyk.mprof")
defer profileFile.Close()
}
targetPort := fmt.Sprintf(":%d", config.ListenPort)
loadAPIEndpoints(http.DefaultServeMux)
// Handle reload when SIGUSR2 is received
l, err := goagain.Listener()
if nil != err {
// Listen on a TCP or a UNIX domain socket (TCP here).
l, err = net.Listen("tcp", targetPort)
if nil != err {
log.Fatalln(err)
}
log.Println("Listening on", l.Addr())
// Accept connections in a new goroutine.
specs := getAPISpecs()
loadApps(specs, http.DefaultServeMux)
go http.Serve(l, nil)
} else {
// Resume accepting connections in a new goroutine.
log.Println("Resuming listening on", l.Addr())
specs := getAPISpecs()
loadApps(specs, http.DefaultServeMux)
go http.Serve(l, nil)
// Kill the parent, now that the child has started successfully.
if err := goagain.Kill(); nil != err {
log.Fatalln(err)
}
}
// Block the main goroutine awaiting signals.
if _, err := goagain.Wait(l); nil != err {
log.Fatalln(err)
}
// Do whatever's necessary to ensure a graceful exit like waiting for
// goroutines to terminate or a channel to become closed.
//
// In this case, we'll simply stop listening and wait one second.
if err := l.Close(); nil != err {
log.Fatalln(err)
}
time.Sleep(1e9)
}