Skip to content

Commit

Permalink
Add resource for GCP Static Account (hashicorp#1094)
Browse files Browse the repository at this point in the history
* Refactor some GCP functions out to a separate file

* Add GCP Static account resource

* Rename Binding type

* Add tests

* Update

* Update prefix

Ref hashicorp/vault-plugin-secrets-gcp@8fba665

* Add docs

* Apply suggestions from code review

Co-authored-by: Austin Gebauer <34121980+austingebauer@users.noreply.github.com>

* Rename function

* Update vault/resource_gcp_secret_static_account.go

Co-authored-by: Ben Ash <32777270+benashz@users.noreply.github.com>

* Add some comments

Co-authored-by: Austin Gebauer <34121980+austingebauer@users.noreply.github.com>
Co-authored-by: Ben Ash <32777270+benashz@users.noreply.github.com>
  • Loading branch information
3 people authored and davidmontoyago committed Aug 17, 2021
1 parent 1cd05eb commit 472c2a0
Show file tree
Hide file tree
Showing 9 changed files with 864 additions and 100 deletions.
1 change: 1 addition & 0 deletions go.mod
Expand Up @@ -22,4 +22,5 @@ require (
github.com/hashicorp/vault/sdk v0.2.1
github.com/mitchellh/go-homedir v1.1.0
github.com/rainycape/unidecode v0.0.0-20150907023854-cb7f23ec59be // indirect
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45
)
99 changes: 99 additions & 0 deletions vault/gcp.go
@@ -0,0 +1,99 @@
package vault

import (
"bytes"
"fmt"
"sort"

"github.com/hashicorp/terraform-plugin-sdk/helper/hashcode"
"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
)

/// GCPBinding is used to generate the HCL binding format that GCP Secret Engine Requires
/// `Resource` is the self-link of a GCP resource
/// Roles is a list of IAM roles to be assigned to an entity for that resource.
type GCPBinding struct {
Resource string
Roles []string
}

func gcpSecretFlattenBinding(v interface{}) interface{} {
transformed := schema.NewSet(gcpSecretBindingHash, []interface{}{})
if v == nil {
return transformed
}

rawBindings := v.((map[string]interface{}))
for resource, roles := range rawBindings {
transformed.Add(map[string]interface{}{
"resource": resource,
"roles": schema.NewSet(schema.HashString, roles.([]interface{})),
})
}

return transformed
}

func gcpSecretBindingHash(v interface{}) int {
var buf bytes.Buffer
m := v.(map[string]interface{})
buf.WriteString(fmt.Sprintf("%s-", m["resource"].(string)))

// We need to make sure to sort the strings below so that we always
// generate the same hash code no matter what is in the set.
if v, ok := m["roles"]; ok {
vs := v.(*schema.Set).List()
s := make([]string, len(vs))
for i, raw := range vs {
s[i] = raw.(string)
}
sort.Strings(s)

for _, v := range s {
buf.WriteString(fmt.Sprintf("%s-", v))
}
}
return hashcode.String(buf.String())
}

func gcpSecretRenderBinding(binding *GCPBinding) string {
output := fmt.Sprintf("resource \"%s\" {\n", binding.Resource)
output = fmt.Sprintf("%s roles = %s\n", output, policyRenderListOfStrings(binding.Roles))
return fmt.Sprintf("%s}\n", output)
}

func gcpSecretRenderBindings(bindings []*GCPBinding) string {
var output string

for i, binding := range bindings {
if i == 0 {
output = fmt.Sprintf("%s", gcpSecretRenderBinding(binding))
} else {
output = fmt.Sprintf("%s\n\n%s", output, gcpSecretRenderBinding(binding))
}
}

return output
}

func gcpSecretRenderBindingsFromData(v interface{}) string {
rawBindings := v.(*schema.Set).List()

bindings := make([]*GCPBinding, len(rawBindings))

for i, binding := range rawBindings {
rawRoles := binding.(map[string]interface{})["roles"].(*schema.Set).List()
roles := make([]string, len(rawRoles))
for j, role := range rawRoles {
roles[j] = role.(string)
}

binding := &GCPBinding{
Resource: binding.(map[string]interface{})["resource"].(string),
Roles: roles,
}
bindings[i] = binding
}

return gcpSecretRenderBindings(bindings)
}
4 changes: 4 additions & 0 deletions vault/provider.go
Expand Up @@ -427,6 +427,10 @@ var (
Resource: gcpSecretRolesetResource(),
PathInventory: []string{"/gcp/roleset/{name}"},
},
"vault_gcp_secret_static_account": {
Resource: gcpSecretStaticAccountResource(),
PathInventory: []string{"/gcp/static-account/{name}"},
},
"vault_cert_auth_backend_role": {
Resource: certAuthBackendRoleResource(),
PathInventory: []string{"/auth/cert/certs/{name}"},
Expand Down
99 changes: 5 additions & 94 deletions vault/resource_gcp_secret_roleset.go
@@ -1,15 +1,12 @@
package vault

import (
"bytes"
"fmt"
"log"
"regexp"
"sort"
"strings"

"github.com/hashicorp/terraform-plugin-sdk/helper/customdiff"
"github.com/hashicorp/terraform-plugin-sdk/helper/hashcode"
"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
"github.com/hashicorp/vault/api"
)
Expand All @@ -19,11 +16,6 @@ var (
gcpSecretRolesetNameFromPathRegex = regexp.MustCompile("^.+/roleset/(.+)$")
)

type Binding struct {
Resource string
Roles []string
}

func gcpSecretRolesetResource() *schema.Resource {
return &schema.Resource{
Create: gcpSecretRolesetCreate,
Expand Down Expand Up @@ -76,7 +68,7 @@ func gcpSecretRolesetResource() *schema.Resource {
"binding": {
Type: schema.TypeSet,
Required: true,
Set: gcpSecretRolesetBindingHash,
Set: gcpSecretBindingHash,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"resource": {
Expand Down Expand Up @@ -107,8 +99,8 @@ func gcpSecretRolesetResource() *schema.Resource {
// Due to https://github.com/hashicorp/terraform/issues/17411
// we cannot use d.HasChange("binding") directly
oldBinding, newBinding := d.GetChange("binding")
oldHcl := renderBindingsFromData(oldBinding)
newHcl := renderBindingsFromData(newBinding)
oldHcl := gcpSecretRenderBindingsFromData(oldBinding)
newHcl := gcpSecretRenderBindingsFromData(newBinding)

return d.HasChange("token_scopes") || oldHcl != newHcl
}),
Expand Down Expand Up @@ -191,7 +183,7 @@ func gcpSecretRolesetRead(d *schema.ResourceData, meta interface{}) error {
return fmt.Errorf("error reading %s for GCP Secrets backend roleset %q", "project", path)
}

if err := d.Set("binding", gcpSecretRolesetFlattenBinding(resp.Data["bindings"])); err != nil {
if err := d.Set("binding", gcpSecretFlattenBinding(resp.Data["bindings"])); err != nil {
return fmt.Errorf("error reading %s for GCP Secrets backend roleset %q", "binding", path)
}

Expand Down Expand Up @@ -230,23 +222,6 @@ func gcpSecretRolesetDelete(d *schema.ResourceData, meta interface{}) error {
return nil
}

func gcpSecretRolesetFlattenBinding(v interface{}) interface{} {
if v == nil {
return v
}

rawBindings := v.((map[string]interface{}))
transformed := schema.NewSet(gcpSecretRolesetBindingHash, []interface{}{})
for resource, roles := range rawBindings {
transformed.Add(map[string]interface{}{
"resource": resource,
"roles": schema.NewSet(schema.HashString, roles.([]interface{})),
})
}

return transformed
}

func gcpSecretRolesetUpdateFields(d *schema.ResourceData, data map[string]interface{}) {
if v, ok := d.GetOk("secret_type"); ok {
data["secret_type"] = v.(string)
Expand All @@ -261,7 +236,7 @@ func gcpSecretRolesetUpdateFields(d *schema.ResourceData, data map[string]interf
}

if v, ok := d.GetOk("binding"); ok {
bindingsHCL := renderBindingsFromData(v)
bindingsHCL := gcpSecretRenderBindingsFromData(v)
log.Printf("[DEBUG] Rendered GCP Secrets backend roleset bindings HCL:\n%s", bindingsHCL)
data["bindings"] = bindingsHCL
}
Expand All @@ -279,28 +254,6 @@ func gcpSecretRolesetExists(d *schema.ResourceData, meta interface{}) (bool, err
return secret != nil, nil
}

func gcpSecretRolesetBindingHash(v interface{}) int {
var buf bytes.Buffer
m := v.(map[string]interface{})
buf.WriteString(fmt.Sprintf("%s-", m["resource"].(string)))

// We need to make sure to sort the strings below so that we always
// generate the same hash code no matter what is in the set.
if v, ok := m["roles"]; ok {
vs := v.(*schema.Set).List()
s := make([]string, len(vs))
for i, raw := range vs {
s[i] = raw.(string)
}
sort.Strings(s)

for _, v := range s {
buf.WriteString(fmt.Sprintf("%s-", v))
}
}
return hashcode.String(buf.String())
}

func gcpSecretRolesetPath(backend, roleset string) string {
return strings.Trim(backend, "/") + "/roleset/" + strings.Trim(roleset, "/")
}
Expand All @@ -326,45 +279,3 @@ func gcpSecretRoleSetdRolesetNameFromPath(path string) (string, error) {
}
return res[1], nil
}

func renderBinding(binding *Binding) string {
output := fmt.Sprintf("resource \"%s\" {\n", binding.Resource)
output = fmt.Sprintf("%s roles = %s\n", output, policyRenderListOfStrings(binding.Roles))
return fmt.Sprintf("%s}\n", output)
}

func renderBindings(bindings []*Binding) string {
var output string

for i, binding := range bindings {
if i == 0 {
output = fmt.Sprintf("%s", renderBinding(binding))
} else {
output = fmt.Sprintf("%s\n\n%s", output, renderBinding(binding))
}
}

return output
}

func renderBindingsFromData(v interface{}) string {
rawBindings := v.(*schema.Set).List()

bindings := make([]*Binding, len(rawBindings))

for i, binding := range rawBindings {
rawRoles := binding.(map[string]interface{})["roles"].(*schema.Set).List()
roles := make([]string, len(rawRoles))
for j, role := range rawRoles {
roles[j] = role.(string)
}

binding := &Binding{
Resource: binding.(map[string]interface{})["resource"].(string),
Roles: roles,
}
bindings[i] = binding
}

return renderBindings(bindings)
}
12 changes: 6 additions & 6 deletions vault/resource_gcp_secret_roleset_test.go
Expand Up @@ -57,10 +57,10 @@ func TestGCPSecretRoleset(t *testing.T) {
),
},
{
ResourceName: "vault_gcp_secret_backend.test",
ResourceName: "vault_gcp_secret_roleset.test",
ImportState: true,
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{"credentials"},
ImportStateVerifyIgnore: []string{},
},
{
Config: updatedConfig,
Expand Down Expand Up @@ -218,9 +218,9 @@ func testGCPSecretRoleset_attrs(backend, roleset string) resource.TestCheckFunc
return fmt.Errorf("expected %s to have %d entries in state, has %d", "binding", remoteLength, localBindingsLength)
}

flattenedBindings := gcpSecretRolesetFlattenBinding(remoteBindings).(*schema.Set)
flattenedBindings := gcpSecretFlattenBinding(remoteBindings).(*schema.Set)
for _, remoteBinding := range flattenedBindings.List() {
bindingHash := strconv.Itoa(gcpSecretRolesetBindingHash(remoteBinding))
bindingHash := strconv.Itoa(gcpSecretBindingHash(remoteBinding))

remoteResource := remoteBinding.(map[string]interface{})["resource"].(string)
localResource := instanceState.Attributes["binding."+bindingHash+".resource"]
Expand Down Expand Up @@ -336,7 +336,7 @@ resource "vault_gcp_secret_roleset" "test" {
binding["resource"] = resource
binding["roles"] = schema.NewSet(schema.HashString, roles)

return terraform, gcpSecretRolesetBindingHash(binding)
return terraform, gcpSecretBindingHash(binding)
}

func testGCPSecretRoleset_service_account_key(backend, roleset, credentials, project, role string) (string, int) {
Expand Down Expand Up @@ -369,5 +369,5 @@ resource "vault_gcp_secret_roleset" "test" {
binding["resource"] = resource
binding["roles"] = schema.NewSet(schema.HashString, roles)

return terraform, gcpSecretRolesetBindingHash(binding)
return terraform, gcpSecretBindingHash(binding)
}

0 comments on commit 472c2a0

Please sign in to comment.