Skip to content

Commit

Permalink
Support AWS auth backend certs.
Browse files Browse the repository at this point in the history
Add a vault_aws_auth_backend_cert resource that can manage the
certificates configured on an AWS auth backend.
  • Loading branch information
paddycarver committed Oct 3, 2017
1 parent 388dbf7 commit 7e59fbe
Show file tree
Hide file tree
Showing 310 changed files with 141,953 additions and 4 deletions.
9 changes: 5 additions & 4 deletions vault/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,10 +88,11 @@ func Provider() terraform.ResourceProvider {
},

ResourcesMap: map[string]*schema.Resource{
"vault_auth_backend": authBackendResource(),
"vault_generic_secret": genericSecretResource(),
"vault_policy": policyResource(),
"vault_mount": mountResource(),
"vault_aws_auth_backend_cert": awsAuthBackendCertResource(),
"vault_auth_backend": authBackendResource(),
"vault_generic_secret": genericSecretResource(),
"vault_policy": policyResource(),
"vault_mount": mountResource(),
},
}
}
Expand Down
180 changes: 180 additions & 0 deletions vault/resource_aws_auth_backend_cert.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
package vault

import (
"encoding/base64"
"fmt"
"log"
"regexp"
"strings"

"github.com/hashicorp/terraform/helper/schema"
"github.com/hashicorp/terraform/helper/validation"
"github.com/hashicorp/vault/api"
)

var (
awsAuthBackendCertBackendFromPathRegex = regexp.MustCompile("^auth/(.+)/config/certificate/.+$")
awsAuthBackendCertNameFromPathRegex = regexp.MustCompile("^auth/.+/config/certificate/(.+$)")
)

func awsAuthBackendCertResource() *schema.Resource {
return &schema.Resource{
Create: awsAuthBackendCertCreate,
Read: awsAuthBackendCertRead,
Delete: awsAuthBackendCertDelete,
Exists: awsAuthBackendCertExists,
Importer: &schema.ResourceImporter{
State: schema.ImportStatePassthrough,
},

Schema: map[string]*schema.Schema{
"cert_name": {
Type: schema.TypeString,
Required: true,
Description: "Name of the certificate to configure.",
ForceNew: true,
},
"aws_public_cert": {
Type: schema.TypeString,
Required: true,
Description: "Base64 encoded AWS Public key required to verify PKCS7 signature of the EC2 instance metadata.",
ForceNew: true,
/*DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool {
return base64.StdEncoding.EncodeToString([]byte(old)) == new ||
base64.RawStdEncoding.EncodeToString([]byte(old)) == new ||
base64.URLEncoding.EncodeToString([]byte(old)) == new
},*/
},
"type": {
Type: schema.TypeString,
Optional: true,
Description: "The type of document that can be verified using the certificate. Must be either \"pkcs7\" or \"identity\".",
ForceNew: true,
Default: "pkcs7",
ValidateFunc: validation.StringInSlice([]string{"pkcs7", "identity"}, false),
},
"backend": {
Type: schema.TypeString,
Optional: true,
Description: "Unique name of the auth backend to configure.",
ForceNew: true,
Default: "aws",
// standardise on no beginning or trailing slashes
StateFunc: func(v interface{}) string {
return strings.Trim(v.(string), "/")
},
},
},
}
}

func awsAuthBackendCertCreate(d *schema.ResourceData, meta interface{}) error {
client := meta.(*api.Client)

backend := d.Get("backend").(string)
certType := d.Get("type").(string)
publicCert := d.Get("aws_public_cert").(string)
name := d.Get("cert_name").(string)

path := awsAuthBackendCertPath(backend, name)

_, err := client.Logical().Write(path, map[string]interface{}{
"aws_public_cert": publicCert,
"type": certType,
})

d.SetId(path)

if err != nil {
d.SetId("")
return fmt.Errorf("Error configuring AWS auth backend cert %q: %s", path, err)
}

return awsAuthBackendCertRead(d, meta)
}

func awsAuthBackendCertRead(d *schema.ResourceData, meta interface{}) error {
client := meta.(*api.Client)

path := d.Id()

backend, err := awsAuthBackendCertBackendFromPath(path)
if err != nil {
return fmt.Errorf("Invalid path %q for AWS auth backend cert: %s", path, err)
}

name, err := awsAuthBackendCertNameFromPath(path)
if err != nil {
return fmt.Errorf("Invalid path %q for AWS auth backend cert: %s", err)
}

resp, err := client.Logical().Read(path)
if err != nil {
return fmt.Errorf("Error reading AWS auth backend cert %q: %s", path, err)
}
if resp == nil {
log.Printf("[WARN] AWS auth backend cert %q not found, removing it from state", path)
d.SetId("")
}

// the cert response gets back as undecoded base64
// to keep it simple for people referencing the cert body
// and make sure they get what they expect, we turn it back
// into base64 before putting it in the state
d.Set("aws_public_cert", base64.RawStdEncoding.EncodeToString([]byte(resp.Data["aws_public_cert"].(string))))
d.Set("type", resp.Data["type"])
d.Set("backend", backend)
d.Set("cert_name", name)

return nil
}

func awsAuthBackendCertDelete(d *schema.ResourceData, meta interface{}) error {
client := meta.(*api.Client)
path := d.Id()

_, err := client.Logical().Delete(path)
if err != nil {
return fmt.Errorf("Error deleting AWS auth backend cert %q: %s", path, err)
}

return nil
}

func awsAuthBackendCertExists(d *schema.ResourceData, meta interface{}) (bool, error) {
client := meta.(*api.Client)

path := d.Id()

resp, err := client.Logical().Read(path)
if err != nil {
return true, fmt.Errorf("Error checking for existence of AWS auth backend cert %q: %s", path, err)
}
return resp != nil, nil
}

func awsAuthBackendCertPath(backend, name string) string {
return "auth/" + strings.Trim(backend, "/") + "/config/certificate/" + strings.Trim(name, "/")
}

func awsAuthBackendCertNameFromPath(path string) (string, error) {
if !awsAuthBackendCertNameFromPathRegex.MatchString(path) {
return "", fmt.Errorf("no name found")
}
res := awsAuthBackendCertNameFromPathRegex.FindStringSubmatch(path)
if len(res) != 2 {
return "", fmt.Errorf("unexpected number of matches (%d) for name", len(res))
}
return res[1], nil
}

func awsAuthBackendCertBackendFromPath(path string) (string, error) {
if !awsAuthBackendCertBackendFromPathRegex.MatchString(path) {
return "", fmt.Errorf("no backend found")
}
res := awsAuthBackendCertBackendFromPathRegex.FindStringSubmatch(path)
if len(res) != 2 {
return "", fmt.Errorf("unexpected number of matches (%d) for backend", len(res))
}
return res[1], nil
}
129 changes: 129 additions & 0 deletions vault/resource_aws_auth_backend_cert_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
package vault

import (
"encoding/base64"
"fmt"
"testing"

"github.com/hashicorp/terraform/helper/acctest"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
"github.com/hashicorp/vault/api"
)

const testAWSPublicCert = "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUM3VENDQXEwQ0NRQ1d1a2paNVY0YVp6QUpCZ2NxaGtqT09BUURNRnd4Q3pBSkJnTlZCQVlUQWxWVE1Sa3cKRndZRFZRUUlFeEJYWVhOb2FXNW5kRzl1SUZOMFlYUmxNUkF3RGdZRFZRUUhFd2RUWldGMGRHeGxNU0F3SGdZRApWUVFLRXhkQmJXRjZiMjRnVjJWaUlGTmxjblpwWTJWeklFeE1RekFlRncweE1qQXhNRFV4TWpVMk1USmFGdzB6Ck9EQXhNRFV4TWpVMk1USmFNRnd4Q3pBSkJnTlZCQVlUQWxWVE1Sa3dGd1lEVlFRSUV4QlhZWE5vYVc1bmRHOXUKSUZOMFlYUmxNUkF3RGdZRFZRUUhFd2RUWldGMGRHeGxNU0F3SGdZRFZRUUtFeGRCYldGNmIyNGdWMlZpSUZObApjblpwWTJWeklFeE1RekNDQWJjd2dnRXNCZ2NxaGtqT09BUUJNSUlCSHdLQmdRQ2prdmNTMmJiMVZRNHl0LzVlCmloNU9PNmtLL24xTHpsbHI3RDhad3RRUDhmT0VwcDVFMm5nK0Q2VWQxWjFnWWlwcjU4S2ozbnNzU05wSTZiWDMKVnlJUXpLN3dMY2xuZC9Zb3pxTk5tZ0l5WmVjTjdFZ2xLOUlUSEpMUCt4OEZ0VXB0M1FieVlYSmRtVk1lZ042UApodmlZdDVKSC9uWWw0aGgzUGExSEpkc2tnUUlWQUxWSjNFUjExK0tvNHRQNm53dkh3aDYrRVJZUkFvR0JBSTFqCmsrdGtxTVZIdUFGY3ZBR0tvY1Rnc2pKZW02LzVxb216SnVLRG1iSk51OVF4dzNyQW90WGF1OFFlK01CY0psL1UKaGh5MUtIVnBDR2w5ZnVlUTJzNklMMENhTy9idXljVTFDaVlRazQwS05IQ2NIZk5pWmJkbHgxRTlycFVwN2JuRgpsUmEydjFudE1YM2NhUlZEZGJ0UEVXbWR4U0NZc1lGRGs0bVpyT0xCQTRHRUFBS0JnRWJtZXZlNWY4TElFL0dmCk1ObVA5Q001ZW92UU9HeDVobzhXcUQrYVRlYnMrazJ0bjkyQkJQcWVacXBXUmE1UC8ranJkS21sMXF4NGxsSFcKTVhyczNJZ0liNitoVUlCK1M4ZHo4L21tTzBicHI3NlJvWlZDWFlhYjJDWmVkRnV0N3FjM1dVSDkrRVVBSDVtdwp2U2VEQ09VTVlRUjdSOUxJTll3b3VISXppcVFZTUFrR0J5cUdTTTQ0QkFNREx3QXdMQUlVV1hCbGs0MHhUd1N3CjdIWDMyTXhYWXJ1c2U5QUNGQk5HbWRYMlpCclZOR3JOOU4yZjZST2swazlLCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K"

func TestAccAWSAuthBackendCert_import(t *testing.T) {
backend := acctest.RandomWithPrefix("aws")
name := acctest.RandomWithPrefix("test-cert")
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testProviders,
CheckDestroy: testAccCheckAWSAuthBackendCertDestroy,
Steps: []resource.TestStep{
{
Config: testAccAWSAuthBackendCertConfig_basic(backend, name),
Check: testAccAWSAuthBackendCertCheck_attrs(backend, name),
},
{
ResourceName: "vault_aws_auth_backend_cert.cert",
ImportState: true,
ImportStateVerify: true,
},
},
})
}

func TestAccAWSAuthBackendCert_basic(t *testing.T) {
backend := acctest.RandomWithPrefix("aws")
name := acctest.RandomWithPrefix("test-cert")
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testProviders,
CheckDestroy: testAccCheckAWSAuthBackendCertDestroy,
Steps: []resource.TestStep{
{
Config: testAccAWSAuthBackendCertConfig_basic(backend, name),
Check: testAccAWSAuthBackendCertCheck_attrs(backend, name),
},
},
})
}

func testAccCheckAWSAuthBackendCertDestroy(s *terraform.State) error {
client := testProvider.Meta().(*api.Client)
for _, rs := range s.RootModule().Resources {
if rs.Type != "vault_aws_auth_backend_cert" {
continue
}
secret, err := client.Logical().Read(rs.Primary.ID)
if err != nil {
return fmt.Errorf("Error checking for AWS Auth Backend certificate %q: %s", rs.Primary.ID, err)
}
if secret != nil {
return fmt.Errorf("AWS auth backend certificate %q still exists", rs.Primary.ID)
}
}
return nil
}

func testAccAWSAuthBackendCertConfig_basic(backend, name string) string {
return fmt.Sprintf(`
resource "vault_auth_backend" "aws" {
path = "%s"
type = "aws"
description = "Test auth backend for AWS backend certificates"
}
resource "vault_aws_auth_backend_cert" "cert" {
backend = "${vault_auth_backend.aws.path}"
cert_name = "%s"
aws_public_cert = "%s"
type = "pkcs7"
}`, backend, name, testAWSPublicCert)
}

func testAccAWSAuthBackendCertCheck_attrs(backend, name string) resource.TestCheckFunc {
return func(s *terraform.State) error {
resourceState := s.Modules[0].Resources["vault_aws_auth_backend_cert.cert"]
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")
}

endpoint := instanceState.ID

if endpoint != "auth/"+backend+"/config/certificate/"+name {
return fmt.Errorf("expected ID to be %q, got %q", "auth/"+backend+"/config/certificate/"+name, endpoint)
}

client := testProvider.Meta().(*api.Client)
resp, err := client.Logical().Read(endpoint)
if err != nil {
return fmt.Errorf("error reading back AWS auth certificate from %q: %s", endpoint, err)
}
if resp == nil {
return fmt.Errorf("AWS auth certificate not configured at %q", endpoint)
}

// the read function re-encodes the response in state to match the config
resp.Data["aws_public_cert"] = base64.RawStdEncoding.EncodeToString([]byte(resp.Data["aws_public_cert"].(string)))
attrs := map[string]string{
"aws_public_cert": "aws_public_cert",
"type": "type",
}
for stateAttr, apiAttr := range attrs {
if resp.Data[apiAttr] == nil && instanceState.Attributes[stateAttr] == "" {
continue
}
if resp.Data[apiAttr] != instanceState.Attributes[stateAttr] {
return fmt.Errorf("Expected %s (%s) of %q to be %q, got %q", apiAttr, stateAttr, endpoint, instanceState.Attributes[stateAttr], resp.Data[apiAttr])
}
}
return nil
}
}
20 changes: 20 additions & 0 deletions vendor/github.com/armon/go-radix/LICENSE

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

38 changes: 38 additions & 0 deletions vendor/github.com/armon/go-radix/README.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 7e59fbe

Please sign in to comment.