From b767b03f211acc78f0aa33a4ef8ddcf969c8e151 Mon Sep 17 00:00:00 2001 From: petems Date: Wed, 3 Oct 2018 14:46:07 +0100 Subject: [PATCH] Adding GCP Auth backend resource * Allows configuration of credentials for backend --- vault/provider.go | 1 + vault/resource_gcp_auth_backend.go | 196 ++++++++++++++++++++++++ vault/resource_gcp_auth_backend_test.go | 87 +++++++++++ website/docs/r/gcp_auth_backend.html.md | 39 +++++ 4 files changed, 323 insertions(+) create mode 100644 vault/resource_gcp_auth_backend.go create mode 100644 vault/resource_gcp_auth_backend_test.go create mode 100644 website/docs/r/gcp_auth_backend.html.md diff --git a/vault/provider.go b/vault/provider.go index 03ab27253..bf89cbce0 100644 --- a/vault/provider.go +++ b/vault/provider.go @@ -110,6 +110,7 @@ func Provider() terraform.ResourceProvider { "vault_consul_secret_backend": consulSecretBackendResource(), "vault_database_secret_backend_connection": databaseSecretBackendConnectionResource(), "vault_database_secret_backend_role": databaseSecretBackendRoleResource(), + "vault_gcp_auth_backend": gcpAuthBackendResource(), "vault_gcp_auth_backend_role": gcpAuthBackendRoleResource(), "vault_gcp_secret_backend": gcpSecretBackendResource(), "vault_cert_auth_backend_role": certAuthBackendRoleResource(), diff --git a/vault/resource_gcp_auth_backend.go b/vault/resource_gcp_auth_backend.go new file mode 100644 index 000000000..904b929e1 --- /dev/null +++ b/vault/resource_gcp_auth_backend.go @@ -0,0 +1,196 @@ +package vault + +import ( + "encoding/json" + "fmt" + "log" + "strings" + + "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/vault/api" +) + +const gcpAuthType string = "gcp" + +func gcpAuthBackendResource() *schema.Resource { + return &schema.Resource{ + + Create: gcpAuthBackendWrite, + Update: gcpAuthBackendUpdate, + Read: gcpAuthBackendRead, + Delete: gcpAuthBackendDelete, + Exists: gcpAuthBackendExists, + + Schema: map[string]*schema.Schema{ + "credentials": { + Type: schema.TypeString, + Required: true, + StateFunc: NormalizeCredentials, + ValidateFunc: ValidateCredentials, + Sensitive: true, + }, + "description": { + Type: schema.TypeString, + Optional: true, + }, + "client_id": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "private_key_id": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "project_id": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "client_email": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "path": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Default: "gcp", + StateFunc: func(v interface{}) string { + return strings.Trim(v.(string), "/") + }, + }, + }, + } +} + +func ValidateCredentials(configI interface{}, k string) ([]string, []error) { + credentials := configI.(string) + dataMap := map[string]interface{}{} + err := json.Unmarshal([]byte(credentials), &dataMap) + if err != nil { + return nil, []error{err} + } + return nil, nil +} + +func NormalizeCredentials(configI interface{}) string { + credentials := configI.(string) + + dataMap := map[string]interface{}{} + err := json.Unmarshal([]byte(credentials), &dataMap) + if err != nil { + // The validate function should've taken care of this. + log.Printf("[ERROR] Invalid JSON data in vault_gcp_auth_backend: %s", err) + return "" + } + + ret, err := json.Marshal(dataMap) + if err != nil { + // Should never happen. + log.Printf("[ERROR] Problem normalizing JSON for vault_gcp_auth_backend: %s", err) + return credentials + } + + return string(ret) +} + +func gcpAuthBackendConfigPath(path string) string { + return "auth/" + strings.Trim(path, "/") + "/config" +} + +func gcpAuthBackendWrite(d *schema.ResourceData, meta interface{}) error { + client := meta.(*api.Client) + + authType := gcpAuthType + path := d.Get("path").(string) + desc := d.Get("description").(string) + + log.Printf("[DEBUG] Enabling gcp auth backend %q", path) + err := client.Sys().EnableAuth(path, authType, desc) + if err != nil { + return fmt.Errorf("error enabling gcp auth backend %q: %s", path, err) + } + log.Printf("[DEBUG] Enabled gcp auth backend %q", path) + + d.SetId(path) + + return gcpAuthBackendUpdate(d, meta) +} + +func gcpAuthBackendUpdate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*api.Client) + + path := gcpAuthBackendConfigPath(d.Id()) + data := map[string]interface{}{} + + if v, ok := d.GetOk("credentials"); ok { + data["credentials"] = v.(string) + } + + log.Printf("[DEBUG] Writing gcp config %q", path) + _, err := client.Logical().Write(path, data) + + if err != nil { + d.SetId("") + return fmt.Errorf("error writing gcp config %q: %s", path, err) + } + log.Printf("[DEBUG] Wrote gcp config %q", path) + + return gcpAuthBackendRead(d, meta) +} + +func gcpAuthBackendRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*api.Client) + path := gcpAuthBackendConfigPath(d.Id()) + + log.Printf("[DEBUG] Reading gcp auth backend config %q", path) + resp, err := client.Logical().Read(path) + if err != nil { + return fmt.Errorf("error reading gcp auth backend config %q: %s", path, err) + } + log.Printf("[DEBUG] Read gcp auth backend config %q", path) + + if resp == nil { + log.Printf("[WARN] gcp auth backend config %q not found, removing from state", path) + d.SetId("") + return nil + } + + d.Set("private_key_id", resp.Data["private_key_id"]) + d.Set("client_id", resp.Data["client_id"]) + d.Set("project_id", resp.Data["project_id"]) + d.Set("client_email", resp.Data["client_email"]) + + return nil +} + +func gcpAuthBackendDelete(d *schema.ResourceData, meta interface{}) error { + client := meta.(*api.Client) + path := d.Id() + + log.Printf("[DEBUG] Deleting gcp auth backend %q", path) + err := client.Sys().DisableAuth(path) + if err != nil { + return fmt.Errorf("error deleting gcp auth backend %q: %q", path, err) + } + log.Printf("[DEBUG] Deleted gcp auth backend %q", path) + + return nil +} + +func gcpAuthBackendExists(d *schema.ResourceData, meta interface{}) (bool, error) { + client := meta.(*api.Client) + path := gcpAuthBackendConfigPath(d.Id()) + + log.Printf("[DEBUG] Checking if gcp auth backend %q exists", path) + resp, err := client.Logical().Read(path) + if err != nil { + return true, fmt.Errorf("error checking for existence of gcp config %q: %s", path, err) + } + log.Printf("[DEBUG] Checked if gcp auth backend %q exists", path) + + return resp != nil, nil +} diff --git a/vault/resource_gcp_auth_backend_test.go b/vault/resource_gcp_auth_backend_test.go new file mode 100644 index 000000000..8d86d9a33 --- /dev/null +++ b/vault/resource_gcp_auth_backend_test.go @@ -0,0 +1,87 @@ +package vault + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + "github.com/hashicorp/vault/api" +) + +const gcpJSONCredentials string = ` +{ + "type": "service_account", + "project_id": "terraform-vault-provider-a13efc8a", + "private_key_id": "b1e1f3cdd7fc134afsdg3547828dc2bb9dff8480", + "private_key": "-----BEGIN PRIVATE KEY-----\nABC123\n-----END PRIVATE KEY-----\n", + "client_email": "terraform-vault-user@terraform-vault-provider-adf134rfds.iam.gserviceaccount.com", + "client_id": "123134135242342423", + "auth_uri": "https://accounts.google.com/o/oauth2/auth", + "token_uri": "https://oauth2.googleapis.com/token", + "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", + "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/vault-auth-checker%40terraform-vault-provider-adf134rfds.iam.gserviceaccount.com" + } +` + +func TestGCPAuthBackend_basic(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testProviders, + CheckDestroy: testGCPAuthBackendDestroy, + Steps: []resource.TestStep{ + { + Config: testGCPAuthBackendConfig_basic(gcpJSONCredentials), + Check: testGCPAuthBackendCheck_attrs(), + }, + }, + }) +} + +func testGCPAuthBackendDestroy(s *terraform.State) error { + client := testProvider.Meta().(*api.Client) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "vault_gcp_auth_backend" { + continue + } + secret, err := client.Logical().Read(rs.Primary.ID) + if err != nil { + return fmt.Errorf("error checking for gcp auth backend %q: %s", rs.Primary.ID, err) + } + if secret != nil { + return fmt.Errorf("gcp auth backend %q still exists", rs.Primary.ID) + } + } + return nil +} + +func testGCPAuthBackendCheck_attrs() resource.TestCheckFunc { + return func(s *terraform.State) error { + resourceState := s.Modules[0].Resources["vault_gcp_auth_backend.test"] + if resourceState == nil { + return fmt.Errorf("resource not found in state") + } + + instanceState := resourceState.Primary + if instanceState == nil { + return fmt.Errorf("resource has no primary instance") + } + + return nil + } +} + +func testGCPAuthBackendConfig_basic(credentials string) string { + return fmt.Sprintf(` +variable "json_credentials" { + type = "string" + default = %q +} + +resource "vault_gcp_auth_backend" "test" { + credentials = "${var.json_credentials}" +} +`, credentials) + +} diff --git a/website/docs/r/gcp_auth_backend.html.md b/website/docs/r/gcp_auth_backend.html.md new file mode 100644 index 000000000..3c87a28d3 --- /dev/null +++ b/website/docs/r/gcp_auth_backend.html.md @@ -0,0 +1,39 @@ +--- +layout: "vault" +page_title: "Vault: vault_auth_backend resource" +sidebar_current: "docs-vault-resource-gcp-auth-backend-role" +description: |- + Managing roles in an GCP auth backend in Vault +--- + +# vault\_gcp\_auth\_backend\ + +Provides a resource to configure the [GCP auth backend within Vault](https://www.vaultproject.io/docs/auth/gcp.html). + +## Example Usage + +```hcl +resource "vault_gcp_auth_backend" "gcp" { + credentials = "${file("vault-gcp-credentials.json")}" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `credentials` - (Required) A JSON string containing the contents of a GCP credentials file. + +For more details on the usage of each argument consult the [Vault GCP API documentation](https://www.vaultproject.io/api/auth/gcp/index.html#configure). + +## Attribute Reference + +In addition to the fields above, the following attributes are also exposed: + +* `client_id` - The Client ID of the credentials + +* `private_key_id` - The ID of the private key from the credentials + +* `project_id` - The GCP Project ID + +* `client_email` - The clients email assosiated with the credentials