github.com/anuvu/tyk@v2.9.0-beta9-dl-apic+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 }