internal/resources/providers/azurelib/inventory/sql_server_provider.go (278 lines of code) (raw):
// Licensed to Elasticsearch B.V. under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. Elasticsearch B.V. licenses this file to you under
// the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package inventory
import (
"context"
"errors"
"fmt"
"strings"
"github.com/Azure/azure-sdk-for-go/sdk/azcore"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/arm"
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/sql/armsql"
"github.com/samber/lo"
"github.com/elastic/cloudbeat/internal/infra/clog"
"github.com/elastic/cloudbeat/internal/resources/utils/pointers"
)
type sqlAzureClientWrapper struct {
AssetEncryptionProtector func(ctx context.Context, subID, resourceGroup, serverName string, clientOptions *arm.ClientOptions, options *armsql.EncryptionProtectorsClientListByServerOptions) ([]armsql.EncryptionProtectorsClientListByServerResponse, error)
AssetBlobAuditingPolicies func(ctx context.Context, subID, resourceGroup, serverName string, clientOptions *arm.ClientOptions, options *armsql.ServerBlobAuditingPoliciesClientGetOptions) (armsql.ServerBlobAuditingPoliciesClientGetResponse, error)
AssetTransparentDataEncryptions func(ctx context.Context, subID, resourceGroup, serverName, dbName string, clientOptions *arm.ClientOptions, options *armsql.TransparentDataEncryptionsClientListByDatabaseOptions) ([]armsql.TransparentDataEncryptionsClientListByDatabaseResponse, error)
AssetDatabases func(ctx context.Context, subID, resourceGroup, serverName string, clientOptions *arm.ClientOptions, options *armsql.DatabasesClientListByServerOptions) ([]armsql.DatabasesClientListByServerResponse, error)
AssetServerAdvancedThreatProtectionSettings func(ctx context.Context, subID, resourceGroup, serverName string, clientOptions *arm.ClientOptions, options *armsql.ServerAdvancedThreatProtectionSettingsClientListByServerOptions) ([]armsql.ServerAdvancedThreatProtectionSettingsClientListByServerResponse, error)
AssetServerFirewallRules func(ctx context.Context, subID, resourceGroup, serverName string, clientOptions *arm.ClientOptions, listOptions *armsql.FirewallRulesClientListByServerOptions) ([]armsql.FirewallRulesClientListByServerResponse, error)
}
type SQLProviderAPI interface {
ListSQLEncryptionProtector(ctx context.Context, subID, resourceGroup, serverName string) ([]AzureAsset, error)
ListSQLTransparentDataEncryptions(ctx context.Context, subID, resourceGroup, serverName string) ([]AzureAsset, error)
GetSQLBlobAuditingPolicies(ctx context.Context, subID, resourceGroup, serverName string) ([]AzureAsset, error)
ListSQLAdvancedThreatProtectionSettings(ctx context.Context, subID, resourceGroup, serverName string) ([]AzureAsset, error)
ListSQLFirewallRules(ctx context.Context, subID, resourceGroup, serverName string) ([]AzureAsset, error)
}
type sqlProvider struct {
client *sqlAzureClientWrapper
log *clog.Logger //nolint:unused
clientOptions *arm.ClientOptions
}
func NewSQLProvider(log *clog.Logger, credentials azcore.TokenCredential) SQLProviderAPI {
// We wrap the client, so we can mock it in tests
wrapper := &sqlAzureClientWrapper{
AssetEncryptionProtector: func(ctx context.Context, subID, resourceGroup, serverName string, clientOptions *arm.ClientOptions, options *armsql.EncryptionProtectorsClientListByServerOptions) ([]armsql.EncryptionProtectorsClientListByServerResponse, error) {
cl, err := armsql.NewEncryptionProtectorsClient(subID, credentials, clientOptions)
if err != nil {
return nil, err
}
return readPager(ctx, cl.NewListByServerPager(resourceGroup, serverName, options))
},
AssetBlobAuditingPolicies: func(ctx context.Context, subID, resourceGroup, serverName string, clientOptions *arm.ClientOptions, options *armsql.ServerBlobAuditingPoliciesClientGetOptions) (armsql.ServerBlobAuditingPoliciesClientGetResponse, error) {
cl, err := armsql.NewServerBlobAuditingPoliciesClient(subID, credentials, clientOptions)
if err != nil {
return armsql.ServerBlobAuditingPoliciesClientGetResponse{}, err
}
return cl.Get(ctx, resourceGroup, serverName, options)
},
AssetTransparentDataEncryptions: func(ctx context.Context, subID, resourceGroup, serverName, dbName string, clientOptions *arm.ClientOptions, options *armsql.TransparentDataEncryptionsClientListByDatabaseOptions) ([]armsql.TransparentDataEncryptionsClientListByDatabaseResponse, error) {
cl, err := armsql.NewTransparentDataEncryptionsClient(subID, credentials, clientOptions)
if err != nil {
return nil, err
}
return readPager(ctx, cl.NewListByDatabasePager(resourceGroup, serverName, dbName, options))
},
AssetDatabases: func(ctx context.Context, subID, resourceGroup, serverName string, clientOptions *arm.ClientOptions, options *armsql.DatabasesClientListByServerOptions) ([]armsql.DatabasesClientListByServerResponse, error) {
cl, err := armsql.NewDatabasesClient(subID, credentials, clientOptions)
if err != nil {
return nil, err
}
return readPager(ctx, cl.NewListByServerPager(resourceGroup, serverName, options))
},
AssetServerAdvancedThreatProtectionSettings: func(ctx context.Context, subID, resourceGroup, serverName string, clientOptions *arm.ClientOptions, options *armsql.ServerAdvancedThreatProtectionSettingsClientListByServerOptions) ([]armsql.ServerAdvancedThreatProtectionSettingsClientListByServerResponse, error) {
cl, err := armsql.NewServerAdvancedThreatProtectionSettingsClient(subID, credentials, clientOptions)
if err != nil {
return nil, err
}
return readPager(ctx, cl.NewListByServerPager(resourceGroup, serverName, options))
},
AssetServerFirewallRules: func(ctx context.Context, subID, resourceGroup, serverName string, clientOptions *arm.ClientOptions, listOptions *armsql.FirewallRulesClientListByServerOptions) ([]armsql.FirewallRulesClientListByServerResponse, error) {
cl, err := armsql.NewFirewallRulesClient(subID, credentials, clientOptions)
if err != nil {
return nil, err
}
return readPager(ctx, cl.NewListByServerPager(resourceGroup, serverName, listOptions))
},
}
return &sqlProvider{
log: log,
client: wrapper,
}
}
func (p *sqlProvider) ListSQLEncryptionProtector(ctx context.Context, subID, resourceGroup, serverName string) ([]AzureAsset, error) {
encryptProtectors, err := p.client.AssetEncryptionProtector(ctx, subID, resourceGroup, serverName, nil, nil)
if err != nil {
return nil, fmt.Errorf("problem on listing sql encryption protectors (%w)", err)
}
capacity := lo.Reduce(encryptProtectors, func(acc int, i armsql.EncryptionProtectorsClientListByServerResponse, _ int) int {
return acc + len(i.Value)
}, 0)
if capacity == 0 {
return nil, nil
}
assets := make([]AzureAsset, 0, capacity)
for _, epWrapper := range encryptProtectors {
for _, ep := range epWrapper.Value {
if ep == nil || ep.Properties == nil {
continue
}
assets = append(assets, convertEncryptionProtector(ep, resourceGroup, subID))
}
}
return assets, nil
}
func (p *sqlProvider) GetSQLBlobAuditingPolicies(ctx context.Context, subID, resourceGroup, serverName string) ([]AzureAsset, error) {
policy, err := p.client.AssetBlobAuditingPolicies(ctx, subID, resourceGroup, serverName, nil, nil)
if err != nil {
return nil, fmt.Errorf("problem on getting sql blob auditing policies (%w)", err)
}
if policy.Properties == nil {
return nil, nil
}
return []AzureAsset{
{
Id: pointers.Deref(policy.ID),
Name: pointers.Deref(policy.Name),
Location: assetLocationGlobal,
Properties: map[string]any{
"state": string(pointers.Deref(policy.Properties.State)),
"isAzureMonitorTargetEnabled": pointers.Deref(policy.Properties.IsAzureMonitorTargetEnabled),
"isDevopsAuditEnabled": pointers.Deref(policy.Properties.IsDevopsAuditEnabled),
"isManagedIdentityInUse": pointers.Deref(policy.Properties.IsManagedIdentityInUse),
"isStorageSecondaryKeyInUse": pointers.Deref(policy.Properties.IsStorageSecondaryKeyInUse),
"queueDelayMs": pointers.Deref(policy.Properties.QueueDelayMs),
"retentionDays": pointers.Deref(policy.Properties.RetentionDays),
"storageAccountAccessKey": pointers.Deref(policy.Properties.StorageAccountAccessKey),
"storageAccountSubscriptionID": pointers.Deref(policy.Properties.StorageAccountSubscriptionID),
"storageEndpoint": pointers.Deref(policy.Properties.StorageEndpoint),
"auditActionsAndGroups": lo.Map(policy.Properties.AuditActionsAndGroups, func(s *string, _ int) string {
return pointers.Deref(s)
}),
},
ResourceGroup: resourceGroup,
SubscriptionId: subID,
TenantId: "",
Type: pointers.Deref(policy.Type),
},
}, nil
}
func (p *sqlProvider) ListSQLTransparentDataEncryptions(ctx context.Context, subID, resourceGroup, serverName string) ([]AzureAsset, error) {
pagedDbs, err := p.client.AssetDatabases(ctx, subID, resourceGroup, serverName, nil, nil)
if err != nil {
return nil, fmt.Errorf("problem on listing sql databases for sql server %s (%w)", serverName, err)
}
dbs := lo.FlatMap(pagedDbs, func(db armsql.DatabasesClientListByServerResponse, _ int) []*armsql.Database {
return db.Value
})
var errs error
assets := make([]AzureAsset, 0)
for _, db := range dbs {
if db == nil {
continue
}
assetsByDb, err := p.listTransparentDataEncryptionsByDB(ctx, subID, resourceGroup, serverName, pointers.Deref(db.Name))
if err != nil {
errs = errors.Join(errs, err)
}
assets = append(assets, assetsByDb...)
}
return assets, errs
}
func (p *sqlProvider) ListSQLAdvancedThreatProtectionSettings(ctx context.Context, subID, resourceGroup, serverName string) ([]AzureAsset, error) {
pagedSettings, err := p.client.AssetServerAdvancedThreatProtectionSettings(ctx, subID, resourceGroup, serverName, nil, nil)
if err != nil {
return nil, fmt.Errorf("problem on listing advanced threat protection settings for server %s (%w)", serverName, err)
}
settings := lo.FlatMap(pagedSettings, func(page armsql.ServerAdvancedThreatProtectionSettingsClientListByServerResponse, _ int) []*armsql.ServerAdvancedThreatProtection {
return page.Value
})
assets := make([]AzureAsset, 0, len(settings))
for _, s := range settings {
if s == nil || s.Properties == nil {
continue
}
assets = append(assets, convertAdvancedThreatProtectionSettings(s, resourceGroup, subID))
}
return assets, nil
}
func (p *sqlProvider) listTransparentDataEncryptionsByDB(ctx context.Context, subID, resourceGroup, serverName, dbName string) ([]AzureAsset, error) {
pagedTdes, err := p.client.AssetTransparentDataEncryptions(ctx, subID, resourceGroup, serverName, dbName, nil, nil)
if err != nil {
return nil, err
}
capacity := lo.Reduce(pagedTdes, func(acc int, i armsql.TransparentDataEncryptionsClientListByDatabaseResponse, _ int) int {
return acc + len(i.Value)
}, 0)
assets := make([]AzureAsset, 0, capacity)
for _, tdes := range pagedTdes {
for _, tde := range tdes.Value {
if tde == nil || tde.Properties == nil {
continue
}
assets = append(assets, convertTransparentDataEncryption(tde, dbName, subID, resourceGroup))
}
}
return assets, nil
}
func convertTransparentDataEncryption(tde *armsql.LogicalDatabaseTransparentDataEncryption, dbName, subID, resourceGroup string) AzureAsset {
return AzureAsset{
Id: pointers.Deref(tde.ID),
Name: pointers.Deref(tde.Name),
Location: assetLocationGlobal,
Properties: map[string]any{
"databaseName": dbName,
"state": string(pointers.Deref(tde.Properties.State)),
},
ResourceGroup: resourceGroup,
SubscriptionId: subID,
TenantId: "",
Type: pointers.Deref(tde.Type),
}
}
func convertEncryptionProtector(ep *armsql.EncryptionProtector, resourceGroup, subID string) AzureAsset {
return AzureAsset{
Id: pointers.Deref(ep.ID),
Name: pointers.Deref(ep.Name),
Location: pointers.Deref(ep.Location),
Properties: map[string]any{
"kind": pointers.Deref(ep.Kind),
"serverKeyType": string(pointers.Deref(ep.Properties.ServerKeyType)),
"autoRotationEnabled": pointers.Deref(ep.Properties.AutoRotationEnabled),
"serverKeyName": pointers.Deref(ep.Properties.ServerKeyName),
"subregion": pointers.Deref(ep.Properties.Subregion),
"thumbprint": pointers.Deref(ep.Properties.Thumbprint),
"uri": pointers.Deref(ep.Properties.URI),
},
ResourceGroup: resourceGroup,
SubscriptionId: subID,
TenantId: "",
Type: pointers.Deref(ep.Type),
}
}
func convertAdvancedThreatProtectionSettings(s *armsql.ServerAdvancedThreatProtection, resourceGroup, subID string) AzureAsset {
return AzureAsset{
Id: pointers.Deref(s.ID),
Name: pointers.Deref(s.Name),
Location: assetLocationGlobal,
Properties: map[string]any{
"state": string(pointers.Deref(s.Properties.State)),
"creationTime": pointers.Deref(s.Properties.CreationTime),
},
ResourceGroup: resourceGroup,
SubscriptionId: subID,
TenantId: "",
Type: pointers.Deref(s.Type),
}
}
func (p *sqlProvider) ListSQLFirewallRules(ctx context.Context, subID, resourceGroup, serverName string) ([]AzureAsset, error) {
responses, err := p.client.AssetServerFirewallRules(ctx, subID, resourceGroup, serverName, p.clientOptions, nil)
if err != nil {
return nil, err
}
return lo.FlatMap(responses, func(item armsql.FirewallRulesClientListByServerResponse, _ int) []AzureAsset {
return lo.FilterMap(item.Value, func(item *armsql.FirewallRule, _ int) (AzureAsset, bool) {
if item == nil {
return AzureAsset{}, false
}
return p.convertFirewallRule(item, subID, resourceGroup), true
})
}), nil
}
func (p *sqlProvider) convertFirewallRule(item *armsql.FirewallRule, subID, resourceGroup string) AzureAsset {
a := AzureAsset{
Id: pointers.Deref(item.ID),
Name: pointers.Deref(item.Name),
Type: strings.ToLower(pointers.Deref(item.Type)),
SubscriptionId: subID,
ResourceGroup: resourceGroup,
}
if item.Properties != nil {
a.Properties = map[string]any{
"startIpAddress": pointers.Deref(item.Properties.StartIPAddress),
"endIpAddress": pointers.Deref(item.Properties.EndIPAddress),
}
}
return a
}