Skip to content

Commit f1eb135

Browse files
chore: Add a functional test for nested sensitives in the framework (#3886)
Add a functional test proving that nested sensitive are properly protected by the framework.
1 parent 62d561b commit f1eb135

File tree

4 files changed

+187
-1
lines changed

4 files changed

+187
-1
lines changed

MIGRATION_GUIDE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -393,7 +393,7 @@ Planning failed. Terraform encountered an error while generating this plan.
393393

394394
Some fields, like secure function definitions, can also contain sensitive values. However, because of [SDK v2](https://developer.hashicorp.com/terraform/plugin/sdkv2) limitations:
395395
- There is no possibility to mark sensitive values conditionally ([reference](https://github.com/hashicorp/terraform-plugin-sdk/issues/736)). This means it is not possible to mark sensitive values based on other fields, like marking `body` based on the value of `secure` field in views, functions, and procedures. As a result, this field is not marked as sensitive. For such cases, we add disclaimers in the resource documentation.
396-
- There is no possibility to mark sensitive values in nested fields ([reference](https://github.com/hashicorp/terraform-plugin-sdk/issues/201)). This means the nested fields, like these in `show_output` and `describe_output` cannot be sensitive. fields, like in `show_output` and `describe_output`, cannot be marked as sensitive.
396+
- There is no possibility to mark sensitive values in nested fields ([reference](https://github.com/hashicorp/terraform-plugin-sdk/issues/201)). This means the nested fields, like these in `show_output` and `describe_output` cannot be marked as sensitive.
397397

398398
Instead, we added notes in the documentation of the related resources. The full list includes:
399399
- `snowflake_execute` resource: `execute`, `revert`, `query` and `query_results` fields,

pkg/testfunctional/3_plugin_framework_functional_tests_provider_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ func (p *pluginFrameworkFunctionalTestsProvider) Resources(_ context.Context) []
7878
testfunctional.NewParameterHandlingPrivateResource,
7979
testfunctional.NewEnumHandlingResource,
8080
testfunctional.NewOptionalComputedResource,
81+
testfunctional.NewNestedSensitiveResource,
8182
}
8283
}
8384

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
package testfunctional
2+
3+
import (
4+
"context"
5+
6+
"github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk"
7+
"github.com/hashicorp/terraform-plugin-framework/attr"
8+
"github.com/hashicorp/terraform-plugin-framework/diag"
9+
"github.com/hashicorp/terraform-plugin-framework/resource"
10+
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
11+
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
12+
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
13+
"github.com/hashicorp/terraform-plugin-framework/types"
14+
)
15+
16+
func NewNestedSensitiveResource() resource.Resource {
17+
return &NestedSensitiveResource{}
18+
}
19+
20+
type NestedSensitiveResource struct{}
21+
22+
type nestedSensitiveResourceModelV0 struct {
23+
Name types.String `tfsdk:"name"`
24+
Id types.String `tfsdk:"id"`
25+
Output types.List `tfsdk:"output"`
26+
}
27+
28+
func (r *NestedSensitiveResource) Metadata(_ context.Context, request resource.MetadataRequest, response *resource.MetadataResponse) {
29+
response.TypeName = request.ProviderTypeName + "_nested_sensitive"
30+
}
31+
32+
func (r *NestedSensitiveResource) Schema(_ context.Context, _ resource.SchemaRequest, response *resource.SchemaResponse) {
33+
response.Schema = schema.Schema{
34+
Version: 0,
35+
Attributes: map[string]schema.Attribute{
36+
"name": schema.StringAttribute{
37+
Description: "Name for this example resource.",
38+
Required: true,
39+
},
40+
"id": schema.StringAttribute{
41+
Computed: true,
42+
Description: "Identifier for this example resource.",
43+
PlanModifiers: []planmodifier.String{
44+
stringplanmodifier.UseStateForUnknown(),
45+
},
46+
},
47+
"output": schema.ListNestedAttribute{
48+
NestedObject: schema.NestedAttributeObject{
49+
Attributes: map[string]schema.Attribute{
50+
"string_sensitive": schema.StringAttribute{
51+
Computed: true,
52+
Sensitive: true,
53+
},
54+
},
55+
},
56+
Computed: true,
57+
},
58+
},
59+
}
60+
}
61+
62+
func (r *NestedSensitiveResource) Create(ctx context.Context, request resource.CreateRequest, response *resource.CreateResponse) {
63+
var data *nestedSensitiveResourceModelV0
64+
response.Diagnostics.Append(request.Plan.Get(ctx, &data)...)
65+
66+
name := data.Name.ValueString()
67+
id := sdk.NewAccountObjectIdentifier(name)
68+
data.Id = types.StringValue(id.FullyQualifiedName())
69+
var diags diag.Diagnostics
70+
data.Output, diags = types.ListValueFrom(ctx, types.ObjectType{AttrTypes: map[string]attr.Type{"string_sensitive": types.StringType}}, []attr.Value{
71+
types.ObjectValueMust(map[string]attr.Type{"string_sensitive": types.StringType}, map[string]attr.Value{
72+
"string_sensitive": types.StringValue("SECRET"),
73+
}),
74+
})
75+
response.Diagnostics.Append(diags...)
76+
77+
if response.Diagnostics.HasError() {
78+
return
79+
}
80+
response.Diagnostics.Append(response.State.Set(ctx, &data)...)
81+
}
82+
83+
func (r *NestedSensitiveResource) Read(_ context.Context, _ resource.ReadRequest, _ *resource.ReadResponse) {
84+
}
85+
86+
func (r *NestedSensitiveResource) Update(_ context.Context, _ resource.UpdateRequest, _ *resource.UpdateResponse) {
87+
}
88+
89+
func (r *NestedSensitiveResource) Delete(_ context.Context, _ resource.DeleteRequest, _ *resource.DeleteResponse) {
90+
}
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
package testfunctional_test
2+
3+
import (
4+
"fmt"
5+
"regexp"
6+
"testing"
7+
8+
"github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk"
9+
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
10+
"github.com/hashicorp/terraform-plugin-testing/knownvalue"
11+
"github.com/hashicorp/terraform-plugin-testing/plancheck"
12+
"github.com/hashicorp/terraform-plugin-testing/tfjsonpath"
13+
"github.com/hashicorp/terraform-plugin-testing/tfversion"
14+
)
15+
16+
// This test proves that in the framework, sensitive attributes are not exposed by default in the output.
17+
// They can be accessed in the output block, but it must be properly marked.
18+
func TestAcc_TerraformPluginFrameworkFunctional_NestedSensitive(t *testing.T) {
19+
id := sdk.NewAccountObjectIdentifier("abc")
20+
resourceType := fmt.Sprintf("%s_nested_sensitive", PluginFrameworkFunctionalTestsProviderName)
21+
resourceReference := fmt.Sprintf("%s.test", resourceType)
22+
23+
resource.Test(t, resource.TestCase{
24+
ProtoV6ProviderFactories: providerForPluginFrameworkFunctionalTestsFactories,
25+
TerraformVersionChecks: []tfversion.TerraformVersionCheck{
26+
tfversion.RequireAbove(tfversion.Version1_5_0),
27+
},
28+
Steps: []resource.TestStep{
29+
{
30+
Config: nestedSensitiveConfig(id, resourceType),
31+
Check: resource.ComposeTestCheckFunc(
32+
resource.TestCheckResourceAttr(resourceReference, "id", id.FullyQualifiedName()),
33+
resource.TestCheckResourceAttr(resourceReference, "output.0.string_sensitive", "SECRET"),
34+
),
35+
ExpectError: regexp.MustCompile("Output refers to sensitive values"),
36+
},
37+
{
38+
Config: nestedSensitiveConfigWholeOutput(id, resourceType),
39+
Check: resource.ComposeTestCheckFunc(
40+
resource.TestCheckResourceAttr(resourceReference, "id", id.FullyQualifiedName()),
41+
resource.TestCheckResourceAttr(resourceReference, "output.0.string_sensitive", "SECRET"),
42+
),
43+
ExpectError: regexp.MustCompile("Output refers to sensitive values"),
44+
},
45+
{
46+
Config: nestedSensitiveConfigMarked(id, resourceType),
47+
ConfigPlanChecks: resource.ConfigPlanChecks{
48+
PostApplyPostRefresh: []plancheck.PlanCheck{
49+
plancheck.ExpectSensitiveValue(resourceReference, tfjsonpath.New("output").AtSliceIndex(0).AtMapKey("string_sensitive")),
50+
plancheck.ExpectKnownOutputValue("nested_sensitive", knownvalue.StringExact("SECRET")),
51+
},
52+
},
53+
Check: resource.ComposeTestCheckFunc(
54+
resource.TestCheckResourceAttr(resourceReference, "id", id.FullyQualifiedName()),
55+
resource.TestCheckResourceAttr(resourceReference, "output.0.string_sensitive", "SECRET"),
56+
),
57+
},
58+
},
59+
})
60+
}
61+
62+
func nestedSensitiveConfig(id sdk.AccountObjectIdentifier, resourceType string) string {
63+
return nestedSensitiveResourceConfig(id, resourceType) + fmt.Sprintf(`
64+
output "nested_sensitive" {
65+
value = resource.%s.test.output[0].string_sensitive
66+
}
67+
`, resourceType)
68+
}
69+
70+
func nestedSensitiveConfigWholeOutput(id sdk.AccountObjectIdentifier, resourceType string) string {
71+
return nestedSensitiveResourceConfig(id, resourceType) + fmt.Sprintf(`
72+
output "nested_sensitive" {
73+
value = resource.%s.test.output[0]
74+
}
75+
`, resourceType)
76+
}
77+
78+
func nestedSensitiveConfigMarked(id sdk.AccountObjectIdentifier, resourceType string) string {
79+
return nestedSensitiveResourceConfig(id, resourceType) + fmt.Sprintf(`
80+
output "nested_sensitive" {
81+
value = resource.%s.test.output[0].string_sensitive
82+
sensitive = true
83+
}
84+
`, resourceType)
85+
}
86+
87+
func nestedSensitiveResourceConfig(id sdk.AccountObjectIdentifier, resourceType string) string {
88+
return fmt.Sprintf(`
89+
resource "%[2]s" "test" {
90+
provider = "%[3]s"
91+
92+
name = "%[1]s"
93+
}
94+
`, id.Name(), resourceType, PluginFrameworkFunctionalTestsProviderName)
95+
}

0 commit comments

Comments
 (0)