feat(misconf): Update SecurityCenter schema (#9674)

Signed-off-by: nikpivkin <nikita.pivkin@smartforce.io>
Co-authored-by: nikpivkin <nikita.pivkin@smartforce.io>
This commit is contained in:
yagreut
2025-11-04 16:52:44 +02:00
committed by GitHub
parent 2690ac9934
commit 58819c5285
6 changed files with 195 additions and 15 deletions

View File

@@ -3,6 +3,7 @@ package securitycenter
import (
"github.com/aquasecurity/trivy/pkg/iac/providers/azure/securitycenter"
"github.com/aquasecurity/trivy/pkg/iac/scanners/azure"
iacTypes "github.com/aquasecurity/trivy/pkg/iac/types"
)
func Adapt(deployment azure.Deployment) securitycenter.SecurityCenter {
@@ -12,7 +13,8 @@ func Adapt(deployment azure.Deployment) securitycenter.SecurityCenter {
}
}
func adaptContacts(deployment azure.Deployment) (contacts []securitycenter.Contact) {
func adaptContacts(deployment azure.Deployment) []securitycenter.Contact {
var contacts []securitycenter.Contact
for _, resource := range deployment.GetResourcesByType("Microsoft.Security/securityContacts") {
contacts = append(contacts, adaptContact(resource))
}
@@ -21,16 +23,59 @@ func adaptContacts(deployment azure.Deployment) (contacts []securitycenter.Conta
}
func adaptContact(resource azure.Resource) securitycenter.Contact {
alertsToAdminsState := resource.Properties.GetMapValue("notificationsByRole").GetMapValue("state").AsStringValue("", resource.Metadata)
isEnabledValue := resource.Properties.GetMapValue("isEnabled").AsBoolValue(false, resource.Metadata)
enableAlertNotifications, minimalSeverity := extractNotificationSettings(resource, isEnabledValue)
return securitycenter.Contact{
Metadata: resource.Metadata,
// TODO: The email field does not exist
// https://learn.microsoft.com/en-us/azure/templates/microsoft.security/securitycontacts?pivots=deployment-language-arm-template#securitycontactproperties-1
EnableAlertNotifications: resource.Properties.GetMapValue("email").AsBoolValue(false, resource.Metadata),
Metadata: resource.Metadata,
EnableAlertNotifications: enableAlertNotifications,
EnableAlertsToAdmins: iacTypes.Bool(alertsToAdminsState.EqualTo("On"), resource.Metadata),
Email: resource.Properties.GetMapValue("emails").AsStringValue("", resource.Metadata),
Phone: resource.Properties.GetMapValue("phone").AsStringValue("", resource.Metadata),
IsEnabled: isEnabledValue,
MinimalSeverity: minimalSeverity,
}
}
func adaptSubscriptions(deployment azure.Deployment) (subscriptions []securitycenter.SubscriptionPricing) {
func extractNotificationSettings(resource azure.Resource, isEnabled iacTypes.BoolValue) (iacTypes.BoolValue, iacTypes.StringValue) {
notificationsSources := resource.Properties.GetMapValue("notificationsSources")
if !notificationsSources.IsNull() {
return extractFromNotificationsSources(notificationsSources, isEnabled, resource)
}
enableAlertNotifications := resource.Properties.GetMapValue("alertNotifications").AsBoolValue(false, resource.Metadata)
minimalSeverity := iacTypes.StringDefault("", resource.Metadata)
return enableAlertNotifications, minimalSeverity
}
func extractFromNotificationsSources(notificationsSources azure.Value, isEnabled iacTypes.BoolValue, resource azure.Resource) (iacTypes.BoolValue, iacTypes.StringValue) {
minimalSeverity := iacTypes.StringDefault("", resource.Metadata)
for _, source := range notificationsSources.AsList() {
sourceMap := source.AsMap()
if sourceMap == nil {
continue
}
sourceType, hasSourceType := sourceMap["sourceType"]
if !hasSourceType || !sourceType.AsStringValue("", resource.Metadata).EqualTo("Alert") {
continue
}
if minimalSeverityVal, hasMinimalSeverity := sourceMap["minimalSeverity"]; hasMinimalSeverity {
minimalSeverity = minimalSeverityVal.AsStringValue("", resource.Metadata)
}
break
}
enableAlertNotifications := iacTypes.Bool(isEnabled.IsTrue() && !minimalSeverity.IsEmpty(), resource.Metadata)
return enableAlertNotifications, minimalSeverity
}
func adaptSubscriptions(deployment azure.Deployment) []securitycenter.SubscriptionPricing {
var subscriptions []securitycenter.SubscriptionPricing
for _, resource := range deployment.GetResourcesByType("Microsoft.Security/pricings") {
subscriptions = append(subscriptions, adaptSubscription(resource))
}

View File

@@ -36,13 +36,18 @@ func TestAdapt(t *testing.T) {
},
},
{
name: "complete",
name: "complete - legacy format",
source: `{
"resources": [
{
"type": "Microsoft.Security/securityContacts",
"properties": {
"phone": "buz"
"emails": "security@example.com",
"phone": "buz",
"alertNotifications": true,
"notificationsByRole": {
"state": "On"
}
}
},
{
@@ -55,13 +60,93 @@ func TestAdapt(t *testing.T) {
}`,
expected: securitycenter.SecurityCenter{
Contacts: []securitycenter.Contact{{
Phone: types.StringTest("buz"),
Email: types.StringTest("security@example.com"),
Phone: types.StringTest("buz"),
EnableAlertNotifications: types.BoolTest(true),
EnableAlertsToAdmins: types.BoolTest(true),
}},
Subscriptions: []securitycenter.SubscriptionPricing{{
Tier: types.StringTest("Standard"),
}},
},
},
{
name: "complete - new format",
source: `{
"resources": [
{
"type": "Microsoft.Security/securityContacts",
"properties": {
"emails": "security@example.com",
"phone": "+1-555-555-5555",
"isEnabled": true,
"notificationsSources": [
{
"sourceType": "Alert",
"minimalSeverity": "High"
}
],
"notificationsByRole": {
"state": "On"
}
}
},
{
"type": "Microsoft.Security/pricings",
"properties": {
"pricingTier": "Standard"
}
}
]
}`,
expected: securitycenter.SecurityCenter{
Contacts: []securitycenter.Contact{{
Email: types.StringTest("security@example.com"),
Phone: types.StringTest("+1-555-555-5555"),
EnableAlertNotifications: types.BoolTest(true),
EnableAlertsToAdmins: types.BoolTest(true),
IsEnabled: types.BoolTest(true),
MinimalSeverity: types.StringTest("High"),
}},
Subscriptions: []securitycenter.SubscriptionPricing{{
Tier: types.StringTest("Standard"),
}},
},
},
{
name: "new format - disabled",
source: `{
"resources": [
{
"type": "Microsoft.Security/securityContacts",
"properties": {
"emails": "security@example.com",
"phone": "+1-555-555-5555",
"isEnabled": false,
"notificationsSources": [
{
"sourceType": "Alert",
"minimalSeverity": "Medium"
}
],
"notificationsByRole": {
"state": "Off"
}
}
}
]
}`,
expected: securitycenter.SecurityCenter{
Contacts: []securitycenter.Contact{{
Email: types.StringTest("security@example.com"),
Phone: types.StringTest("+1-555-555-5555"),
EnableAlertNotifications: types.BoolTest(false),
EnableAlertsToAdmins: types.BoolTest(false),
IsEnabled: types.BoolTest(false),
MinimalSeverity: types.StringTest("Medium"),
}},
},
},
}
for _, tt := range tests {

View File

@@ -3,6 +3,7 @@ package securitycenter
import (
"github.com/aquasecurity/trivy/pkg/iac/providers/azure/securitycenter"
"github.com/aquasecurity/trivy/pkg/iac/terraform"
iacTypes "github.com/aquasecurity/trivy/pkg/iac/types"
)
func Adapt(modules terraform.Modules) securitycenter.SecurityCenter {
@@ -38,13 +39,24 @@ func adaptContact(resource *terraform.Block) securitycenter.Contact {
enableAlertNotifAttr := resource.GetAttribute("alert_notifications")
enableAlertNotifVal := enableAlertNotifAttr.AsBoolValueOrDefault(false, resource)
// TODO: add support for the new format https://github.com/hashicorp/terraform-provider-azurerm/issues/30797
alertsToAdminsAttr := resource.GetAttribute("alerts_to_admins")
alertsToAdminsVal := alertsToAdminsAttr.AsBoolValueOrDefault(false, resource)
emailAttr := resource.GetAttribute("email")
emailVal := emailAttr.AsStringValueOrDefault("", resource)
phoneAttr := resource.GetAttribute("phone")
phoneVal := phoneAttr.AsStringValueOrDefault("", resource)
return securitycenter.Contact{
Metadata: resource.GetMetadata(),
EnableAlertNotifications: enableAlertNotifVal,
EnableAlertsToAdmins: alertsToAdminsVal,
Email: emailVal,
Phone: phoneVal,
IsEnabled: iacTypes.BoolValue{}, // Not supported in Terraform provider yet
MinimalSeverity: iacTypes.StringValue{}, // Not supported in Terraform provider yet
}
}

View File

@@ -22,14 +22,20 @@ func Test_adaptContact(t *testing.T) {
name: "defined",
terraform: `
resource "azurerm_security_center_contact" "example" {
email = "contact@example.com"
phone = "+1-555-555-5555"
alert_notifications = true
alerts_to_admins = true
}
`,
expected: securitycenter.Contact{
Metadata: iacTypes.NewTestMetadata(),
EnableAlertNotifications: iacTypes.Bool(true, iacTypes.NewTestMetadata()),
EnableAlertsToAdmins: iacTypes.Bool(true, iacTypes.NewTestMetadata()),
Email: iacTypes.String("contact@example.com", iacTypes.NewTestMetadata()),
Phone: iacTypes.String("+1-555-555-5555", iacTypes.NewTestMetadata()),
IsEnabled: iacTypes.BoolValue{},
MinimalSeverity: iacTypes.StringValue{},
},
},
{
@@ -41,7 +47,11 @@ func Test_adaptContact(t *testing.T) {
expected: securitycenter.Contact{
Metadata: iacTypes.NewTestMetadata(),
EnableAlertNotifications: iacTypes.Bool(false, iacTypes.NewTestMetadata()),
EnableAlertsToAdmins: iacTypes.Bool(false, iacTypes.NewTestMetadata()),
Email: iacTypes.String("", iacTypes.NewTestMetadata()),
Phone: iacTypes.String("", iacTypes.NewTestMetadata()),
IsEnabled: iacTypes.BoolValue{},
MinimalSeverity: iacTypes.StringValue{},
},
},
}
@@ -107,8 +117,10 @@ func Test_adaptSubscription(t *testing.T) {
func TestLines(t *testing.T) {
src := `
resource "azurerm_security_center_contact" "example" {
email = "contact@example.com"
phone = "+1-555-555-5555"
alert_notifications = true
alerts_to_admins = true
}
resource "azurerm_security_center_subscription_pricing" "example" {
@@ -124,12 +136,18 @@ func TestLines(t *testing.T) {
contact := adapted.Contacts[0]
sub := adapted.Subscriptions[0]
assert.Equal(t, 3, contact.Phone.GetMetadata().Range().GetStartLine())
assert.Equal(t, 3, contact.Phone.GetMetadata().Range().GetEndLine())
assert.Equal(t, 3, contact.Email.GetMetadata().Range().GetStartLine())
assert.Equal(t, 3, contact.Email.GetMetadata().Range().GetEndLine())
assert.Equal(t, 4, contact.EnableAlertNotifications.GetMetadata().Range().GetStartLine())
assert.Equal(t, 4, contact.EnableAlertNotifications.GetMetadata().Range().GetEndLine())
assert.Equal(t, 4, contact.Phone.GetMetadata().Range().GetStartLine())
assert.Equal(t, 4, contact.Phone.GetMetadata().Range().GetEndLine())
assert.Equal(t, 8, sub.Tier.GetMetadata().Range().GetStartLine())
assert.Equal(t, 8, sub.Tier.GetMetadata().Range().GetEndLine())
assert.Equal(t, 5, contact.EnableAlertNotifications.GetMetadata().Range().GetStartLine())
assert.Equal(t, 5, contact.EnableAlertNotifications.GetMetadata().Range().GetEndLine())
assert.Equal(t, 6, contact.EnableAlertsToAdmins.GetMetadata().Range().GetStartLine())
assert.Equal(t, 6, contact.EnableAlertsToAdmins.GetMetadata().Range().GetEndLine())
assert.Equal(t, 10, sub.Tier.GetMetadata().Range().GetStartLine())
assert.Equal(t, 10, sub.Tier.GetMetadata().Range().GetEndLine())
}

View File

@@ -12,7 +12,11 @@ type SecurityCenter struct {
type Contact struct {
Metadata iacTypes.Metadata
EnableAlertNotifications iacTypes.BoolValue
EnableAlertsToAdmins iacTypes.BoolValue
Email iacTypes.StringValue
Phone iacTypes.StringValue
IsEnabled iacTypes.BoolValue
MinimalSeverity iacTypes.StringValue
}
const (

View File

@@ -5407,10 +5407,26 @@
"type": "object",
"$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.Metadata"
},
"email": {
"type": "object",
"$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue"
},
"enablealertnotifications": {
"type": "object",
"$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue"
},
"enablealertstoadmins": {
"type": "object",
"$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue"
},
"isenabled": {
"type": "object",
"$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.BoolValue"
},
"minimalseverity": {
"type": "object",
"$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue"
},
"phone": {
"type": "object",
"$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue"