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  }