Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat azurerm container group image registry credential identity #28

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

|Name|Severity|Enabled|
| --- | --- | --- |
|[azurerm_container_group_image_registry_credential_identity](./rules/azurerm_container_group_image_registry_credential_identity.md)|Warning|✔|
|[azurerm_eventhub_namespace_public_network_access_enabled](./rules/azurerm_eventhub_namespace_public_network_access_enabled.md)|Notice|✔|
|[azurerm_eventhub_namespace_unsecure_tls](./rules/azurerm_eventhub_namespace_unsecure_tls.md)|Warning|✔|
|[azurerm_iothub_endpoint_eventhub_authentication_type](./rules/azurerm_iothub_endpoint_eventhub_authentication_type.md)|Notice|✔|
Expand Down Expand Up @@ -50,6 +51,10 @@

## Rules by Resource

### azurerm_container_group

- [azurerm_container_group_image_registry_credential_identity](./rules/azurerm_container_group_image_registry_credential_identity.md)

### azurerm_eventhub_namespace

- [azurerm_eventhub_namespace_public_network_access_enabled](./rules/azurerm_eventhub_namespace_public_network_access_enabled.md)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# azurerm_container_group_image_registry_credential_identity

**Severity:** Warning


## Example

```hcl
resource "azurerm_container_group" "example" {
image_registry_credential {
server = "example.azurecr.io"
}
}
```

## Why

Using user_assigned_identity_id for image_registry_credential ensures secure, passwordless authentication to Azure Container Registry (ACR) via a managed identity, reducing the risk of credential exposure and enhancing access control.

## How to Fix

```hcl
resource "azurerm_container_group" "example" {
identity{
type = "UserAssigned"
identity_ids = [ data.azurerm_user_assigned_identity.example.id ]
}

image_registry_credential {
server = "example.azurecr.io"
user_assigned_identity_id = data.azurerm_user_assigned_identity.example.id
}
}
```


## How to disable

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

1 change: 1 addition & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ func createRuleSet() *tflint.BuiltinRuleSet {
Name: "azurerm-security",
Version: project.Version,
Rules: []tflint.Rule{
rules.NewAzurermContainerGroupImageRegistryCredentialIdentity(),
rules.NewAzurermEventhubNamespacePublicNetworkAccessEnabled(),
rules.NewAzurermEventhubNamespaceUnsecureTLS(),
rules.NewAzurermIoTHubEndpointEventHubAuthenticationType(),
Expand Down
92 changes: 92 additions & 0 deletions rules/azurem_container_group_image_credentials_identity.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package rules

import (
"fmt"
"strings"

"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"
)

// AzurermContainerGroupImageRegistryCredentialIdentity checks if ACR images have proper identity credentials
type AzurermContainerGroupImageRegistryCredentialIdentity struct {
tflint.DefaultRule

resourceType string
attributePath []string
}

// NewAzurermContainerGroupImageRegistryCredentialIdentity returns new rule
func NewAzurermContainerGroupImageRegistryCredentialIdentity() *AzurermContainerGroupImageRegistryCredentialIdentity {
return &AzurermContainerGroupImageRegistryCredentialIdentity{
resourceType: "azurerm_container_group",
attributePath: []string{"image_registry_credential", "user_assigned_identity_id"},
}
}

// Name returns the rule name
func (r *AzurermContainerGroupImageRegistryCredentialIdentity) Name() string {
return "azurerm_container_group_image_registry_credential_identity"

Check warning on line 31 in rules/azurem_container_group_image_credentials_identity.go

View check run for this annotation

Codecov / codecov/patch

rules/azurem_container_group_image_credentials_identity.go#L30-L31

Added lines #L30 - L31 were not covered by tests
}

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

Check warning on line 36 in rules/azurem_container_group_image_credentials_identity.go

View check run for this annotation

Codecov / codecov/patch

rules/azurem_container_group_image_credentials_identity.go#L35-L36

Added lines #L35 - L36 were not covered by tests
}

// Enabled returns whether the rule is enabled by default
func (r *AzurermContainerGroupImageRegistryCredentialIdentity) Enabled() bool {
return true

Check warning on line 41 in rules/azurem_container_group_image_credentials_identity.go

View check run for this annotation

Codecov / codecov/patch

rules/azurem_container_group_image_credentials_identity.go#L40-L41

Added lines #L40 - L41 were not covered by tests
}

// Link returns the rule reference link
func (r *AzurermContainerGroupImageRegistryCredentialIdentity) Link() string {
return project.ReferenceLink(r.Name())

Check warning on line 46 in rules/azurem_container_group_image_credentials_identity.go

View check run for this annotation

Codecov / codecov/patch

rules/azurem_container_group_image_credentials_identity.go#L45-L46

Added lines #L45 - L46 were not covered by tests
}

// Check runs the rule
func (r *AzurermContainerGroupImageRegistryCredentialIdentity) Check(runner tflint.Runner) error {
resources, err := runner.GetResourceContent("azurerm_container_group", &hclext.BodySchema{
Blocks: []hclext.BlockSchema{
{
Type: "image_registry_credential",
Body: &hclext.BodySchema{
Attributes: []hclext.AttributeSchema{
{Name: "user_assigned_identity_id"},
{Name: "server"},
},
},
},
},
}, nil)

if err != nil {
return err
}

Check warning on line 67 in rules/azurem_container_group_image_credentials_identity.go

View check run for this annotation

Codecov / codecov/patch

rules/azurem_container_group_image_credentials_identity.go#L66-L67

Added lines #L66 - L67 were not covered by tests

for _, resource := range resources.Blocks {
// Check credentials
for _, cred := range resource.Body.Blocks {
server, exists := cred.Body.Attributes["server"]
if !exists {
continue

Check warning on line 74 in rules/azurem_container_group_image_credentials_identity.go

View check run for this annotation

Codecov / codecov/patch

rules/azurem_container_group_image_credentials_identity.go#L74

Added line #L74 was not covered by tests
}

val, _ := server.Expr.Value(nil)
serverStr := val.AsString()
if strings.HasSuffix(serverStr, ".azurecr.io") {
if _, exists := cred.Body.Attributes["user_assigned_identity_id"]; !exists {
runner.EmitIssue(
r,
fmt.Sprintf("user_assigned_identity_id is missing in image_registry_credential for Azure Container Registry image for server %s", serverStr),
cred.DefRange,
)
}
}
}
}

return nil
}
60 changes: 60 additions & 0 deletions rules/azurem_container_group_image_credentials_identity_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package rules

import (
"testing"

hcl "github.com/hashicorp/hcl/v2"
"github.com/terraform-linters/tflint-plugin-sdk/helper"
)

func Test_AzurermContainerGroupImageRegistryCredentialIdentity(t *testing.T) {
rule := NewAzurermContainerGroupImageRegistryCredentialIdentity()

tests := []struct {
name string
content string
expected helper.Issues
}{
{
name: "missing user_assigned_identity_id",
content: `
resource "azurerm_container_group" "example" {
image_registry_credential {
server = "example.azurecr.io"
}
}`,
expected: helper.Issues{
{
Rule: rule,
Message: "user_assigned_identity_id is missing in image_registry_credential for Azure Container Registry image for server example.azurecr.io",
Range: hcl.Range{
Filename: "resource.tf",
Start: hcl.Pos{Line: 3, Column: 3},
End: hcl.Pos{Line: 3, Column: 28},
},
},
},
},
{
name: "with user_assigned_identity_id",
content: `
resource "azurerm_container_group" "example" {
image_registry_credential {
server = "example.azurecr.io"
user_assigned_identity_id = "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/test/providers/Microsoft.ManagedIdentity/userAssignedIdentities/test"
}
}`,
expected: helper.Issues{},
},
}

for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
runner := helper.TestRunner(t, map[string]string{"resource.tf": test.content})
if err := rule.Check(runner); err != nil {
t.Fatalf("Unexpected error occurred: %s", err)
}
helper.AssertIssues(t, test.expected, runner.Issues)
})
}
}
8 changes: 4 additions & 4 deletions rules/azurerm_eventhub_namespace_minimum_tls_version.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

"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 Down Expand Up @@ -46,7 +46,7 @@
}

// Link returns the rule reference link
func (r *AzurermEventhubNamespaceUnsecureTLS) Link() string {
func (r *AzurermEventhubNamespaceUnsecureTLS) Link() string {

Check warning on line 49 in rules/azurerm_eventhub_namespace_minimum_tls_version.go

View check run for this annotation

Codecov / codecov/patch

rules/azurerm_eventhub_namespace_minimum_tls_version.go#L49

Added line #L49 was not covered by tests
return project.ReferenceLink(r.Name())
}

Expand All @@ -66,7 +66,7 @@
if !exists {
continue
}
err := runner.EvaluateExpr(attribute.Expr, func (val string) error {
err := runner.EvaluateExpr(attribute.Expr, func(val string) error {
found := false
for _, item := range r.enum {
if item == val {
Expand All @@ -88,4 +88,4 @@
}

return nil
}
}
7 changes: 7 additions & 0 deletions scripts/update_readme_version.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#!/bin/sh

# Read the version from project/main.go
version=$(grep -oP 'const Version string = "\K[^"]+' project/main.go)

# Update the version in README.md
sed -i "s/0\.1\.[0-9]\+/$version/" README.md
Loading