Skip to content

Commit d235132

Browse files
authored
feat: sql server rules (#13)
* feat: sql server rules * fix copypaste * linter
1 parent 35128c0 commit d235132

10 files changed

+705
-0
lines changed

docs/README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,20 @@
3030
| --- | --- | --- | --- | --- |
3131
|azurerm_mssql_database_transparent_data_encryption_enabled|Enforce transparant data encryption|WARNING|||
3232

33+
## azurerm_mssql_server
34+
|Name|Description|Severity|Enabled|Link|
35+
| --- | --- | --- | --- | --- |
36+
|azurerm_mssql_server_azuread_authentication_only |Only user Azure AD authentication to SQL |WARNING|||
37+
|azurerm_mssql_server_public_network_access_enabled|Consider disabling public network access on SQL servers. |NOTICE|||
38+
|azurerm_mssql_server_minimum_tls_version|Enforce TLS 1.2 on SQL servers. |WARNING|||
39+
40+
## azurerm_mssql_firewall_rule
41+
42+
|Name|Description|Severity|Enabled|Link|
43+
| --- | --- | --- | --- | --- |
44+
|azurerm_mssql_firewall_rule_all_allowed|Remove a firewall rule that allows the any ip.|ERROR|||
45+
46+
3347
## azurerm_storage_account
3448
|Name|Description|Severity|Enabled|Link|
3549
| --- | --- | --- | --- | --- |

main.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ func createRuleSet() *tflint.BuiltinRuleSet {
2222
rules.NewAzurermLinuxWebAppHTTPSOnly(),
2323
rules.NewAzurermLinuxWebAppMinimumTLSVersion(),
2424
rules.NewAzurermMssqlDatabaseEncryption(),
25+
rules.NewAzurermMsSQLFirewallRuleAllAllowed(),
26+
rules.NewAzurermMsSQLServerAdAuthOnly(),
27+
rules.NewAzurermMsSQLServerPublicNetworkAccessEnabled(),
28+
rules.NewAzurermMsSQLServerUnsecureTLS(),
2529
rules.NewAzurermStorageAccountPublicNetworkAccessEnabled(),
2630
rules.NewAzurermStorageAccountUnsecureTLS(),
2731
rules.NewAzurermWindowsFunctionAppFtpsState(),
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
package rules
2+
3+
import (
4+
"github.com/terraform-linters/tflint-plugin-sdk/hclext"
5+
"github.com/terraform-linters/tflint-plugin-sdk/tflint"
6+
)
7+
8+
// AzurermMsSQLFirewallRuleAllAllowed checks if the firewall rule allows all IP addresses
9+
type AzurermMsSQLFirewallRuleAllAllowed struct {
10+
tflint.DefaultRule
11+
12+
resourceType string
13+
startIPAttr string
14+
endIPAttr string
15+
}
16+
17+
// NewAzurermMsSQLFirewallRuleAllAllowed returns a new rule instance
18+
func NewAzurermMsSQLFirewallRuleAllAllowed() *AzurermMsSQLFirewallRuleAllAllowed {
19+
return &AzurermMsSQLFirewallRuleAllAllowed{
20+
resourceType: "azurerm_mssql_firewall_rule",
21+
startIPAttr: "start_ip_address",
22+
endIPAttr: "end_ip_address",
23+
}
24+
}
25+
26+
// Name returns the rule name
27+
func (r *AzurermMsSQLFirewallRuleAllAllowed) Name() string {
28+
return "azurerm_mssql_firewall_rule_all_allowed"
29+
}
30+
31+
// Enabled returns whether the rule is enabled by default
32+
func (r *AzurermMsSQLFirewallRuleAllAllowed) Enabled() bool {
33+
return true
34+
}
35+
36+
// Severity returns the rule severity
37+
func (r *AzurermMsSQLFirewallRuleAllAllowed) Severity() tflint.Severity {
38+
return tflint.ERROR
39+
}
40+
41+
// Link returns the rule reference link
42+
func (r *AzurermMsSQLFirewallRuleAllAllowed) Link() string {
43+
return ""
44+
}
45+
46+
// Check checks if the firewall rule allows all IP addresses
47+
func (r *AzurermMsSQLFirewallRuleAllAllowed) Check(runner tflint.Runner) error {
48+
resources, err := runner.GetResourceContent(r.resourceType, &hclext.BodySchema{
49+
Attributes: []hclext.AttributeSchema{
50+
{Name: r.startIPAttr},
51+
{Name: r.endIPAttr},
52+
},
53+
}, nil)
54+
if err != nil {
55+
return err
56+
}
57+
58+
for _, resource := range resources.Blocks {
59+
startIP, exists := resource.Body.Attributes[r.startIPAttr]
60+
if !exists {
61+
continue
62+
}
63+
64+
endIP, exists := resource.Body.Attributes[r.endIPAttr]
65+
if !exists {
66+
continue
67+
}
68+
69+
var startIPValue, endIPValue string
70+
err := runner.EvaluateExpr(startIP.Expr, func(val string) error {
71+
startIPValue = val
72+
return nil
73+
}, nil)
74+
if err != nil {
75+
return err
76+
}
77+
78+
err = runner.EvaluateExpr(endIP.Expr, func(val string) error {
79+
endIPValue = val
80+
return nil
81+
}, nil)
82+
if err != nil {
83+
return err
84+
}
85+
86+
if startIPValue == "0.0.0.0" && endIPValue == "255.255.255.255" {
87+
runner.EmitIssue(
88+
r,
89+
"Firewall rule allows access from all IP addresses (0.0.0.0-255.255.255.255). Consider restricting the IP range for better security.",
90+
resource.DefRange,
91+
)
92+
}
93+
}
94+
95+
return nil
96+
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
package rules
2+
3+
import (
4+
"testing"
5+
6+
hcl "github.com/hashicorp/hcl/v2"
7+
"github.com/terraform-linters/tflint-plugin-sdk/helper"
8+
)
9+
10+
func Test_AzurermMsSQLFirewallRuleAllAllowed(t *testing.T) {
11+
tests := []struct {
12+
Name string
13+
Content string
14+
Expected helper.Issues
15+
}{
16+
{
17+
Name: "all IPs allowed",
18+
Content: `
19+
resource "azurerm_mssql_firewall_rule" "example" {
20+
start_ip_address = "0.0.0.0"
21+
end_ip_address = "255.255.255.255"
22+
}`,
23+
Expected: helper.Issues{
24+
{
25+
Rule: NewAzurermMsSQLFirewallRuleAllAllowed(),
26+
Message: "Firewall rule allows access from all IP addresses (0.0.0.0-255.255.255.255). Consider restricting the IP range for better security.",
27+
Range: hcl.Range{
28+
Filename: "resource.tf",
29+
Start: hcl.Pos{Line: 2, Column: 1},
30+
End: hcl.Pos{Line: 2, Column: 49},
31+
},
32+
},
33+
},
34+
},
35+
{
36+
Name: "specific IP range",
37+
Content: `
38+
resource "azurerm_mssql_firewall_rule" "example" {
39+
start_ip_address = "10.0.0.0"
40+
end_ip_address = "10.0.0.255"
41+
}`,
42+
Expected: helper.Issues{},
43+
},
44+
{
45+
Name: "missing IP addresses",
46+
Content: `
47+
resource "azurerm_mssql_firewall_rule" "example" {
48+
}`,
49+
Expected: helper.Issues{},
50+
},
51+
}
52+
53+
rule := NewAzurermMsSQLFirewallRuleAllAllowed()
54+
55+
for _, test := range tests {
56+
t.Run(test.Name, func(t *testing.T) {
57+
runner := helper.TestRunner(t, map[string]string{"resource.tf": test.Content})
58+
59+
if err := rule.Check(runner); err != nil {
60+
t.Fatalf("Unexpected error occurred: %s", err)
61+
}
62+
63+
helper.AssertIssues(t, test.Expected, runner.Issues)
64+
})
65+
}
66+
}
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
package rules
2+
3+
import (
4+
"fmt"
5+
"strings"
6+
7+
"github.com/terraform-linters/tflint-plugin-sdk/hclext"
8+
"github.com/terraform-linters/tflint-plugin-sdk/tflint"
9+
)
10+
11+
// AzurermMsSQLServerAdAuthOnly checks that azuread_authentication_only is set to true
12+
type AzurermMsSQLServerAdAuthOnly struct {
13+
tflint.DefaultRule
14+
15+
resourceType string
16+
attributePath []string
17+
expectedValue string
18+
}
19+
20+
// NewAzurermMsSQLServerAdAuthOnly returns a new rule instance
21+
func NewAzurermMsSQLServerAdAuthOnly() *AzurermMsSQLServerAdAuthOnly {
22+
return &AzurermMsSQLServerAdAuthOnly{
23+
resourceType: "azurerm_mssql_server",
24+
attributePath: []string{"azuread_administrator", "azuread_authentication_only"},
25+
expectedValue: "true",
26+
}
27+
}
28+
29+
// Name returns the rule name
30+
func (r *AzurermMsSQLServerAdAuthOnly) Name() string {
31+
return "azurerm_mssql_server_azuread_authentication_only"
32+
}
33+
34+
// Enabled returns whether the rule is enabled by default
35+
func (r *AzurermMsSQLServerAdAuthOnly) Enabled() bool {
36+
return true
37+
}
38+
39+
// Severity returns the rule severity
40+
func (r *AzurermMsSQLServerAdAuthOnly) Severity() tflint.Severity {
41+
return tflint.WARNING
42+
}
43+
44+
// Link returns the rule reference link
45+
func (r *AzurermMsSQLServerAdAuthOnly) Link() string {
46+
return ""
47+
}
48+
49+
// Check verifies that azuread_authentication_only is set to "Disabled"
50+
func (r *AzurermMsSQLServerAdAuthOnly) Check(runner tflint.Runner) error {
51+
resources, err := runner.GetResourceContent(r.resourceType, &hclext.BodySchema{
52+
Blocks: []hclext.BlockSchema{
53+
{
54+
Type: "azuread_administrator",
55+
Body: &hclext.BodySchema{
56+
Attributes: []hclext.AttributeSchema{
57+
{Name: "azuread_authentication_only"},
58+
},
59+
},
60+
},
61+
},
62+
}, nil)
63+
if err != nil {
64+
return err
65+
}
66+
67+
for _, resource := range resources.Blocks {
68+
siteConfigBlocks := resource.Body.Blocks.OfType("azuread_administrator")
69+
if len(siteConfigBlocks) == 0 {
70+
runner.EmitIssue(
71+
r,
72+
"azuread_administrator block is missing, azuread_authentication_only should be set to true",
73+
resource.DefRange,
74+
)
75+
continue
76+
}
77+
78+
siteConfig := siteConfigBlocks[0]
79+
attribute, exists := siteConfig.Body.Attributes["azuread_authentication_only"]
80+
if !exists {
81+
runner.EmitIssue(
82+
r,
83+
"azuread_authentication_only is missing in azuread_administrator, should be set to true",
84+
siteConfig.DefRange,
85+
)
86+
continue
87+
}
88+
89+
err := runner.EvaluateExpr(attribute.Expr, func(val string) error {
90+
if !strings.EqualFold(val, r.expectedValue) {
91+
runner.EmitIssue(
92+
r,
93+
fmt.Sprintf("azuread_authentication_only is set to %s, should be set to true", val),
94+
attribute.Expr.Range(),
95+
)
96+
}
97+
return nil
98+
}, nil)
99+
if err != nil {
100+
return err
101+
}
102+
}
103+
104+
return nil
105+
}

0 commit comments

Comments
 (0)