diff --git a/README.md b/README.md index 01de1d6f..eb083ad3 100644 --- a/README.md +++ b/README.md @@ -104,6 +104,8 @@ CloudGraph needs read permissions in order to ingest your data. To keep things e | replicationNetworks | resourceGroup | | replicationPolicies | resourceGroup | | resourceGroup | **all services** | +| routeFilter | | +| routeTable | | | securityAssessments | | | securityContacts | | | securityGroup | networkInterface, resourceGroup | diff --git a/src/enums/schemasMap.ts b/src/enums/schemasMap.ts index db58d622..9cb53e20 100644 --- a/src/enums/schemasMap.ts +++ b/src/enums/schemasMap.ts @@ -77,6 +77,8 @@ export default { [services.replicationNetworks]: 'azureReplicationNetwork', [services.replicationPolicies]: 'azureReplicationPolicy', [services.resourceGroup]: 'azureResourceGroup', + [services.routeFilter]: 'azureRouteFilter', + [services.routeTable]: 'azureRouteTable', [services.securityAssesments]: 'azureSecurityAssesment', [services.securityContacts]: 'azureSecurityContact', [services.securityGroup]: 'azureNetworkSecurityGroup', diff --git a/src/enums/serviceAliases.ts b/src/enums/serviceAliases.ts index 6ccdabbe..60df620f 100644 --- a/src/enums/serviceAliases.ts +++ b/src/enums/serviceAliases.ts @@ -65,6 +65,8 @@ export default { [services.replicationNetworks]: 'replicationNetworks', [services.replicationPolicies]: 'replicationPolicies', [services.resourceGroup]: 'resourceGroups', + [services.routeFilter]: 'routeFilters', + [services.routeTable]: 'routeTables', [services.securityAssesments]: 'securityAssesments', [services.securityContacts]: 'securityContacts', [services.securityGroup]: 'securityGroups', diff --git a/src/enums/serviceMap.ts b/src/enums/serviceMap.ts index fdeb7f2e..b421954a 100644 --- a/src/enums/serviceMap.ts +++ b/src/enums/serviceMap.ts @@ -89,6 +89,8 @@ import AzureBilling from '../services/billing' import AzureLogProfiles from '../services/logProfiles' import Subscription from '../services/subscription' import AzureNetworkWatcher from '../services/networkWatcher' +import AzureRouteTable from '../services/routeTable' +import AzureRouteFilter from '../services/routeFilter' /** * serviceMap is an object that contains all currently supported services for AZURE @@ -169,6 +171,8 @@ export default { [services.replicationNetworks]: AzureReplicationNetwork, [services.replicationPolicies]: AzureReplicationPolicy, [services.resourceGroup]: AzureResourceGroup, + [services.routeFilter]: AzureRouteFilter, + [services.routeTable]: AzureRouteTable, [services.securityAssesments]: AzureSecurityAssesments, [services.securityContacts]: AzureSecurityContacts, [services.securityGroup]: AzureNetworkSecurityGroup, diff --git a/src/enums/services.ts b/src/enums/services.ts index 006d416e..ee26660e 100644 --- a/src/enums/services.ts +++ b/src/enums/services.ts @@ -71,6 +71,8 @@ export default { replicationNetworks: 'replicationNetworks', replicationPolicies: 'replicationPolicies', resourceGroup: 'resourceGroup', + routeFilter: 'routeFilter', + routeTable: 'routeTable', securityAssesments: 'securityAssesments', securityContacts: 'securityContacts', securityGroup: 'securityGroup', diff --git a/src/properties/logger.ts b/src/properties/logger.ts index 8b14e71c..58cc7a3f 100644 --- a/src/properties/logger.ts +++ b/src/properties/logger.ts @@ -173,6 +173,10 @@ export default { `Found ${num} replication policies`, // Resource Groups foundResourceGroups: (num: number): string => `Found ${num} resource groups`, + // Route Filters + foundRouteFilters: (num: number): string => `Found ${num} route filters`, + // Route Tables + foundRouteTables: (num: number): string => `Found ${num} route tables`, // Security Assesments foundSecurityAssesments: (num: number): string => `Found ${num} security assesments`, diff --git a/src/services/routeFilter/data.ts b/src/services/routeFilter/data.ts new file mode 100644 index 00000000..23bcbb28 --- /dev/null +++ b/src/services/routeFilter/data.ts @@ -0,0 +1,79 @@ +import { NetworkManagementClient, RouteFilter } 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 = 'RouteFilter' + +export interface RawAzureRouteFilter + extends Omit { + region: string + resourceGroupId: string + Tags: TagMap +} + +export default async ({ + regions, + config, +}: AzureServiceInput): Promise<{ + [property: string]: RawAzureRouteFilter[] +}> => { + try { + const { tokenCredentials, subscriptionId } = config + const client = new NetworkManagementClient(tokenCredentials, subscriptionId) + + const routeFilterData: RawAzureRouteFilter[] = [] + await tryCatchWrapper( + async () => { + const routeFilterIterable: PagedAsyncIterableIterator = + client.routeFilters.list() + for await (const routeFilter of routeFilterIterable) { + if (routeFilter) { + const { location, tags, ...rest } = routeFilter + const resourceGroupId = getResourceGroupFromEntity(rest) + const region = lowerCaseLocation(location) + routeFilterData.push({ + ...rest, + region, + resourceGroupId, + Tags: tags || {}, + }) + } + } + }, + { + service: serviceName, + client, + scope: 'routeFilters', + operation: 'list', + } + ) + const result: { [property: string]: RawAzureRouteFilter[] } = {} + let numOfGroups = 0 + routeFilterData.forEach(({ region, ...rest }) => { + if (regions.includes(region)) { + if (!result[region]) { + result[region] = [] + } + result[region].push({ + region, + ...rest, + }) + numOfGroups += 1 + } + }) + logger.debug(lt.foundRouteFilters(numOfGroups)) + + return result + } catch (e) { + logger.error(e) + return {} + } +} diff --git a/src/services/routeFilter/format.ts b/src/services/routeFilter/format.ts new file mode 100644 index 00000000..dabf5fe4 --- /dev/null +++ b/src/services/routeFilter/format.ts @@ -0,0 +1,22 @@ +import { RawAzureRouteFilter } from './data' +import { AzureRouteFilter } from '../../types/generated' +import { formatTagsFromMap } from '../../utils/format' + +export default ({ + service, + account: subscriptionId, +}: { + service: RawAzureRouteFilter + account: string +}): AzureRouteFilter => { + const { id, name, type, region, Tags } = service + + return { + id, + name, + type, + region, + subscriptionId, + tags: formatTagsFromMap(Tags), + } +} diff --git a/src/services/routeFilter/index.ts b/src/services/routeFilter/index.ts new file mode 100644 index 00000000..50f341bb --- /dev/null +++ b/src/services/routeFilter/index.ts @@ -0,0 +1,13 @@ +import { Service } from '@cloudgraph/sdk' +import BaseService from '../base' +import format from './format' +import mutation from './mutation' +import getData from './data' + +export default class AzureRouteFilter extends BaseService implements Service { + format = format.bind(this) + + getData = getData.bind(this) + + mutation = mutation +} diff --git a/src/services/routeFilter/mutation.ts b/src/services/routeFilter/mutation.ts new file mode 100644 index 00000000..3dbc99d7 --- /dev/null +++ b/src/services/routeFilter/mutation.ts @@ -0,0 +1,5 @@ +export default `mutation($input: [AddazureRouteFilterInput!]!) { + addazureRouteFilter(input: $input, upsert: true) { + numUids + } +}`; diff --git a/src/services/routeFilter/schema.graphql b/src/services/routeFilter/schema.graphql new file mode 100644 index 00000000..2b186487 --- /dev/null +++ b/src/services/routeFilter/schema.graphql @@ -0,0 +1,9 @@ +type azureRouteFilter implements azureResource + @generate( + query: { get: true, query: true, aggregate: true } + mutation: { add: true, delete: false } + subscription: false + ) + @key(fields: "id") { + name: String @search(by: [hash, regexp]) +} diff --git a/src/services/routeTable/data.ts b/src/services/routeTable/data.ts new file mode 100644 index 00000000..9b11ee35 --- /dev/null +++ b/src/services/routeTable/data.ts @@ -0,0 +1,105 @@ +import { NetworkManagementClient, Route, RouteTable } 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 = 'RouteTable' + +export interface RawAzureRouteTable + extends Omit { + region: string + resourceGroupId: string + Tags: TagMap + routes: Route[] +} + +const listRouteTableRoute = async ( + client: NetworkManagementClient, + resourceGroup: string, + routeTableName: string +): Promise => { + const routes: Route[] = [] + const routesIterable = client.routes.list(resourceGroup, routeTableName) + await tryCatchWrapper( + async () => { + for await (const route of routesIterable) { + if (route) { + routes.push(route) + } + } + }, + { + service: serviceName, + client, + scope: 'routes', + operation: 'list', + } + ) + return routes +} + +export default async ({ + regions, + config, +}: AzureServiceInput): Promise<{ + [property: string]: RawAzureRouteTable[] +}> => { + try { + const { tokenCredentials, subscriptionId } = config + const client = new NetworkManagementClient(tokenCredentials, subscriptionId) + + const routeTableData: RouteTable[] = [] + await tryCatchWrapper( + async () => { + const routeTableIterable: PagedAsyncIterableIterator = + client.routeTables.listAll() + for await (const routeTable of routeTableIterable) { + routeTable && routeTableData.push(routeTable) + } + }, + { + service: serviceName, + client, + scope: 'routeTables', + operation: 'listAll', + } + ) + + const result: { + [property: string]: RawAzureRouteTable[] + } = {} + let numOfGroups = 0 + await Promise.all( + routeTableData.map(async ({ name, 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 || {}, + routes: await listRouteTableRoute(client, resourceGroupId, name), + }) + numOfGroups += 1 + } + }) + ) + logger.debug(lt.foundRouteTables(numOfGroups)) + + return result + } catch (e) { + logger.error(e) + return {} + } +} diff --git a/src/services/routeTable/format.ts b/src/services/routeTable/format.ts new file mode 100644 index 00000000..fa55470a --- /dev/null +++ b/src/services/routeTable/format.ts @@ -0,0 +1,34 @@ +import { RawAzureRouteTable } from './data' +import { AzureRouteTable, AzureRouteTableRoute } from '../../types/generated' +import { formatTagsFromMap } from '../../utils/format' + +export default ({ + service, + account: subscriptionId, +}: { + service: RawAzureRouteTable + account: string +}): AzureRouteTable => { + const { id, name, type, region, Tags, routes = [] } = service + + return { + id, + name, + type, + region, + subscriptionId, + tags: formatTagsFromMap(Tags), + routes: + routes?.map( + ({ + id: routeId, + name: routeName, + type: routeType, + }): AzureRouteTableRoute => ({ + id: routeId, + name: routeName, + type: routeType, + }) + ) ?? [], + } +} diff --git a/src/services/routeTable/index.ts b/src/services/routeTable/index.ts new file mode 100644 index 00000000..7c7c53fa --- /dev/null +++ b/src/services/routeTable/index.ts @@ -0,0 +1,13 @@ +import { Service } from '@cloudgraph/sdk' +import BaseService from '../base' +import format from './format' +import mutation from './mutation' +import getData from './data' + +export default class AzureRouteTable extends BaseService implements Service { + format = format.bind(this) + + getData = getData.bind(this) + + mutation = mutation +} diff --git a/src/services/routeTable/mutation.ts b/src/services/routeTable/mutation.ts new file mode 100644 index 00000000..40b5350d --- /dev/null +++ b/src/services/routeTable/mutation.ts @@ -0,0 +1,5 @@ +export default `mutation($input: [AddazureRouteTableInput!]!) { + addazureRouteTable(input: $input, upsert: true) { + numUids + } +}`; diff --git a/src/services/routeTable/schema.graphql b/src/services/routeTable/schema.graphql new file mode 100644 index 00000000..19f09818 --- /dev/null +++ b/src/services/routeTable/schema.graphql @@ -0,0 +1,22 @@ +type azureRouteTableRoute + @generate( + query: { get: false, query: true, aggregate: false } + mutation: { add: false, delete: false } + subscription: false + ) + @key(fields: "id") { + id: String! @id @search(by: [hash]) + name: String @search(by: [hash, regexp]) + type: String @search(by: [hash, regexp]) +} + +type azureRouteTable implements azureResource + @generate( + query: { get: true, query: true, aggregate: true } + mutation: { add: true, delete: false } + subscription: false + ) + @key(fields: "id") { + name: String @search(by: [hash, regexp]) + routes: [azureRouteTableRoute] +} diff --git a/src/types/generated.ts b/src/types/generated.ts index 25ac1891..ab1a5d36 100644 --- a/src/types/generated.ts +++ b/src/types/generated.ts @@ -5075,6 +5075,21 @@ export type AzureResourceSystemData = { lastModifiedByType?: Maybe; }; +export type AzureRouteFilter = AzureResource & { + name?: Maybe; +}; + +export type AzureRouteTable = AzureResource & { + name?: Maybe; + routes?: Maybe>>; +}; + +export type AzureRouteTableRoute = { + id: Scalars['String']; + name?: Maybe; + type?: Maybe; +}; + export type AzureSecretBaseUnion = { secretName?: Maybe; secretVersion?: Maybe;