diff --git a/README.md b/README.md index 01de1d6f..ba0ee265 100644 --- a/README.md +++ b/README.md @@ -4,10 +4,13 @@ Scan cloud infrastructure via the [Azure SDK](https://github.com/Azure/azure-sdk -- [Install](#install) -- [Authentication](#authentication) -- [Supported Services](#supported-services) - +- [CloudGraph Azure Provider](#cloudgraph-azure-provider) + - [Docs](#docs) + - [Install](#install) + - [Authentication](#authentication) + - [Supported Services](#supported-services) + - [Development](#development) + - [Testing](#testing) ## Docs @@ -95,6 +98,7 @@ CloudGraph needs read permissions in order to ingest your data. To keep things e | postgreSqlServers | resourceGroup, databasePostgreSql | | privateDns | resourceGroup | | publicIp | networkInterface, resourceGroup | +| publicIpPrefix | | | recoveryInstances | recoveryVaults, resourceGroup | | recoveryPolicies | recoveryVaults, resourceGroup | | recoveryVaults | recoveryInstances, recoveryPolicies, resourceGroup | diff --git a/src/enums/serviceAliases.ts b/src/enums/serviceAliases.ts index 6ccdabbe..53971bf3 100644 --- a/src/enums/serviceAliases.ts +++ b/src/enums/serviceAliases.ts @@ -56,6 +56,7 @@ export default { [services.postgreSqlServers]: 'postgreSqlServers', [services.privateDns]: 'privateDnsZones', [services.publicIp]: 'publicIps', + [services.publicIpPrefix]: 'publicIpPrefixes', [services.recoveryVaults]: 'recoveryVaults', [services.recoveryInstances]: 'recoveryInstances', [services.recoveryPolicies]: 'recoveryPolicies', diff --git a/src/enums/serviceMap.ts b/src/enums/serviceMap.ts index fdeb7f2e..282f0fe2 100644 --- a/src/enums/serviceMap.ts +++ b/src/enums/serviceMap.ts @@ -51,6 +51,7 @@ import AzurePolicyAssignment from '../services/policyAssignment' import AzurePostgreSqlServer from '../services/postgreSqlServers' import AzurePrivateDns from '../services/privateDns' import AzurePublicIp from '../services/publicIp' +import AzurePublicIpPrefix from '../services/publicIpPrefix' import AzureRecoveryVault from '../services/recoveryVaults' import AzureRecoveryInstance from '../services/recoveryInstances' import AzureRecoveryPolicy from '../services/recoveryPolicies' @@ -160,6 +161,7 @@ export default { [services.postgreSqlServers]: AzurePostgreSqlServer, [services.privateDns]: AzurePrivateDns, [services.publicIp]: AzurePublicIp, + [services.publicIpPrefix]: AzurePublicIpPrefix, [services.recoveryVaults]: AzureRecoveryVault, [services.recoveryInstances]: AzureRecoveryInstance, [services.recoveryPolicies]: AzureRecoveryPolicy, diff --git a/src/enums/services.ts b/src/enums/services.ts index 006d416e..fc15d0d1 100644 --- a/src/enums/services.ts +++ b/src/enums/services.ts @@ -62,6 +62,7 @@ export default { postgreSqlServers: 'postgreSqlServers', privateDns: 'privateDns', publicIp: 'publicIp', + publicIpPrefix: 'publicIpPrefix', recoveryVaults: 'recoveryVaults', recoveryInstances: 'recoveryInstances', recoveryPolicies: 'recoveryPolicies', diff --git a/src/properties/logger.ts b/src/properties/logger.ts index 8b14e71c..e48831cc 100644 --- a/src/properties/logger.ts +++ b/src/properties/logger.ts @@ -20,7 +20,8 @@ export default { foundAKSManagedClusters: (num: number): string => `Found ${num} AKS managed clusters`, /* App Gateway */ - foundApplicationGateway: (num: number): string => `Found ${num} Application Gateways`, + foundApplicationGateway: (num: number): string => + `Found ${num} Application Gateways`, /* App Service */ foundAppServiceEnvironments: (num: number): string => `Found ${num} app service environments`, @@ -152,11 +153,14 @@ export default { `Found ${num} private dns zones`, // Public Ips foundPublicIps: (num: number): string => `Found ${num} public ips`, + foundPublicIpPrefixes: (num: number): string => + `Found ${num} public ip prefixes`, // Recovery Vaults foundRecoveryVaults: (num: number): string => `Found ${num} recovery vaults`, foundRecoveryInstances: (num: number): string => - `Found ${num} recovery instances`, - foundRecoveryPolicies: (num: number): string => `Found ${num} recovery policies`, + `Found ${num} recovery instances`, + foundRecoveryPolicies: (num: number): string => + `Found ${num} recovery policies`, // RedisCache foundRedisCaches: (num: number): string => `Found ${num} Redis caches`, // Replication Appliances diff --git a/src/services/publicIpPrefix/data.ts b/src/services/publicIpPrefix/data.ts new file mode 100644 index 00000000..6649d493 --- /dev/null +++ b/src/services/publicIpPrefix/data.ts @@ -0,0 +1,76 @@ +import { NetworkManagementClient, PublicIPPrefix } from '@azure/arm-network' +import { PagedAsyncIterableIterator } from '@azure/core-paging' +import CloudGraph from '@cloudgraph/sdk' + +import azureLoggerText from '../../properties/logger' +import { AzureServiceInput, TagMap } from '../../types' +import { tryCatchWrapper } from '../../utils' +import { lowerCaseLocation } from '../../utils/format' +import { getResourceGroupFromEntity } from '../../utils/idParserUtils' + +const { logger } = CloudGraph +const lt = { ...azureLoggerText } +const serviceName = 'PublicIp' + +export interface RawAzurePublicIpPrefix + extends Omit { + region: string + resourceGroupId: string + Tags: TagMap +} + +export default async ({ + regions, + config, +}: AzureServiceInput): Promise<{ + [property: string]: RawAzurePublicIpPrefix[] +}> => { + try { + const { tokenCredentials, subscriptionId } = config + const client = new NetworkManagementClient(tokenCredentials, subscriptionId) + + const publicIpPrefixData: PublicIPPrefix[] = [] + await tryCatchWrapper( + async () => { + const publicIpPrefixIterable: PagedAsyncIterableIterator = + client.publicIPPrefixes.listAll() + for await (const publicIpPrefix of publicIpPrefixIterable) { + publicIpPrefix && publicIpPrefixData.push(publicIpPrefix) + } + }, + { + service: serviceName, + client, + scope: 'publicIPPrefixes', + operation: 'listAll', + } + ) + + const result: { + [property: string]: RawAzurePublicIpPrefix[] + } = {} + let numOfGroups = 0 + publicIpPrefixData.forEach(({ tags, location, ...rest }) => { + const region = lowerCaseLocation(location) + if (regions.includes(region)) { + if (!result[region]) { + result[region] = [] + } + const resourceGroupId = getResourceGroupFromEntity(rest) + result[region].push({ + ...rest, + region, + resourceGroupId, + Tags: tags || {}, + }) + numOfGroups += 1 + } + }) + logger.debug(lt.foundPublicIpPrefixes(numOfGroups)) + + return result + } catch (e) { + logger.error(e) + return {} + } +} diff --git a/src/services/publicIpPrefix/format.ts b/src/services/publicIpPrefix/format.ts new file mode 100644 index 00000000..bb3608e7 --- /dev/null +++ b/src/services/publicIpPrefix/format.ts @@ -0,0 +1,24 @@ +import { AzurePublicIpPrefix } from '../../types/generated' +import { formatTagsFromMap } from '../../utils/format' +import { RawAzurePublicIpPrefix } from './data' + +export default ({ + service, + account: subscriptionId, + region, +}: { + service: RawAzurePublicIpPrefix + account: string + region: string +}): AzurePublicIpPrefix => { + const { id, name, ipTags, resourceGroupId, Tags } = service + return { + id, + name, + region, + resourceGroupId, + subscriptionId, + ipTags, + tags: formatTagsFromMap(Tags), + } +} diff --git a/src/services/publicIpPrefix/index.ts b/src/services/publicIpPrefix/index.ts new file mode 100644 index 00000000..671f3cdd --- /dev/null +++ b/src/services/publicIpPrefix/index.ts @@ -0,0 +1,16 @@ +import { Service } from '@cloudgraph/sdk' +import BaseService from '../base' +import format from './format' +import mutation from './mutation' +import getData from './data' + +export default class AzurePublicIpPrefix + extends BaseService + implements Service +{ + format = format.bind(this) + + getData = getData.bind(this) + + mutation = mutation +} diff --git a/src/services/publicIpPrefix/mutation.ts b/src/services/publicIpPrefix/mutation.ts new file mode 100644 index 00000000..ec15bd9a --- /dev/null +++ b/src/services/publicIpPrefix/mutation.ts @@ -0,0 +1,5 @@ +export default `mutation($input: [AddazurePublicIpPrefixInput!]!) { + addazurePublicIpPrefix(input: $input, upsert: true) { + numUids + } +}` diff --git a/src/services/publicIpPrefix/schema.graphql b/src/services/publicIpPrefix/schema.graphql new file mode 100644 index 00000000..5ea40d8f --- /dev/null +++ b/src/services/publicIpPrefix/schema.graphql @@ -0,0 +1,18 @@ +type azurePublicIpTags + @generate( + query: { get: false, query: true, aggregate: false } + mutation: { add: false, delete: false } + subscription: false + ) { + ipTagType: String @search(by: [hash, regexp]) + tag: String @search(by: [hash, regexp]) +} + +type azurePublicIpPrefix implements azureResource + @generate( + query: { get: true, query: true, aggregate: true } + mutation: { add: true, delete: false } + ) + @key(fields: "id") { + ipTags: [azurePublicIpTags] +} diff --git a/src/types/generated.ts b/src/types/generated.ts index ae57b14a..8faebd6f 100644 --- a/src/types/generated.ts +++ b/src/types/generated.ts @@ -4438,6 +4438,10 @@ export type AzurePublicIpDnsSettings = { reverseFqdn?: Maybe; }; +export type AzurePublicIpPrefix = AzureResource & { + ipTags?: Maybe>>; +}; + export type AzurePublicIpTags = { ipTagType?: Maybe; tag?: Maybe;