Do not panic on Vault PKI roles without the cn_validations field (#2398)

* Do not panic on Vault PKI roles without the cn_validations field

 - When we added support for the cn_validations field within PR1820
   the code was tested against Vault versions 1.12 and higher.
 - If Vault is an earlier version than 1.12 TFVP will panic as we
   attempt to cast the missing field to an []interface{} value.
 - This fix hardens the parsing path for slice values within the
   PKI role parsing to properly extract out a []string value

* Add changelog
This commit is contained in:
Steven Clark 2025-01-27 13:01:16 -05:00 committed by GitHub
parent 4b26c7a2c3
commit 6af08b851b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 84 additions and 5 deletions

View file

@ -1,5 +1,10 @@
## Unreleased
BUGS:
* Do not panic on Vault PKI roles without the cn_validations field: ([#2398](https://github.com/hashicorp/terraform-provider-vault/pull/2398))
## 4.6.0 (Jan 15, 2025)
FEATURES:

View file

@ -14,6 +14,7 @@ import (
"time"
"github.com/cenkalti/backoff/v4"
"github.com/hashicorp/go-secure-stdlib/parseutil"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/vault/api"
)
@ -450,3 +451,40 @@ func RetryWrite(client *api.Client, path string, data map[string]interface{}, re
log.Printf("[WARN] Writing to path %q failed, retrying in %s", path, duration)
})
}
// GetStringSliceFromSecret will return a string slice from the secret data within the provided field name if it exists.
// The bool return value will be false if the field does not exist or is not a string slice. It will be true if the field
// exists and was an empty slice.
func GetStringSliceFromSecret(secret *api.Secret, fieldName string) ([]string, bool) {
if secret == nil || secret.Data == nil {
return nil, false
}
rawVal, exists := secret.Data[fieldName]
if !exists {
return nil, false
}
rv := reflect.ValueOf(rawVal)
switch rv.Kind() {
case reflect.Slice:
if rv.IsNil() {
return nil, false
}
case reflect.Array:
default:
return nil, false
}
output := make([]string, rv.Len())
for i := 0; i < rv.Len(); i++ {
myStr, err := parseutil.ParseString(rv.Index(i).Interface())
if err != nil {
return nil, false
}
output[i] = myStr
}
return output, true
}

View file

@ -871,3 +871,39 @@ func TestRetryWrite(t *testing.T) {
})
}
}
func TestGetStringSliceFromSecret(t *testing.T) {
fieldName := "foo"
var testArray [2]string
testArray[0] = "1"
testArray[1] = "2"
tests := []struct {
name string
secretData map[string]interface{}
want []string
wantOk bool
}{
{"nil-data", nil, nil, false},
{"field-missing", map[string]interface{}{}, nil, false},
{"nil-element", map[string]interface{}{fieldName: nil}, nil, false},
{"not-a-slice", map[string]interface{}{fieldName: "test-value"}, nil, false},
{"array-val", map[string]interface{}{fieldName: testArray}, []string{"1", "2"}, true},
{"mixed-slice", map[string]interface{}{fieldName: []interface{}{1, "2"}}, []string{"1", "2"}, true},
{"int-slice", map[string]interface{}{fieldName: []int{1, 2}}, []string{"1", "2"}, true},
{"string-slice", map[string]interface{}{fieldName: []string{"test1", "test2"}}, []string{"test1", "test2"}, true},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
secret := &api.Secret{Data: tt.secretData}
got, gotOk := GetStringSliceFromSecret(secret, fieldName)
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("GetStringSliceFromSecret() got = %v, want %v", got, tt.want)
}
if gotOk != tt.wantOk {
t.Errorf("GetStringSliceFromSecret() gotOk = %v, wantOk %v", gotOk, tt.wantOk)
}
})
}
}

View file

@ -14,10 +14,10 @@ import (
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
"github.com/hashicorp/terraform-provider-vault/internal/consts"
"github.com/hashicorp/terraform-provider-vault/internal/pki"
"github.com/hashicorp/terraform-provider-vault/internal/provider"
"github.com/hashicorp/terraform-provider-vault/util"
)
var (
@ -575,10 +575,10 @@ func pkiSecretBackendRoleRead(_ context.Context, d *schema.ResourceData, meta in
listFields := append(pkiSecretListFields, consts.FieldKeyUsage)
// handle TypeList
for _, k := range listFields {
list := expandStringSlice(secret.Data[k].([]interface{}))
if len(list) > 0 {
d.Set(k, list)
if list, ok := util.GetStringSliceFromSecret(secret, k); ok {
if len(list) > 0 {
d.Set(k, list)
}
}
}