Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding GCP Auth backend resource #198

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions vault/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down
196 changes: 196 additions & 0 deletions vault/resource_gcp_auth_backend.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
package vault

import (
"encoding/json"
"fmt"
"log"
"strings"

"github.com/hashicorp/terraform/helper/schema"
"github.com/hashicorp/vault/api"
petems marked this conversation as resolved.
Show resolved Hide resolved
)

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
}
87 changes: 87 additions & 0 deletions vault/resource_gcp_auth_backend_test.go
Original file line number Diff line number Diff line change
@@ -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 {
petems marked this conversation as resolved.
Show resolved Hide resolved
return fmt.Sprintf(`
variable "json_credentials" {
type = "string"
default = %q
}

resource "vault_gcp_auth_backend" "test" {
credentials = "${var.json_credentials}"
}
`, credentials)

}
39 changes: 39 additions & 0 deletions website/docs/r/gcp_auth_backend.html.md
Original file line number Diff line number Diff line change
@@ -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