Skip to content

Commit 61d9403

Browse files
authored
Merge pull request #1880 from lucqui/adding-native-list-support-for-keys
Resolves #1864. Adds Native List as an option for configuring keys.
2 parents fd277e0 + ce5694a commit 61d9403

2 files changed

Lines changed: 157 additions & 24 deletions

File tree

config/config.go

Lines changed: 120 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -170,23 +170,86 @@ type destinationRule struct {
170170
}
171171

172172
type creationRule struct {
173-
PathRegex string `yaml:"path_regex"`
174-
KMS string
175-
AwsProfile string `yaml:"aws_profile"`
176-
Age string `yaml:"age"`
177-
PGP string
178-
GCPKMS string `yaml:"gcp_kms"`
179-
AzureKeyVault string `yaml:"azure_keyvault"`
180-
VaultURI string `yaml:"hc_vault_transit_uri"`
181-
KeyGroups []keyGroup `yaml:"key_groups"`
182-
ShamirThreshold int `yaml:"shamir_threshold"`
183-
UnencryptedSuffix string `yaml:"unencrypted_suffix"`
184-
EncryptedSuffix string `yaml:"encrypted_suffix"`
185-
UnencryptedRegex string `yaml:"unencrypted_regex"`
186-
EncryptedRegex string `yaml:"encrypted_regex"`
187-
UnencryptedCommentRegex string `yaml:"unencrypted_comment_regex"`
188-
EncryptedCommentRegex string `yaml:"encrypted_comment_regex"`
189-
MACOnlyEncrypted bool `yaml:"mac_only_encrypted"`
173+
PathRegex string `yaml:"path_regex"`
174+
KMS interface{} `yaml:"kms"` // string or []string
175+
AwsProfile string `yaml:"aws_profile"`
176+
Age interface{} `yaml:"age"` // string or []string
177+
PGP interface{} `yaml:"pgp"` // string or []string
178+
GCPKMS interface{} `yaml:"gcp_kms"` // string or []string
179+
AzureKeyVault interface{} `yaml:"azure_keyvault"` // string or []string
180+
VaultURI interface{} `yaml:"hc_vault_transit_uri"` // string or []string
181+
KeyGroups []keyGroup `yaml:"key_groups"`
182+
ShamirThreshold int `yaml:"shamir_threshold"`
183+
UnencryptedSuffix string `yaml:"unencrypted_suffix"`
184+
EncryptedSuffix string `yaml:"encrypted_suffix"`
185+
UnencryptedRegex string `yaml:"unencrypted_regex"`
186+
EncryptedRegex string `yaml:"encrypted_regex"`
187+
UnencryptedCommentRegex string `yaml:"unencrypted_comment_regex"`
188+
EncryptedCommentRegex string `yaml:"encrypted_comment_regex"`
189+
MACOnlyEncrypted bool `yaml:"mac_only_encrypted"`
190+
}
191+
192+
// Helper methods to safely extract keys as []string
193+
func (c *creationRule) GetKMSKeys() ([]string, error) {
194+
return parseKeyField(c.KMS, "kms")
195+
}
196+
197+
func (c *creationRule) GetAgeKeys() ([]string, error) {
198+
return parseKeyField(c.Age, "age")
199+
}
200+
201+
func (c *creationRule) GetPGPKeys() ([]string, error) {
202+
return parseKeyField(c.PGP, "pgp")
203+
}
204+
205+
func (c *creationRule) GetGCPKMSKeys() ([]string, error) {
206+
return parseKeyField(c.GCPKMS, "gcp_kms")
207+
}
208+
209+
func (c *creationRule) GetAzureKeyVaultKeys() ([]string, error) {
210+
return parseKeyField(c.AzureKeyVault, "azure_keyvault")
211+
}
212+
213+
func (c *creationRule) GetVaultURIs() ([]string, error) {
214+
return parseKeyField(c.VaultURI, "hc_vault_transit_uri")
215+
}
216+
217+
// Utility function to handle both string and []string
218+
func parseKeyField(field interface{}, fieldName string) ([]string, error) {
219+
if field == nil {
220+
return []string{}, nil
221+
}
222+
223+
switch v := field.(type) {
224+
case string:
225+
if v == "" {
226+
return []string{}, nil
227+
}
228+
// Existing CSV parsing logic
229+
keys := strings.Split(v, ",")
230+
result := make([]string, 0, len(keys))
231+
for _, key := range keys {
232+
trimmed := strings.TrimSpace(key)
233+
if trimmed != "" { // Skip empty strings (fixes trailing comma issue)
234+
result = append(result, trimmed)
235+
}
236+
}
237+
return result, nil
238+
case []interface{}:
239+
result := make([]string, len(v))
240+
for i, item := range v {
241+
if str, ok := item.(string); ok {
242+
result[i] = str
243+
} else {
244+
return nil, fmt.Errorf("invalid %s key configuration: expected string in list, got %T", fieldName, item)
245+
}
246+
}
247+
return result, nil
248+
case []string:
249+
return v, nil
250+
default:
251+
return nil, fmt.Errorf("invalid %s key configuration: expected string, []string, or nil, got %T", fieldName, field)
252+
}
190253
}
191254

192255
func NewStoresConfig() *StoresConfig {
@@ -279,6 +342,14 @@ func extractMasterKeys(group keyGroup) (sops.KeyGroup, error) {
279342
return deduplicateKeygroup(keyGroup), nil
280343
}
281344

345+
func getKeysWithValidation(getKeysFunc func() ([]string, error), keyType string) ([]string, error) {
346+
keys, err := getKeysFunc()
347+
if err != nil {
348+
return nil, fmt.Errorf("invalid %s key configuration: %w", keyType, err)
349+
}
350+
return keys, nil
351+
}
352+
282353
func getKeyGroupsFromCreationRule(cRule *creationRule, kmsEncryptionContext map[string]*string) ([]sops.KeyGroup, error) {
283354
var groups []sops.KeyGroup
284355
if len(cRule.KeyGroups) > 0 {
@@ -291,8 +362,13 @@ func getKeyGroupsFromCreationRule(cRule *creationRule, kmsEncryptionContext map[
291362
}
292363
} else {
293364
var keyGroup sops.KeyGroup
294-
if cRule.Age != "" {
295-
ageKeys, err := age.MasterKeysFromRecipients(cRule.Age)
365+
ageKeys, err := getKeysWithValidation(cRule.GetAgeKeys, "age")
366+
if err != nil {
367+
return nil, err
368+
}
369+
370+
if len(ageKeys) > 0 {
371+
ageKeys, err := age.MasterKeysFromRecipients(strings.Join(ageKeys, ","))
296372
if err != nil {
297373
return nil, err
298374
} else {
@@ -301,23 +377,43 @@ func getKeyGroupsFromCreationRule(cRule *creationRule, kmsEncryptionContext map[
301377
}
302378
}
303379
}
304-
for _, k := range pgp.MasterKeysFromFingerprintString(cRule.PGP) {
380+
pgpKeys, err := getKeysWithValidation(cRule.GetPGPKeys, "pgp")
381+
if err != nil {
382+
return nil, err
383+
}
384+
for _, k := range pgp.MasterKeysFromFingerprintString(strings.Join(pgpKeys, ",")) {
305385
keyGroup = append(keyGroup, k)
306386
}
307-
for _, k := range kms.MasterKeysFromArnString(cRule.KMS, kmsEncryptionContext, cRule.AwsProfile) {
387+
kmsKeys, err := getKeysWithValidation(cRule.GetKMSKeys, "kms")
388+
if err != nil {
389+
return nil, err
390+
}
391+
for _, k := range kms.MasterKeysFromArnString(strings.Join(kmsKeys, ","), kmsEncryptionContext, cRule.AwsProfile) {
308392
keyGroup = append(keyGroup, k)
309393
}
310-
for _, k := range gcpkms.MasterKeysFromResourceIDString(cRule.GCPKMS) {
394+
gcpkmsKeys, err := getKeysWithValidation(cRule.GetGCPKMSKeys, "gcpkms")
395+
if err != nil {
396+
return nil, err
397+
}
398+
for _, k := range gcpkms.MasterKeysFromResourceIDString(strings.Join(gcpkmsKeys, ",")) {
311399
keyGroup = append(keyGroup, k)
312400
}
313-
azureKeys, err := azkv.MasterKeysFromURLs(cRule.AzureKeyVault)
401+
azKeys, err := getKeysWithValidation(cRule.GetAzureKeyVaultKeys, "azure_keyvault")
402+
if err != nil {
403+
return nil, err
404+
}
405+
azureKeys, err := azkv.MasterKeysFromURLs(strings.Join(azKeys, ","))
314406
if err != nil {
315407
return nil, err
316408
}
317409
for _, k := range azureKeys {
318410
keyGroup = append(keyGroup, k)
319411
}
320-
vaultKeys, err := hcvault.NewMasterKeysFromURIs(cRule.VaultURI)
412+
vaultKeyUris, err := getKeysWithValidation(cRule.GetVaultURIs, "vault")
413+
if err != nil {
414+
return nil, err
415+
}
416+
vaultKeys, err := hcvault.NewMasterKeysFromURIs(strings.Join(vaultKeyUris, ","))
321417
if err != nil {
322418
return nil, err
323419
}

config/config_test.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -718,3 +718,40 @@ func TestLoadConfigFileWithVaultDestinationRules(t *testing.T) {
718718
assert.NotNil(t, conf.Destination)
719719
assert.Contains(t, conf.Destination.Path("barfoo"), "/v1/kv/barfoo/barfoo")
720720
}
721+
722+
func TestCreationRuleNativeKeyLists(t *testing.T) {
723+
var sampleConfigWithNativeKeyLists = []byte(`
724+
creation_rules:
725+
- path_regex: native_list*
726+
pgp:
727+
- "85D77543B3D624B63CEA9E6DBC17301B491B3F21" # name@email.com
728+
- "FBC7B9E2A4F9289AC0C1D4843D16CEE4A27381B4" # server_XYZ
729+
kms:
730+
- "arn:aws:kms:us-east-1:123456789012:key/12345678-1234-1234-1234-123456789012"
731+
age:
732+
- "age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p"
733+
gcp_kms:
734+
- "projects/test-project/locations/global/keyRings/test-ring/cryptoKeys/test-key"
735+
hc_vault_transit_uri:
736+
- "https://vault.example.com:8200/v1/transit/keys/key1"
737+
`)
738+
conf, err := parseCreationRuleForFile(parseConfigFile(sampleConfigWithNativeKeyLists, t), "/conf/path", "native_list_test", nil)
739+
assert.Nil(t, err)
740+
if conf == nil {
741+
t.Fatal("Expected configuration but got nil")
742+
}
743+
744+
assert.True(t, len(conf.KeyGroups) == 1)
745+
assert.True(t, len(conf.KeyGroups[0]) == 6)
746+
747+
keyTypeCounts := make(map[string]int)
748+
for _, key := range conf.KeyGroups[0] {
749+
keyTypeCounts[key.TypeToIdentifier()]++
750+
}
751+
752+
assert.Equal(t, 2, keyTypeCounts["pgp"])
753+
assert.Equal(t, 1, keyTypeCounts["kms"])
754+
assert.Equal(t, 1, keyTypeCounts["age"])
755+
assert.Equal(t, 1, keyTypeCounts["gcp_kms"])
756+
assert.Equal(t, 1, keyTypeCounts["hc_vault"])
757+
}

0 commit comments

Comments
 (0)