├── .gitignore ├── LICENSE ├── README.md ├── jaeger └── jaeger.go ├── jaegerdb └── jaegerdb.go └── jaegerh └── jaegerh.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | 24 | # Random stuff 25 | \#* 26 | \._* 27 | *~ 28 | *.orig 29 | .DS_Store 30 | *.swp 31 | 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Julian Yap 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Jaeger 2 | 3 | Jaeger is a JSON encoded GPG encrypted key value store. It is useful for generating and keeping configuration files secure. Jaeger is written in Go. 4 | 5 | PaaS providers assume configuration settings should be stored as environment variables. There is nothing inherently wrong with this but it does assume your code base and application is set up to support reading settings from environment variables. 6 | 7 | Jaeger simplifies this by providing a system for storing, managing and generating configuration files that applications commonly use. 8 | 9 | A basic set of files consists of the following. 10 | 11 | * Template file. A file in [Go text template format](http://golang.org/pkg/text/template/) which holds the structure of the generated file. 12 | * Jaeger assumes the template file name is in the form `filename.txt.jgrt` where `filename.txt` is the file name of the generated file. 13 | * JSON encoded GPG encrypted key value store file. This file is managed using the `jaegerdb` program. 14 | * Jaeger assumes the template file name is in the form `filename.txt.jgrdb` where `filename.txt` is the file name of the generated file. 15 | 16 | The best way to experience Jaeger is to run through the Quickstart below. 17 | 18 | Jaeger uses the Go standard library except for the `golang.org/x/crypto/openpgp` package. 19 | 20 | > Stacker Pentecost: Haven't you heard Mr. Beckett? The world is coming to an end. So where would you rather die? Here? Or in a Jaeger! 21 | > 22 | > -- Pacific Rim 23 | 24 | 25 | ## Quickstart 26 | 27 | ### Installation 28 | 29 | Install Jaeger: 30 | 31 | go get github.com/jyap808/jaeger/jaeger 32 | go get github.com/jyap808/jaeger/jaegerdb 33 | 34 | This will create the binaries `jaeger` and `jaegerdb` in your `$GOPATH/bin` directory. 35 | 36 | ### Make standalone public and private GPG keys 37 | 38 | Jaeger can also read public and private keys in ASCII armored format via command line flags. Here we are creating standalone GPG keys that Jaeger will use by default. 39 | 40 | gpg --keyring ~/.gnupg/jaeger_pubring.gpg --secret-keyring ~/.gnupg/jaeger_secring.gpg --gen-key --no-default-keyring 41 | 42 | Select: 43 | 44 | (1) RSA and RSA (default) 45 | 46 | Select: 47 | 48 | What keysize do you want? 2048 49 | 50 | Select: 51 | 52 | Key is valid for? 0 53 | 54 | Enter: 55 | 56 | Real name: Jaeger Test 57 | Email address: jaeger@example.com 58 | Comment: 59 | 60 | Select: 61 | 62 | (O)kay 63 | 64 | Set a passphrase for the private key. eg. 'test passphrase' 65 | 66 | 67 | ### Verify your keys were created OK 68 | 69 | Secret key: 70 | 71 | gpg --no-default-keyring --list-secret-keys --secret-keyring ~/.gnupg/jaeger_secring.gpg 72 | 73 | Public key: 74 | 75 | gpg --no-default-keyring --list-keys --keyring ~/.gnupg/jaeger_pubring.gpg 76 | 77 | ### Create a test Template file 78 | 79 | Create `test.txt.jgrt` with contents: 80 | 81 | datebase.username = dbuser 82 | database.password = {{.DatabasePassword}} 83 | field2.user = user2 84 | field2.password = {{.Field2}} 85 | 86 | ### Create an empty JSON GPG database 87 | 88 | Jaeger can also read public and private keys in ASCII armored format via command line flags. 89 | 90 | jaegerdb -init -j test.txt.jgrdb 91 | 92 | ### Add some properties and values 93 | 94 | jaegerdb -j test.txt.jgrdb -a DatabasePassword -v "This is the database password" 95 | 96 | jaegerdb -j test.txt.jgrdb -a Field2 -v "This is field 2" 97 | 98 | Take a look at the database file. Note that the values are encrypted: 99 | 100 | cat test.txt.jgrdb 101 | 102 | ### Generate a file 103 | 104 | jaeger -i test.txt.jgrt -p "test passphrase" 105 | 106 | Take a look at the generated file. Note that the decrypted values have now been injected: 107 | 108 | cat test.txt 109 | 110 | ### Change a property value 111 | 112 | jaegerdb -j test.txt.jgrdb -c DatabasePassword -v "This is the NEW database password" 113 | 114 | ### Regenerate the file 115 | 116 | jaeger -i test.txt.jgrt -p "test passphrase" 117 | 118 | Take a look at the newly generated file. Note that the generated file now has the new property value: 119 | 120 | cat test.txt 121 | 122 | ## More options 123 | 124 | Use `jaeger -h` and `jaegerdb -h` to list all options. 125 | 126 | 127 | ## License 128 | 129 | Copyright (c) 2014 Julian Yap 130 | 131 | [MIT License](https://github.com/jyap808/jaeger/blob/master/LICENSE) 132 | -------------------------------------------------------------------------------- /jaeger/jaeger.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "golang.org/x/crypto/openpgp" 6 | "encoding/base64" 7 | "encoding/json" 8 | "flag" 9 | "fmt" 10 | "io/ioutil" 11 | "log" 12 | "os" 13 | "os/user" 14 | "strings" 15 | "text/template" 16 | ) 17 | 18 | const jaegerTemplateExtension = ".jgrt" 19 | const jaegerDBExtension = ".jgrdb" 20 | const jaegerDescription = "Jaeger - Template injection program\n\nJaeger is a JSON encoded GPG encrypted key value store. It is useful for separating development with operations and keeping configuration files secure." 21 | const jaegerQuote = "\"Stacker Pentecost: Haven't you heard Mr. Beckett? The world is coming to an end. So where would you rather die? Here? Or in a Jaeger!\" - Pacific Rim" 22 | const jaegerRecommendedUsage = "RECOMMENDED:\n jaeger -i file.txt.jgrt\n\nThis will run Jaeger with the default options and assume the following:\n JSON GPG database file: file.txt.jgrdb\n Output file: file.txt\n Keyring file: ~/.gnupg/jaeger_secring.gpg\n No passphrase" 23 | 24 | var debug debugging = false 25 | 26 | type debugging bool 27 | 28 | func (d debugging) Printf(format string, args ...interface{}) { 29 | // From: https://groups.google.com/forum/#!msg/golang-nuts/gU7oQGoCkmg/BNIl-TqB-4wJ 30 | if d { 31 | log.Printf(format, args...) 32 | } 33 | } 34 | 35 | type Data struct { 36 | Properties []Property 37 | } 38 | 39 | type Property struct { 40 | Name string //`json:"Name"` 41 | EncryptedValue string //`json:"EncryptedValue"` 42 | } 43 | 44 | func main() { 45 | // Define flags 46 | var ( 47 | debugFlag = flag.Bool("d", false, "Enable Debug") 48 | inputTemplate = flag.String("i", "", "Input Template file. eg. file.txt.jgrt") 49 | jsonGPGDB = flag.String("j", "", "JSON GPG database file. eg. file.txt.jgrdb") 50 | outputFile = flag.String("o", "", "Output file. eg. file.txt") 51 | keyringFile = flag.String("k", "", "Keyring file. Secret key in ASCII armored format. eg. secret.asc") 52 | passphraseKeyring = flag.String("p", "", "Passphrase for keyring. If this is not set the passphrase will be blank or read from the environment variable PASSPHRASE.") 53 | ) 54 | 55 | flag.Usage = func() { 56 | fmt.Printf("%s\n%s\n\n%s\n\n", jaegerDescription, jaegerQuote, jaegerRecommendedUsage) 57 | fmt.Fprintf(os.Stderr, "OPTIONS:\n") 58 | flag.PrintDefaults() 59 | } 60 | 61 | flag.Parse() 62 | 63 | if *debugFlag { 64 | debug = true 65 | } 66 | 67 | if *inputTemplate == "" { 68 | flag.Usage() 69 | log.Fatalf("\n\nError: No input template file specified") 70 | } 71 | 72 | basefilename := "" 73 | 74 | if strings.HasSuffix(*inputTemplate, jaegerTemplateExtension) { 75 | basefilename = strings.TrimSuffix(*inputTemplate, jaegerTemplateExtension) 76 | } 77 | 78 | if *jsonGPGDB == "" { 79 | if basefilename == "" { 80 | flag.Usage() 81 | log.Fatalf("\n\nERROR: No JSON GPG DB file specified or input file does not have a %v extension", jaegerTemplateExtension) 82 | } 83 | // Set from the basefilename 84 | *jsonGPGDB = fmt.Sprintf("%v%v", basefilename, jaegerDBExtension) 85 | } 86 | 87 | if *outputFile == "" { 88 | if basefilename == "" { 89 | flag.Usage() 90 | log.Fatalf("\n\nERROR: No Output file specified or input file does not have a %v extension", jaegerTemplateExtension) 91 | } 92 | // Set from the basefilename 93 | *outputFile = basefilename 94 | } 95 | 96 | if *passphraseKeyring == "" { 97 | passphrase := os.Getenv("PASSPHRASE") 98 | if len(passphrase) != 0 { 99 | *passphraseKeyring = passphrase 100 | } 101 | } 102 | 103 | debug.Printf("basefilename:", basefilename) 104 | debug.Printf("jsonGPGDB:", *jsonGPGDB) 105 | debug.Printf("outputFile:", *outputFile) 106 | debug.Printf("passphrase:", *passphraseKeyring) 107 | debug.Printf("keyringFile:", *keyringFile) 108 | 109 | // Read armored private key or default keyring into type EntityList 110 | // An EntityList contains one or more Entities. 111 | // This assumes there is only one Entity involved 112 | // TODO: Support to prompt for passphrase 113 | 114 | var entity *openpgp.Entity 115 | var entitylist openpgp.EntityList 116 | 117 | if *keyringFile == "" { 118 | entity, entitylist = processSecretKeyRing() 119 | } else { 120 | entity, entitylist = processArmoredKeyRingFile(keyringFile) 121 | } 122 | 123 | entity = decryptPrivateKeyRing(passphraseKeyring, entity) 124 | 125 | p := make(map[string]string) 126 | p, err := parseJaegerDBFile(jsonGPGDB, entitylist) 127 | if err != nil { 128 | log.Fatal(err) 129 | } 130 | 131 | if err := writeOutputFile(inputTemplate, outputFile, p); err == nil { 132 | fmt.Println("Wrote file:", *outputFile) 133 | } 134 | 135 | } 136 | 137 | func decodeBase64EncryptedMessage(s string, keyring openpgp.KeyRing) string { 138 | // Decrypt base64 encoded encrypted message using decrypted private key 139 | dec, err := base64.StdEncoding.DecodeString(s) 140 | if err != nil { 141 | log.Fatalln("ERR:", err) 142 | } 143 | debug.Printf("keyring: #%v", keyring) 144 | md, err := openpgp.ReadMessage(bytes.NewBuffer(dec), keyring, nil, nil) 145 | if err != nil { 146 | log.Fatalln("ERR: Error reading message - ", err) 147 | } 148 | 149 | bytes, err := ioutil.ReadAll(md.UnverifiedBody) 150 | debug.Printf("md:", string(bytes)) 151 | return string(bytes) 152 | } 153 | 154 | func processSecretKeyRing() (entity *openpgp.Entity, entitylist openpgp.EntityList) { 155 | // Get default secret keyring location 156 | usr, err := user.Current() 157 | if err != nil { 158 | log.Fatal(err) 159 | } 160 | 161 | jaegerSecretKeyRing := fmt.Sprintf("%v/.gnupg/jaeger_secring.gpg", usr.HomeDir) 162 | secretKeyRing := "" 163 | 164 | if _, err := os.Stat(jaegerSecretKeyRing); err == nil { 165 | secretKeyRing = jaegerSecretKeyRing 166 | } else { 167 | secretKeyRing = fmt.Sprintf("%v/.gnupg/secring.gpg", usr.HomeDir) 168 | } 169 | 170 | debug.Printf("secretKeyRing file:", secretKeyRing) 171 | secretKeyRingBuffer, err := os.Open(secretKeyRing) 172 | if err != nil { 173 | panic(err) 174 | } 175 | entitylist, err = openpgp.ReadKeyRing(secretKeyRingBuffer) 176 | if err != nil { 177 | log.Fatal(err) 178 | } 179 | 180 | entity = entitylist[0] 181 | debug.Printf("Private key default keyring:", entity.Identities) 182 | 183 | return entity, entitylist 184 | } 185 | 186 | func processArmoredKeyRingFile(keyringFile *string) (entity *openpgp.Entity, entitylist openpgp.EntityList) { 187 | keyringFileBuffer, err := os.Open(*keyringFile) 188 | if err != nil { 189 | log.Fatalln("ERROR: Unable to read keyring file") 190 | } 191 | entitylist, err = openpgp.ReadArmoredKeyRing(keyringFileBuffer) 192 | if err != nil { 193 | log.Fatal(err) 194 | } 195 | entity = entitylist[0] 196 | debug.Printf("Private key from armored string:", entity.Identities) 197 | 198 | return entity, entitylist 199 | } 200 | 201 | func decryptPrivateKeyRing(passphraseKeyring *string, entity *openpgp.Entity) *openpgp.Entity { 202 | // Decrypt private key using passphrase 203 | passphrase := []byte(*passphraseKeyring) 204 | if entity.PrivateKey != nil && entity.PrivateKey.Encrypted { 205 | debug.Printf("Decrypting private key using passphrase") 206 | err := entity.PrivateKey.Decrypt(passphrase) 207 | if err != nil { 208 | log.Fatalln("ERROR: Failed to decrypt key using passphrase. Make sure you specify a passphrase if required.") 209 | } 210 | } 211 | for _, subkey := range entity.Subkeys { 212 | if subkey.PrivateKey != nil && subkey.PrivateKey.Encrypted { 213 | err := subkey.PrivateKey.Decrypt(passphrase) 214 | if err != nil { 215 | log.Fatalln("ERROR: Failed to decrypt subkey") 216 | } 217 | } 218 | } 219 | return entity 220 | } 221 | 222 | func parseJaegerDBFile(jsonGPGDB *string, entitylist openpgp.EntityList) (map[string]string, error) { 223 | // json handling 224 | jsonGPGDBBuffer, err := ioutil.ReadFile(*jsonGPGDB) 225 | if err != nil { 226 | return nil, fmt.Errorf("ERROR: Unable to read JSON GPG DB file") 227 | } 228 | 229 | var j Data 230 | if err := json.Unmarshal(jsonGPGDBBuffer, &j); err != nil { 231 | return nil, fmt.Errorf("error:", err) 232 | } 233 | debug.Printf("json unmarshal:", j) 234 | 235 | p := make(map[string]string) 236 | 237 | for _, v := range j.Properties { 238 | debug.Printf("Name: %#v, EncryptedValue: %#v\n", v.Name, v.EncryptedValue) 239 | p[v.Name] = decodeBase64EncryptedMessage(v.EncryptedValue, entitylist) 240 | } 241 | 242 | debug.Printf("properties map:", p) 243 | return p, nil 244 | } 245 | 246 | func writeOutputFile(inputTemplate *string, outputFile *string, p map[string]string) error { 247 | // Template parsing 248 | t := template.Must(template.ParseFiles(*inputTemplate)) 249 | 250 | buf := new(bytes.Buffer) 251 | t.Execute(buf, p) //merge template ‘t’ with content of ‘p’ 252 | 253 | bytes, _ := ioutil.ReadAll(buf) 254 | debug.Printf(string(bytes)) 255 | 256 | // Writing file 257 | // To handle large files, use a file buffer: http://stackoverflow.com/a/9739903/603745 258 | if err := ioutil.WriteFile(*outputFile, bytes, 0644); err != nil { 259 | return fmt.Errorf("error:", err) 260 | } 261 | 262 | return nil 263 | } 264 | -------------------------------------------------------------------------------- /jaegerdb/jaegerdb.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "encoding/base64" 6 | "encoding/json" 7 | "flag" 8 | "fmt" 9 | "golang.org/x/crypto/openpgp" 10 | "io/ioutil" 11 | "log" 12 | "os" 13 | "os/user" 14 | "path/filepath" 15 | "strings" 16 | ) 17 | 18 | const jaegerTemplateExtension = ".jgrt" 19 | const jaegerDBExtension = ".jgrdb" 20 | const jaegerDBDescription = "JaegerDB - Jaeger database management program\n\nJaeger is a JSON encoded GPG encrypted key value store. It is useful for separating development with operations and keeping configuration files secure." 21 | const jaegerQuote = "\"Stacker Pentecost: Haven't you heard Mr. Beckett? The world is coming to an end. So where would you rather die? Here? Or in a Jaeger!\" - Pacific Rim" 22 | const jaegerDBRecommendedUsage = "RECOMMENDED:\n jaegerdb -j file.txt.jgrdb -a \"Field1\" -v \"Secret value\"\n\nThis will run JaegerDB with the default options and assume the following:\n Keyring file: ~/.gnupg/jaeger_pubring.gpg" 23 | 24 | var debug debugging = false 25 | 26 | type debugging bool 27 | 28 | func (d debugging) Printf(format string, args ...interface{}) { 29 | // From: https://groups.google.com/forum/#!msg/golang-nuts/gU7oQGoCkmg/BNIl-TqB-4wJ 30 | if d { 31 | log.Printf(format, args...) 32 | } 33 | } 34 | 35 | type Data struct { 36 | Properties []Property 37 | } 38 | 39 | type Property struct { 40 | Name string `json:"Name"` 41 | EncryptedValue string `json:"EncryptedValue"` 42 | } 43 | 44 | func main() { 45 | // Define flags 46 | // TODO: View individual property and unencrypted value. 'get' 47 | var ( 48 | addKey = flag.String("a", "", "Add property") 49 | changeKey = flag.String("c", "", "Change property") 50 | debugFlag = flag.Bool("d", false, "Enable Debug") 51 | deleteKey = flag.String("delete", "", "Delete property") 52 | initializeFlag = flag.Bool("init", false, "Create an initial blank JSON GPG database file") 53 | jsonGPGDB = flag.String("j", "", "JSON GPG database file. eg. file.txt.jgrdb") 54 | keyringFile = flag.String("k", "", "Keyring file. Public key in ASCII armored format. eg. pubring.asc") 55 | value = flag.String("v", "", "Value for property to use") 56 | ) 57 | 58 | flag.Usage = func() { 59 | fmt.Printf("%s\n%s\n\n%s\n\n", jaegerDBDescription, jaegerQuote, jaegerDBRecommendedUsage) 60 | fmt.Fprintf(os.Stderr, "OPTIONS:\n") 61 | flag.PrintDefaults() 62 | } 63 | 64 | flag.Parse() 65 | 66 | if *debugFlag { 67 | debug = true 68 | } 69 | 70 | if *jsonGPGDB == "" { 71 | assumedJaegerDB, err := checkExistsJaegerDB() 72 | if err != nil { 73 | flag.Usage() 74 | log.Fatalf("\n\nError: %s", err) 75 | } 76 | *jsonGPGDB = assumedJaegerDB 77 | } 78 | 79 | if *initializeFlag { 80 | err := initializeJSONGPGDB(jsonGPGDB) 81 | if err != nil { 82 | log.Fatal(err) 83 | } else { 84 | fmt.Println("Initialized JSON GPG database and wrote to file:", *jsonGPGDB) 85 | os.Exit(0) 86 | } 87 | } 88 | 89 | if *deleteKey != "" { 90 | err := deleteKeyJaegerDB(deleteKey, jsonGPGDB) 91 | if err != nil { 92 | log.Fatal(err) 93 | } else { 94 | fmt.Println("Deleted property and wrote to file:", *jsonGPGDB) 95 | os.Exit(0) 96 | } 97 | } 98 | 99 | var entitylist openpgp.EntityList 100 | 101 | if *keyringFile == "" { 102 | _, entitylist = processPublicKeyRing() 103 | } else { 104 | _, entitylist = processArmoredKeyRingFile(keyringFile) 105 | } 106 | 107 | if *addKey != "" { 108 | if *value == "" { 109 | flag.Usage() 110 | log.Fatalf("\n\nError: No value for add key operation specified") 111 | } 112 | err := addKeyJaegerDB(addKey, value, jsonGPGDB, entitylist) 113 | if err != nil { 114 | log.Fatal(err) 115 | } else { 116 | fmt.Println("Added property and wrote to file:", *jsonGPGDB) 117 | os.Exit(0) 118 | } 119 | } 120 | 121 | if *changeKey != "" { 122 | if *value == "" { 123 | flag.Usage() 124 | log.Fatalf("\n\nError: No value for change key operation specified") 125 | } 126 | err := changeKeyJaegerDB(changeKey, value, jsonGPGDB, entitylist) 127 | if err != nil { 128 | log.Fatal(err) 129 | } else { 130 | fmt.Println("Changed property and wrote to file:", *jsonGPGDB) 131 | os.Exit(0) 132 | } 133 | } 134 | 135 | if *deleteKey == "" && *addKey == "" && *changeKey == "" { 136 | log.Fatalf("\n\nError: No JSON GPG database operations specified") 137 | } 138 | 139 | } 140 | 141 | func checkExistsJaegerT() (string, error) { 142 | // Check that one template file is in the current directory and use that file 143 | files, err := filepath.Glob("*.jgrt") 144 | if err != nil { 145 | return "", err 146 | } 147 | if len(files) == 1 { 148 | return files[0], nil 149 | } 150 | return "", fmt.Errorf("No input template file specified") 151 | } 152 | 153 | func checkExistsJaegerDB() (string, error) { 154 | // If no JSON GPG database file is explicitly specified, check that one JSON GPG database file is in the 155 | // current directory and use that file 156 | files, err := filepath.Glob("*.jgrdb") 157 | if err != nil { 158 | return "", err 159 | } 160 | if len(files) == 1 { 161 | return files[0], nil 162 | } 163 | 164 | assumedTemplate, err := checkExistsJaegerT() 165 | 166 | if assumedTemplate != "" { 167 | basefilename := strings.TrimSuffix(assumedTemplate, jaegerTemplateExtension) 168 | jsonGPGDB := fmt.Sprintf("%v%v", basefilename, jaegerDBExtension) 169 | return jsonGPGDB, nil 170 | } 171 | 172 | return "", fmt.Errorf("Please specify a JSON GPG database file") 173 | } 174 | 175 | func initializeJSONGPGDB(jsonGPGDB *string) error { 176 | if _, err := os.Stat(*jsonGPGDB); err == nil { 177 | return fmt.Errorf("ERR: File already exists: %v", *jsonGPGDB) 178 | } 179 | 180 | var newP []Property 181 | 182 | newData := Data{newP} 183 | 184 | bytes, err := json.MarshalIndent(newData, "", " ") 185 | if err != nil { 186 | return fmt.Errorf("error:", err) 187 | } 188 | 189 | debug.Printf("b: %v", string(bytes)) 190 | 191 | // Writing file 192 | // To handle large files, use a file buffer: http://stackoverflow.com/a/9739903/603745 193 | if err := ioutil.WriteFile(*jsonGPGDB, bytes, 0644); err != nil { 194 | return fmt.Errorf("error:", err) 195 | } 196 | 197 | return nil 198 | } 199 | 200 | func encodeBase64EncryptedMessage(s string, entitylist openpgp.EntityList) string { 201 | // Encrypt message using public key and then encode with base64 202 | debug.Printf("entitylist: #%v", entitylist) 203 | buf := new(bytes.Buffer) 204 | w, err := openpgp.Encrypt(buf, entitylist, nil, nil, nil) 205 | if err != nil { 206 | log.Fatalln("ERR: Error encrypting message - ", err) 207 | } 208 | 209 | _, err = w.Write([]byte(s)) 210 | err = w.Close() 211 | 212 | // Output as base64 encoded string 213 | bytes, err := ioutil.ReadAll(buf) 214 | str := base64.StdEncoding.EncodeToString(bytes) 215 | 216 | debug.Printf("Public key encrypted message (base64 encoded): %v", str) 217 | 218 | return str 219 | } 220 | 221 | func processPublicKeyRing() (entity *openpgp.Entity, entitylist openpgp.EntityList) { 222 | // TODO: Handle a specified recipient 223 | // Get default public keyring location 224 | usr, err := user.Current() 225 | if err != nil { 226 | log.Fatal(err) 227 | } 228 | 229 | jaegerPublicKeyRing := fmt.Sprintf("%v/.gnupg/jaeger_pubring.gpg", usr.HomeDir) 230 | publicKeyRing := "" 231 | 232 | if _, err := os.Stat(jaegerPublicKeyRing); err == nil { 233 | publicKeyRing = jaegerPublicKeyRing 234 | } else { 235 | publicKeyRing = fmt.Sprintf("%v/.gnupg/pubring.gpg", usr.HomeDir) 236 | } 237 | 238 | debug.Printf("publicKeyRing file:", publicKeyRing) 239 | publicKeyRingBuffer, err := os.Open(publicKeyRing) 240 | if err != nil { 241 | panic(err) 242 | } 243 | entitylist, err = openpgp.ReadKeyRing(publicKeyRingBuffer) 244 | if err != nil { 245 | log.Fatal(err) 246 | } 247 | 248 | entity = entitylist[0] 249 | debug.Printf("Public key default keyring:", entity.Identities) 250 | 251 | return entity, entitylist 252 | } 253 | 254 | func processArmoredKeyRingFile(keyringFile *string) (entity *openpgp.Entity, entitylist openpgp.EntityList) { 255 | keyringFileBuffer, err := os.Open(*keyringFile) 256 | if err != nil { 257 | log.Fatalln("ERROR: Unable to read keyring file") 258 | } 259 | entitylist, err = openpgp.ReadArmoredKeyRing(keyringFileBuffer) 260 | if err != nil { 261 | log.Fatal(err) 262 | } 263 | entity = entitylist[0] 264 | debug.Printf("Public key from ASCII armored string:", entity.Identities) 265 | 266 | return entity, entitylist 267 | } 268 | 269 | func addKeyJaegerDB(key *string, value *string, jsonGPGDB *string, entitylist openpgp.EntityList) error { 270 | // json handling 271 | jsonGPGDBBuffer, err := ioutil.ReadFile(*jsonGPGDB) 272 | if err != nil { 273 | return fmt.Errorf("ERROR: Unable to read JSON GPG DB file") 274 | } 275 | 276 | var j Data 277 | if err := json.Unmarshal(jsonGPGDBBuffer, &j); err != nil { 278 | return fmt.Errorf("error:", err) 279 | } 280 | debug.Printf("json unmarshal: %v", j) 281 | 282 | found := false 283 | 284 | var newP []Property 285 | 286 | p := Property{Name: *key, EncryptedValue: encodeBase64EncryptedMessage(*value, entitylist)} 287 | 288 | // Search 289 | for i := range j.Properties { 290 | property := &j.Properties[i] 291 | debug.Printf("i: %v, Name: %#v, EncryptedValue: %#v\n", i, property.Name, property.EncryptedValue) 292 | if property.Name == *key { 293 | j.Properties[i] = p 294 | found = true 295 | break 296 | } 297 | } 298 | 299 | if found { 300 | return fmt.Errorf("\n\nError: Property '%s' already exists.", *key) 301 | } 302 | 303 | newP = append(j.Properties, p) 304 | 305 | debug.Printf("new properties: %v", newP) 306 | 307 | newData := Data{newP} 308 | 309 | bytes, err := json.MarshalIndent(newData, "", " ") 310 | if err != nil { 311 | return fmt.Errorf("error:", err) 312 | } 313 | 314 | debug.Printf("b: %v", string(bytes)) 315 | 316 | // Writing file 317 | // To handle large files, use a file buffer: http://stackoverflow.com/a/9739903/603745 318 | if err := ioutil.WriteFile(*jsonGPGDB, bytes, 0644); err != nil { 319 | return fmt.Errorf("error:", err) 320 | } 321 | 322 | return nil 323 | } 324 | 325 | func changeKeyJaegerDB(key *string, value *string, jsonGPGDB *string, entitylist openpgp.EntityList) error { 326 | // json handling 327 | jsonGPGDBBuffer, err := ioutil.ReadFile(*jsonGPGDB) 328 | if err != nil { 329 | return fmt.Errorf("ERROR: Unable to read JSON GPG DB file") 330 | } 331 | 332 | var j Data 333 | if err := json.Unmarshal(jsonGPGDBBuffer, &j); err != nil { 334 | return fmt.Errorf("error:", err) 335 | } 336 | debug.Printf("json unmarshal: %v", j) 337 | 338 | found := false 339 | 340 | // New property to replace the old 341 | p := Property{Name: *key, EncryptedValue: encodeBase64EncryptedMessage(*value, entitylist)} 342 | 343 | // Search and replace 344 | for i := range j.Properties { 345 | property := &j.Properties[i] 346 | debug.Printf("i: %v, Name: %#v, EncryptedValue: %#v\n", i, property.Name, property.EncryptedValue) 347 | if property.Name == *key { 348 | j.Properties[i] = p 349 | found = true 350 | break 351 | } 352 | } 353 | 354 | if !found { 355 | return fmt.Errorf("\n\nError: Property '%s' not found.", *key) 356 | } 357 | 358 | bytes, err := json.MarshalIndent(j, "", " ") 359 | if err != nil { 360 | return fmt.Errorf("error:", err) 361 | } 362 | 363 | debug.Printf("b: %v", string(bytes)) 364 | 365 | // Writing file 366 | // To handle large files, use a file buffer: http://stackoverflow.com/a/9739903/603745 367 | if err := ioutil.WriteFile(*jsonGPGDB, bytes, 0644); err != nil { 368 | return fmt.Errorf("error:", err) 369 | } 370 | 371 | return nil 372 | } 373 | 374 | func deleteKeyJaegerDB(key *string, jsonGPGDB *string) error { 375 | debug.Printf("deleteKeyJaegerDB key: %v", *key) 376 | 377 | // json handling 378 | jsonGPGDBBuffer, err := ioutil.ReadFile(*jsonGPGDB) 379 | if err != nil { 380 | return fmt.Errorf("ERROR: Unable to read JSON GPG DB file") 381 | } 382 | 383 | var j Data 384 | if err := json.Unmarshal(jsonGPGDBBuffer, &j); err != nil { 385 | return fmt.Errorf("error:", err) 386 | } 387 | debug.Printf("json unmarshal: %v", j) 388 | 389 | var newP []Property 390 | found := false 391 | 392 | for i := range j.Properties { 393 | property := &j.Properties[i] 394 | debug.Printf("i: %v, Name: %#v, EncryptedValue: %#v\n", i, property.Name, property.EncryptedValue) 395 | if property.Name == *key { 396 | // https://code.google.com/p/go-wiki/wiki/SliceTricks 397 | newP = j.Properties[:i+copy(j.Properties[i:], j.Properties[i+1:])] 398 | found = true 399 | break 400 | } 401 | } 402 | 403 | if !found { 404 | return fmt.Errorf("\n\nError: Property '%s' not found.", *key) 405 | } 406 | 407 | debug.Printf("new properties: %v", newP) 408 | 409 | newData := Data{newP} 410 | 411 | bytes, err := json.MarshalIndent(newData, "", " ") 412 | if err != nil { 413 | return fmt.Errorf("error:", err) 414 | } 415 | 416 | debug.Printf("b: %v", string(bytes)) 417 | 418 | // Writing file 419 | // To handle large files, use a file buffer: http://stackoverflow.com/a/9739903/603745 420 | if err := ioutil.WriteFile(*jsonGPGDB, bytes, 0644); err != nil { 421 | return fmt.Errorf("error:", err) 422 | } 423 | 424 | return nil 425 | } 426 | -------------------------------------------------------------------------------- /jaegerh/jaegerh.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "flag" 7 | "fmt" 8 | "log" 9 | "os" 10 | "path/filepath" 11 | "regexp" 12 | "strings" 13 | ) 14 | 15 | const jaegerTemplateExtension = ".jgrt" 16 | const jaegerDescription = "JaegerH - Jaeger Helper program\n\nJaeger is a JSON encoded GPG encrypted key value store. It is useful for separating development with operations and keeping configuration files secure." 17 | const jaegerQuote = "\"Stacker Pentecost: Haven't you heard Mr. Beckett? The world is coming to an end. So where would you rather die? Here? Or in a Jaeger!\" - Pacific Rim" 18 | const jaegerRecommendedUsage = "RECOMMENDED:\n jaegerh -i file.txt.jgrt" 19 | 20 | var debug debugging = false 21 | 22 | type debugging bool 23 | 24 | func (d debugging) Printf(format string, args ...interface{}) { 25 | // From: https://groups.google.com/forum/#!msg/golang-nuts/gU7oQGoCkmg/BNIl-TqB-4wJ 26 | if d { 27 | log.Printf(format, args...) 28 | } 29 | } 30 | 31 | func main() { 32 | // Define flags 33 | var ( 34 | debugFlag = flag.Bool("d", false, "Enable Debug") 35 | inputTemplate = flag.String("i", "", "Input Template file. eg. file.txt.jgrt") 36 | ) 37 | 38 | flag.Usage = func() { 39 | fmt.Printf("%s\n%s\n\n%s\n\n", jaegerDescription, jaegerQuote, jaegerRecommendedUsage) 40 | fmt.Fprintf(os.Stderr, "OPTIONS:\n") 41 | flag.PrintDefaults() 42 | } 43 | 44 | flag.Parse() 45 | 46 | if *debugFlag { 47 | debug = true 48 | } 49 | 50 | if *inputTemplate == "" { 51 | assumedTemplate, err := checkExistsJaegerT() 52 | if err != nil { 53 | flag.Usage() 54 | log.Fatalf("\n\nError: %s", err) 55 | } 56 | *inputTemplate = assumedTemplate 57 | } 58 | 59 | processInputFile(inputTemplate) 60 | } 61 | 62 | func checkExistsJaegerT() (string, error) { 63 | // Check that one template file is in the current directory and use that file 64 | files, err := filepath.Glob("*.jgrt") 65 | if err != nil { 66 | return "", err 67 | } 68 | if len(files) == 1 { 69 | return files[0], nil 70 | } 71 | return "", fmt.Errorf("No input template file specified") 72 | } 73 | 74 | func processInputFile(inputFile *string) { 75 | file, err := os.Open(*inputFile) 76 | if err != nil { 77 | log.Fatal(err) 78 | } 79 | defer file.Close() 80 | 81 | scanner := bufio.NewScanner(file) 82 | for scanner.Scan() { 83 | parseString := scanner.Text() 84 | matched, _ := regexp.MatchString("^[[:space:]]*#", parseString) 85 | if matched { 86 | if debug { 87 | fmt.Println("Ignoring: %s", parseString) 88 | } 89 | continue 90 | } 91 | matched, _ = regexp.MatchString(".*=.*", parseString) 92 | if matched { 93 | parseLine(parseString) 94 | } 95 | } 96 | 97 | if err := scanner.Err(); err != nil { 98 | log.Fatal(err) 99 | } 100 | } 101 | 102 | func parseLine(line string) { 103 | s := regexp.MustCompile("=").Split(line, 2) 104 | rawKey := strings.TrimSpace(s[0]) 105 | key := camelKey(rawKey) 106 | value := strings.TrimSpace(s[1]) 107 | if value != "" { 108 | fmt.Printf("jaegerdb -a %s -v '%s' # %s = {{.%s}}\n", key, value, rawKey, key) 109 | } 110 | } 111 | 112 | func camelKey(src string) string { 113 | // From: https://github.com/etgryphon/stringUp 114 | 115 | var camelingRegex = regexp.MustCompile("[0-9A-Za-z]+") 116 | 117 | byteSrc := []byte(src) 118 | chunks := camelingRegex.FindAll(byteSrc, -1) 119 | for idx, val := range chunks { 120 | chunks[idx] = bytes.Title(val) 121 | } 122 | return string(bytes.Join(chunks, nil)) 123 | } 124 | --------------------------------------------------------------------------------