From a2c8e4498da9a049eba36cd500bf822c9f5786b4 Mon Sep 17 00:00:00 2001 From: "mariano.pizarro" Date: Tue, 31 May 2022 15:35:28 -0300 Subject: [PATCH 1/2] fet(services): Create Azure logProfiles service --- README.md | 3 +- src/enums/schemasMap.ts | 1 + src/enums/serviceMap.ts | 2 + src/enums/services.ts | 1 + src/properties/logger.ts | 2 + src/services/logProfiles/connections.ts | 52 +++++++++++++++++ src/services/logProfiles/data.ts | 65 ++++++++++++++++++++++ src/services/logProfiles/format.ts | 40 +++++++++++++ src/services/logProfiles/index.ts | 16 ++++++ src/services/logProfiles/mutation.ts | 5 ++ src/services/logProfiles/schema.graphql | 26 +++++++++ src/services/storageAccount/schema.graphql | 1 + src/services/tag/connections.ts | 25 +++++++++ src/services/tag/schema.graphql | 1 + src/types/generated.ts | 19 +++++++ 15 files changed, 258 insertions(+), 1 deletion(-) create mode 100644 src/services/logProfiles/connections.ts create mode 100644 src/services/logProfiles/data.ts create mode 100644 src/services/logProfiles/format.ts create mode 100644 src/services/logProfiles/index.ts create mode 100644 src/services/logProfiles/mutation.ts create mode 100644 src/services/logProfiles/schema.graphql diff --git a/README.md b/README.md index cf1ead3d..acecde0b 100644 --- a/README.md +++ b/README.md @@ -87,6 +87,7 @@ CloudGraph needs read permissions in order to ingest your data. To keep things e | loadBalancer | loadBalancer, publicIp, resourceGroup, virtualNetwork | | logAnalyticsSolution | resourceGroup, logAnalyticsWorkspace | | logAnalyticsWorkspace | resourceGroup, dataCollectionRule, logAnalyticsSolution | +| logProfiles | storageAccount | | networkInterface | publicIp, resourceGroup, securityGroup, virtualMachine, virtualNetwork | | policyAssignment | | | postgreSqlServers | resourceGroup, databasePostgreSql | @@ -106,7 +107,7 @@ CloudGraph needs read permissions in order to ingest your data. To keep things e | securitySettings | | | serviceBus | resourceGroup | | sqlServers | databaseSql, resourceGroup | -| storageAccount | diagnosticSetting, resourceGroup, storageContainer | +| storageAccount | diagnosticSetting, logProfile, resourceGroup, storageContainer | | storageBlob | resourceGroup, storageContainer | | storageContainer | resourceGroup, storageAccount | | synapseBigDataPools | resourceGroup, synapseWorkspaces | diff --git a/src/enums/schemasMap.ts b/src/enums/schemasMap.ts index 25bc82ce..ad0d36af 100644 --- a/src/enums/schemasMap.ts +++ b/src/enums/schemasMap.ts @@ -57,6 +57,7 @@ export default { [services.loadBalancer]: 'azureLoadBalancer', [services.logAnalyticsSolution]: 'azureLogAnalyticsSolution', [services.logAnalyticsWorkspace]: 'azureLogAnalyticsWorkspace', + [services.logProfiles]: 'azureLogProfiles', [services.machineLearningWorkspaces]: 'azureMachineLearningWorkspace', [services.metricAlert]:'azureMetricAlert', [services.mySqlServers]: 'azureMySqlServer', diff --git a/src/enums/serviceMap.ts b/src/enums/serviceMap.ts index 574f489c..ede1f2b2 100644 --- a/src/enums/serviceMap.ts +++ b/src/enums/serviceMap.ts @@ -82,6 +82,7 @@ import AzureServiceBus from '../services/serviceBus' import AzureBackupVault from '../services/backupVault' import AzureBackupInstance from '../services/backupInstance' import AzureBackupPolicy from '../services/backupPolicy' +import AzureLogProfiles from '../services/logProfiles' /** * serviceMap is an object that contains all currently supported services for AWS @@ -142,6 +143,7 @@ export default { [services.loadBalancer]: AzureLoadBalancer, [services.logAnalyticsSolution]: AzureLogAnalyticsSolutions, [services.logAnalyticsWorkspace]: AzureLogAnalyticsWorkspaces, + [services.logProfiles]: AzureLogProfiles, [services.machineLearningWorkspaces]: AzureMachineLearningWorkspace, [services.mySqlServers]: AzureMySqlServer, [services.networkInterface]: AzureNetworkInterface, diff --git a/src/enums/services.ts b/src/enums/services.ts index 687b3a60..a62c82d0 100644 --- a/src/enums/services.ts +++ b/src/enums/services.ts @@ -50,6 +50,7 @@ export default { loadBalancer: 'loadBalancer', logAnalyticsSolution: 'logAnalyticsSolution', logAnalyticsWorkspace: 'logAnalyticsWorkspace', + logProfiles: 'logProfiles', machineLearningWorkspaces: 'machineLearningWorkspaces', metricAlert: 'metricAlert', mySqlServers: 'mySqlServers', diff --git a/src/properties/logger.ts b/src/properties/logger.ts index 1344fa15..2c7a5966 100644 --- a/src/properties/logger.ts +++ b/src/properties/logger.ts @@ -120,6 +120,8 @@ export default { `Found ${num} Key Vault secrets`, // Load balancer foundLoadBalancers: (num: number): string => `Found ${num} load balancers`, + // Metric Profiles + foundLogProfiles: (num: number): string => `Found ${num} log profiles`, // Machine Learning Workspaces foundMachineLearningWorkspaces: (num: number): string => `Found ${num} machine learning workspaces`, // Metric Alert diff --git a/src/services/logProfiles/connections.ts b/src/services/logProfiles/connections.ts new file mode 100644 index 00000000..ae2c4441 --- /dev/null +++ b/src/services/logProfiles/connections.ts @@ -0,0 +1,52 @@ +import { ServiceConnection } from '@cloudgraph/sdk' +import { isEmpty } from 'lodash' + +import services from '../../enums/services' +import { RawAzureStorageAccount } from '../storageAccount/data' +import { RawAzureLogProfileResource } from './data' + +export default ({ + service, + data, + region, +}: { + service: RawAzureLogProfileResource + data: Array<{ name: string; data: { [property: string]: any[] } }> + region: string +}): { + [property: string]: ServiceConnection[] +} => { + const connections: ServiceConnection[] = [] + const { id, storageAccountId } = service + + /** + * Find storage account related to this log profile + */ + const storageAccounts: { + name: string + data: { [property: string]: RawAzureStorageAccount[] } + } = data.find(({ name }) => name === services.storageAccount) + + if (storageAccounts?.data?.[region]) { + const storageAccountsInRegion: RawAzureStorageAccount[] = + storageAccounts.data[region].filter( + ({ id: saId }: RawAzureStorageAccount) => saId === storageAccountId + ) + + if (!isEmpty(storageAccountsInRegion)) { + for (const rg of storageAccountsInRegion) { + connections.push({ + id: rg.id, + resourceType: services.storageAccount, + relation: 'child', + field: 'storageAccount', + }) + } + } + } + + const rgResult = { + [id]: connections, + } + return rgResult +} diff --git a/src/services/logProfiles/data.ts b/src/services/logProfiles/data.ts new file mode 100644 index 00000000..b52e70bc --- /dev/null +++ b/src/services/logProfiles/data.ts @@ -0,0 +1,65 @@ +import CloudGraph from '@cloudgraph/sdk' +import { MonitorClient, LogProfileResource } from '@azure/arm-monitor' +import { PagedAsyncIterableIterator } from '@azure/core-paging' +import azureLoggerText from '../../properties/logger' +import { AzureServiceInput, TagMap } from '../../types' +import { tryCatchWrapper } from '../../utils' +import { regionMap } from '../../enums/regions' + +const { logger } = CloudGraph +const lt = { ...azureLoggerText } +const serviceName = 'Log Profiles' + +export interface RawAzureLogProfileResource + extends Omit { + region: string + Tags: TagMap +} + +export default async ({ + config, +}: AzureServiceInput): Promise<{ + [property: string]: RawAzureLogProfileResource[] +}> => { + try { + const { tokenCredentials, subscriptionId } = config + const client = new MonitorClient(tokenCredentials, subscriptionId) + const logProfiles: RawAzureLogProfileResource[] = [] + const result = { global: [] } + await tryCatchWrapper( + async () => { + const logProfilesIterable: PagedAsyncIterableIterator = + client.logProfiles.list() + for await (const logProfile of logProfilesIterable) { + if (logProfile) { + const { tags, ...rest } = logProfile + const region = regionMap.global + logProfiles.push({ + ...rest, + region, + Tags: tags || {}, + }) + } + } + }, + { + service: serviceName, + client, + scope: 'logProfiles', + operation: 'list', + } + ) + logger.debug(lt.foundLogProfiles(logProfiles.length)) + + logProfiles.map(({ region, ...rest }) => { + result.global.push({ + ...rest, + region, + }) + }) + return result + } catch (e) { + logger.error(e) + return {} + } +} \ No newline at end of file diff --git a/src/services/logProfiles/format.ts b/src/services/logProfiles/format.ts new file mode 100644 index 00000000..6b5397e8 --- /dev/null +++ b/src/services/logProfiles/format.ts @@ -0,0 +1,40 @@ +import cuid from 'cuid' +import { AzureLogProfile } from '../../types/generated' +import { formatTagsFromMap } from '../../utils/format' +import { RawAzureLogProfileResource } from './data' + +export default ({ + service, + account, + region, +}: { + service: RawAzureLogProfileResource + account: string + region: string +}): AzureLogProfile => { + const { + id, + name, + type, + storageAccountId, + serviceBusRuleId, + locations = [], + categories = [], + retentionPolicy = {}, + Tags: tags = {}, + } = service + + return { + id: id || cuid(), + name, + region, + subscriptionId: account, + type, + storageAccountId, + serviceBusRuleId, + locations, + categories, + retentionPolicy, + tags: formatTagsFromMap(tags), + } +} diff --git a/src/services/logProfiles/index.ts b/src/services/logProfiles/index.ts new file mode 100644 index 00000000..a7e8513f --- /dev/null +++ b/src/services/logProfiles/index.ts @@ -0,0 +1,16 @@ +import { Service } from '@cloudgraph/sdk' +import BaseService from '../base' +import getConnections from './connections' +import format from './format' +import mutation from './mutation' +import getData from './data' + +export default class LogProfiles extends BaseService implements Service { + format = format.bind(this) + + getConnections = getConnections.bind(this) + + getData = getData.bind(this) + + mutation = mutation +} diff --git a/src/services/logProfiles/mutation.ts b/src/services/logProfiles/mutation.ts new file mode 100644 index 00000000..4475a699 --- /dev/null +++ b/src/services/logProfiles/mutation.ts @@ -0,0 +1,5 @@ +export default `mutation($input: [AddazureLogProfileInput!]!) { + addazureLogProfile(input: $input, upsert: true) { + numUids + } +}`; diff --git a/src/services/logProfiles/schema.graphql b/src/services/logProfiles/schema.graphql new file mode 100644 index 00000000..cce82d46 --- /dev/null +++ b/src/services/logProfiles/schema.graphql @@ -0,0 +1,26 @@ +type azureLogProfileRetentionPolicy + @generate( + query: { get: false, query: true, aggregate: false } + mutation: { add: false, delete: false } + subscription: false + ) { + enabled: Boolean @search + days: Int @search +} + +type azureLogProfile implements azureBaseResource + @generate( + query: { get: true, query: true, aggregate: true } + mutation: { add: true, delete: false } + ) + @key(fields: "id") { + region: String @search(by: [hash, regexp]) + subscriptionId: String @search(by: [hash, regexp]) + storageAccountId: String @search(by: [hash, regexp]) + serviceBusRuleId: String @search(by: [hash, regexp]) + locations: [String] @search(by: [hash, regexp]) + categories: [String] @search(by: [hash, regexp]) + retentionPolicy: azureLogProfileRetentionPolicy + tags: [azureRawTag] + storageAccount: [azureStorageAccount] @hasInverse(field: logProfiles) +} diff --git a/src/services/storageAccount/schema.graphql b/src/services/storageAccount/schema.graphql index 7d7b9c54..b197f79e 100644 --- a/src/services/storageAccount/schema.graphql +++ b/src/services/storageAccount/schema.graphql @@ -215,4 +215,5 @@ type azureStorageAccount implements azureResource eventHubs: [azureEventHub] @hasInverse(field: storageAccount) diagnosticSettings: [azureDiagnosticSetting] @hasInverse(field: storageAccount) fileShares: [azureFileShare] @hasInverse(field: storageAccount) + logProfiles: [azureLogProfile] @hasInverse(field: storageAccount) } diff --git a/src/services/tag/connections.ts b/src/services/tag/connections.ts index 27d4f905..44640d63 100644 --- a/src/services/tag/connections.ts +++ b/src/services/tag/connections.ts @@ -1197,6 +1197,31 @@ export default ({ } } } + + /** + * Find related Log Profiles + */ + const logProfiles: { + name: string + data: { [property: string]: any[] } + } = data.find(({ name }) => name === services.logProfiles) + if (logProfiles?.data?.[region]) { + const dataAtRegion: any = findServiceInstancesWithTag( + tag, + logProfiles.data[region] + ) + if (!isEmpty(dataAtRegion)) { + for (const logProfile of dataAtRegion) { + const { id } = logProfile + connections.push({ + id, + resourceType: services.logProfiles, + relation: 'child', + field: 'logProfiles', + }) + } + } + } } const tagResult = { diff --git a/src/services/tag/schema.graphql b/src/services/tag/schema.graphql index 1d1173f3..be494db9 100644 --- a/src/services/tag/schema.graphql +++ b/src/services/tag/schema.graphql @@ -31,6 +31,7 @@ type azureTag @key(fields: "id") { loadBalancers: [azureLoadBalancer] logAnalyticsSolutions: [azureLogAnalyticsSolution] logAnalyticsWorkspaces: [azureLogAnalyticsWorkspace] + logProfiles: [azureLogProfile] machineLearningWorkspaces: [azureMachineLearningWorkspace] metricAlerts: [azureMetricAlert] mySqlServers: [azureMySqlServer] diff --git a/src/types/generated.ts b/src/types/generated.ts index f4b35ddf..c60ad11d 100644 --- a/src/types/generated.ts +++ b/src/types/generated.ts @@ -3120,6 +3120,23 @@ export type AzureLogAnalyticsWorkspaceSku = { name: Scalars['String']; }; +export type AzureLogProfile = AzureBaseResource & { + categories?: Maybe>>; + locations?: Maybe>>; + region?: Maybe; + retentionPolicy?: Maybe; + serviceBusRuleId?: Maybe; + storageAccount?: Maybe>>; + storageAccountId?: Maybe; + subscriptionId?: Maybe; + tags?: Maybe>>; +}; + +export type AzureLogProfileRetentionPolicy = { + days?: Maybe; + enabled?: Maybe; +}; + export type AzureMachineLearningWorkspace = AzureResource & { allowPublicAccessWhenBehindVnet?: Maybe; applicationInsights?: Maybe; @@ -4393,6 +4410,7 @@ export type AzureStorageAccount = AzureResource & { keyPolicyExpirationPeriodInDays?: Maybe; largeFileSharesState?: Maybe; lastGeoFailoverTime?: Maybe; + logProfiles?: Maybe>>; minimumTlsVersion?: Maybe; networkRuleIpRules?: Maybe>>; networkRuleResourceAccessRules?: Maybe>>; @@ -4842,6 +4860,7 @@ export type AzureTag = { loadBalancers?: Maybe>>; logAnalyticsSolutions?: Maybe>>; logAnalyticsWorkspaces?: Maybe>>; + logProfiles?: Maybe>>; machineLearningWorkspaces?: Maybe>>; metricAlerts?: Maybe>>; mySqlServers?: Maybe>>; From c049a3628bc80d3091fbe6e16d7a48152ccb1316 Mon Sep 17 00:00:00 2001 From: "mariano.pizarro" Date: Tue, 31 May 2022 15:37:30 -0300 Subject: [PATCH 2/2] feat(services): Update readme file --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index acecde0b..e8226203 100644 --- a/README.md +++ b/README.md @@ -107,7 +107,7 @@ CloudGraph needs read permissions in order to ingest your data. To keep things e | securitySettings | | | serviceBus | resourceGroup | | sqlServers | databaseSql, resourceGroup | -| storageAccount | diagnosticSetting, logProfile, resourceGroup, storageContainer | +| storageAccount | diagnosticSetting, logProfiles, resourceGroup, storageContainer | | storageBlob | resourceGroup, storageContainer | | storageContainer | resourceGroup, storageAccount | | synapseBigDataPools | resourceGroup, synapseWorkspaces |