github.com/Tyktechnologies/tyk@v2.9.5+incompatible/cli/bundler/bundler.go (about)

     1  package bundler
     2  
     3  import (
     4  	"archive/zip"
     5  	"bufio"
     6  	"bytes"
     7  	"crypto/md5"
     8  	"encoding/base64"
     9  	"encoding/json"
    10  	"errors"
    11  	"fmt"
    12  	"io"
    13  	"io/ioutil"
    14  	"os"
    15  
    16  	"github.com/TykTechnologies/goverify"
    17  	"github.com/TykTechnologies/tyk/apidef"
    18  	logger "github.com/TykTechnologies/tyk/log"
    19  
    20  	kingpin "gopkg.in/alecthomas/kingpin.v2"
    21  )
    22  
    23  const (
    24  	cmdName = "bundle"
    25  	cmdDesc = "Manage plugin bundles"
    26  
    27  	defaultManifestPath = "manifest.json"
    28  	defaultBundlePath   = "bundle.zip"
    29  	defaultBundlePerm   = 0755
    30  )
    31  
    32  var (
    33  	bundler *Bundler
    34  
    35  	errNoHooks      = errors.New("No hooks defined")
    36  	errNoDriver     = errors.New("No driver specified")
    37  	errManifestLoad = errors.New("Couldn't load manifest file")
    38  	errBundleData   = errors.New("Couldn't read/write bundle data")
    39  	errBundleSign   = errors.New("Couldn't sign bundle")
    40  
    41  	log = logger.Get().WithField("prefix", "tyk")
    42  )
    43  
    44  // Bundler wraps the bundler data structure.
    45  type Bundler struct {
    46  	keyPath      *string
    47  	bundlePath   *string
    48  	skipSigning  *bool
    49  	manifestPath *string
    50  }
    51  
    52  func init() {
    53  	bundler = &Bundler{}
    54  }
    55  
    56  // Bundle is the entrypoint function for this subcommand.
    57  func (b *Bundler) Bundle(ctx *kingpin.ParseContext) error {
    58  	return nil
    59  }
    60  
    61  // Build builds a bundle.
    62  func (b *Bundler) Build(ctx *kingpin.ParseContext) error {
    63  	manifestPath := *b.manifestPath
    64  	bundlePath := *b.bundlePath
    65  	skipSigning := *b.skipSigning
    66  	key := *b.keyPath
    67  
    68  	log.Infof("Building bundle using '%s'", manifestPath)
    69  	manifest, err := b.loadManifest(manifestPath)
    70  	if err != nil {
    71  		return err
    72  	}
    73  	if bundlePath == defaultBundlePath {
    74  		log.Warningf("Using default bundle path '%s'", defaultBundlePath)
    75  	}
    76  
    77  	// Write the file:
    78  	bundleBuf := new(bytes.Buffer)
    79  	for _, file := range manifest.FileList {
    80  		var data []byte
    81  		data, err = ioutil.ReadFile(file)
    82  		if err != nil {
    83  			break
    84  		}
    85  		bundleBuf.Write(data)
    86  	}
    87  	if err != nil {
    88  		return err
    89  	}
    90  
    91  	// Compute the checksum and append it to the manifest data structure:
    92  	manifest.Checksum = fmt.Sprintf("%x", md5.Sum(bundleBuf.Bytes()))
    93  
    94  	if key == "" {
    95  		if skipSigning {
    96  			log.Warning("The bundle will be unsigned")
    97  		} else {
    98  			log.Warning("The bundle will be unsigned, type \"y\" or \"yes\" to confirm:")
    99  			reader := bufio.NewReader(os.Stdin)
   100  			text, _ := reader.ReadString('\n')
   101  			ch := text[0:1]
   102  			if ch != "y" {
   103  				log.Fatal("Aborting")
   104  				os.Exit(1)
   105  			}
   106  		}
   107  	} else {
   108  		err = b.sign(key, manifest, bundleBuf)
   109  		if err != nil {
   110  			return err
   111  		}
   112  	}
   113  
   114  	manifestData, err := json.Marshal(&manifest)
   115  	if err != nil {
   116  		return err
   117  	}
   118  
   119  	// Write the ZIP contents into a buffer:
   120  	buf := new(bytes.Buffer)
   121  	zipWriter := zip.NewWriter(buf)
   122  	for _, file := range manifest.FileList {
   123  		var outputFile io.Writer
   124  		outputFile, err = zipWriter.Create(file)
   125  		if err != nil {
   126  			break
   127  		}
   128  		var data []byte
   129  		data, err = ioutil.ReadFile(file)
   130  		if err != nil {
   131  			break
   132  		}
   133  		if _, err = outputFile.Write(data); err != nil {
   134  			break
   135  		}
   136  	}
   137  	if err != nil {
   138  		return err
   139  	}
   140  
   141  	// Append the updated manifest file to the ZIP file:
   142  	newManifest, err := zipWriter.Create(defaultManifestPath)
   143  	_, err = newManifest.Write(manifestData)
   144  	zipWriter.Close()
   145  	err = ioutil.WriteFile(bundlePath, buf.Bytes(), defaultBundlePerm)
   146  	if err != nil {
   147  		return err
   148  	}
   149  	log.Infof("Wrote '%s' (%d bytes)", bundlePath, buf.Len())
   150  	return nil
   151  }
   152  
   153  func (b *Bundler) sign(key string, manifest *apidef.BundleManifest, bundle *bytes.Buffer) (err error) {
   154  	signer, err := goverify.LoadPrivateKeyFromFile(key)
   155  	if err != nil {
   156  		return err
   157  	}
   158  	signed, err := signer.Sign(bundle.Bytes())
   159  	if err != nil {
   160  		return err
   161  	}
   162  	manifest.Signature = base64.StdEncoding.EncodeToString(signed)
   163  	log.Infof("Signing bundle with key '%s'", key)
   164  	return nil
   165  }
   166  
   167  func (b *Bundler) validateManifest(manifest *apidef.BundleManifest) (err error) {
   168  	for _, f := range manifest.FileList {
   169  		if _, err := os.Stat(f); err != nil {
   170  			err = errors.New("Referencing a nonexistent file: " + f)
   171  			return err
   172  		}
   173  	}
   174  
   175  	// The custom middleware block must specify at least one hook:
   176  	definedHooks := len(manifest.CustomMiddleware.Pre) + len(manifest.CustomMiddleware.Post) + len(manifest.CustomMiddleware.PostKeyAuth)
   177  
   178  	// We should count the auth check middleware (single), if it's present:
   179  	if manifest.CustomMiddleware.AuthCheck.Name != "" {
   180  		definedHooks++
   181  	}
   182  
   183  	if definedHooks == 0 {
   184  		return errNoHooks
   185  	}
   186  
   187  	// The custom middleware block must specify a driver:
   188  	if manifest.CustomMiddleware.Driver == "" {
   189  		return errNoDriver
   190  	}
   191  
   192  	return nil
   193  }
   194  
   195  func (b *Bundler) loadManifest(path string) (manifest *apidef.BundleManifest, err error) {
   196  	rawManifest, err := ioutil.ReadFile(path)
   197  	if err != nil {
   198  		return manifest, errManifestLoad
   199  	}
   200  	err = json.Unmarshal(rawManifest, &manifest)
   201  	if err != nil {
   202  		return manifest, err
   203  	}
   204  	err = b.validateManifest(manifest)
   205  	if err != nil {
   206  		return manifest, err
   207  	}
   208  	return manifest, err
   209  }
   210  
   211  // AddTo initializes an importer object.
   212  func AddTo(app *kingpin.Application) {
   213  	cmd := app.Command(cmdName, cmdDesc)
   214  
   215  	buildCmd := cmd.Command("build", "Build a new plugin bundle using a manifest file and its specified files")
   216  	bundler.keyPath = buildCmd.Flag("key", "Key for bundle signature").Short('k').String()
   217  	bundler.bundlePath = buildCmd.Flag("output", "Output file").Short('o').Default(defaultBundlePath).String()
   218  	bundler.skipSigning = buildCmd.Flag("skip-signing", "Skip bundle signing").Short('y').Bool()
   219  	bundler.manifestPath = buildCmd.Flag("manifest", "Path to manifest file").Default(defaultManifestPath).Short('m').String()
   220  	buildCmd.Action(bundler.Build)
   221  }