Skip to content

Commit ed1a604

Browse files
committed
feat(api-key): add store_sensitive_state provider flag
Add provider-level control over sensitive data storage in state: - New store_sensitive_state provider configuration (defaults true) - API key resources respect flag to conditionally store key values - When false, key field is set to null in state for security - Updated documentation with ephemeral resource usage patterns - Examples demonstrating secure ephemeral resource patterns Enables users to prevent sensitive API keys from being stored in Terraform state while maintaining resource functionality through ephemeral resource access patterns.
1 parent b7a9129 commit ed1a604

File tree

7 files changed

+143
-12
lines changed

7 files changed

+143
-12
lines changed

datadog/fwprovider/data_source_datadog_api_key.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ func (d *apiKeyDataSource) Metadata(_ context.Context, req datasource.MetadataRe
4444

4545
func (d *apiKeyDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
4646
resp.Schema = schema.Schema{
47-
Description: "Use this data source to retrieve information about an existing api key. Deprecated. This will be removed in a future release with prior notice. Securely store your API keys using a secret management system or use the datadog_api_key resource to manage API keys in your Datadog account.",
47+
Description: "Use this data source to retrieve information about an existing API key. **Deprecated**: This will be removed in a future release with prior notice. For secure access to API key values without storing them in Terraform state, use the ephemeral `datadog_api_key` resource instead. See the ephemeral resource documentation for examples of secure API key access patterns.",
4848
Attributes: map[string]schema.Attribute{
4949
"name": schema.StringAttribute{
5050
Description: "Name for API Key.",
@@ -59,7 +59,7 @@ func (d *apiKeyDataSource) Schema(_ context.Context, _ datasource.SchemaRequest,
5959
Optional: true,
6060
},
6161
"key": schema.StringAttribute{
62-
Description: "The value of the API Key.",
62+
Description: "The value of the API Key. **Security Note**: This field exposes sensitive data in Terraform state. For secure access without state storage, use the ephemeral `datadog_api_key` resource instead.",
6363
Computed: true,
6464
Sensitive: true,
6565
},
@@ -68,7 +68,7 @@ func (d *apiKeyDataSource) Schema(_ context.Context, _ datasource.SchemaRequest,
6868
Computed: true,
6969
},
7070
},
71-
DeprecationMessage: "Deprecated. This will be removed in a future release with prior notice. Securely store your API keys using a secret management system or use the datadog_api_key resource to manage API keys in your Datadog account.",
71+
DeprecationMessage: "This data source is deprecated and will be removed in a future release with prior notice. For secure access to API key values without storing them in Terraform state, use the ephemeral datadog_api_key resource instead.",
7272
}
7373
}
7474

@@ -168,7 +168,7 @@ func (r *apiKeyDataSource) updateState(state *apiKeyDataSourceModel, apiKeyData
168168
func (r *apiKeyDataSource) checkAPIDeprecated(apiKeyData *datadogV2.FullAPIKey, resp *datasource.ReadResponse) bool {
169169
apiKeyAttributes := apiKeyData.GetAttributes()
170170
if !apiKeyAttributes.HasKey() {
171-
resp.Diagnostics.AddError("Deprecated", "The datadog_api_key data source is deprecated and will be removed in a future release. Securely store your API key using a secret management system or use the datadog_api_key resource to manage API keys in your Datadog account.")
171+
resp.Diagnostics.AddError("Deprecated", "The datadog_api_key data source is deprecated and will be removed in a future release. For secure access to API key values without storing them in Terraform state, use the ephemeral datadog_api_key resource instead.")
172172
return true
173173
}
174174
return false

datadog/fwprovider/framework_provider.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,7 @@ type FrameworkProvider struct {
147147
CommunityClient *datadogCommunity.Client
148148
DatadogApiInstances *utils.ApiInstances
149149
Auth context.Context
150+
StoreSensitiveState bool
150151

151152
ConfigureCallbackFunc func(p *FrameworkProvider, request *provider.ConfigureRequest, config *ProviderSchema) diag.Diagnostics
152153
Now func() time.Time
@@ -170,6 +171,7 @@ type ProviderSchema struct {
170171
HttpClientRetryBackoffBase types.Int64 `tfsdk:"http_client_retry_backoff_base"`
171172
HttpClientRetryMaxRetries types.Int64 `tfsdk:"http_client_retry_max_retries"`
172173
DefaultTags types.List `tfsdk:"default_tags"`
174+
StoreSensitiveState types.String `tfsdk:"store_sensitive_state"`
173175
}
174176

175177
func New() provider.Provider {
@@ -285,6 +287,10 @@ func (p *FrameworkProvider) Schema(_ context.Context, _ provider.SchemaRequest,
285287
Optional: true,
286288
Description: "The HTTP request maximum retry number. Defaults to 3.",
287289
},
290+
"store_sensitive_state": schema.StringAttribute{
291+
Optional: true,
292+
Description: "Whether to expose API key values in Terraform state. Valid values are [`true`, `false`]. Defaults to `true` for backwards compatibility. When false, API key resources will not include the key value, requiring the use of ephemeral datadog_api_key resources instead.",
293+
},
288294
},
289295
Blocks: map[string]schema.Block{
290296
"default_tags": schema.ListNestedBlock{
@@ -425,6 +431,9 @@ func (p *FrameworkProvider) ConfigureConfigDefaults(ctx context.Context, config
425431
if config.HttpClientRetryEnabled.IsNull() {
426432
config.HttpClientRetryEnabled = types.StringValue("true")
427433
}
434+
if config.StoreSensitiveState.IsNull() {
435+
config.StoreSensitiveState = types.StringValue("true")
436+
}
428437

429438
// Run validations on the provider config after defaults and values from
430439
// env var has been set.
@@ -478,6 +487,8 @@ func defaultConfigureFunc(p *FrameworkProvider, request *provider.ConfigureReque
478487
diags := diag.Diagnostics{}
479488
validate, _ := strconv.ParseBool(config.Validate.ValueString())
480489
httpClientRetryEnabled, _ := strconv.ParseBool(config.HttpClientRetryEnabled.ValueString())
490+
storeSensitiveState, _ := strconv.ParseBool(config.StoreSensitiveState.ValueString())
491+
p.StoreSensitiveState = storeSensitiveState
481492

482493
cloudProviderType := config.CloudProviderType.ValueString()
483494
cloudProviderRegion := config.CloudProviderRegion.ValueString()

datadog/fwprovider/resource_datadog_api_key.go

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,14 +33,16 @@ type apiKeyResourceModel struct {
3333
}
3434

3535
type apiKeyResource struct {
36-
Api *datadogV2.KeyManagementApi
37-
Auth context.Context
36+
Api *datadogV2.KeyManagementApi
37+
Auth context.Context
38+
StoreSensitiveState bool
3839
}
3940

4041
func (r *apiKeyResource) Configure(_ context.Context, request resource.ConfigureRequest, response *resource.ConfigureResponse) {
4142
providerData := request.ProviderData.(*FrameworkProvider)
4243
r.Api = providerData.DatadogApiInstances.GetKeyManagementApiV2()
4344
r.Auth = providerData.Auth
45+
r.StoreSensitiveState = providerData.StoreSensitiveState
4446
}
4547

4648
func (r *apiKeyResource) Metadata(_ context.Context, request resource.MetadataRequest, response *resource.MetadataResponse) {
@@ -49,14 +51,14 @@ func (r *apiKeyResource) Metadata(_ context.Context, request resource.MetadataRe
4951

5052
func (r *apiKeyResource) Schema(_ context.Context, _ resource.SchemaRequest, response *resource.SchemaResponse) {
5153
response.Schema = schema.Schema{
52-
Description: "Provides a Datadog API Key resource. This can be used to create and manage Datadog API Keys. Import functionality for this resource is deprecated and will be removed in a future release with prior notice. Securely store your API keys using a secret management system or use this resource to create and manage new API keys.",
54+
Description: "Provides a Datadog API Key resource. This can be used to create and manage Datadog API Keys. Import functionality for this resource is deprecated and will be removed in a future release with prior notice. For enhanced security when `store_sensitive_state = false`, use the ephemeral `datadog_api_key` resource to access key values without storing them in state.",
5355
Attributes: map[string]schema.Attribute{
5456
"name": schema.StringAttribute{
5557
Description: "Name for API Key.",
5658
Required: true,
5759
},
5860
"key": schema.StringAttribute{
59-
Description: "The value of the API Key.",
61+
Description: "The value of the API Key. This field is only populated when the provider's `store_sensitive_state` is set to `true` (default). When `store_sensitive_state` is `false`, use the ephemeral `datadog_api_key` resource to access the key value without storing it in state.",
6062
Computed: true,
6163
Sensitive: true,
6264
PlanModifiers: []planmodifier.String{stringplanmodifier.UseStateForUnknown()},
@@ -194,8 +196,14 @@ func (r *apiKeyResource) updateState(state *apiKeyResourceModel, apiKeyData *dat
194196
d = frameworkDiag.NewErrorDiagnostic("remote_config_read_enabled is true but Remote config is not enabled at org level", "Please either remove remote_config_read_enabled from the resource configuration or enable Remote config at org level")
195197
}
196198
state.RemoteConfig = types.BoolValue(apiKeyAttributes.GetRemoteConfigReadEnabled())
197-
if apiKeyAttributes.HasKey() {
199+
if apiKeyAttributes.HasKey() && r.StoreSensitiveState {
200+
// API has key AND user wants to store sensitive state
198201
state.Key = types.StringValue(apiKeyAttributes.GetKey())
202+
} else if !r.StoreSensitiveState {
203+
// User explicitly chose not to store sensitive state - always set to null
204+
state.Key = types.StringNull()
199205
}
206+
// If HasKey() is false but StoreSensitiveState is true, leave unchanged
207+
// (PlanModifier will preserve existing state)
200208
return d
201209
}

datadog/provider.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,12 @@ func Provider() *schema.Provider {
204204
},
205205
},
206206
},
207+
"store_sensitive_state": {
208+
Type: schema.TypeString,
209+
Optional: true,
210+
Description: "Whether to expose API key values in Terraform state. Valid values are [`true`, `false`]. Defaults to `true` for backwards compatibility. When false, API key resources will not include the key value, requiring the use of ephemeral datadog_api_key resources instead.",
211+
ValidateFunc: validation.StringInSlice([]string{"true", "false"}, true),
212+
},
207213
},
208214

209215
// NEW RESOURCES ARE NOT ALLOWED TO BE ADDED HERE
@@ -290,6 +296,7 @@ type ProviderConfiguration struct {
290296
DatadogApiInstances *utils.ApiInstances
291297
Auth context.Context
292298
DefaultTags map[string]interface{}
299+
StoreSensitiveState bool
293300

294301
Now func() time.Time
295302
}
@@ -346,6 +353,11 @@ func providerConfigure(ctx context.Context, d *schema.ResourceData) (interface{}
346353
validate, _ = strconv.ParseBool(v)
347354
}
348355

356+
storeSensitiveState := true
357+
if v := d.Get("store_sensitive_state").(string); v != "" {
358+
storeSensitiveState, _ = strconv.ParseBool(v)
359+
}
360+
349361
if validate {
350362
if cloudProviderType == "" && (apiKey == "" || appKey == "") {
351363
return nil, diag.FromErr(errors.New("api_key and app_key or orgUUID must be set unless validate = false"))
@@ -542,6 +554,7 @@ func providerConfigure(ctx context.Context, d *schema.ResourceData) (interface{}
542554
CommunityClient: communityClient,
543555
DatadogApiInstances: apiInstances,
544556
Auth: auth,
557+
StoreSensitiveState: storeSensitiveState,
545558

546559
Now: time.Now,
547560
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
---
2+
# generated by https://github.com/hashicorp/terraform-plugin-docs
3+
page_title: "datadog_api_key Ephemeral Resource - terraform-provider-datadog"
4+
subcategory: ""
5+
description: |-
6+
Retrieves an existing Datadog API key as an ephemeral resource. The API key value is retrieved securely and made available for use in other resources without being stored in state.
7+
---
8+
9+
# datadog_api_key (Ephemeral Resource)
10+
11+
Retrieves an existing Datadog API key as an ephemeral resource. The API key value is retrieved securely and made available for use in other resources without being stored in state.
12+
13+
## Example Usage
14+
15+
```terraform
16+
# Example: Using ephemeral resources for enhanced security
17+
# Set store_sensitive_state = false in your provider configuration
18+
19+
terraform {
20+
required_providers {
21+
datadog = {
22+
source = "DataDog/datadog"
23+
}
24+
}
25+
}
26+
27+
provider "datadog" {
28+
# Enhanced security: API key values won't be stored in state
29+
store_sensitive_state = false
30+
}
31+
32+
# Create the API key resource (key value won't be stored in state)
33+
resource "datadog_api_key" "example" {
34+
name = "Example API Key"
35+
}
36+
37+
# Access the key value using ephemeral resource (not stored in state)
38+
ephemeral "datadog_api_key" "example" {
39+
id = datadog_api_key.example.id
40+
}
41+
42+
# Use the ephemeral key value in other resources
43+
resource "some_external_resource" "example" {
44+
api_key = ephemeral.datadog_api_key.example.key
45+
}
46+
47+
# Or store in locals for reuse
48+
locals {
49+
api_key = ephemeral.datadog_api_key.example.key
50+
}
51+
```
52+
53+
<!-- schema generated by tfplugindocs -->
54+
## Schema
55+
56+
### Required
57+
58+
- `id` (String) The ID of the API key to retrieve.
59+
60+
### Read-Only
61+
62+
- `key` (String, Sensitive) The actual API key value (sensitive).
63+
- `name` (String) The name of the API key.
64+
- `remote_config_read_enabled` (Boolean) Whether remote configuration reads are enabled for this key.

docs/resources/api_key.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@
33
page_title: "datadog_api_key Resource - terraform-provider-datadog"
44
subcategory: ""
55
description: |-
6-
Provides a Datadog API Key resource. This can be used to create and manage Datadog API Keys. Import functionality for this resource is deprecated and will be removed in a future release with prior notice. Securely store your API keys using a secret management system or use this resource to create and manage new API keys.
6+
Provides a Datadog API Key resource. This can be used to create and manage Datadog API Keys. Import functionality for this resource is deprecated and will be removed in a future release with prior notice. For enhanced security when store_sensitive_state = false, use the ephemeral datadog_api_key resource to access key values without storing them in state.
77
---
88

99
# datadog_api_key (Resource)
1010

11-
Provides a Datadog API Key resource. This can be used to create and manage Datadog API Keys. Import functionality for this resource is deprecated and will be removed in a future release with prior notice. Securely store your API keys using a secret management system or use this resource to create and manage new API keys.
11+
Provides a Datadog API Key resource. This can be used to create and manage Datadog API Keys. Import functionality for this resource is deprecated and will be removed in a future release with prior notice. For enhanced security when `store_sensitive_state = false`, use the ephemeral `datadog_api_key` resource to access key values without storing them in state.
1212

1313
## Example Usage
1414

@@ -33,7 +33,7 @@ resource "datadog_api_key" "foo" {
3333
### Read-Only
3434

3535
- `id` (String) The ID of this resource.
36-
- `key` (String, Sensitive) The value of the API Key.
36+
- `key` (String, Sensitive) The value of the API Key. This field is only populated when the provider's `store_sensitive_state` is set to `true` (default). When `store_sensitive_state` is `false`, use the ephemeral `datadog_api_key` resource to access the key value without storing it in state.
3737

3838
## Import
3939

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# Example: Using ephemeral resources for enhanced security
2+
# Set store_sensitive_state = false in your provider configuration
3+
4+
terraform {
5+
required_providers {
6+
datadog = {
7+
source = "DataDog/datadog"
8+
}
9+
}
10+
}
11+
12+
provider "datadog" {
13+
# Enhanced security: API key values won't be stored in state
14+
store_sensitive_state = false
15+
}
16+
17+
# Create the API key resource (key value won't be stored in state)
18+
resource "datadog_api_key" "example" {
19+
name = "Example API Key"
20+
}
21+
22+
# Access the key value using ephemeral resource (not stored in state)
23+
ephemeral "datadog_api_key" "example" {
24+
id = datadog_api_key.example.id
25+
}
26+
27+
# Use the ephemeral key value in other resources
28+
resource "some_external_resource" "example" {
29+
api_key = ephemeral.datadog_api_key.example.key
30+
}
31+
32+
# Or store in locals for reuse
33+
locals {
34+
api_key = ephemeral.datadog_api_key.example.key
35+
}

0 commit comments

Comments
 (0)