github.com/Tyktechnologies/tyk@v2.9.5+incompatible/gateway/coprocess_bundle.go (about) 1 package gateway 2 3 import ( 4 "path" 5 6 "github.com/sirupsen/logrus" 7 8 "github.com/TykTechnologies/goverify" 9 "github.com/TykTechnologies/tyk/apidef" 10 "github.com/TykTechnologies/tyk/config" 11 12 "archive/zip" 13 "bytes" 14 "crypto/md5" 15 "crypto/tls" 16 "encoding/base64" 17 "encoding/json" 18 "errors" 19 "fmt" 20 "io" 21 "io/ioutil" 22 "net/http" 23 "net/url" 24 "os" 25 "path/filepath" 26 ) 27 28 // Bundle is the basic bundle data structure, it holds the bundle name and the data. 29 type Bundle struct { 30 Name string 31 Data []byte 32 Path string 33 Spec *APISpec 34 Manifest apidef.BundleManifest 35 } 36 37 // Verify performs a signature verification on the bundle file. 38 func (b *Bundle) Verify() error { 39 log.WithFields(logrus.Fields{ 40 "prefix": "main", 41 }).Info("----> Verifying bundle: ", b.Spec.CustomMiddlewareBundle) 42 43 var useSignature bool 44 var bundleVerifier goverify.Verifier 45 46 // Perform signature verification if a public key path is set: 47 if config.Global().PublicKeyPath != "" { 48 if b.Manifest.Signature == "" { 49 // Error: A public key is set, but the bundle isn't signed. 50 return errors.New("Bundle isn't signed") 51 } 52 if notificationVerifier == nil { 53 var err error 54 bundleVerifier, err = goverify.LoadPublicKeyFromFile(config.Global().PublicKeyPath) 55 if err != nil { 56 return err 57 } 58 } 59 60 useSignature = true 61 } 62 63 var bundleData bytes.Buffer 64 65 for _, f := range b.Manifest.FileList { 66 extractedPath := filepath.Join(b.Path, f) 67 68 f, err := os.Open(extractedPath) 69 if err != nil { 70 return err 71 } 72 _, err = io.Copy(&bundleData, f) 73 f.Close() 74 if err != nil { 75 return err 76 } 77 } 78 79 checksum := fmt.Sprintf("%x", md5.Sum(bundleData.Bytes())) 80 81 if checksum != b.Manifest.Checksum { 82 return errors.New("Invalid checksum") 83 } 84 85 if useSignature { 86 signed, err := base64.StdEncoding.DecodeString(b.Manifest.Signature) 87 if err != nil { 88 return err 89 } 90 if err := bundleVerifier.Verify(bundleData.Bytes(), signed); err != nil { 91 return err 92 } 93 94 } 95 96 return nil 97 } 98 99 // AddToSpec attaches the custom middleware settings to an API definition. 100 func (b *Bundle) AddToSpec() { 101 b.Spec.CustomMiddleware = b.Manifest.CustomMiddleware 102 103 // Load Python interpreter if the 104 if loadedDrivers[b.Spec.CustomMiddleware.Driver] == nil && b.Spec.CustomMiddleware.Driver == apidef.PythonDriver { 105 var err error 106 loadedDrivers[apidef.PythonDriver], err = NewPythonDispatcher() 107 if err != nil { 108 log.WithFields(logrus.Fields{ 109 "prefix": "coprocess", 110 }).WithError(err).Error("Couldn't load Python dispatcher") 111 return 112 } 113 log.WithFields(logrus.Fields{ 114 "prefix": "coprocess", 115 }).Info("Python dispatcher was initialized") 116 } 117 dispatcher := loadedDrivers[b.Spec.CustomMiddleware.Driver] 118 if dispatcher != nil { 119 dispatcher.HandleMiddlewareCache(&b.Manifest, b.Path) 120 } 121 } 122 123 // BundleGetter is used for downloading bundle data, see HttpBundleGetter for reference. 124 type BundleGetter interface { 125 Get() ([]byte, error) 126 } 127 128 // HTTPBundleGetter is a simple HTTP BundleGetter. 129 type HTTPBundleGetter struct { 130 URL string 131 InsecureSkipVerify bool 132 } 133 134 // MockBundleGetter is a BundleGetter for testing. 135 type MockBundleGetter struct { 136 URL string 137 InsecureSkipVerify bool 138 } 139 140 // Get performs an HTTP GET request. 141 func (g *HTTPBundleGetter) Get() ([]byte, error) { 142 tr := &(*http.DefaultTransport.(*http.Transport)) 143 tr.TLSClientConfig = &tls.Config{InsecureSkipVerify: g.InsecureSkipVerify} 144 client := &http.Client{Transport: tr} 145 146 resp, err := client.Get(g.URL) 147 if err != nil { 148 return nil, err 149 } 150 151 if resp.StatusCode != 200 { 152 httpError := fmt.Sprintf("HTTP Error, got status code %d", resp.StatusCode) 153 return nil, errors.New(httpError) 154 } 155 156 defer resp.Body.Close() 157 return ioutil.ReadAll(resp.Body) 158 } 159 160 // Get mocks an HTTP(S) GET request. 161 func (g *MockBundleGetter) Get() ([]byte, error) { 162 if g.InsecureSkipVerify { 163 return []byte("bundle-insecure"), nil 164 } 165 return []byte("bundle"), nil 166 } 167 168 // BundleSaver is an interface used by bundle saver structures. 169 type BundleSaver interface { 170 Save(*Bundle, string, *APISpec) error 171 } 172 173 // ZipBundleSaver is a BundleSaver for ZIP files. 174 type ZipBundleSaver struct{} 175 176 // Save implements the main method of the BundleSaver interface. It makes use of archive/zip. 177 func (ZipBundleSaver) Save(bundle *Bundle, bundlePath string, spec *APISpec) error { 178 buf := bytes.NewReader(bundle.Data) 179 reader, err := zip.NewReader(buf, int64(len(bundle.Data))) 180 if err != nil { 181 return err 182 } 183 184 for _, f := range reader.File { 185 destPath := filepath.Join(bundlePath, f.Name) 186 187 if f.FileHeader.Mode().IsDir() { 188 if err := os.Mkdir(destPath, 0700); err != nil { 189 return err 190 } 191 continue 192 } 193 rc, err := f.Open() 194 if err != nil { 195 return err 196 } 197 newFile, err := os.Create(destPath) 198 if err != nil { 199 return err 200 } 201 if _, err = io.Copy(newFile, rc); err != nil { 202 return err 203 } 204 rc.Close() 205 if err := newFile.Close(); err != nil { 206 return err 207 } 208 } 209 return nil 210 } 211 212 // fetchBundle will fetch a given bundle, using the right BundleGetter. The first argument is the bundle name, the base bundle URL will be used as prefix. 213 func fetchBundle(spec *APISpec) (bundle Bundle, err error) { 214 215 if !config.Global().EnableBundleDownloader { 216 log.WithFields(logrus.Fields{ 217 "prefix": "main", 218 }).Warning("Bundle downloader is disabled.") 219 err = errors.New("Bundle downloader is disabled") 220 return bundle, err 221 } 222 223 u, err := url.Parse(config.Global().BundleBaseURL) 224 if err != nil { 225 return bundle, err 226 } 227 228 u.Path = path.Join(u.Path, spec.CustomMiddlewareBundle) 229 230 bundleURL := u.String() 231 232 var getter BundleGetter 233 234 switch u.Scheme { 235 case "http": 236 getter = &HTTPBundleGetter{ 237 URL: bundleURL, 238 InsecureSkipVerify: false, 239 } 240 case "https": 241 getter = &HTTPBundleGetter{ 242 URL: bundleURL, 243 InsecureSkipVerify: config.Global().BundleInsecureSkipVerify, 244 } 245 case "mock": 246 getter = &MockBundleGetter{ 247 URL: bundleURL, 248 InsecureSkipVerify: config.Global().BundleInsecureSkipVerify, 249 } 250 default: 251 err = errors.New("Unknown URL scheme") 252 } 253 if err != nil { 254 return bundle, err 255 } 256 257 bundleData, err := getter.Get() 258 259 bundle.Name = spec.CustomMiddlewareBundle 260 bundle.Data = bundleData 261 bundle.Spec = spec 262 return bundle, err 263 } 264 265 // saveBundle will save a bundle to the disk, see ZipBundleSaver methods for reference. 266 func saveBundle(bundle *Bundle, destPath string, spec *APISpec) error { 267 bundleFormat := "zip" 268 269 var bundleSaver BundleSaver 270 271 // TODO: use enums? 272 switch bundleFormat { 273 case "zip": 274 bundleSaver = ZipBundleSaver{} 275 } 276 bundleSaver.Save(bundle, destPath, spec) 277 278 return nil 279 } 280 281 // loadBundleManifest will parse the manifest file and return the bundle parameters. 282 func loadBundleManifest(bundle *Bundle, spec *APISpec, skipVerification bool) error { 283 log.WithFields(logrus.Fields{ 284 "prefix": "main", 285 }).Info("----> Loading bundle: ", spec.CustomMiddlewareBundle) 286 287 manifestPath := filepath.Join(bundle.Path, "manifest.json") 288 f, err := os.Open(manifestPath) 289 if err != nil { 290 return err 291 } 292 defer f.Close() 293 294 if err := json.NewDecoder(f).Decode(&bundle.Manifest); err != nil { 295 log.WithFields(logrus.Fields{ 296 "prefix": "main", 297 }).Info("----> Couldn't unmarshal the manifest file for bundle: ", spec.CustomMiddlewareBundle) 298 return err 299 } 300 301 if skipVerification { 302 return nil 303 } 304 305 if err := bundle.Verify(); err != nil { 306 log.WithFields(logrus.Fields{ 307 "prefix": "main", 308 }).Info("----> Bundle verification failed: ", spec.CustomMiddlewareBundle) 309 } 310 return nil 311 } 312 313 func getBundleDestPath(spec *APISpec) string { 314 tykBundlePath := filepath.Join(config.Global().MiddlewarePath, "bundles") 315 bundleNameHash := md5.New() 316 io.WriteString(bundleNameHash, spec.CustomMiddlewareBundle) 317 bundlePath := fmt.Sprintf("%s_%x", spec.APIID, bundleNameHash.Sum(nil)) 318 return filepath.Join(tykBundlePath, bundlePath) 319 } 320 321 // loadBundle wraps the load and save steps, it will return if an error occurs at any point. 322 func loadBundle(spec *APISpec) error { 323 // Skip if no custom middleware bundle name is set. 324 if spec.CustomMiddlewareBundle == "" { 325 return nil 326 } 327 328 // Skip if no bundle base URL is set. 329 if config.Global().BundleBaseURL == "" { 330 return bundleError(spec, nil, "No bundle base URL set, skipping bundle") 331 } 332 333 // get bundle destination on disk 334 destPath := getBundleDestPath(spec) 335 336 // Skip if the bundle destination path already exists. 337 // The bundle exists, load and return: 338 if _, err := os.Stat(destPath); err == nil { 339 log.WithFields(logrus.Fields{ 340 "prefix": "main", 341 }).Info("Loading existing bundle: ", spec.CustomMiddlewareBundle) 342 343 bundle := Bundle{ 344 Name: spec.CustomMiddlewareBundle, 345 Path: destPath, 346 Spec: spec, 347 } 348 349 err = loadBundleManifest(&bundle, spec, true) 350 if err != nil { 351 log.WithFields(logrus.Fields{ 352 "prefix": "main", 353 }).Info("----> Couldn't load bundle: ", spec.CustomMiddlewareBundle, " ", err) 354 } 355 356 log.WithFields(logrus.Fields{ 357 "prefix": "main", 358 }).Info("----> Using bundle: ", spec.CustomMiddlewareBundle) 359 360 bundle.AddToSpec() 361 362 return nil 363 } 364 365 log.WithFields(logrus.Fields{ 366 "prefix": "main", 367 }).Info("----> Fetching Bundle: ", spec.CustomMiddlewareBundle) 368 369 bundle, err := fetchBundle(spec) 370 if err != nil { 371 return bundleError(spec, err, "Couldn't fetch bundle") 372 } 373 374 if err := os.MkdirAll(destPath, 0700); err != nil { 375 return bundleError(spec, err, "Couldn't create bundle directory") 376 } 377 378 if err := saveBundle(&bundle, destPath, spec); err != nil { 379 return bundleError(spec, err, "Couldn't save bundle") 380 } 381 382 log.WithFields(logrus.Fields{ 383 "prefix": "main", 384 }).Debug("----> Saving Bundle: ", spec.CustomMiddlewareBundle) 385 386 // Set the destination path: 387 bundle.Path = destPath 388 389 if err := loadBundleManifest(&bundle, spec, false); err != nil { 390 bundleError(spec, err, "Couldn't load bundle") 391 392 if err := os.RemoveAll(bundle.Path); err != nil { 393 bundleError(spec, err, "Couldn't remove bundle") 394 } 395 return nil 396 } 397 398 log.WithFields(logrus.Fields{ 399 "prefix": "main", 400 }).Info("----> Bundle is valid, adding to spec: ", spec.CustomMiddlewareBundle) 401 402 bundle.AddToSpec() 403 404 return nil 405 } 406 407 // bundleError is a log helper. 408 func bundleError(spec *APISpec, err error, message string) error { 409 if err != nil { 410 message = fmt.Sprintf("%s: %s", message, err.Error()) 411 } 412 log.WithFields(logrus.Fields{ 413 "prefix": "main", 414 "user_ip": "-", 415 "server_name": spec.Proxy.TargetURL, 416 "user_id": "-", 417 "org_id": spec.OrgID, 418 "api_id": spec.APIID, 419 "path": "-", 420 }).Error(message) 421 return errors.New(message) 422 }