-
Notifications
You must be signed in to change notification settings - Fork 9.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #9158 from hashicorp/f-vault-provider
Vault Provider
- Loading branch information
Showing
43 changed files
with
4,471 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
package main | ||
|
||
import ( | ||
"github.com/hashicorp/terraform/builtin/providers/vault" | ||
"github.com/hashicorp/terraform/plugin" | ||
) | ||
|
||
func main() { | ||
plugin.Serve(&plugin.ServeOpts{ | ||
ProviderFunc: vault.Provider, | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
package vault | ||
|
||
import ( | ||
"encoding/json" | ||
"fmt" | ||
"log" | ||
"time" | ||
|
||
"github.com/hashicorp/terraform/helper/schema" | ||
|
||
"github.com/hashicorp/vault/api" | ||
) | ||
|
||
func genericSecretDataSource() *schema.Resource { | ||
return &schema.Resource{ | ||
Read: genericSecretDataSourceRead, | ||
|
||
Schema: map[string]*schema.Schema{ | ||
"path": &schema.Schema{ | ||
Type: schema.TypeString, | ||
Required: true, | ||
Description: "Full path from which a secret will be read.", | ||
}, | ||
|
||
"data_json": &schema.Schema{ | ||
Type: schema.TypeString, | ||
Computed: true, | ||
Description: "JSON-encoded secret data read from Vault.", | ||
}, | ||
|
||
"data": &schema.Schema{ | ||
Type: schema.TypeMap, | ||
Computed: true, | ||
Description: "Map of strings read from Vault.", | ||
}, | ||
|
||
"lease_id": &schema.Schema{ | ||
Type: schema.TypeString, | ||
Computed: true, | ||
Description: "Lease identifier assigned by vault.", | ||
}, | ||
|
||
"lease_duration": &schema.Schema{ | ||
Type: schema.TypeInt, | ||
Computed: true, | ||
Description: "Lease duration in seconds relative to the time in lease_start_time.", | ||
}, | ||
|
||
"lease_start_time": &schema.Schema{ | ||
Type: schema.TypeString, | ||
Computed: true, | ||
Description: "Time at which the lease was read, using the clock of the system where Terraform was running", | ||
}, | ||
|
||
"lease_renewable": &schema.Schema{ | ||
Type: schema.TypeBool, | ||
Computed: true, | ||
Description: "True if the duration of this lease can be extended through renewal.", | ||
}, | ||
}, | ||
} | ||
} | ||
|
||
func genericSecretDataSourceRead(d *schema.ResourceData, meta interface{}) error { | ||
client := meta.(*api.Client) | ||
|
||
path := d.Get("path").(string) | ||
|
||
log.Printf("[DEBUG] Reading %s from Vault", path) | ||
secret, err := client.Logical().Read(path) | ||
if err != nil { | ||
return fmt.Errorf("error reading from Vault: %s", err) | ||
} | ||
|
||
d.SetId(secret.RequestID) | ||
|
||
// Ignoring error because this value came from JSON in the | ||
// first place so no reason why it should fail to re-encode. | ||
jsonDataBytes, _ := json.Marshal(secret.Data) | ||
d.Set("data_json", string(jsonDataBytes)) | ||
|
||
// Since our "data" map can only contain string values, we | ||
// will take strings from Data and write them in as-is, | ||
// and write everything else in as a JSON serialization of | ||
// whatever value we get so that complex types can be | ||
// passed around and processed elsewhere if desired. | ||
dataMap := map[string]string{} | ||
for k, v := range secret.Data { | ||
if vs, ok := v.(string); ok { | ||
dataMap[k] = vs | ||
} else { | ||
// Again ignoring error because we know this value | ||
// came from JSON in the first place and so must be valid. | ||
vBytes, _ := json.Marshal(v) | ||
dataMap[k] = string(vBytes) | ||
} | ||
} | ||
d.Set("data", dataMap) | ||
|
||
d.Set("lease_id", secret.LeaseID) | ||
d.Set("lease_duration", secret.LeaseDuration) | ||
d.Set("lease_start_time", time.Now().Format("RFC3339")) | ||
d.Set("lease_renewable", secret.Renewable) | ||
|
||
return nil | ||
} |
62 changes: 62 additions & 0 deletions
62
builtin/providers/vault/data_source_generic_secret_test.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
package vault | ||
|
||
import ( | ||
"fmt" | ||
"testing" | ||
|
||
r "github.com/hashicorp/terraform/helper/resource" | ||
"github.com/hashicorp/terraform/terraform" | ||
) | ||
|
||
func TestDataSourceGenericSecret(t *testing.T) { | ||
r.Test(t, r.TestCase{ | ||
Providers: testProviders, | ||
PreCheck: func() { testAccPreCheck(t) }, | ||
Steps: []r.TestStep{ | ||
r.TestStep{ | ||
Config: testDataSourceGenericSecret_config, | ||
Check: testDataSourceGenericSecret_check, | ||
}, | ||
}, | ||
}) | ||
} | ||
|
||
var testDataSourceGenericSecret_config = ` | ||
resource "vault_generic_secret" "test" { | ||
path = "secret/foo" | ||
data_json = <<EOT | ||
{ | ||
"zip": "zap" | ||
} | ||
EOT | ||
} | ||
data "vault_generic_secret" "test" { | ||
path = "${vault_generic_secret.test.path}" | ||
} | ||
` | ||
|
||
func testDataSourceGenericSecret_check(s *terraform.State) error { | ||
resourceState := s.Modules[0].Resources["data.vault_generic_secret.test"] | ||
if resourceState == nil { | ||
return fmt.Errorf("resource not found in state %v", s.Modules[0].Resources) | ||
} | ||
|
||
iState := resourceState.Primary | ||
if iState == nil { | ||
return fmt.Errorf("resource has no primary instance") | ||
} | ||
|
||
wantJson := `{"zip":"zap"}` | ||
if got, want := iState.Attributes["data_json"], wantJson; got != want { | ||
return fmt.Errorf("data_json contains %s; want %s", got, want) | ||
} | ||
|
||
if got, want := iState.Attributes["data.zip"], "zap"; got != want { | ||
return fmt.Errorf("data[\"zip\"] contains %s; want %s", got, want) | ||
} | ||
|
||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,160 @@ | ||
package vault | ||
|
||
import ( | ||
"fmt" | ||
"log" | ||
"strings" | ||
|
||
"github.com/hashicorp/terraform/helper/schema" | ||
"github.com/hashicorp/terraform/terraform" | ||
"github.com/hashicorp/vault/api" | ||
) | ||
|
||
func Provider() terraform.ResourceProvider { | ||
return &schema.Provider{ | ||
Schema: map[string]*schema.Schema{ | ||
"address": &schema.Schema{ | ||
Type: schema.TypeString, | ||
Required: true, | ||
DefaultFunc: schema.EnvDefaultFunc("VAULT_ADDR", nil), | ||
Description: "URL of the root of the target Vault server.", | ||
}, | ||
"token": &schema.Schema{ | ||
Type: schema.TypeString, | ||
Required: true, | ||
DefaultFunc: schema.EnvDefaultFunc("VAULT_TOKEN", nil), | ||
Description: "Token to use to authenticate to Vault.", | ||
}, | ||
"ca_cert_file": &schema.Schema{ | ||
Type: schema.TypeString, | ||
Optional: true, | ||
ConflictsWith: []string{"ca_cert_dir"}, | ||
DefaultFunc: schema.EnvDefaultFunc("VAULT_CACERT", nil), | ||
Description: "Path to a CA certificate file to validate the server's certificate.", | ||
}, | ||
"ca_cert_dir": &schema.Schema{ | ||
Type: schema.TypeString, | ||
Optional: true, | ||
ConflictsWith: []string{"ca_cert_file"}, | ||
DefaultFunc: schema.EnvDefaultFunc("VAULT_CAPATH", nil), | ||
Description: "Path to directory containing CA certificate files to validate the server's certificate.", | ||
}, | ||
"client_auth": &schema.Schema{ | ||
Type: schema.TypeList, | ||
Optional: true, | ||
Description: "Client authentication credentials.", | ||
Elem: &schema.Resource{ | ||
Schema: map[string]*schema.Schema{ | ||
"cert_file": &schema.Schema{ | ||
Type: schema.TypeString, | ||
Required: true, | ||
DefaultFunc: schema.EnvDefaultFunc("VAULT_CLIENT_CERT", nil), | ||
Description: "Path to a file containing the client certificate.", | ||
}, | ||
"key_file": &schema.Schema{ | ||
Type: schema.TypeString, | ||
Required: true, | ||
DefaultFunc: schema.EnvDefaultFunc("VAULT_CLIENT_KEY", nil), | ||
Description: "Path to a file containing the private key that the certificate was issued for.", | ||
}, | ||
}, | ||
}, | ||
}, | ||
"skip_tls_verify": &schema.Schema{ | ||
Type: schema.TypeBool, | ||
Optional: true, | ||
DefaultFunc: schema.EnvDefaultFunc("VAULT_SKIP_VERIFY", nil), | ||
Description: "Set this to true only if the target Vault server is an insecure development instance.", | ||
}, | ||
"max_lease_ttl_seconds": &schema.Schema{ | ||
Type: schema.TypeInt, | ||
Optional: true, | ||
|
||
// Default is 20min, which is intended to be enough time for | ||
// a reasonable Terraform run can complete but not | ||
// significantly longer, so that any leases are revoked shortly | ||
// after Terraform has finished running. | ||
DefaultFunc: schema.EnvDefaultFunc("TERRAFORM_VAULT_MAX_TTL", 1200), | ||
|
||
Description: "Maximum TTL for secret leases requested by this provider", | ||
}, | ||
}, | ||
|
||
ConfigureFunc: providerConfigure, | ||
|
||
DataSourcesMap: map[string]*schema.Resource{ | ||
"vault_generic_secret": genericSecretDataSource(), | ||
}, | ||
|
||
ResourcesMap: map[string]*schema.Resource{ | ||
"vault_generic_secret": genericSecretResource(), | ||
}, | ||
} | ||
} | ||
|
||
func providerConfigure(d *schema.ResourceData) (interface{}, error) { | ||
config := &api.Config{ | ||
Address: d.Get("address").(string), | ||
} | ||
|
||
clientAuthI := d.Get("client_auth").([]interface{}) | ||
if len(clientAuthI) > 1 { | ||
return nil, fmt.Errorf("client_auth block may appear only once") | ||
} | ||
|
||
clientAuthCert := "" | ||
clientAuthKey := "" | ||
if len(clientAuthI) == 1 { | ||
clientAuth := clientAuthI[0].(map[string]interface{}) | ||
clientAuthCert = clientAuth["cert_file"].(string) | ||
clientAuthKey = clientAuth["key_file"].(string) | ||
} | ||
|
||
config.ConfigureTLS(&api.TLSConfig{ | ||
CACert: d.Get("ca_cert_file").(string), | ||
CAPath: d.Get("ca_cert_dir").(string), | ||
Insecure: d.Get("skip_tls_verify").(bool), | ||
|
||
ClientCert: clientAuthCert, | ||
ClientKey: clientAuthKey, | ||
}) | ||
|
||
client, err := api.NewClient(config) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to configure Vault API: %s", err) | ||
} | ||
|
||
// In order to enforce our relatively-short lease TTL, we derive a | ||
// temporary child token that inherits all of the policies of the | ||
// token we were given but expires after max_lease_ttl_seconds. | ||
// | ||
// The intent here is that Terraform will need to re-fetch any | ||
// secrets on each run and so we limit the exposure risk of secrets | ||
// that end up stored in the Terraform state, assuming that they are | ||
// credentials that Vault is able to revoke. | ||
// | ||
// Caution is still required with state files since not all secrets | ||
// can explicitly be revoked, and this limited scope won't apply to | ||
// any secrets that are *written* by Terraform to Vault. | ||
|
||
client.SetToken(d.Get("token").(string)) | ||
renewable := false | ||
childTokenLease, err := client.Auth().Token().Create(&api.TokenCreateRequest{ | ||
DisplayName: "terraform", | ||
TTL: fmt.Sprintf("%ds", d.Get("max_lease_ttl_seconds").(int)), | ||
ExplicitMaxTTL: fmt.Sprintf("%ds", d.Get("max_lease_ttl_seconds").(int)), | ||
Renewable: &renewable, | ||
}) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to create limited child token: %s", err) | ||
} | ||
|
||
childToken := childTokenLease.Auth.ClientToken | ||
policies := childTokenLease.Auth.Policies | ||
|
||
log.Printf("[INFO] Using Vault token with the following policies: %s", strings.Join(policies, ", ")) | ||
|
||
client.SetToken(childToken) | ||
|
||
return client, nil | ||
} |
Oops, something went wrong.