github.com/dbernstein1/tyk@v2.9.0-beta9-dl-apic+incompatible/gateway/mw_js_plugin.go (about) 1 package gateway 2 3 import ( 4 "bytes" 5 "crypto/tls" 6 "encoding/base64" 7 "encoding/json" 8 "errors" 9 "io/ioutil" 10 "net/http" 11 "net/url" 12 "os" 13 "path/filepath" 14 "reflect" 15 "strings" 16 "time" 17 18 "github.com/robertkrimen/otto" 19 _ "github.com/robertkrimen/otto/underscore" 20 21 "github.com/TykTechnologies/tyk/config" 22 "github.com/TykTechnologies/tyk/user" 23 24 "github.com/sirupsen/logrus" 25 ) 26 27 // Lets the user override and return a response from middleware 28 type ReturnOverrides struct { 29 ResponseCode int 30 ResponseError string 31 ResponseHeaders map[string]string 32 } 33 34 // MiniRequestObject is marshalled to JSON string and passed into JSON middleware 35 type MiniRequestObject struct { 36 Headers map[string][]string 37 SetHeaders map[string]string 38 DeleteHeaders []string 39 Body []byte 40 URL string 41 Params map[string][]string 42 AddParams map[string]string 43 ExtendedParams map[string][]string 44 DeleteParams []string 45 ReturnOverrides ReturnOverrides 46 IgnoreBody bool 47 Method string 48 RequestURI string 49 Scheme string 50 } 51 52 type VMReturnObject struct { 53 Request MiniRequestObject 54 SessionMeta map[string]string 55 Session user.SessionState 56 AuthValue string 57 } 58 59 // DynamicMiddleware is a generic middleware that will execute JS code before continuing 60 type DynamicMiddleware struct { 61 BaseMiddleware 62 MiddlewareClassName string 63 Pre bool 64 UseSession bool 65 Auth bool 66 } 67 68 func (d *DynamicMiddleware) Name() string { 69 return "DynamicMiddleware" 70 } 71 72 func specToJson(spec *APISpec) string { 73 m := map[string]interface{}{ 74 "OrgID": spec.OrgID, 75 "APIID": spec.APIID, 76 // For backwards compatibility within 2.x. 77 // TODO: simplify or refactor in 3.x or later. 78 "config_data": spec.ConfigData, 79 } 80 bs, err := json.Marshal(m) 81 if err != nil { 82 log.Error("Failed to encode configuration data: ", err) 83 return "" 84 } 85 return string(bs) 86 } 87 88 // ProcessRequest will run any checks on the request on the way through the system, return an error to have the chain fail 89 func (d *DynamicMiddleware) ProcessRequest(w http.ResponseWriter, r *http.Request, _ interface{}) (error, int) { 90 t1 := time.Now().UnixNano() 91 logger := d.Logger() 92 93 // Create the proxy object 94 originalBody, err := ioutil.ReadAll(r.Body) 95 if err != nil { 96 logger.WithError(err).Error("Failed to read request body") 97 return nil, http.StatusOK 98 } 99 defer r.Body.Close() 100 101 headers := r.Header 102 host := r.Host 103 if host == "" && r.URL != nil { 104 host = r.URL.Host 105 } 106 if host != "" { 107 headers = make(http.Header) 108 for k, v := range r.Header { 109 headers[k] = v 110 } 111 headers.Set("Host", host) 112 } 113 scheme := "http" 114 if r.TLS != nil { 115 scheme = "https" 116 } 117 118 requestData := MiniRequestObject{ 119 Headers: headers, 120 SetHeaders: map[string]string{}, 121 DeleteHeaders: []string{}, 122 Body: originalBody, 123 URL: r.URL.String(), 124 Params: r.URL.Query(), 125 AddParams: map[string]string{}, 126 ExtendedParams: map[string][]string{}, 127 DeleteParams: []string{}, 128 Method: r.Method, 129 RequestURI: r.RequestURI, 130 Scheme: scheme, 131 } 132 133 requestAsJson, err := json.Marshal(requestData) 134 if err != nil { 135 logger.WithError(err).Error("Failed to encode request object for dynamic middleware") 136 return nil, http.StatusOK 137 } 138 139 specAsJson := specToJson(d.Spec) 140 141 session := new(user.SessionState) 142 143 // Encode the session object (if not a pre-process) 144 if !d.Pre && d.UseSession { 145 session = ctxGetSession(r) 146 } 147 148 sessionAsJson, err := json.Marshal(session) 149 if err != nil { 150 logger.WithError(err).Error("Failed to encode session for VM") 151 return nil, http.StatusOK 152 } 153 154 // Run the middleware 155 middlewareClassname := d.MiddlewareClassName 156 vm := d.Spec.JSVM.VM.Copy() 157 vm.Interrupt = make(chan func(), 1) 158 logger.Debug("Running: ", middlewareClassname) 159 // buffered, leaving no chance of a goroutine leak since the 160 // spawned goroutine will send 0 or 1 values. 161 ret := make(chan otto.Value, 1) 162 errRet := make(chan error, 1) 163 go func() { 164 defer func() { 165 // the VM executes the panic func that gets it 166 // to stop, so we must recover here to not crash 167 // the whole Go program. 168 recover() 169 }() 170 returnRaw, err := vm.Run(middlewareClassname + `.DoProcessRequest(` + string(requestAsJson) + `, ` + string(sessionAsJson) + `, ` + specAsJson + `);`) 171 ret <- returnRaw 172 errRet <- err 173 }() 174 var returnRaw otto.Value 175 t := time.NewTimer(d.Spec.JSVM.Timeout) 176 select { 177 case returnRaw = <-ret: 178 if err := <-errRet; err != nil { 179 logger.WithError(err).Error("Failed to run JS middleware") 180 return nil, http.StatusOK 181 } 182 t.Stop() 183 case <-t.C: 184 t.Stop() 185 logger.Error("JS middleware timed out after ", d.Spec.JSVM.Timeout) 186 vm.Interrupt <- func() { 187 // only way to stop the VM is to send it a func 188 // that panics. 189 panic("stop") 190 } 191 return nil, http.StatusOK 192 } 193 returnDataStr, _ := returnRaw.ToString() 194 195 // Decode the return object 196 newRequestData := VMReturnObject{} 197 if err := json.Unmarshal([]byte(returnDataStr), &newRequestData); err != nil { 198 logger.WithError(err).Error("Failed to decode middleware request data on return from VM. Returned data: ", returnDataStr) 199 return nil, http.StatusOK 200 } 201 202 // Reconstruct the request parts 203 if newRequestData.Request.IgnoreBody { 204 r.ContentLength = int64(len(originalBody)) 205 r.Body = ioutil.NopCloser(bytes.NewReader(originalBody)) 206 } else { 207 r.ContentLength = int64(len(newRequestData.Request.Body)) 208 r.Body = ioutil.NopCloser(bytes.NewReader(newRequestData.Request.Body)) 209 } 210 211 r.URL, err = url.ParseRequestURI(newRequestData.Request.URL) 212 if err != nil { 213 return nil, http.StatusOK 214 } 215 216 // Delete and set headers 217 for _, dh := range newRequestData.Request.DeleteHeaders { 218 r.Header.Del(dh) 219 } 220 221 for h, v := range newRequestData.Request.SetHeaders { 222 r.Header.Set(h, v) 223 } 224 225 // Delete and set request parameters 226 values := r.URL.Query() 227 for _, k := range newRequestData.Request.DeleteParams { 228 values.Del(k) 229 } 230 231 for p, v := range newRequestData.Request.AddParams { 232 values.Set(p, v) 233 } 234 235 for p, v := range newRequestData.Request.ExtendedParams { 236 for _, val := range v { 237 values.Add(p, val) 238 } 239 } 240 241 r.URL.RawQuery = values.Encode() 242 243 // Save the session data (if modified) 244 if !d.Pre && d.UseSession { 245 newMeta := mapStrsToIfaces(newRequestData.SessionMeta) 246 if !reflect.DeepEqual(session.MetaData, newMeta) { 247 session.MetaData = newMeta 248 ctxScheduleSessionUpdate(r) 249 } 250 } 251 252 logger.Debug("JSVM middleware execution took: (ns) ", time.Now().UnixNano()-t1) 253 254 if newRequestData.Request.ReturnOverrides.ResponseCode >= http.StatusBadRequest { 255 return errors.New(newRequestData.Request.ReturnOverrides.ResponseError), newRequestData.Request.ReturnOverrides.ResponseCode 256 } 257 258 if newRequestData.Request.ReturnOverrides.ResponseCode != 0 { 259 responseObject := VMResponseObject{ 260 Response: ResponseObject{ 261 Body: newRequestData.Request.ReturnOverrides.ResponseError, 262 Code: newRequestData.Request.ReturnOverrides.ResponseCode, 263 Headers: newRequestData.Request.ReturnOverrides.ResponseHeaders, 264 }, 265 } 266 267 forceResponse(w, r, &responseObject, d.Spec, session, d.Pre, logger) 268 return nil, mwStatusRespond 269 } 270 271 if d.Auth { 272 ctxSetSession(r, &newRequestData.Session, newRequestData.AuthValue, true) 273 } 274 275 return nil, http.StatusOK 276 } 277 278 func mapStrsToIfaces(m map[string]string) map[string]interface{} { 279 // TODO: do we really need this conversion? note that we can't 280 // make user.SessionState.MetaData a map[string]string, however. 281 m2 := make(map[string]interface{}, len(m)) 282 for k, v := range m { 283 m2[k] = v 284 } 285 return m2 286 } 287 288 // --- Utility functions during startup to ensure a sane VM is present for each API Def ---- 289 290 type JSVM struct { 291 Spec *APISpec 292 VM *otto.Otto 293 Timeout time.Duration 294 Log *logrus.Entry // logger used by the JS code 295 RawLog *logrus.Logger // logger used by `rawlog` func to avoid formatting 296 } 297 298 const defaultJSVMTimeout = 5 299 300 // Init creates the JSVM with the core library and sets up a default 301 // timeout. 302 func (j *JSVM) Init(spec *APISpec, logger *logrus.Entry) { 303 vm := otto.New() 304 logger = logger.WithField("prefix", "jsvm") 305 306 // Init TykJS namespace, constructors etc. 307 if _, err := vm.Run(coreJS); err != nil { 308 logger.WithError(err).Error("Could not load TykJS") 309 return 310 } 311 312 // Load user's TykJS on top, if any 313 if path := config.Global().TykJSPath; path != "" { 314 f, err := os.Open(path) 315 if err == nil { 316 _, err = vm.Run(f) 317 f.Close() 318 319 if err != nil { 320 logger.WithError(err).Error("Could not load user's TykJS") 321 } 322 } 323 } 324 325 j.VM = vm 326 j.Spec = spec 327 328 // Add environment API 329 j.LoadTykJSApi() 330 331 if jsvmTimeout := config.Global().JSVMTimeout; jsvmTimeout <= 0 { 332 j.Timeout = time.Duration(defaultJSVMTimeout) * time.Second 333 logger.Debugf("Default JSVM timeout used: %v", j.Timeout) 334 } else { 335 j.Timeout = time.Duration(jsvmTimeout) * time.Second 336 logger.Debugf("Custom JSVM timeout: %v", j.Timeout) 337 } 338 339 j.Log = logger // use the global logger by default 340 j.RawLog = rawLog 341 } 342 343 // LoadJSPaths will load JS classes and functionality in to the VM by file 344 func (j *JSVM) LoadJSPaths(paths []string, prefix string) { 345 for _, mwPath := range paths { 346 if prefix != "" { 347 mwPath = filepath.Join(prefix, mwPath) 348 } 349 j.Log.Info("Loading JS File: ", mwPath) 350 f, err := os.Open(mwPath) 351 if err != nil { 352 j.Log.WithError(err).Error("Failed to open JS middleware file") 353 continue 354 } 355 if _, err := j.VM.Run(f); err != nil { 356 j.Log.WithError(err).Error("Failed to load JS middleware") 357 } 358 f.Close() 359 } 360 } 361 362 type TykJSHttpRequest struct { 363 Method string 364 Body string 365 Headers map[string]string 366 Domain string 367 Resource string 368 FormData map[string]string 369 } 370 371 type TykJSHttpResponse struct { 372 Code int 373 Body string 374 Headers map[string][]string 375 376 // Make this compatible with BatchReplyUnit 377 CodeComp int `json:"code"` 378 BodyComp string `json:"body"` 379 HeadersComp map[string][]string `json:"headers"` 380 } 381 382 func (j *JSVM) LoadTykJSApi() { 383 // Enable a log 384 j.VM.Set("log", func(call otto.FunctionCall) otto.Value { 385 j.Log.WithFields(logrus.Fields{ 386 "type": "log-msg", 387 }).Info(call.Argument(0).String()) 388 return otto.Value{} 389 }) 390 j.VM.Set("rawlog", func(call otto.FunctionCall) otto.Value { 391 j.RawLog.Print(call.Argument(0).String() + "\n") 392 return otto.Value{} 393 }) 394 395 // these two needed for non-utf8 bodies 396 j.VM.Set("b64dec", func(call otto.FunctionCall) otto.Value { 397 in := call.Argument(0).String() 398 out, err := base64.StdEncoding.DecodeString(in) 399 400 // Fallback to RawStdEncoding: 401 if err != nil { 402 out, err = base64.RawStdEncoding.DecodeString(in) 403 if err != nil { 404 j.Log.WithError(err).Error("Failed to base64 decode") 405 return otto.Value{} 406 } 407 } 408 returnVal, err := j.VM.ToValue(string(out)) 409 if err != nil { 410 j.Log.WithError(err).Error("Failed to base64 decode") 411 return otto.Value{} 412 } 413 return returnVal 414 }) 415 j.VM.Set("b64enc", func(call otto.FunctionCall) otto.Value { 416 in := []byte(call.Argument(0).String()) 417 out := base64.StdEncoding.EncodeToString(in) 418 returnVal, err := j.VM.ToValue(out) 419 if err != nil { 420 j.Log.WithError(err).Error("Failed to base64 encode") 421 return otto.Value{} 422 } 423 return returnVal 424 }) 425 426 j.VM.Set("rawb64dec", func(call otto.FunctionCall) otto.Value { 427 in := call.Argument(0).String() 428 out, err := base64.RawStdEncoding.DecodeString(in) 429 if err != nil { 430 if err != nil { 431 j.Log.WithError(err).Error("Failed to base64 decode") 432 return otto.Value{} 433 } 434 } 435 returnVal, err := j.VM.ToValue(string(out)) 436 if err != nil { 437 j.Log.WithError(err).Error("Failed to base64 decode") 438 return otto.Value{} 439 } 440 return returnVal 441 }) 442 j.VM.Set("rawb64enc", func(call otto.FunctionCall) otto.Value { 443 in := []byte(call.Argument(0).String()) 444 out := base64.RawStdEncoding.EncodeToString(in) 445 returnVal, err := j.VM.ToValue(out) 446 if err != nil { 447 j.Log.WithError(err).Error("Failed to base64 encode") 448 return otto.Value{} 449 } 450 return returnVal 451 }) 452 453 // Enable the creation of HTTP Requsts 454 j.VM.Set("TykMakeHttpRequest", func(call otto.FunctionCall) otto.Value { 455 jsonHRO := call.Argument(0).String() 456 if jsonHRO == "undefined" { 457 // Nope, return nothing 458 return otto.Value{} 459 } 460 hro := TykJSHttpRequest{} 461 if err := json.Unmarshal([]byte(jsonHRO), &hro); err != nil { 462 j.Log.WithError(err).Error("JSVM: Failed to deserialise HTTP Request object") 463 return otto.Value{} 464 } 465 466 // Make the request 467 domain := hro.Domain 468 data := url.Values{} 469 for k, v := range hro.FormData { 470 data.Set(k, v) 471 } 472 473 u, _ := url.ParseRequestURI(domain + hro.Resource) 474 urlStr := u.String() // "https://api.com/user/" 475 476 var d string 477 if hro.Body != "" { 478 d = hro.Body 479 } else if len(hro.FormData) > 0 { 480 d = data.Encode() 481 } 482 483 r, _ := http.NewRequest(hro.Method, urlStr, nil) 484 485 if d != "" { 486 r, _ = http.NewRequest(hro.Method, urlStr, strings.NewReader(d)) 487 } 488 489 for k, v := range hro.Headers { 490 r.Header.Set(k, v) 491 } 492 r.Close = true 493 494 tr := &http.Transport{TLSClientConfig: &tls.Config{}} 495 if cert := getUpstreamCertificate(r.Host, j.Spec); cert != nil { 496 tr.TLSClientConfig.Certificates = []tls.Certificate{*cert} 497 } 498 499 if config.Global().ProxySSLInsecureSkipVerify { 500 tr.TLSClientConfig.InsecureSkipVerify = true 501 } 502 503 if j.Spec.Proxy.Transport.SSLInsecureSkipVerify { 504 tr.TLSClientConfig.InsecureSkipVerify = true 505 } 506 507 tr.DialTLS = dialTLSPinnedCheck(j.Spec, tr.TLSClientConfig) 508 509 tr.Proxy = proxyFromAPI(j.Spec) 510 511 // using new Client each time should be ok, since we closing connection every time 512 client := &http.Client{Transport: tr} 513 resp, err := client.Do(r) 514 if err != nil { 515 j.Log.WithError(err).Error("Request failed") 516 return otto.Value{} 517 } 518 519 body, _ := ioutil.ReadAll(resp.Body) 520 bodyStr := string(body) 521 tykResp := TykJSHttpResponse{ 522 Code: resp.StatusCode, 523 Body: bodyStr, 524 Headers: resp.Header, 525 CodeComp: resp.StatusCode, 526 BodyComp: bodyStr, 527 HeadersComp: resp.Header, 528 } 529 530 retAsStr, _ := json.Marshal(tykResp) 531 returnVal, err := j.VM.ToValue(string(retAsStr)) 532 if err != nil { 533 j.Log.WithError(err).Error("Failed to encode return value") 534 return otto.Value{} 535 } 536 537 return returnVal 538 }) 539 540 // Expose Setters and Getters in the REST API for a key: 541 j.VM.Set("TykGetKeyData", func(call otto.FunctionCall) otto.Value { 542 apiKey := call.Argument(0).String() 543 apiId := call.Argument(1).String() 544 545 obj, _ := handleGetDetail(apiKey, apiId, false) 546 bs, _ := json.Marshal(obj) 547 548 returnVal, err := j.VM.ToValue(string(bs)) 549 if err != nil { 550 j.Log.WithError(err).Error("Failed to encode return value") 551 return otto.Value{} 552 } 553 554 return returnVal 555 }) 556 557 j.VM.Set("TykSetKeyData", func(call otto.FunctionCall) otto.Value { 558 apiKey := call.Argument(0).String() 559 encoddedSession := call.Argument(1).String() 560 suppressReset := call.Argument(2).String() 561 562 newSession := user.SessionState{} 563 err := json.Unmarshal([]byte(encoddedSession), &newSession) 564 if err != nil { 565 j.Log.WithError(err).Error("Failed to decode the sesison data") 566 return otto.Value{} 567 } 568 569 doAddOrUpdate(apiKey, &newSession, suppressReset == "1", false) 570 571 return otto.Value{} 572 }) 573 574 // Batch request method 575 unsafeBatchHandler := BatchRequestHandler{} 576 j.VM.Set("TykBatchRequest", func(call otto.FunctionCall) otto.Value { 577 requestSet := call.Argument(0).String() 578 j.Log.Debug("Batch input is: ", requestSet) 579 580 bs, err := unsafeBatchHandler.ManualBatchRequest([]byte(requestSet)) 581 if err != nil { 582 j.Log.WithError(err).Error("Batch request error") 583 return otto.Value{} 584 } 585 586 returnVal, err := j.VM.ToValue(string(bs)) 587 if err != nil { 588 j.Log.WithError(err).Error("Failed to encode return value") 589 return otto.Value{} 590 } 591 592 return returnVal 593 }) 594 595 j.VM.Run(`function TykJsResponse(response, session_meta) { 596 return JSON.stringify({Response: response, SessionMeta: session_meta}) 597 }`) 598 } 599 600 const coreJS = ` 601 var TykJS = { 602 TykMiddleware: { 603 MiddlewareComponentMeta: function(configuration) { 604 this.configuration = configuration 605 } 606 }, 607 TykEventHandlers: { 608 EventHandlerComponentMeta: function() {} 609 } 610 } 611 612 TykJS.TykMiddleware.MiddlewareComponentMeta.prototype.ProcessRequest = function(request, session, config) { 613 log("Process Request Not Implemented") 614 return request 615 } 616 617 TykJS.TykMiddleware.MiddlewareComponentMeta.prototype.DoProcessRequest = function(request, session, config) { 618 request.Body = b64dec(request.Body) 619 var processed_request = this.ProcessRequest(request, session, config) 620 621 if (!processed_request) { 622 log("Middleware didn't return request object!") 623 return 624 } 625 626 // Reset the headers object 627 processed_request.Request.Headers = {} 628 processed_request.Request.Body = b64enc(processed_request.Request.Body) 629 630 return JSON.stringify(processed_request) 631 } 632 633 // The user-level middleware component 634 TykJS.TykMiddleware.NewMiddleware = function(configuration) { 635 TykJS.TykMiddleware.MiddlewareComponentMeta.call(this, configuration) 636 } 637 638 // Set up object inheritance 639 TykJS.TykMiddleware.NewMiddleware.prototype = Object.create(TykJS.TykMiddleware.MiddlewareComponentMeta.prototype) 640 TykJS.TykMiddleware.NewMiddleware.prototype.constructor = TykJS.TykMiddleware.NewMiddleware 641 642 TykJS.TykMiddleware.NewMiddleware.prototype.NewProcessRequest = function(callback) { 643 this.ProcessRequest = callback 644 } 645 646 TykJS.TykMiddleware.NewMiddleware.prototype.ReturnData = function(request, session) { 647 return {Request: request, SessionMeta: session} 648 } 649 650 TykJS.TykMiddleware.NewMiddleware.prototype.ReturnAuthData = function(request, session) { 651 return {Request: request, Session: session} 652 } 653 654 // Event Handler implementation 655 656 TykJS.TykEventHandlers.EventHandlerComponentMeta.prototype.DoProcessEvent = function(event, context) { 657 // call the handler 658 log("Calling built - in handle") 659 this.Handle(event, context) 660 return 661 } 662 663 TykJS.TykEventHandlers.EventHandlerComponentMeta.prototype.Handle = function(request, context) { 664 log("Handler not implemented!") 665 return request 666 } 667 668 // The user-level event handler component 669 TykJS.TykEventHandlers.NewEventHandler = function() { 670 TykJS.TykEventHandlers.EventHandlerComponentMeta.call(this) 671 } 672 673 // Set up object inheritance for events 674 TykJS.TykEventHandlers.NewEventHandler.prototype = Object.create(TykJS.TykEventHandlers.EventHandlerComponentMeta.prototype) 675 TykJS.TykEventHandlers.NewEventHandler.prototype.constructor = TykJS.TykEventHandlers.NewEventHandler 676 677 TykJS.TykEventHandlers.NewEventHandler.prototype.NewHandler = function(callback) { 678 this.Handle = callback 679 };`