func realMain() int { var mode string var value string var keystore string var keyname string var rotate bool var fileName string flag.Usage = usage flag.StringVar( &mode, "mode", "encrypt", "mode of operation, either keygen, encrypt, or decrypt; defaults to encrypt") flag.StringVar( &value, "value", "", "value to encrypt/decrypt in lieu of file") flag.StringVar( &keystore, "keystore", "/keys/", "directory in which keys are stored") flag.StringVar( &keyname, "key", "", "name of a key file to use for encryption") flag.BoolVar( &rotate, "rotate", true, "if encrypting, whether to rotate any already-encrypted tags to the new key") flag.Parse() if value == "" { if flag.NArg() != 1 { flag.Usage() return 1 } else { fileName = flag.Args()[0] } } if mode == "encrypt" { if keyname == "" { fmt.Println("A -key must be provided for encryption") return 2 } rawBytes := getBytes(value, fileName) fileContents, err := gosecret.EncryptTags(rawBytes, keyname, keystore, rotate) if err != nil { fmt.Println("encryption failed", err) return 4 } data := string(fileContents) // Create a template, add the function map, and parse the text. // FuncMap maps the goEncrypt (and goDecrypt below) names to functions so that the // template recognizes and knows what to do when it encounters such tags during parsing funcs := template.FuncMap{ // Template functions "goEncrypt": goEncryptFunc(keystore), } tmpl, err := template.New("encryption").Funcs(funcs).Parse(data) if err != nil { fmt.Println("Could not parse template", err) return 99 } // Run the template to verify the output. buff := new(bytes.Buffer) err = tmpl.Execute(buff, nil) if err != nil { fmt.Println("Could not execute template", err) return 98 } fmt.Printf(string(buff.Bytes())) } else if mode == "decrypt" { rawBytes := getBytes(value, fileName) fileContents, err := gosecret.DecryptTags(rawBytes, keystore) if err != nil { fmt.Println("err", err) return 8 } data := string(fileContents) funcs := template.FuncMap{ // Template functions "goDecrypt": goDecryptFunc(keystore), } tmpl, err := template.New("decryption").Funcs(funcs).Parse(data) if err != nil { fmt.Println("Could not parse template", err) return 99 } // Run the template to verify the output. buff := new(bytes.Buffer) err = tmpl.Execute(buff, nil) if err != nil { fmt.Println("Could not execute template", err) return 98 } fmt.Printf(string(buff.Bytes())) } else if mode == "keygen" { key := gosecret.CreateKey() encodedKey := make([]byte, base64.StdEncoding.EncodedLen(len(key))) base64.StdEncoding.Encode(encodedKey, key) ioutil.WriteFile(fileName, encodedKey, 0666) } else { fmt.Println("Unknown mode", mode) return 16 } return 0 }
// Connects to Consul and watches a given K/V prefix and uses that to // write to the filesystem. func watchMappingAndExec(config *WatchConfig, mappingConfig *MappingConfig) (int, error) { client, err := buildConsulClient(config.Consul) if err != nil { return 0, err } // If prefix starts with /, trim it. if mappingConfig.Prefix[0] == '/' { mappingConfig.Prefix = mappingConfig.Prefix[1:] } // If the config path is lacking a trailing separator, add it. if mappingConfig.Path[len(mappingConfig.Path)-1] != os.PathSeparator { mappingConfig.Path += string(os.PathSeparator) } isWindows := os.PathSeparator != '/' // Remove an unhandled trailing quote, which presented itself on Windows when // the given path contained spaces (requiring quotes) and also had a trailing // backslash. if mappingConfig.Path[len(mappingConfig.Path)-1] == 34 { mappingConfig.Path = mappingConfig.Path[:len(mappingConfig.Path)-1] } // Start the watcher goroutine that watches for changes in the // K/V and notifies us on a channel. errCh := make(chan error, 1) pairCh := make(chan consulapi.KVPairs) quitCh := make(chan struct{}) // Defer close of quitCh if we're running more than once if !config.RunOnce { defer close(quitCh) } go watch( client, mappingConfig.Prefix, mappingConfig.Path, config.Consul.Token, pairCh, errCh, quitCh) var env map[string]string for { var pairs consulapi.KVPairs // Wait for new pairs to come on our channel or an error // to occur. select { case pairs = <-pairCh: case err := <-errCh: return 0, err } newEnv := make(map[string]string) for _, pair := range pairs { log.WithFields(log.Fields{ "key": pair.Key, }).Debug("Key present in source") k := strings.TrimPrefix(pair.Key, mappingConfig.Prefix) k = strings.TrimLeft(k, "/") newEnv[k] = string(pair.Value) } // If the variables didn't actually change, // then don't do anything. if reflect.DeepEqual(env, newEnv) { continue } // Iterate over all objects in the current env. If they are not in the newEnv, they // were deleted from Consul and should be deleted from disk. for k := range env { if _, ok := newEnv[k]; !ok { log.WithFields(log.Fields{ "key": k, }).Debug("Key no longer present locally") // Write file to disk keyfile := fmt.Sprintf("%s%s", mappingConfig.Path, k) if isWindows { keyfile = strings.Replace(keyfile, "/", "\\", -1) } err := os.Remove(keyfile) if err != nil { log.WithFields(log.Fields{ "error": err, }).Error("Failed to remove key") } } } // Replace the env so we can detect future changes env = newEnv // Write the updated keys to the filesystem at the specified path for k, v := range newEnv { // Write file to disk keyfile := fmt.Sprintf("%s%s", mappingConfig.Path, k) // if Windows, replace / with windows path delimiter if isWindows { keyfile = strings.Replace(keyfile, "/", "\\", -1) // mkdirp the file's path err := mkdirp.Mk(keyfile[:strings.LastIndex(keyfile, "\\")], 0777) if err != nil { log.WithFields(log.Fields{ "error": err, }).Error("Failed to create parent directory for key") } } else { // mkdirp the file's path err := mkdirp.Mk(keyfile[:strings.LastIndex(keyfile, "/")], 0777) if err != nil { log.WithFields(log.Fields{ "error": err, }).Error("Failed to create parent directory for key") } } f, err := os.Create(keyfile) if err != nil { log.WithFields(log.Fields{ "error": err, "file": keyfile, }).Error("Failed to create file") continue } defer f.Close() log.WithFields(log.Fields{ "length": len(v), }).Debug("Input value length") buff := new(bytes.Buffer) buff.Write([]byte(v)) if len(mappingConfig.Keystore) > 0 { decryptedValue, err := gosecret.DecryptTags([]byte(v), mappingConfig.Keystore) if err != nil { log.WithFields(log.Fields{ "error": err, }).Error("Failed to decrypt value") continue } log.WithFields(log.Fields{ "length": len(decryptedValue), }).Debug("Output value length") data := string(decryptedValue) funcs := template.FuncMap{ // Template functions "goDecrypt": goDecryptFunc(mappingConfig.Keystore), } tmpl, err := template.New("decryption").Funcs(funcs).Parse(data) if err != nil { log.WithFields(log.Fields{ "error": err, }).Error("Could not parse template") continue } // Run the template to verify the output. buff = new(bytes.Buffer) err = tmpl.Execute(buff, nil) if err != nil { log.WithFields(log.Fields{ "error": err, }).Error("Could not execute template") continue } } wrote, err := f.Write(buff.Bytes()) if err != nil { log.WithFields(log.Fields{ "error": err, "file": keyfile, }).Error("Failed to write to file") continue } log.WithFields(log.Fields{ "length": wrote, "file": keyfile, }).Debug("Successfully wrote value to file") err = f.Sync() if err != nil { log.WithFields(log.Fields{ "error": err, "file": keyfile, }).Error("Failed to sync file") } err = f.Close() if err != nil { log.WithFields(log.Fields{ "error": err, "file": keyfile, }).Error("Failed to close file") } } // Configuration changed, run our onchange command, if one was specified. if mappingConfig.OnChange != nil { var cmd = exec.Command(mappingConfig.OnChange[0], mappingConfig.OnChange[1:]...) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr // Always wait for the forked process to exit. We may wish to revisit this, but I think // it's the safest approach since it avoids a case where rapid key updates DOS a system // by slurping all proc handles. err = cmd.Run() if err != nil { return 111, err } } // If we are only running once, close the channel on this watcher. if config.RunOnce { close(quitCh) return 0, nil } } }