Skip to content

Commit

Permalink
purge_soft_delete_on_destroy & recover_soft_deleted_key_vaults on the…
Browse files Browse the repository at this point in the history
… provider
  • Loading branch information
pregress committed Nov 24, 2024
1 parent 83ea962 commit 917e3f0
Show file tree
Hide file tree
Showing 4 changed files with 185 additions and 101 deletions.
2 changes: 2 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
|[azurerm_key_vault_enable_rbac_authorization](./rules/azurerm_key_vault_enable_rbac_authorization.md)|Warning||
|[azurerm_key_vault_key_rotation_policy](./rules/azurerm_key_vault_key_rotation_policy.md)|Warning||
|[azurerm_key_vault_public_network_access_enabled](./rules/azurerm_key_vault_public_network_access_enabled.md)|Notice||
|[azurerm_keyvault_features_check](./rules/azurerm_keyvault_features_check.md)|Warning||
|[azurerm_linux_function_app_ftps_state](./rules/azurerm_linux_function_app_ftps_state.md)|Warning||
|[azurerm_linux_function_app_https_only](./rules/azurerm_linux_function_app_https_only.md)|Warning||
|[azurerm_linux_function_app_minimum_tls_version](./rules/azurerm_linux_function_app_minimum_tls_version.md)|Warning||
Expand Down Expand Up @@ -62,6 +63,7 @@

- [azurerm_key_vault_enable_rbac_authorization](./rules/azurerm_key_vault_enable_rbac_authorization.md)
- [azurerm_key_vault_public_network_access_enabled](./rules/azurerm_key_vault_public_network_access_enabled.md)
- [azurerm_keyvault_features_check](./rules/azurerm_keyvault_features_check.md)

### azurerm_key_vault_certificate

Expand Down
46 changes: 46 additions & 0 deletions docs/rules/azurerm_keyvault_features_check.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# azurerm_keyvault_features_check

**Severity:** Warning


## Example

```hcl
provider "azurerm" {
alias = "prod"
features {
key_vault {
purge_soft_delete_on_destroy = false
recover_soft_deleted_key_vaults = false
}
}
}
```

## Why

Setting purge_soft_delete_on_destroy and recover_soft_deleted_key_vaults to true ensures that deleted Key Vaults are securely purged and recoverable, preventing unintended data loss while adhering to compliance and security policies.

## How to Fix

```hcl
provider "azurerm" {
alias = "prod"
features {
key_vault {
purge_soft_delete_on_destroy = true
recover_soft_deleted_key_vaults = true
}
}
}
```


## How to disable

```hcl
rule "azurerm_keyvault_features_check" {
enabled = false
}
```

208 changes: 122 additions & 86 deletions rules/azurerm_keyvault_features_check.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"fmt"
"strings"

"github.com/hashicorp/hcl/v2"
"github.com/terraform-linters/tflint-plugin-sdk/hclext"
"github.com/terraform-linters/tflint-plugin-sdk/tflint"
"github.com/terraform-linters/tflint-ruleset-azurerm-security/project"
Expand All @@ -25,7 +26,7 @@ func NewAzureRmKeyVaultFeaturesRule() *AzureRmKeyVaultFeaturesRule {

// Name returns the rule name
func (r *AzureRmKeyVaultFeaturesRule) Name() string {
return "azure_keyvault_features_check"
return "azurerm_keyvault_features_check"

Check warning on line 29 in rules/azurerm_keyvault_features_check.go

View check run for this annotation

Codecov / codecov/patch

rules/azurerm_keyvault_features_check.go#L28-L29

Added lines #L28 - L29 were not covered by tests
}

// Enabled returns whether the rule is enabled by default
Expand All @@ -35,7 +36,7 @@ func (r *AzureRmKeyVaultFeaturesRule) Enabled() bool {

// Severity returns the rule severity
func (r *AzureRmKeyVaultFeaturesRule) Severity() tflint.Severity {
return tflint.ERROR
return tflint.WARNING

Check warning on line 39 in rules/azurerm_keyvault_features_check.go

View check run for this annotation

Codecov / codecov/patch

rules/azurerm_keyvault_features_check.go#L38-L39

Added lines #L38 - L39 were not covered by tests
}

// Link returns the rule reference link
Expand All @@ -45,14 +46,11 @@ func (r *AzureRmKeyVaultFeaturesRule) Link() string {

// Check runs the rule
func (r *AzureRmKeyVaultFeaturesRule) Check(runner tflint.Runner) error {
// Schema for resource attributes
resourceSchema := &hclext.BodySchema{
resources, err := runner.GetResourceContent(r.resourceType, &hclext.BodySchema{
Attributes: []hclext.AttributeSchema{
{Name: "provider"},
},
}

resources, err := runner.GetResourceContent(r.resourceType, resourceSchema, nil)
}, nil)
if err != nil {
return err
}

Check warning on line 56 in rules/azurerm_keyvault_features_check.go

View check run for this annotation

Codecov / codecov/patch

rules/azurerm_keyvault_features_check.go#L55-L56

Added lines #L55 - L56 were not covered by tests
Expand All @@ -61,8 +59,10 @@ func (r *AzureRmKeyVaultFeaturesRule) Check(runner tflint.Runner) error {
return nil
}

// Schema for provider features
providerSchema := &hclext.BodySchema{
providers, err := runner.GetProviderContent("azurerm", &hclext.BodySchema{
Attributes: []hclext.AttributeSchema{
{Name: "alias"},
},
Blocks: []hclext.BlockSchema{
{
Type: "features",
Expand All @@ -81,35 +81,16 @@ func (r *AzureRmKeyVaultFeaturesRule) Check(runner tflint.Runner) error {
},
},
},
}, nil)
if err != nil {
return err
}

Check warning on line 87 in rules/azurerm_keyvault_features_check.go

View check run for this annotation

Codecov / codecov/patch

rules/azurerm_keyvault_features_check.go#L86-L87

Added lines #L86 - L87 were not covered by tests

// For each Key Vault resource
for _, resource := range resources.Blocks {
var providerName string

// Check if provider is specified on the resource
if attr, exists := resource.Body.Attributes["provider"]; exists {
// Extract provider name from the reference (e.g., "azurerm.prod" -> "azurerm")
providerVal, diags := attr.Expr.Value(nil)
if !diags.HasErrors() && providerVal.Type() == cty.String {
providerStr := providerVal.AsString()
if strings.Contains(providerStr, ".") {
providerName = strings.Split(providerStr, ".")[0]
} else {
providerName = providerStr
}
}
} else {
providerName = "azurerm"
}

// Get the provider configuration
providers, err := runner.GetProviderContent(providerName, providerSchema, nil)
if err != nil {
return err
}
targetProvider := r.findTargetProvider(resource, providers.Blocks)
providerDisplayName := r.getProviderDisplayName(targetProvider)

if len(providers.Blocks) == 0 {
if targetProvider == nil {
runner.EmitIssue(
r,
"No provider configuration found for Azure Key Vault resource",
Expand All @@ -118,84 +99,139 @@ func (r *AzureRmKeyVaultFeaturesRule) Check(runner tflint.Runner) error {
continue

Check warning on line 99 in rules/azurerm_keyvault_features_check.go

View check run for this annotation

Codecov / codecov/patch

rules/azurerm_keyvault_features_check.go#L94-L99

Added lines #L94 - L99 were not covered by tests
}

provider := providers.Blocks[0]

// Check features block
var featuresBlock *hclext.Block
for _, block := range provider.Body.Blocks {
if block.Type == "features" {
featuresBlock = block
break
}
}

featuresBlock := r.findFeaturesBlock(targetProvider)
if featuresBlock == nil {
runner.EmitIssue(
r,
fmt.Sprintf("features block is missing in the Azure provider configuration of provider %s", providerName),
provider.DefRange,
fmt.Sprintf("features block is missing in the Azure provider configuration of provider %s", providerDisplayName),
targetProvider.DefRange,
)
continue
}

// Check key_vault block within features
var keyVaultBlock *hclext.Block
for _, block := range featuresBlock.Body.Blocks {
if block.Type == "key_vault" {
keyVaultBlock = block
break
}
}

// Check key_vault block
keyVaultBlock := r.findKeyVaultBlock(featuresBlock)
if keyVaultBlock == nil {
runner.EmitIssue(
r,
fmt.Sprintf("key_vault block is missing in the features configuration of provider %s", providerName),
featuresBlock.DefRange,
fmt.Sprintf("key_vault block is missing in the features configuration of provider %s", providerDisplayName),
targetProvider.DefRange,
)
continue
}

// Check purge_soft_delete_on_destroy setting
// Check purge_soft_delete_on_destroy
if attr, exists := keyVaultBlock.Body.Attributes["purge_soft_delete_on_destroy"]; exists {
val, diags := attr.Expr.Value(nil)
if !diags.HasErrors() && val.Type() == cty.Bool {
if !val.True() {
runner.EmitIssue(
r,
fmt.Sprintf("purge_soft_delete_on_destroy must be set to true in key_vault features of provider %s", providerName),
attr.Range,
)
}
if !diags.HasErrors() && val.Type() == cty.Bool && !val.True() {
runner.EmitIssue(
r,
fmt.Sprintf("purge_soft_delete_on_destroy must be set to true in key_vault features of provider %s", providerDisplayName),
attr.Range,
)
}
} else {
runner.EmitIssue(
r,
fmt.Sprintf("purge_soft_delete_on_destroy must be set to true in key_vault features of provider %s", providerName),
keyVaultBlock.DefRange,
)
}

// Check recover_soft_deleted_key_vaults setting
// Check recover_soft_deleted_key_vaults
if attr, exists := keyVaultBlock.Body.Attributes["recover_soft_deleted_key_vaults"]; exists {
val, diags := attr.Expr.Value(nil)
if !diags.HasErrors() && val.Type() == cty.Bool {
if !val.True() {
runner.EmitIssue(
r,
fmt.Sprintf("recover_soft_deleted_key_vaults must be set to true in key_vault features of provider %s", providerName),
attr.Range,
)
if !diags.HasErrors() && val.Type() == cty.Bool && !val.True() {
runner.EmitIssue(
r,
fmt.Sprintf("recover_soft_deleted_key_vaults must be set to true in key_vault features of provider %s", providerDisplayName),
attr.Range,
)
}
}
}

return nil
}

func (r *AzureRmKeyVaultFeaturesRule) findTargetProvider(resource *hclext.Block, providers []*hclext.Block) *hclext.Block {
providerAttr, hasProvider := resource.Body.Attributes["provider"]
if !hasProvider {
// If no provider specified, look for the default provider (no alias)
for _, p := range providers {
if _, hasAlias := p.Body.Attributes["alias"]; !hasAlias {
return p
}
}
return nil

Check warning on line 161 in rules/azurerm_keyvault_features_check.go

View check run for this annotation

Codecov / codecov/patch

rules/azurerm_keyvault_features_check.go#L161

Added line #L161 was not covered by tests
}

// Get the raw provider reference traversal
traversal := providerAttr.Expr.Variables()
if len(traversal) > 0 {
// For provider references like azurerm.prod, we expect traversal with 2 parts
if len(traversal[0]) >= 2 {
// The second part of the traversal should be the alias
if aliasStep, ok := traversal[0][1].(hcl.TraverseAttr); ok {
targetAlias := aliasStep.Name
// Find provider with matching alias
for _, p := range providers {
if aliasAttr, exists := p.Body.Attributes["alias"]; exists {
aliasVal, diags := aliasAttr.Expr.Value(nil)
if !diags.HasErrors() && aliasVal.Type() == cty.String && aliasVal.AsString() == targetAlias {
return p
}
}
}
}
} else {
runner.EmitIssue(
r,
fmt.Sprintf("recover_soft_deleted_key_vaults must be set to true in key_vault features of provider %s", providerName),
keyVaultBlock.DefRange,
)
}
return nil

Check warning on line 183 in rules/azurerm_keyvault_features_check.go

View check run for this annotation

Codecov / codecov/patch

rules/azurerm_keyvault_features_check.go#L183

Added line #L183 was not covered by tests
}

// If we can't get the traversal, try string evaluation as fallback
providerVal, diags := providerAttr.Expr.Value(nil)
if !diags.HasErrors() && providerVal.Type() == cty.String {
parts := strings.Split(providerVal.AsString(), ".")
if len(parts) == 2 {
targetAlias := parts[1]
// Find provider with matching alias
for _, p := range providers {
if aliasAttr, exists := p.Body.Attributes["alias"]; exists {
aliasVal, diags := aliasAttr.Expr.Value(nil)
if !diags.HasErrors() && aliasVal.Type() == cty.String && aliasVal.AsString() == targetAlias {
return p
}

Check warning on line 198 in rules/azurerm_keyvault_features_check.go

View check run for this annotation

Codecov / codecov/patch

rules/azurerm_keyvault_features_check.go#L187-L198

Added lines #L187 - L198 were not covered by tests
}
}
}
}

return nil

Check warning on line 204 in rules/azurerm_keyvault_features_check.go

View check run for this annotation

Codecov / codecov/patch

rules/azurerm_keyvault_features_check.go#L204

Added line #L204 was not covered by tests
}

func (r *AzureRmKeyVaultFeaturesRule) getProviderDisplayName(provider *hclext.Block) string {
aliasAttr, hasAlias := provider.Body.Attributes["alias"]
if !hasAlias {
return "azurerm"
}

aliasVal, diags := aliasAttr.Expr.Value(nil)
if diags.HasErrors() || aliasVal.Type() != cty.String {
return "azurerm"
}

Check warning on line 216 in rules/azurerm_keyvault_features_check.go

View check run for this annotation

Codecov / codecov/patch

rules/azurerm_keyvault_features_check.go#L215-L216

Added lines #L215 - L216 were not covered by tests

return aliasVal.AsString()
}

func (r *AzureRmKeyVaultFeaturesRule) findFeaturesBlock(provider *hclext.Block) *hclext.Block {
for _, block := range provider.Body.Blocks {
if block.Type == "features" {
return block
}
}
return nil
}

func (r *AzureRmKeyVaultFeaturesRule) findKeyVaultBlock(features *hclext.Block) *hclext.Block {
for _, block := range features.Body.Blocks {
if block.Type == "key_vault" {
return block
}
}
return nil
}
Loading

0 comments on commit 917e3f0

Please sign in to comment.