Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add table azure_bastion_host. Closes #576 #580

Merged
merged 8 commits into from
Mar 15, 2023
1 change: 1 addition & 0 deletions azure/plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ func Plugin(ctx context.Context) *plugin.Plugin {
"azure_application_security_group": tableAzureApplicationSecurityGroup(ctx),
"azure_automation_account": tableAzureApAutomationAccount(ctx),
"azure_automation_variable": tableAzureApAutomationVariable(ctx),
"azure_bastion_host": tableAzureBastionHost(ctx),
"azure_batch_account": tableAzureBatchAccount(ctx),
"azure_cognitive_account": tableAzureCognitiveAccount(ctx),
"azure_compute_availability_set": tableAzureComputeAvailabilitySet(ctx),
Expand Down
186 changes: 186 additions & 0 deletions azure/table_azure_bastion_host.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
package azure

import (
"context"

"github.com/Azure/azure-sdk-for-go/services/network/mgmt/2020-05-01/network"
"github.com/turbot/steampipe-plugin-sdk/v5/grpc/proto"
"github.com/turbot/steampipe-plugin-sdk/v5/plugin/transform"

"github.com/turbot/steampipe-plugin-sdk/v5/plugin"
)

//// TABLE DEFINITION ////

func tableAzureBastionHost(_ context.Context) *plugin.Table {
return &plugin.Table{
Name: "azure_bastion_host",
Description: "Azure Bastion Host",
Get: &plugin.GetConfig{
KeyColumns: plugin.AllColumns([]string{"name", "resource_group"}),
Hydrate: getBastionHost,
IgnoreConfig: &plugin.IgnoreConfig{
ShouldIgnoreErrorFunc: isNotFoundError([]string{"ResourceGroupNotFound", "ResourceNotFound", "404"}),
},
},
List: &plugin.ListConfig{
Hydrate: listBastionHosts,
},
Columns: azureColumns([]*plugin.Column{
{
Name: "name",
Type: proto.ColumnType_STRING,
Description: "The friendly name that identifies the bastion host",
karanpopat marked this conversation as resolved.
Show resolved Hide resolved
},
{
Name: "id",
Description: "Contains ID to identify a bastion host uniquely",
Transform: transform.FromGo(),
Type: proto.ColumnType_STRING,
},
{
Name: "dns_name",
Description: "FQDN for the endpoint on which bastion host is accessible",
Transform: transform.FromField("BastionHostPropertiesFormat.DNSName"),
Type: proto.ColumnType_STRING,
},
{
Name: "etag",
Description: "An unique read-only string that changes whenever the resource is updated",
Type: proto.ColumnType_STRING,
},
{
Name: "provisioning_state",
Description: "The provisioning state of the bastion host resource",
Transform: transform.FromField("BastionHostPropertiesFormat.ProvisioningState"),
Type: proto.ColumnType_STRING,
},
{
Name: "type",
Description: "The resource type of the bastion host",
Type: proto.ColumnType_STRING,
},
{
Name: "ip_configurations",
Description: "IP configuration of the bastion host resource",
Transform: transform.FromField("BastionHostPropertiesFormat.IPConfigurations"),
Type: proto.ColumnType_JSON,
},

// Steampipe standard columns
{
Name: "akas",
Description: ColumnDescriptionAkas,
Transform: transform.FromField("ID").Transform(idToAkas),
Type: proto.ColumnType_JSON,
},
{
Name: "tags",
Description: ColumnDescriptionTags,
Type: proto.ColumnType_JSON,
},
{
Name: "title",
Description: ColumnDescriptionTitle,
Transform: transform.FromField("Name"),
Type: proto.ColumnType_STRING,
},

// Azure standard columns
{
Name: "region",
Description: ColumnDescriptionRegion,
Transform: transform.FromField("Location").Transform(toLower),
Type: proto.ColumnType_STRING,
},
{
Name: "resource_group",
Description: ColumnDescriptionResourceGroup,
Transform: transform.FromField("ID").Transform(extractResourceGroupFromID),
Type: proto.ColumnType_STRING,
},
}),
}
}

//// FETCH FUNCTIONS ////

func listBastionHosts(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) {
logger := plugin.Logger(ctx)
session, err := GetNewSession(ctx, d, "MANAGEMENT")
if err != nil {
logger.Error("azure_bastion_host.listBastionHosts", "client_error", err)
return nil, err
}
subscriptionID := session.SubscriptionID
bastionClient := network.NewBastionHostsClientWithBaseURI(session.ResourceManagerEndpoint, subscriptionID)
bastionClient.Authorizer = session.Authorizer

result, err := bastionClient.List(ctx)
if err != nil {
logger.Error("azure_bastion_host.listBastionHosts", "api_error", err)
return nil, err
}

for _, host := range result.Values() {
d.StreamListItem(ctx, host)

// Check if context has been cancelled or if the limit has been hit (if specified)
// if there is a limit, it will return the number of rows required to reach this limit
if d.RowsRemaining(ctx) == 0 {
return nil, nil
}
}

for result.NotDone() {
err = result.NextWithContext(ctx)
if err != nil {
logger.Error("azure_bastion_host.listBastionHosts", "api_error", err)
return nil, err
}

for _, host := range result.Values() {
d.StreamListItem(ctx, host)

// Check if context has been cancelled or if the limit has been hit (if specified)
// if there is a limit, it will return the number of rows required to reach this limit
if d.RowsRemaining(ctx) == 0 {
return nil, nil
}
}
}

return nil, nil
}

func getBastionHost(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) {
logger := plugin.Logger(ctx)
name := d.EqualsQuals["name"].GetStringValue()
resourceGroup := d.EqualsQuals["resource_group"].GetStringValue()
if name == "" || resourceGroup == "" {
return nil, nil
}

session, err := GetNewSession(ctx, d, "MANAGEMENT")
if err != nil {
logger.Error("azure_bastion_host.getBastionHost", "client_error", err)
return nil, err
}
subscriptionID := session.SubscriptionID
bastionClient := network.NewBastionHostsClientWithBaseURI(session.ResourceManagerEndpoint, subscriptionID)
bastionClient.Authorizer = session.Authorizer

result, err := bastionClient.Get(ctx, resourceGroup, name)
if err != nil {
logger.Error("azure_bastion_host.getBastionHost", "api_error", err)
return nil, err
}

// In some cases resource does not give any notFound error
// instead of notFound error, it returns empty data
if result.ID != nil {
return result, nil
}

return nil, nil
}
69 changes: 69 additions & 0 deletions docs/tables/azure_bastion_host.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# Table: azure_bastion_host

Azure Bastion is a service you deploy that lets you connect to a virtual machine using your browser and the Azure portal, or via the native SSH or RDP client already installed on your local computer. The Azure Bastion service is a fully platform-managed PaaS service that you provision inside your virtual network. It provides secure and seamless RDP/SSH connectivity to your virtual machines directly from the Azure portal over TLS. When you connect via Azure Bastion, your virtual machines don't need a public IP address, agent, or special client software.

## Examples

### Basic info

```sql
select
name,
dns_name,
provisioning_state,
region,
resource_group
from
azure_bastion_host;
karanpopat marked this conversation as resolved.
Show resolved Hide resolved
```


### List bastion hosts that failed creation
karanpopat marked this conversation as resolved.
Show resolved Hide resolved

```sql
select
name,
dns_name,
provisioning_state,
region,
resource_group
from
azure_bastion_host
where
provisioning_state = 'Failed';
```


### Get subnet details associated with each host

```sql
select
h.name as bastion_host_name,
s.id as subnet_id,
s.name as subnet_name,
address_prefix
from
azure_bastion_host h,
jsonb_array_elements(ip_configurations) ip,
azure_subnet s
where
s.id = ip -> 'properties' -> 'subnet' ->> 'id';
```

### Get ip configuration details associated with each host
karanpopat marked this conversation as resolved.
Show resolved Hide resolved

```sql
select
h.name as bastion_host_name,
i.name as ip_configuration_name,
ip_configuration_id,
ip_address,
public_ip_allocation_method,
sku_name as ip_configuration_sku
from
azure_bastion_host h,
jsonb_array_elements(ip_configurations) ip,
azure_public_ip i
where
i.id = ip -> 'properties' -> 'publicIPAddress' ->> 'id';
```