-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmain.go
344 lines (305 loc) · 8.42 KB
/
main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
package main
import (
"context"
"crypto/aes"
"crypto/cipher"
"encoding/hex"
"encoding/json"
"fmt"
"io/ioutil"
"log"
"sync"
vault "github.com/hashicorp/vault/api"
)
// SecretStore defines the interface for storing and retrieving secrets.
type SecretStore interface {
Put(ctx context.Context, key string, secret map[string]interface{}) error
Get(ctx context.Context, key string) (map[string]interface{}, error)
}
// VaultSecretStore is the implementation of SecretStore using HashiCorp Vault.
type VaultSecretStore struct {
client *vault.Client
}
// NewVaultSecretStore creates a new instance of VaultSecretStore.
func NewVaultSecretStore(address, token string) (*VaultSecretStore, error) {
config := vault.DefaultConfig()
config.Address = address
client, err := vault.NewClient(config)
if err != nil {
return nil, err
}
client.SetToken(token)
return &VaultSecretStore{client: client}, nil
}
// Put writes a secret to HashiCorp Vault.
func (s *VaultSecretStore) Put(ctx context.Context, key string, secret map[string]interface{}) error {
// Encrypt the secret value
if val, ok := secret["value"].(string); ok {
fmt.Println("\n\n\nVALUE IS:", val)
encryptedVal, err := encryptValue(val)
if err != nil {
return err
}
secret["value"] = encryptedVal
}
_, err := s.client.KVv2("secret").Put(ctx, key, secret)
return err
}
// Get reads a secret from HashiCorp Vault.
func (s *VaultSecretStore) Get(ctx context.Context, key string) (map[string]interface{}, error) {
secret, err := s.client.KVv2("secret").Get(ctx, key)
if err != nil {
return nil, err
}
// Decrypt the secret value
if val, ok := secret.Data["value"].(string); ok {
decryptedVal, err := decryptValue(val)
if err != nil {
return nil, err
}
secret.Data["value"] = decryptedVal
}
return secret.Data, nil
}
// InMemorySecretStore is the implementation of SecretStore using an in-memory map.
type InMemorySecretStore struct {
secrets map[string]map[string]interface{}
mu sync.RWMutex
}
// NewInMemorySecretStore creates a new instance of InMemorySecretStore.
func NewInMemorySecretStore() *InMemorySecretStore {
return &InMemorySecretStore{
secrets: make(map[string]map[string]interface{}),
}
}
// Put writes a secret to the in-memory map and a text file.
func (s *InMemorySecretStore) Put(ctx context.Context, key string, secret map[string]interface{}) error {
s.mu.Lock()
defer s.mu.Unlock()
// Convert the secret map to JSON and encrypt it
jsonSecret, err := json.Marshal(secret)
if err != nil {
return err
}
encryptedVal, err := encryptValue(string(jsonSecret))
if err != nil {
return err
}
secretData := map[string]interface{}{
"value": encryptedVal,
}
s.secrets[key] = secretData
// Write the encrypted secret to a text file
err = ioutil.WriteFile(fmt.Sprintf("%s.txt", key), []byte(encryptedVal), 0644)
if err != nil {
return err
}
return nil
}
// Get reads a secret from the in-memory map or the text file.
func (s *InMemorySecretStore) Get(ctx context.Context, key string) (map[string]interface{}, error) {
s.mu.RLock()
defer s.mu.RUnlock()
secret, exists := s.secrets[key]
if !exists {
// If the secret is not found in the map, try to read it from the text file
encryptedVal, err := ioutil.ReadFile(fmt.Sprintf("%s.txt", key))
if err != nil {
return nil, fmt.Errorf("secret not found")
}
// Decrypt the secret value and convert it back to a map
decryptedVal, err := decryptValue(string(encryptedVal))
if err != nil {
return nil, err
}
var secretMap map[string]interface{}
err = json.Unmarshal([]byte(decryptedVal), &secretMap)
if err != nil {
return nil, err
}
return secretMap, nil
}
// Decrypt the secret value and convert it back to a map
if val, ok := secret["value"].(string); ok {
decryptedVal, err := decryptValue(val)
if err != nil {
return nil, err
}
var secretMap map[string]interface{}
err = json.Unmarshal([]byte(decryptedVal), &secretMap)
if err != nil {
return nil, err
}
return secretMap, nil
}
return nil, fmt.Errorf("secret value not found or not a string")
}
// Encryption key (should be stored securely)
var encryptionKey = []byte("mysecretencryptionkey32charslong") // Must be 16, 24, or 32 bytes long
// encryptValue encrypts a string value using AES.
func encryptValue(value string) (string, error) {
block, err := aes.NewCipher(encryptionKey)
if err != nil {
return "", err
}
gcm, err := cipher.NewGCM(block)
if err != nil {
return "", err
}
nonce := make([]byte, gcm.NonceSize())
ciphertext := gcm.Seal(nonce, nonce, []byte(value), nil)
return hex.EncodeToString(ciphertext), nil
}
// decryptValue decrypts a string value using AES.
func decryptValue(encryptedValue string) (string, error) {
ciphertext, err := hex.DecodeString(encryptedValue)
if err != nil {
return "", err
}
block, err := aes.NewCipher(encryptionKey)
if err != nil {
return "", err
}
gcm, err := cipher.NewGCM(block)
if err != nil {
return "", err
}
nonceSize := gcm.NonceSize()
if len(ciphertext) < nonceSize {
return "", fmt.Errorf("ciphertext too short")
}
nonce, ciphertext := ciphertext[:nonceSize], ciphertext[nonceSize:]
plaintext, err := gcm.Open(nil, nonce, ciphertext, nil)
if err != nil {
return "", err
}
return string(plaintext), nil
}
func main() {
var store SecretStore
var input int
var err error
for {
fmt.Println("\nSelect the storage method:")
fmt.Println("1. HashiCorp Vault")
fmt.Println("2. In-Memory Store")
fmt.Println("3. Exit")
fmt.Print("Enter a number: ")
_, err = fmt.Scan(&input)
if err != nil {
fmt.Println("Error:", err)
return
}
if input == 1 {
store, err = NewVaultSecretStore("http://vault:8200", "dev-only-token") // For a Docker container
// store, err = NewVaultSecretStore("http://127.0.0.1:8200", "dev-only-token") // For Windows localhost
} else if input == 2 {
store = NewInMemorySecretStore()
} else {
return
}
if err != nil {
log.Fatalf("unable to initialize secret store: %v", err)
}
L:
for {
fmt.Println("\nMenu:")
fmt.Println("1. Write a secret")
fmt.Println("2. Read a secret")
fmt.Println("3. Back")
fmt.Print("Enter your choice: ")
var choice int
_, err := fmt.Scan(&choice)
if err != nil {
fmt.Println("Error:", err)
continue
}
switch choice {
case 1:
var key, value, my_secret string
fmt.Print("Enter secret: ")
_, err := fmt.Scan(&my_secret)
if err != nil {
fmt.Println("Error:", err)
continue
}
fmt.Print("Enter key: ")
_, err = fmt.Scan(&key)
if err != nil {
fmt.Println("Error:", err)
continue
}
fmt.Print("Enter value: ")
_, err = fmt.Scan(&value)
if err != nil {
fmt.Println("Error:", err)
continue
}
// Encrypt the value
encryptedValue, err := encryptValue(value)
if err != nil {
fmt.Println("Error encrypting value:", err)
continue
}
// Check if the secret already exists
existingSecret, _ := store.Get(context.Background(), my_secret)
// if err != nil && !apiErrNotFound(err) {
// fmt.Println("Error checking secret existence:", err)
// continue
// }
secretData := map[string]interface{}{
key: encryptedValue,
}
if existingSecret != nil {
// Patch the existing secret
for k, v := range secretData {
existingSecret[k] = v
}
err = store.Put(context.Background(), my_secret, existingSecret)
} else {
// Write a new secret
err = store.Put(context.Background(), my_secret, secretData)
}
if err != nil {
fmt.Println("Error writing secret:", err)
} else {
fmt.Println("Secret written successfully.")
}
case 2:
var key, my_secret string
fmt.Print("Enter secret: ")
_, err := fmt.Scan(&my_secret)
if err != nil {
fmt.Println("Error:", err)
continue
}
fmt.Print("Enter key: ")
_, err = fmt.Scan(&key)
if err != nil {
fmt.Println("Error:", err)
continue
}
secret, err := store.Get(context.Background(), my_secret)
if err != nil {
fmt.Println("Error reading secret:", err)
} else {
value, ok := secret[key].(string)
if !ok {
fmt.Println("Error: invalid secret value type")
} else {
value, err = decryptValue(value)
if err != nil {
fmt.Println("Error decrypting value:", err)
} else {
fmt.Printf("Key: %s, Value: %s\n", key, value)
}
}
}
case 3:
break L
default:
fmt.Println("Invalid choice")
}
}
}
}