diff --git a/azure-test/tests/azure_virtual_network_gateway/dependencies.txt b/azure-test/tests/azure_virtual_network_gateway/dependencies.txt new file mode 100644 index 00000000..e69de29b diff --git a/azure-test/tests/azure_virtual_network_gateway/posttest-variables.tf b/azure-test/tests/azure_virtual_network_gateway/posttest-variables.tf new file mode 100644 index 00000000..1559b025 --- /dev/null +++ b/azure-test/tests/azure_virtual_network_gateway/posttest-variables.tf @@ -0,0 +1,14 @@ +resource "null_resource" "delay" { + provisioner "local-exec" { + command = "sleep 3600" + } +} + +resource "null_resource" "named_test_resource" { + depends_on = [ + null_resource.delay + ] + provisioner "local-exec" { + command = "az network vnet-gateway delete -g {{ resourceName }} -n {{ resourceName }}" + } +} diff --git a/azure-test/tests/azure_virtual_network_gateway/test-get-expected.json b/azure-test/tests/azure_virtual_network_gateway/test-get-expected.json new file mode 100644 index 00000000..73400a1d --- /dev/null +++ b/azure-test/tests/azure_virtual_network_gateway/test-get-expected.json @@ -0,0 +1,10 @@ +[ + { + "enable_bgp": false, + "id": "{{ output.resource_id.value }}", + "name": "{{ resourceName }}", + "region": "{{ output.region.value }}", + "resource_group": "{{ resourceName }}", + "type": "Microsoft.Network/virtualNetworkGateways" + } +] diff --git a/azure-test/tests/azure_virtual_network_gateway/test-get-query.sql b/azure-test/tests/azure_virtual_network_gateway/test-get-query.sql new file mode 100644 index 00000000..932eee9c --- /dev/null +++ b/azure-test/tests/azure_virtual_network_gateway/test-get-query.sql @@ -0,0 +1,3 @@ +select name, id, region, type, enable_bgp, resource_group +from azure.azure_virtual_network_gateway +where name = '{{ resourceName }}' and resource_group = '{{ resourceName }}'; \ No newline at end of file diff --git a/azure-test/tests/azure_virtual_network_gateway/test-list-expected.json b/azure-test/tests/azure_virtual_network_gateway/test-list-expected.json new file mode 100644 index 00000000..e854f350 --- /dev/null +++ b/azure-test/tests/azure_virtual_network_gateway/test-list-expected.json @@ -0,0 +1,6 @@ +[ + { + "id": "{{ output.resource_id.value }}", + "name": "{{ resourceName }}" + } +] diff --git a/azure-test/tests/azure_virtual_network_gateway/test-list-query.sql b/azure-test/tests/azure_virtual_network_gateway/test-list-query.sql new file mode 100644 index 00000000..e69b2c22 --- /dev/null +++ b/azure-test/tests/azure_virtual_network_gateway/test-list-query.sql @@ -0,0 +1,3 @@ +select id, name +from azure.azure_virtual_network_gateway +where name = '{{ resourceName }}'; \ No newline at end of file diff --git a/azure-test/tests/azure_virtual_network_gateway/test-not-found-expected.json b/azure-test/tests/azure_virtual_network_gateway/test-not-found-expected.json new file mode 100644 index 00000000..19765bd5 --- /dev/null +++ b/azure-test/tests/azure_virtual_network_gateway/test-not-found-expected.json @@ -0,0 +1 @@ +null diff --git a/azure-test/tests/azure_virtual_network_gateway/test-not-found-query.sql b/azure-test/tests/azure_virtual_network_gateway/test-not-found-query.sql new file mode 100644 index 00000000..ef40b273 --- /dev/null +++ b/azure-test/tests/azure_virtual_network_gateway/test-not-found-query.sql @@ -0,0 +1,3 @@ +select name, akas, title +from azure.azure_virtual_network_gateway +where name = 'dummy-{{ resourceName }}' and resource_group = '{{ resourceName }}'; \ No newline at end of file diff --git a/azure-test/tests/azure_virtual_network_gateway/test-turbot-expected.json b/azure-test/tests/azure_virtual_network_gateway/test-turbot-expected.json new file mode 100644 index 00000000..02cd3c76 --- /dev/null +++ b/azure-test/tests/azure_virtual_network_gateway/test-turbot-expected.json @@ -0,0 +1,10 @@ +[ + { + "akas": [ + "{{ output.resource_aka.value }}", + "{{ output.resource_aka_lower.value }}" + ], + "name": "{{ resourceName }}", + "title": "{{ resourceName }}" + } +] diff --git a/azure-test/tests/azure_virtual_network_gateway/test-turbot-query.sql b/azure-test/tests/azure_virtual_network_gateway/test-turbot-query.sql new file mode 100644 index 00000000..22daec24 --- /dev/null +++ b/azure-test/tests/azure_virtual_network_gateway/test-turbot-query.sql @@ -0,0 +1,3 @@ +select name, akas, title +from azure.azure_virtual_network_gateway +where name = '{{ resourceName }}' and resource_group = '{{ resourceName }}'; \ No newline at end of file diff --git a/azure-test/tests/azure_virtual_network_gateway/variables.json b/azure-test/tests/azure_virtual_network_gateway/variables.json new file mode 100644 index 00000000..0967ef42 --- /dev/null +++ b/azure-test/tests/azure_virtual_network_gateway/variables.json @@ -0,0 +1 @@ +{} diff --git a/azure-test/tests/azure_virtual_network_gateway/variables.tf b/azure-test/tests/azure_virtual_network_gateway/variables.tf new file mode 100644 index 00000000..fb5d7c82 --- /dev/null +++ b/azure-test/tests/azure_virtual_network_gateway/variables.tf @@ -0,0 +1,104 @@ +variable "resource_name" { + type = string + default = "turbot-test-20200125-create-update" + description = "Name of the resource used throughout the test." +} + +variable "azure_environment" { + type = string + default = "public" + description = "Azure environment used for the test." +} + +variable "azure_subscription" { + type = string + default = "3510ae4d-530b-497d-8f30-53b9616fc6c1" + description = "Azure subscription used for the test." +} + +provider "azurerm" { + # Cannot be passed as a variable + version = "=1.36.0" + environment = var.azure_environment + subscription_id = var.azure_subscription +} + +data "azurerm_client_config" "current" {} + +data "null_data_source" "resource" { + inputs = { + scope = "azure:///subscriptions/${data.azurerm_client_config.current.subscription_id}" + } +} + +resource "azurerm_resource_group" "named_test_resource" { + name = var.resource_name + location = "East US" +} + +resource "azurerm_virtual_network" "named_test_resource" { + name = var.resource_name + address_space = ["10.0.0.0/16"] + location = azurerm_resource_group.named_test_resource.location + resource_group_name = azurerm_resource_group.named_test_resource.name +} + +resource "azurerm_subnet" "named_test_resource" { + name = "GatewaySubnet" + resource_group_name = azurerm_resource_group.named_test_resource.name + virtual_network_name = azurerm_virtual_network.named_test_resource.name + address_prefix = "10.0.1.0/24" +} + +resource "azurerm_public_ip" "named_test_resource" { + depends_on = [azurerm_subnet.named_test_resource] + name = var.resource_name + location = azurerm_resource_group.named_test_resource.location + resource_group_name = azurerm_resource_group.named_test_resource.name + allocation_method = "Dynamic" +} + +locals { + path = "${path.cwd}/info.json" +} + +resource "null_resource" "named_test_resource" { + depends_on = [azurerm_public_ip.named_test_resource] + provisioner "local-exec" { + command = "az network vnet-gateway create --gateway-type Vpn --location ${azurerm_resource_group.named_test_resource.location} --name ${var.resource_name} --no-wait --public-ip-addresses ${var.resource_name} --resource-group ${var.resource_name} --vnet ${var.resource_name}" + } + provisioner "local-exec" { + command = "az network vnet-gateway show -g ${var.resource_name} -n ${var.resource_name} > ${local.path}" + } +} + +data "local_file" "input" { + depends_on = [null_resource.named_test_resource] + filename = local.path +} + +output "resource_aka" { + depends_on = [null_resource.named_test_resource] + value = "azure://${jsondecode(data.local_file.input.content).id}" +} + +output "resource_aka_lower" { + depends_on = [null_resource.named_test_resource] + value = "azure://${lower(jsondecode(data.local_file.input.content).id)}" +} + +output "resource_name" { + value = var.resource_name +} + +output "resource_id" { + value = jsondecode(data.local_file.input.content).id +} + +output "subscription_id" { + value = var.azure_subscription +} + +output "region" { + value = azurerm_resource_group.named_test_resource.location +} \ No newline at end of file diff --git a/azure/plugin.go b/azure/plugin.go index e5467c02..03008e67 100644 --- a/azure/plugin.go +++ b/azure/plugin.go @@ -93,6 +93,7 @@ func Plugin(ctx context.Context) *plugin.Plugin { "azure_subscription": tableAzureSubscription(ctx), "azure_tenant": tableAzureTenant(ctx), "azure_virtual_network": tableAzureVirtualNetwork(ctx), + "azure_virtual_network_gateway": tableAzureVirtualNetworkGateway(ctx), // "azure_storage_table": tableAzureStorageTable(ctx), }, } diff --git a/azure/table_azure_virtual_network_gateway.go b/azure/table_azure_virtual_network_gateway.go new file mode 100644 index 00000000..47e3030d --- /dev/null +++ b/azure/table_azure_virtual_network_gateway.go @@ -0,0 +1,301 @@ +package azure + +import ( + "context" + "strings" + + "github.com/Azure/azure-sdk-for-go/services/network/mgmt/2020-05-01/network" + "github.com/turbot/steampipe-plugin-sdk/grpc/proto" + "github.com/turbot/steampipe-plugin-sdk/plugin" + "github.com/turbot/steampipe-plugin-sdk/plugin/transform" +) + +//// TABLE DEFINITION + +func tableAzureVirtualNetworkGateway(_ context.Context) *plugin.Table { + return &plugin.Table{ + Name: "azure_virtual_network_gateway", + Description: "Azure Virtual Network Gateway", + Get: &plugin.GetConfig{ + KeyColumns: plugin.AllColumns([]string{"name", "resource_group"}), + Hydrate: getVirtualNetworkGateway, + ShouldIgnoreError: isNotFoundError([]string{"ResourceGroupNotFound", "ResourceNotFound", "404"}), + }, + List: &plugin.ListConfig{ + ParentHydrate: listVirtualNetworks, + Hydrate: listVirtualNetworkGateways, + }, + Columns: []*plugin.Column{ + { + Name: "name", + Description: "The friendly name that identifies the virtual network gateway.", + Type: proto.ColumnType_STRING, + }, + { + Name: "id", + Description: "Contains ID to identify a virtual network gateway uniquely.", + Type: proto.ColumnType_STRING, + Transform: transform.FromGo(), + }, + { + Name: "type", + Description: "Type of the resource.", + Type: proto.ColumnType_STRING, + }, + { + Name: "gateway_type", + Description: "The type of this virtual network gateway. Possible values include: 'Vpn', 'ExpressRoute'.", + Type: proto.ColumnType_STRING, + Transform: transform.FromField("VirtualNetworkGatewayPropertiesFormat.GatewayType").Transform(transform.ToString), + }, + { + Name: "vpn_type", + Description: "The type of this virtual network gateway. Valid values are: 'PolicyBased', 'RouteBased'.", + Type: proto.ColumnType_STRING, + Transform: transform.FromField("VirtualNetworkGatewayPropertiesFormat.VpnType").Transform(transform.ToString), + }, + { + Name: "vpn_gateway_generation", + Description: "The generation for this virtual network gateway. Must be None if gatewayType is not VPN. Valid values are: 'None', 'Generation1', 'Generation2'.", + Type: proto.ColumnType_STRING, + Transform: transform.FromField("VirtualNetworkGatewayPropertiesFormat.VpnGatewayGeneration").Transform(transform.ToString), + }, + { + Name: "provisioning_state", + Description: "The provisioning state of the virtual network gateway resource.", + Type: proto.ColumnType_STRING, + Transform: transform.FromField("VirtualNetworkGatewayPropertiesFormat.ProvisioningState").Transform(transform.ToString), + }, + { + Name: "active_active", + Description: "Indicates whether virtual network gateway configured with active-active mode, or not. If true, each Azure gateway instance will have a unique public IP address, and each will establish an IPsec/IKE S2S VPN tunnel to your on-premises VPN device specified in your local network gateway and connection.", + Type: proto.ColumnType_BOOL, + Transform: transform.FromField("VirtualNetworkGatewayPropertiesFormat.ActiveActive"), + }, + { + Name: "enable_bgp", + Description: "Indicates whether BGP is enabled for this virtual network gateway, or not.", + Type: proto.ColumnType_BOOL, + Transform: transform.FromField("VirtualNetworkGatewayPropertiesFormat.EnableBgp"), + }, + { + Name: "enable_dns_forwarding", + Description: "Indicates whether DNS forwarding is enabled, or not.", + Type: proto.ColumnType_BOOL, + Transform: transform.FromField("VirtualNetworkGatewayPropertiesFormat.EnableVMProtection"), + }, + { + Name: "enable_private_ip_address", + Description: "Indicates whether private IP needs to be enabled on this gateway for connections or not.", + Type: proto.ColumnType_BOOL, + Transform: transform.FromField("VirtualNetworkGatewayPropertiesFormat.EnablePrivateIPAddress"), + }, + { + Name: "etag", + Description: "An unique read-only string that changes whenever the resource is updated.", + Type: proto.ColumnType_STRING, + }, + { + Name: "gateway_default_site", + Description: "The reference to the LocalNetworkGateway resource, which represents local network site having default routes. Assign Null value in case of removing existing default site setting.", + Type: proto.ColumnType_STRING, + Transform: transform.FromField("VirtualNetworkGatewayPropertiesFormat.GatewayDefaultSite.ID"), + }, + { + Name: "inbound_dns_forwarding_endpoint", + Description: "The IP address allocated by the gateway to which dns requests can be sent.", + Type: proto.ColumnType_STRING, + Transform: transform.FromField("VirtualNetworkGatewayPropertiesFormat.InboundDNSForwardingEndpoint"), + }, + { + Name: "resource_guid", + Description: "The resource GUID property of the virtual network gateway resource.", + Type: proto.ColumnType_STRING, + Transform: transform.FromField("VirtualNetworkGatewayPropertiesFormat.ResourceGUID"), + }, + { + Name: "sku_name", + Description: "Gateway SKU name.", + Type: proto.ColumnType_STRING, + Transform: transform.FromField("VirtualNetworkGatewayPropertiesFormat.Sku.Name").Transform(transform.ToString), + }, + { + Name: "sku_tier", + Description: "Gateway SKU tier.", + Type: proto.ColumnType_STRING, + Transform: transform.FromField("VirtualNetworkGatewayPropertiesFormat.Sku.Tier").Transform(transform.ToString), + }, + { + Name: "sku_capacity", + Description: "Gateway SKU capacity.", + Type: proto.ColumnType_INT, + Transform: transform.FromField("VirtualNetworkGatewayPropertiesFormat.Sku.Capacity"), + }, + { + Name: "bgp_settings", + Description: "Virtual network gateway's BGP speaker settings.", + Type: proto.ColumnType_JSON, + Transform: transform.FromField("VirtualNetworkGatewayPropertiesFormat.BgpSettings"), + }, + { + Name: "custom_routes_address_prefixes", + Description: "A list of address blocks reserved for this virtual network in CIDR notation.", + Type: proto.ColumnType_JSON, + Transform: transform.FromField("VirtualNetworkGatewayPropertiesFormat.CustomRoutes.AddressPrefixes"), + }, + { + Name: "gateway_connections", + Description: "A list of virtual network gateway connection resources that exists in a resource group.", + Type: proto.ColumnType_JSON, + Hydrate: getVirtualNetworkGatewayConnection, + Transform: transform.FromValue(), + }, + { + Name: "ip_configurations", + Description: "IP configurations for virtual network gateway.", + Type: proto.ColumnType_JSON, + Transform: transform.FromField("VirtualNetworkGatewayPropertiesFormat.IPConfigurations"), + }, + { + Name: "vpn_client_configuration", + Description: "The reference to the VpnClientConfiguration resource which represents the P2S VpnClient configurations.", + Type: proto.ColumnType_JSON, + Transform: transform.FromField("VirtualNetworkGatewayPropertiesFormat.VpnClientConfiguration"), + }, + + // Steampipe standard columns + { + Name: "title", + Description: ColumnDescriptionTitle, + Type: proto.ColumnType_STRING, + Transform: transform.FromField("Name"), + }, + { + Name: "tags", + Description: ColumnDescriptionTags, + Type: proto.ColumnType_JSON, + }, + { + Name: "akas", + Description: ColumnDescriptionAkas, + Type: proto.ColumnType_JSON, + Transform: transform.FromField("ID").Transform(idToAkas), + }, + + // Azure standard columns + { + Name: "region", + Description: ColumnDescriptionRegion, + Type: proto.ColumnType_STRING, + Transform: transform.FromField("Location").Transform(toLower), + }, + { + Name: "resource_group", + Description: ColumnDescriptionResourceGroup, + Type: proto.ColumnType_STRING, + Transform: transform.FromField("ID").Transform(extractResourceGroupFromID), + }, + { + Name: "subscription_id", + Description: ColumnDescriptionSubscription, + Type: proto.ColumnType_STRING, + Transform: transform.FromField("ID").Transform(idToSubscriptionID), + }, + }, + } +} + +//// LIST FUNCTION + +func listVirtualNetworkGateways(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { + session, err := GetNewSession(ctx, d, "MANAGEMENT") + if err != nil { + return nil, err + } + subscriptionID := session.SubscriptionID + + networkClient := network.NewVirtualNetworkGatewaysClient(subscriptionID) + networkClient.Authorizer = session.Authorizer + + virtualNetwork := h.Item.(network.VirtualNetwork) + resourceGroupName := strings.Split(*virtualNetwork.ID, "/")[4] + + pagesLeft := true + for pagesLeft { + result, err := networkClient.List(ctx, resourceGroupName) + if err != nil { + return nil, err + } + + for _, networkGateway := range result.Values() { + d.StreamListItem(ctx, networkGateway) + } + result.NextWithContext(context.Background()) + pagesLeft = result.NotDone() + } + + return nil, err +} + +//// HYDRATE FUNCTIONS + +func getVirtualNetworkGateway(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { + plugin.Logger(ctx).Trace("getVirtualNetworkGateway") + + // Create session + session, err := GetNewSession(ctx, d, "MANAGEMENT") + if err != nil { + return nil, err + } + subscriptionID := session.SubscriptionID + + name := d.KeyColumnQuals["name"].GetStringValue() + resourceGroup := d.KeyColumnQuals["resource_group"].GetStringValue() + + // Handle empty name or resourceGroup + if name == "" || resourceGroup == "" { + return nil, nil + } + + networkClient := network.NewVirtualNetworkGatewaysClient(subscriptionID) + networkClient.Authorizer = session.Authorizer + + op, err := networkClient.Get(ctx, resourceGroup, name) + if err != nil { + return nil, err + } + + return op, nil +} + +func getVirtualNetworkGatewayConnection(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { + plugin.Logger(ctx).Trace("getVirtualNetworkGatewayConnection") + + // Create session + session, err := GetNewSession(ctx, d, "MANAGEMENT") + if err != nil { + return nil, err + } + subscriptionID := session.SubscriptionID + + virtualNetworkGateway := h.Item.(network.VirtualNetworkGateway) + name := *virtualNetworkGateway.Name + resourceGroup := strings.Split(*virtualNetworkGateway.ID, "/")[4] + + networkClient := network.NewVirtualNetworkGatewaysClient(subscriptionID) + networkClient.Authorizer = session.Authorizer + + var gatewayConnections []network.VirtualNetworkGatewayConnectionListEntity + pagesLeft := true + for pagesLeft { + result, err := networkClient.ListConnections(ctx, resourceGroup, name) + if err != nil { + return nil, err + } + gatewayConnections = append(gatewayConnections, result.Values()...) + result.NextWithContext(context.Background()) + pagesLeft = result.NotDone() + } + + return gatewayConnections, nil +} diff --git a/docs/tables/azure_virtual_network_gateway.md b/docs/tables/azure_virtual_network_gateway.md new file mode 100644 index 00000000..5ee196fc --- /dev/null +++ b/docs/tables/azure_virtual_network_gateway.md @@ -0,0 +1,33 @@ +# Table: azure_virtual_network_gateway + +A virtual network gateway is used to establish secure, cross-premises connectivity. + +## Examples + +### Basic info + +```sql +select + name, + id, + enable_bgp, + region, + resource_group +from + azure_virtual_network_gateway; +``` + +### List network gateways with no connections + +```sql +select + name, + id, + enable_bgp, + region, + resource_group +from + azure_virtual_network_gateway +where + gateway_connection is null; +```