Skip to content

Commit cbfbceb

Browse files
tstraleybenashz
andauthored
Create optional provider config for opting out of child token creation (#775)
Add skip_child_token to the provider configuration with a default value of false. When enabled, the batch child token will not be created, instead the provided token will be used for all provisioning tasks. Co-authored-by: Ben Ash <32777270+benashz@users.noreply.github.com>
1 parent 772a274 commit cbfbceb

3 files changed

Lines changed: 166 additions & 46 deletions

File tree

vault/provider.go

Lines changed: 35 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,15 @@ func Provider() *schema.Provider {
7171
DefaultFunc: schema.EnvDefaultFunc("VAULT_TOKEN_NAME", ""),
7272
Description: "Token name to use for creating the Vault child token.",
7373
},
74+
"skip_child_token": {
75+
Type: schema.TypeBool,
76+
Optional: true,
77+
DefaultFunc: schema.EnvDefaultFunc("TERRAFORM_VAULT_SKIP_CHILD_TOKEN", false),
78+
79+
// Setting to true will cause max_lease_ttl_seconds and token_name to be ignored (not used).
80+
// Note that this is strongly discouraged due to the potential of exposing sensitive secret data.
81+
Description: "Set this to true to prevent the creation of ephemeral child token used by this provider.",
82+
},
7483
"ca_cert_file": {
7584
Type: schema.TypeString,
7685
Optional: true,
@@ -148,7 +157,7 @@ func Provider() *schema.Provider {
148157
// after Terraform has finished running.
149158
DefaultFunc: schema.EnvDefaultFunc("TERRAFORM_VAULT_MAX_TTL", 1200),
150159

151-
Description: "Maximum TTL for secret leases requested by this provider",
160+
Description: "Maximum TTL for secret leases requested by this provider.",
152161
},
153162
"max_retries": {
154163
Type: schema.TypeInt,
@@ -161,7 +170,7 @@ func Provider() *schema.Provider {
161170
Type: schema.TypeString,
162171
Optional: true,
163172
DefaultFunc: schema.EnvDefaultFunc("VAULT_NAMESPACE", ""),
164-
Description: "The namespace to use. Available only for Vault Enterprise",
173+
Description: "The namespace to use. Available only for Vault Enterprise.",
165174
},
166175
"headers": {
167176
Type: schema.TypeList,
@@ -810,6 +819,23 @@ func providerConfigure(d *schema.ResourceData) (interface{}, error) {
810819
return nil, errors.New("no vault token found")
811820
}
812821

822+
skipChildToken := d.Get("skip_child_token").(bool)
823+
if !skipChildToken {
824+
err := setChildToken(d, client)
825+
if err != nil {
826+
return nil, err
827+
}
828+
}
829+
830+
// Set the namespace to the requested namespace, if provided
831+
namespace := d.Get("namespace").(string)
832+
if namespace != "" {
833+
client.SetNamespace(namespace)
834+
}
835+
return client, nil
836+
}
837+
838+
func setChildToken(d *schema.ResourceData, c *api.Client) error {
813839
tokenName := d.Get("token_name").(string)
814840
if tokenName == "" {
815841
tokenName = "terraform"
@@ -830,26 +856,26 @@ func providerConfigure(d *schema.ResourceData) (interface{}, error) {
830856

831857
// Set the namespace to the token's namespace only for the
832858
// child token creation
833-
tokenInfo, err := client.Auth().Token().LookupSelf()
859+
tokenInfo, err := c.Auth().Token().LookupSelf()
834860
if err != nil {
835-
return nil, err
861+
return err
836862
}
837863
if tokenNamespaceRaw, ok := tokenInfo.Data["namespace_path"]; ok {
838864
tokenNamespace := tokenNamespaceRaw.(string)
839865
if tokenNamespace != "" {
840-
client.SetNamespace(tokenNamespace)
866+
c.SetNamespace(tokenNamespace)
841867
}
842868
}
843869

844870
renewable := false
845-
childTokenLease, err := client.Auth().Token().Create(&api.TokenCreateRequest{
871+
childTokenLease, err := c.Auth().Token().Create(&api.TokenCreateRequest{
846872
DisplayName: tokenName,
847873
TTL: fmt.Sprintf("%ds", d.Get("max_lease_ttl_seconds").(int)),
848874
ExplicitMaxTTL: fmt.Sprintf("%ds", d.Get("max_lease_ttl_seconds").(int)),
849875
Renewable: &renewable,
850876
})
851877
if err != nil {
852-
return nil, fmt.Errorf("failed to create limited child token: %s", err)
878+
return fmt.Errorf("failed to create limited child token: %s", err)
853879
}
854880

855881
childToken := childTokenLease.Auth.ClientToken
@@ -858,14 +884,9 @@ func providerConfigure(d *schema.ResourceData) (interface{}, error) {
858884
log.Printf("[INFO] Using Vault token with the following policies: %s", strings.Join(policies, ", "))
859885

860886
// Set the token to the generated child token
861-
client.SetToken(childToken)
887+
c.SetToken(childToken)
862888

863-
// Set the namespace to the requested namespace, if provided
864-
namespace := d.Get("namespace").(string)
865-
if namespace != "" {
866-
client.SetNamespace(namespace)
867-
}
868-
return client, nil
889+
return nil
869890
}
870891

871892
func parse(descs map[string]*Description) (map[string]*schema.Resource, error) {

vault/provider_test.go

Lines changed: 116 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,7 @@ func TestTokenReadProviderConfigureWithHeaders(t *testing.T) {
205205
Steps: []resource.TestStep{
206206
{
207207
Config: testHeaderConfig("auth", "123"),
208-
Check: testTokenName_check("token-testtoken"),
208+
Check: checkSelfToken("display_name", "token-testtoken"),
209209
},
210210
},
211211
})
@@ -547,7 +547,7 @@ func TestAccProviderToken(t *testing.T) {
547547
}
548548

549549
func TestAccTokenName(t *testing.T) {
550-
550+
defer os.Unsetenv("VAULT_TOKEN_NAME")
551551
tests := []struct {
552552
TokenNameEnv string
553553
UseTokenNameEnv bool
@@ -618,8 +618,103 @@ func TestAccTokenName(t *testing.T) {
618618
}
619619
}
620620
},
621-
Config: testTokenNameConfig(test.UseTokenNameSchema, test.TokenNameSchema),
622-
Check: testTokenName_check(test.WantTokenName),
621+
Config: testProviderConfig(test.UseTokenNameSchema, `token_name = "`+test.TokenNameSchema+`"`),
622+
Check: checkSelfToken("display_name", test.WantTokenName),
623+
},
624+
},
625+
})
626+
}
627+
}
628+
629+
func TestAccChildToken(t *testing.T) {
630+
defer os.Unsetenv("TERRAFORM_VAULT_SKIP_CHILD_TOKEN")
631+
632+
checkTokenUsed := func(expectChildToken bool) resource.TestCheckFunc {
633+
if expectChildToken {
634+
// If the default child token was created, we expect the token
635+
// used by the provider was named the default "token-terraform"
636+
return checkSelfToken("display_name", "token-terraform")
637+
} else {
638+
// If the child token setting was disabled, the used token
639+
// should match the user-provided VAULT_TOKEN
640+
return checkSelfToken("id", os.Getenv("VAULT_TOKEN"))
641+
}
642+
}
643+
644+
tests := []struct {
645+
skipChildTokenEnv string
646+
useChildTokenEnv bool
647+
skipChildTokenSchema string
648+
useChildTokenSchema bool
649+
expectChildToken bool
650+
}{
651+
{
652+
useChildTokenSchema: false,
653+
useChildTokenEnv: false,
654+
expectChildToken: true,
655+
},
656+
{
657+
skipChildTokenEnv: "",
658+
useChildTokenEnv: true,
659+
expectChildToken: true,
660+
},
661+
{
662+
skipChildTokenEnv: "true",
663+
useChildTokenEnv: true,
664+
expectChildToken: false,
665+
},
666+
{
667+
skipChildTokenEnv: "false",
668+
useChildTokenEnv: true,
669+
expectChildToken: true,
670+
},
671+
{
672+
skipChildTokenSchema: "true",
673+
useChildTokenSchema: true,
674+
expectChildToken: false,
675+
},
676+
{
677+
skipChildTokenSchema: "false",
678+
useChildTokenSchema: true,
679+
expectChildToken: true,
680+
},
681+
{
682+
skipChildTokenEnv: "true",
683+
useChildTokenEnv: true,
684+
skipChildTokenSchema: "false",
685+
useChildTokenSchema: true,
686+
expectChildToken: true,
687+
},
688+
{
689+
skipChildTokenEnv: "false",
690+
useChildTokenEnv: true,
691+
skipChildTokenSchema: "true",
692+
useChildTokenSchema: true,
693+
expectChildToken: false,
694+
},
695+
}
696+
697+
for _, test := range tests {
698+
resource.Test(t, resource.TestCase{
699+
Providers: testProviders,
700+
PreCheck: func() { testAccPreCheck(t) },
701+
Steps: []resource.TestStep{
702+
{
703+
PreConfig: func() {
704+
if test.useChildTokenEnv {
705+
err := os.Setenv("TERRAFORM_VAULT_SKIP_CHILD_TOKEN", test.skipChildTokenEnv)
706+
if err != nil {
707+
t.Fatal(err)
708+
}
709+
} else {
710+
err := os.Unsetenv("TERRAFORM_VAULT_SKIP_CHILD_TOKEN")
711+
if err != nil {
712+
t.Fatal(err)
713+
}
714+
}
715+
},
716+
Config: testProviderConfig(test.useChildTokenSchema, `skip_child_token = `+test.skipChildTokenSchema),
717+
Check: checkTokenUsed(test.expectChildToken),
623718
},
624719
},
625720
})
@@ -628,44 +723,34 @@ func TestAccTokenName(t *testing.T) {
628723

629724
func testHeaderConfig(headerName, headerValue string) string {
630725
providerConfig := fmt.Sprintf(`
631-
provider "vault" {
632726
headers {
633727
name = "%s"
634728
value = "%s"
635729
}
636730
token_name = "testtoken"
637-
}
638-
639-
data "vault_generic_secret" "test" {
640-
path = "/auth/token/lookup-self"
641-
}
642731
`, headerName, headerValue)
643-
return providerConfig
732+
return testProviderConfig(true, providerConfig)
644733
}
645734

646735
// Using the data lookup generic_secret to inspect used token
647736
// by terraform (this enables check of token name)
648-
func testTokenNameConfig(tokenNameSchema bool, tokenName string) string {
649-
testConfig := ""
650-
providerConfig := `
651-
provider "vault" {
652-
token_name = "` + tokenName + `"
653-
}`
737+
func testProviderConfig(includeProviderConfig bool, config string) string {
738+
providerConfig := fmt.Sprintf(`
739+
provider "vault" {
740+
%s
741+
}`, config)
654742

655743
dataConfig := `
656-
data "vault_generic_secret" "test" {
657-
path = "/auth/token/lookup-self"
658-
}
659-
`
660-
if tokenNameSchema {
661-
testConfig = providerConfig + dataConfig
662-
} else {
663-
testConfig = dataConfig
744+
data "vault_generic_secret" "test" {
745+
path = "/auth/token/lookup-self"
746+
}`
747+
if includeProviderConfig {
748+
return providerConfig + dataConfig
664749
}
665-
return testConfig
750+
return dataConfig
666751
}
667752

668-
func testTokenName_check(expectedTokenName string) resource.TestCheckFunc {
753+
func checkSelfToken(attrName string, expectedValue string) resource.TestCheckFunc {
669754
return func(s *terraform.State) error {
670755
resourceState := s.Modules[0].Resources["data.vault_generic_secret.test"]
671756
if resourceState == nil {
@@ -677,13 +762,13 @@ func testTokenName_check(expectedTokenName string) resource.TestCheckFunc {
677762
return fmt.Errorf("resource has no primary instance")
678763
}
679764

680-
tokenName, ok := resourceState.Primary.Attributes["data.display_name"]
765+
actualValue, ok := resourceState.Primary.Attributes["data."+attrName]
681766
if !ok {
682-
return fmt.Errorf("cannot access token [%s] for check", "display_name")
767+
return fmt.Errorf("cannot access attribute [%s] for check", attrName)
683768
}
684769

685-
if tokenName != expectedTokenName {
686-
return fmt.Errorf("token name [%s] expected, but got [%s]", expectedTokenName, tokenName)
770+
if actualValue != expectedValue {
771+
return fmt.Errorf("%s [%s] expected, but got [%s]", attrName, expectedValue, actualValue)
687772
}
688773

689774
return nil

website/docs/index.html.markdown

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,8 @@ variables in order to keep credential information out of the configuration.
103103
If none is otherwise supplied, Terraform will attempt to read it from
104104
`~/.vault-token` (where the vault command stores its current token).
105105
Terraform will issue itself a new token that is a child of the one given,
106-
with a short TTL to limit the exposure of any requested secrets. Note that
106+
with a short TTL to limit the exposure of any requested secrets, unless
107+
`skip_child_token` is set to `true` (see below). Note that
107108
the given token must have the update capability on the auth/token/create
108109
path in Vault in order to create child tokens.
109110

@@ -139,6 +140,19 @@ variables in order to keep credential information out of the configuration.
139140
that Terraform can be tricked into writing secrets to a server controlled
140141
by an intruder. May be set via the `VAULT_SKIP_VERIFY` environment variable.
141142

143+
* `skip_child_token` - (Optional) Set this to `true` to disable
144+
creation of an intermediate ephemeral Vault token for Terraform to
145+
use. This is strongly discouraged in most cases and environments because it
146+
can result in the provided Vault token being exposed by Terraform's output
147+
when `TF_LOG` is set to `debug`.
148+
Only change this setting when the provided token cannot be permitted to
149+
create child tokens and there is no risk of exposure from the output of
150+
Terraform. May be set via the `TERRAFORM_VAULT_SKIP_CHILD_TOKEN` environment
151+
variable. **Note**: Setting to `true` will cause `token_name`
152+
and `max_lease_ttl_seconds` to be ignored.
153+
Please see [Using Vault credentials in Terraform configuration](#using-vault-credentials-in-terraform-configuration)
154+
before enabling this setting.
155+
142156
* `max_lease_ttl_seconds` - (Optional) Used as the duration for the
143157
intermediate Vault token Terraform issues itself, which in turn limits
144158
the duration of secret leases issued by Vault. Defaults to 20 minutes

0 commit comments

Comments
 (0)