Skip to content

Commit

Permalink
Merge pull request #8 from sharathrnair87/feature/encyption
Browse files Browse the repository at this point in the history
Encypt using pbkdf2 algo
  • Loading branch information
DFW1N committed Jun 19, 2024
2 parents f5abf0a + 4db035f commit 7f12cc4
Show file tree
Hide file tree
Showing 8 changed files with 173 additions and 18 deletions.
7 changes: 6 additions & 1 deletion cmd/compare.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ import (
)

func Compare() {
passphrase := os.Getenv("VAULTIFY_PASSPHRASE")
if passphrase == "" {
fmt.Println("❌ Error: \033[33mVAULTIFY_PASSPHRASE\033[0m environemnt variable not set.")
os.Exit(1)
}
if _, err := os.Stat("terraform.tfstate"); os.IsNotExist(err) {
fmt.Println("❌ Error: \033[33mLocal terraform.tfstate\033[0m file not found.")
return
Expand All @@ -26,7 +31,7 @@ func Compare() {
fmt.Println("Pulling state file from \033[33mVault\033[0m...")
Pull()

if err := unwrapAndSaveAs("terraform_remote_compare_pull.tfstate"); err != nil {
if err := unwrapAndSaveAs("terraform_remote_compare_pull.tfstate", passphrase); err != nil {
fmt.Println("❌ Error:", err)
os.Exit(1)
}
Expand Down
125 changes: 125 additions & 0 deletions cmd/crypto.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
package cmd

import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"crypto/sha256"
"encoding/hex"
"fmt"
"golang.org/x/crypto/pbkdf2"
"io"
"math/big"
"os"
"strings"
)

func deriveKey(passphrase string, salt []byte) ([]byte, []byte, error) {
if salt == nil {
salt = make([]byte, 32)
if _, err := io.ReadFull(rand.Reader, salt); err != nil {
return nil, nil, fmt.Errorf("%w", err)
}
}
key := pbkdf2.Key([]byte(passphrase), salt, 4096, 32, sha256.New)
return key, salt, nil
}

func encryptContents(filestream []byte, passphrase string) ([]byte, error) {
key, salt, err := deriveKey(passphrase, nil)
if err != nil {
return nil, fmt.Errorf("❌ Error generating salt: %w", err)
}
block, err := aes.NewCipher(key)
if err != nil {
return nil, fmt.Errorf("❌ Error creating cipher: %w", err)
}

aesgcm, err := cipher.NewGCM(block)
if err != nil {
return nil, fmt.Errorf("❌ Error creating GCM: %w", err)
}

nonce := make([]byte, aesgcm.NonceSize())
if _, err = io.ReadFull(rand.Reader, nonce); err != nil {
return nil, fmt.Errorf("❌ Error generating nonce: %w", err)
}

ciphertext := aesgcm.Seal(nonce, nonce, filestream, nil)

if ciphertext == nil {
return nil, fmt.Errorf("❌ Error creating output file: %w", err)
}

encryptStream := fmt.Sprintf("%s-%s", hex.EncodeToString(salt), hex.EncodeToString(ciphertext))

return []byte(encryptStream), nil
}

func decryptFile(filename string, passphrase string) (string, error) {
outfileName := strings.TrimSuffix(filename, ".enc")
data, err := os.ReadFile(filename)
if err != nil {
return "", fmt.Errorf("❌ Error reading file: %w", err)
}

parts := strings.Split(string(data), "-")
if len(parts) != 2 {
return "", fmt.Errorf("❌ Error invalid ciphertext format")
}

salt, _ := hex.DecodeString(parts[0])
ciphertext, _ := hex.DecodeString(parts[1])

key, _, err := deriveKey(passphrase, salt)
if err != nil {
return "", fmt.Errorf("❌ Error parsing salt: %w", err)
}
block, err := aes.NewCipher(key)
if err != nil {
return "", fmt.Errorf("❌ Error creating cipher: %w", err)
}

aesgcm, err := cipher.NewGCM(block)
if err != nil {
return "", fmt.Errorf("❌ Error creating GCM: %w", err)
}

nonceSize := aesgcm.NonceSize()
nonce, ciphertext := ciphertext[:nonceSize], ciphertext[nonceSize:]
plaintext, err := aesgcm.Open(nil, nonce, ciphertext, nil)
if err != nil {
return "", fmt.Errorf("❌ Error decrypting: %w", err)
}

err = os.WriteFile(outfileName, plaintext, 0644)
if err != nil {
return "", fmt.Errorf("❌ Error writing decrypted file: %w", err)
}

return "", nil
}

func GenPassphrase() {
min := 24
max := 48

bigN, err := rand.Int(rand.Reader, big.NewInt(int64(max-min+1)))
if err != nil {
fmt.Println("❌ Unable to generate random integer" + err.Error())
}

n := bigN.Int64() + int64(min)

const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()-_=+<>?"

result := make([]byte, n)
for i := range result {
randomIndex, err := rand.Int(rand.Reader, big.NewInt(int64(len(charset))))
if err != nil {
fmt.Println("❌ Unable to generate passphrase" + err.Error())
}
result[i] = charset[randomIndex.Int64()]
}
fmt.Println("Passphrase: " + string(result))
}
1 change: 1 addition & 0 deletions cmd/help.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ func Help() {
fmt.Println(" \033[33mretrieve\033[0m Pull and Unwrap a secret from base64")
fmt.Println(" \033[33mstatus\033[0m Checks if Vaultify is still authenticated to Hashicorp Vault.")
fmt.Println(" \033[33mconfigure\033[0m Configures the Vaultify project, allowing customization of settings such as the Vault address, authentication method, and data paths")
fmt.Println(" \033[33mpwgen\033[0m Generate a secure passphrase for use with Vaultify.\n\t\t\t\t\033[31mNOTE\033[0m: If you choose to use the passphrase generated by this command, ensure you store it in a secure location")
fmt.Println(" \033[33m-v\033[0m, \033[33m--version\033[0m Show the Vaultify version")
fmt.Println(" \033[33m-h\033[0m, \033[33m--help\033[0m Show this help message")
}
10 changes: 5 additions & 5 deletions cmd/pull.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,9 +124,9 @@ func pullFromVault() {
fmt.Println("❌ Error: Specific \033[33mkey\033[0m not found in the data")
}

targetFilePath := "terraform.tfstate.gz.b64"
targetFilePath := "terraform.tfstate.gz.enc.b64"
if _, err := os.Stat(targetFilePath); err == nil {
fmt.Println("❌ Error: File \033[33mterraform.tfstate.gz.b64\033[0m already exists in the directory.")
fmt.Println("❌ Error: File \033[33mterraform.tfstate.gz.enc.b64\033[0m already exists in the directory.")
return
} else if !os.IsNotExist(err) {
fmt.Printf("❌ Error checking if file exists: %v\n", err)
Expand All @@ -138,7 +138,7 @@ func pullFromVault() {
return
}

fmt.Println("✅ Secret retrieved and saved as \033[33mterraform.tfstate.gz.b64\033[0m")
fmt.Println("✅ Secret retrieved and saved as \033[33mterraform.tfstate.gz.enc.b64\033[0m")
}

func pullBlobFromAzureStorage(accountName, key string) error {
Expand Down Expand Up @@ -187,7 +187,7 @@ func pullBlobFromAzureStorage(accountName, key string) error {
return fmt.Errorf("❌ Failed to download blob, status code: \033[33m%d\033[0m, response: \033[33m%s\033[0m", resp.StatusCode, string(responseBody))
}

outputFile, err := os.Create("terraform.tfstate.gz.b64")
outputFile, err := os.Create("terraform.tfstate.gz.enc.b64")
if err != nil {
return fmt.Errorf("❌ Error creating file to save downloaded blob: \033[33m%v\033[0m", err)
}
Expand All @@ -198,7 +198,7 @@ func pullBlobFromAzureStorage(accountName, key string) error {
return fmt.Errorf("❌ Error writing downloaded blob to file: \033[33m%v\033[0m", err)
}

fmt.Println("✅ Blob downloaded successfully and saved as \033[33mterraform.tfstate.gz.b64\033[0m")
fmt.Println("✅ Blob downloaded successfully and saved as \033[33mterraform.tfstate.gz.enc.b64\033[0m")
return nil
}

Expand Down
30 changes: 20 additions & 10 deletions cmd/unwrap.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,13 @@ import (
)

func Unwrap() {
if _, err := os.Stat("terraform.tfstate.gz.b64"); os.IsNotExist(err) {
fmt.Println("❌ Error: \033[33mterraform.tfstate.gz.b64\033[0m file not found in the current directory.")
passphrase := os.Getenv("VAULTIFY_PASSPHRASE")
if passphrase == "" {
fmt.Println("❌ Error: \033[33mVAULTIFY_PASSPHRASE\033[0m environemnt variable not set.")
os.Exit(1)
}
if _, err := os.Stat("terraform.tfstate.gz.enc.b64"); os.IsNotExist(err) {
fmt.Println("❌ Error: \033[33mterraform.tfstate.gz.enc.b64\033[0m file not found in the current directory.")
fmt.Println("⚠️ Please run vaultify pull to get this file from your vault, if it exists.")
os.Exit(1)
}
Expand All @@ -28,7 +33,7 @@ func Unwrap() {
return
} else if response == "rename" {
fmt.Println("Saving as terraform_remote_pull.tfstate instead.")
if err := unwrapAndSaveAs("terraform_remote_pull.tfstate"); err != nil {
if err := unwrapAndSaveAs("terraform_remote_pull.tfstate", passphrase); err != nil {
fmt.Println("❌ Error:", err)
os.Exit(1)
}
Expand All @@ -39,29 +44,34 @@ func Unwrap() {
}
}

if err := unwrapAndSaveAs("terraform.tfstate"); err != nil {
if err := unwrapAndSaveAs("terraform.tfstate", passphrase); err != nil {
fmt.Println("❌ Error:", err)
os.Exit(1)
}
}

func unwrapAndSaveAs(outputFileName string) error {
err := decodeBase64("terraform.tfstate.gz.b64", "terraform.tfstate.gz")
func unwrapAndSaveAs(outputFileName string, passphrase string) error {
err := decodeBase64("terraform.tfstate.gz.enc.b64", "terraform.tfstate.gz.enc")
if err != nil {
return fmt.Errorf("base64 decoding failed: %w", err)
}
// decrypt
_, err = decryptFile("terraform.tfstate.gz.enc", passphrase)
if err != nil {
return fmt.Errorf("%w", err)
}

err = gunzipFile("terraform.tfstate.gz", outputFileName)
if err != nil {
return fmt.Errorf("decompression failed: %w", err)
}

if err = os.Remove("terraform.tfstate.gz"); err != nil {
return fmt.Errorf("failed to delete terraform.tfstate.gz: %w", err)
if err = os.Remove("terraform.tfstate.gz.enc"); err != nil {
return fmt.Errorf("failed to delete terraform.tfstate.gz.enc: %w", err)
}

if err = os.Remove("terraform.tfstate.gz.b64"); err != nil {
return fmt.Errorf("failed to delete terraform.tfstate.gz.b64: %w", err)
if err = os.Remove("terraform.tfstate.gz.enc.b64"); err != nil {
return fmt.Errorf("failed to delete terraform.tfstate.gz.enc.b64: %w", err)
}

fmt.Printf("✅ Unwrapped state file saved as \033[33m%s\033[0m\n", outputFileName)
Expand Down
14 changes: 13 additions & 1 deletion cmd/wrap.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,15 @@ import (
"path/filepath"
)

var encryptedStateFile string

Check failure on line 24 in cmd/wrap.go

View workflow job for this annotation

GitHub Actions / build

var `encryptedStateFile` is unused (unused)
var encodedStateFile string

func Wrap() {
passphrase := os.Getenv("VAULTIFY_PASSPHRASE")
if passphrase == "" {
fmt.Println("❌ Error: \033[33mVAULTIFY_PASSPHRASE\033[0m environemnt variable not set.")
os.Exit(1)
}
files, err := filepath.Glob("*.tfstate")
if err != nil {
fmt.Println("❌ Error searching for .tfstate files:", err)
Expand All @@ -50,7 +56,13 @@ func Wrap() {
os.Exit(1)
}

encodedStateFile = base64.StdEncoding.EncodeToString(compressedStateFile)
encryptedFile, err := encryptContents(compressedStateFile, passphrase)
if err != nil {
fmt.Println("❌ Error encrypting contents of compressed state file:", err)
os.Exit(1)
}

encodedStateFile = base64.StdEncoding.EncodeToString(encryptedFile)

// Set the environment variable
os.Setenv("TERRAFORM_STATE_BASE64", encodedStateFile)
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ go 1.21.4
require (
github.com/Azure/azure-sdk-for-go v68.0.0+incompatible
github.com/hashicorp/vault/api v1.13.0
golang.org/x/crypto v0.19.0
)

require (
Expand Down Expand Up @@ -32,7 +33,6 @@ require (
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/ryanuber/go-glob v1.0.0 // indirect
golang.org/x/crypto v0.19.0 // indirect
golang.org/x/net v0.17.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/time v0.0.0-20220922220347-f3bd1da661af // indirect
Expand Down
2 changes: 2 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@ func main() {
handleDeleteVaultCommand(os.Args[2:])
return
}
case "pwgen":
cmd.GenPassphrase()
default:
fmt.Printf("Unknown command: \033[33m%s\033[0m\n", os.Args[1])
fmt.Println("Use \033[33m'vaultify -h'\033[0m for help.")
Expand Down

0 comments on commit 7f12cc4

Please sign in to comment.