-
Notifications
You must be signed in to change notification settings - Fork 75
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Add keychain provider for reading text/yaml/json secrets (#585)
* feat: Add keychain provider for reading text/yaml/json secrets Signed-off-by: Dmitry K. Anisimov <mail@anisimov.dk> * fix: use security cmd for keychain secret Signed-off-by: Dmitry K. Anisimov <mail@anisimov.dk> * fix: add some tests Signed-off-by: Dmitry K. Anisimov <mail@anisimov.dk> * fix: fix test Signed-off-by: Dmitry K. Anisimov <mail@anisimov.dk> --------- Signed-off-by: Dmitry K. Anisimov <mail@anisimov.dk>
- Loading branch information
1 parent
258e43d
commit ab686ca
Showing
4 changed files
with
151 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
package keychain | ||
|
||
import ( | ||
"encoding/hex" | ||
"errors" | ||
"os/exec" | ||
"runtime" | ||
"strings" | ||
|
||
"gopkg.in/yaml.v3" | ||
|
||
"github.com/helmfile/vals/pkg/api" | ||
) | ||
|
||
const keychainKind = "vals-secret" | ||
|
||
type provider struct { | ||
} | ||
|
||
func New(cfg api.StaticConfig) *provider { | ||
p := &provider{} | ||
return p | ||
} | ||
|
||
// isHex checks if a string is a valid hexadecimal string | ||
func isHex(s string) bool { | ||
// Check if the string length is even | ||
if len(s)%2 != 0 { | ||
return false | ||
} | ||
|
||
// Attempt to decode the string | ||
_, err := hex.DecodeString(s) | ||
return err == nil // If no error, it's valid hex | ||
} | ||
|
||
// isDarwin checks if the current OS is macOS | ||
func isDarwin() bool { | ||
return runtime.GOOS == "darwin" | ||
} | ||
|
||
// getKeychainSecret retrieves a secret from the macOS keychain with security find-generic-password | ||
func getKeychainSecret(key string) ([]byte, error) { | ||
if !isDarwin() { | ||
return nil, errors.New("keychain provider is only supported on macOS") | ||
} | ||
|
||
// Get the secret from the keychain with 'security find-generic-password' command | ||
getKeyCmd := exec.Command("security", "find-generic-password", "-w", "-D", keychainKind, "-s", key) | ||
|
||
result, err := getKeyCmd.Output() | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
stringResult := string(result) | ||
stringResult = strings.TrimSpace(stringResult) | ||
|
||
// If the result is a hexadecimal string, decode it. | ||
if isHex(stringResult) { | ||
result, err = hex.DecodeString(stringResult) | ||
if err != nil { | ||
return nil, err | ||
} | ||
} | ||
|
||
return result, nil | ||
} | ||
|
||
func (p *provider) GetString(key string) (string, error) { | ||
key = strings.TrimSuffix(key, "/") | ||
key = strings.TrimSpace(key) | ||
|
||
secret, err := getKeychainSecret(key) | ||
if err != nil { | ||
return "", err | ||
} | ||
|
||
return string(secret), err | ||
} | ||
|
||
func (p *provider) GetStringMap(key string) (map[string]interface{}, error) { | ||
key = strings.TrimSuffix(key, "/") | ||
key = strings.TrimSpace(key) | ||
|
||
secret, err := getKeychainSecret(key) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
m := map[string]interface{}{} | ||
if err := yaml.Unmarshal(secret, &m); err != nil { | ||
return nil, err | ||
} | ||
return m, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
package keychain | ||
|
||
import ( | ||
"testing" | ||
) | ||
|
||
func Test_isHex(t *testing.T) { | ||
tests := []struct { | ||
input string | ||
expected bool | ||
}{ | ||
{"a1b2c3", true}, | ||
{"A1B2C3", true}, | ||
{"1234567890abcdef", true}, | ||
{"12345", false}, // Odd length | ||
{"g1h2", false}, // Non-hex characters | ||
{"!@#$", false}, // Special characters | ||
{"abcdefa", false}, // Odd length with valid hex characters | ||
{"ABCDEF", true}, | ||
{"abcdef", true}, | ||
{"1234abcd", true}, | ||
{"1234abcg", false}, // Contains 'g' | ||
{"12 34", false}, // Contains space | ||
{"", true}, // Empty string | ||
} | ||
|
||
for _, tt := range tests { | ||
t.Run(tt.input, func(t *testing.T) { | ||
result := isHex(tt.input) | ||
if result != tt.expected { | ||
t.Errorf("isHex(%q) = %v; want %v", tt.input, result, tt.expected) | ||
} | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters