diff --git a/azure/plugin.go b/azure/plugin.go index 15c2d62d..38c8fc8f 100644 --- a/azure/plugin.go +++ b/azure/plugin.go @@ -32,6 +32,7 @@ func Plugin(ctx context.Context) *plugin.Plugin { "azure_ad_user": tableAzureAdUser(ctx), "azure_alert_management": tableAzureAlertMangement(ctx), "azure_api_management": tableAzureAPIManagement(ctx), + "azure_api_management_backend": tableAzureAPIManagementBackend(ctx), "azure_app_configuration": tableAzureAppConfiguration(ctx), "azure_app_service_environment": tableAzureAppServiceEnvironment(ctx), "azure_app_service_function_app": tableAzureAppServiceFunctionApp(ctx), diff --git a/azure/table_azure_api_management_backend.go b/azure/table_azure_api_management_backend.go new file mode 100644 index 00000000..f9fa98ba --- /dev/null +++ b/azure/table_azure_api_management_backend.go @@ -0,0 +1,290 @@ +package azure + +import ( + "context" + "fmt" + "strings" + + "github.com/Azure/azure-sdk-for-go/services/apimanagement/mgmt/2020-12-01/apimanagement" + "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 tableAzureAPIManagementBackend(_ context.Context) *plugin.Table { + return &plugin.Table{ + Name: "azure_api_management_backend", + Description: "Azure API Management Backend", + Get: &plugin.GetConfig{ + KeyColumns: plugin.AllColumns([]string{"backend_id", "resource_group", "service_name"}), + Hydrate: getAPIManagementBackend, + IgnoreConfig: &plugin.IgnoreConfig{ + ShouldIgnoreErrorFunc: isNotFoundError([]string{"ResourceNotFound", "ResourceGroupNotFound"}), + }, + }, + List: &plugin.ListConfig{ + ParentHydrate: listAPIManagements, + Hydrate: listAPIManagementBackends, + KeyColumns: plugin.KeyColumnSlice{ + { + Name: "service_name", + Require: plugin.Optional, + Operators: []string{"="}, + }, + { + Name: "name", + Require: plugin.Optional, + Operators: []string{"=", "<>"}, + }, + { + Name: "url", + Require: plugin.Optional, + Operators: []string{"=", "<>"}, + }, + { + Name: "resource_group", + Require: plugin.Optional, + Operators: []string{"="}, + }, + }, + }, + Columns: azureColumns([]*plugin.Column{ + { + Name: "name", + Type: proto.ColumnType_STRING, + Description: "A friendly name that identifies an API management backend.", + }, + { + Name: "id", + Description: "Contains ID to identify an API management backend uniquely.", + Type: proto.ColumnType_STRING, + Transform: transform.FromGo(), + }, + { + Name: "url", + Description: "Runtime Url of the API management backend.", + Type: proto.ColumnType_STRING, + Transform: transform.FromField("BackendContractProperties.URL"), + }, + { + Name: "type", + Description: "Resource type for API Management resource.", + Type: proto.ColumnType_STRING, + }, + { + Name: "protocol", + Description: "API management backend communication protocol. Possible values include: 'BackendProtocolHTTP', 'BackendProtocolSoap'.", + Type: proto.ColumnType_STRING, + Transform: transform.FromField("BackendContractProperties.Protocol"), + }, + { + Name: "description", + Description: "The API management backend Description.", + Type: proto.ColumnType_STRING, + Transform: transform.FromField("BackendContractProperties.Description"), + }, + { + Name: "resource_id", + Description: "Management Uri of the Resource in External System. This url can be the Arm Resource Id of Logic Apps, Function Apps or Api Apps.", + Type: proto.ColumnType_STRING, + Transform: transform.FromField("BackendContractProperties.ResourceID"), + }, + { + Name: "properties", + Description: "The API management backend Properties contract.", + Type: proto.ColumnType_JSON, + Transform: transform.FromField("BackendContractProperties.Properties"), + }, + { + Name: "credentials", + Description: "The API management backend credentials contract properties.", + Type: proto.ColumnType_JSON, + Transform: transform.FromField("BackendContractProperties.Credentials"), + }, + { + Name: "proxy", + Description: "The API management backend proxy contract properties.", + Type: proto.ColumnType_JSON, + Transform: transform.FromField("BackendContractProperties.Proxy"), + }, + { + Name: "tls", + Description: "The API management backend TLS properties.", + Type: proto.ColumnType_JSON, + Transform: transform.FromField("BackendContractProperties.TLS"), + }, + { + Name: "service_name", + Description: "Name of the API management service.", + Type: proto.ColumnType_STRING, + }, + // We have added this as an extra column because the get call takes only the last path of the id as the backend_id which we do not get from the API + { + Name: "backend_id", + Description: "The API management backend ID.", + Type: proto.ColumnType_STRING, + Transform: transform.FromField("ID").Transform(lastPathElement), + }, + + // Steampipe standard columns + { + Name: "title", + Description: ColumnDescriptionTitle, + Type: proto.ColumnType_STRING, + Transform: transform.FromField("Name"), + }, + { + Name: "akas", + Description: ColumnDescriptionAkas, + Type: proto.ColumnType_JSON, + Transform: transform.FromField("ID").Transform(transform.EnsureStringArray), + }, + + // Azure standard columns + { + Name: "resource_group", + Description: ColumnDescriptionResourceGroup, + Type: proto.ColumnType_STRING, + Transform: transform.FromField("ID").Transform(extractResourceGroupFromID), + }, + }), + } +} + +type BackendWithServiceName struct { + apimanagement.BackendContract + ServiceName string +} + +//// LIST FUNCTION + +func listAPIManagementBackends(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { + serviceInfo := h.Item.(apimanagement.ServiceResource) + serviceName := *serviceInfo.Name + resourceGroup := strings.Split(*serviceInfo.ID, "/")[4] + + if d.EqualsQualString("service_name") != "" || d.EqualsQualString("resource_group") != "" { + if d.EqualsQualString("service_name") != "" && d.EqualsQualString("service_name") != serviceName { + return nil, nil + } + if d.EqualsQualString("resource_group") != "" && d.EqualsQualString("resource_group") != resourceGroup { + return nil, nil + } + } + + session, err := GetNewSession(ctx, d, "MANAGEMENT") + if err != nil { + plugin.Logger(ctx).Error("azure_api_management_backend.listAPIManagementBackends", "session_error", err) + return nil, err + } + subscriptionID := session.SubscriptionID + + apiManagementBackendClient := apimanagement.NewBackendClientWithBaseURI(session.ResourceManagerEndpoint, subscriptionID) + apiManagementBackendClient.Authorizer = session.Authorizer + + // Build filter string + filter := "" + if d.EqualsQualString("name") != "" || d.EqualsQualString("url") != "" { + filterQuals := []string{"name", "url"} + for _, columnName := range filterQuals { + if d.Quals[columnName] != nil { + quals := d.Quals[columnName].Quals + for _, q := range quals { + switch q.Operator { + case "=": + if filter == "" { + filter = fmt.Sprintf(columnName+" eq '%s' ", q.Value.GetStringValue()) + } else { + filter = filter + " and " + fmt.Sprintf(columnName+" eq '%s' ", q.Value.GetStringValue()) + } + case "<>": + if filter == "" { + filter = fmt.Sprintf(columnName+" ne '%s' ", q.Value.GetStringValue()) + } else { + filter = filter + " and " + fmt.Sprintf(columnName+" ne '%s' ", q.Value.GetStringValue()) + } + } + } + } + } + } + + result, err := apiManagementBackendClient.ListByService(ctx, resourceGroup, serviceName, filter, nil, nil) + if err != nil { + // API throws error during the resource creation with status code 400. + // azure: apimanagement.BackendClient#ListByService: Failure responding to request: StatusCode=400 -- Original Error: autorest/azure: Service returned an error. Status=400 Code="InvalidOperation" Message="API Management service is activating" (SQLSTATE HV000) + if strings.Contains(err.Error(), "API Management service is activating") { + return nil, nil + } + plugin.Logger(ctx).Error("azure_api_management_backend.listAPIManagementBackends", "api_error", err) + return nil, err + } + for _, apiManagementBackend := range result.Values() { + backendWithService := &BackendWithServiceName{ + apiManagementBackend, + serviceName, + } + d.StreamListItem(ctx, backendWithService) + // 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 { + plugin.Logger(ctx).Error("azure_api_management_backend.listAPIManagementBackends", "list_paging", err) + return nil, err + } + + for _, apiManagementBackend := range result.Values() { + d.StreamListItem(ctx, apiManagementBackend) + // 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, err +} + +//// HYDRATE FUNCTIONS + +func getAPIManagementBackend(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { + + backendID := d.EqualsQualString("backend_id") + serviceName := d.EqualsQualString("service_name") + resourceGroup := d.EqualsQualString("resource_group") + + // resourceGroupName can't be empty + // Error: pq: rpc error: code = Unknown desc = apimanagement.ServiceClient#Get: Invalid input: autorest/validation: validation failed: parameter=serviceName + // constraint=MinLength value="" details: value length must be greater than or equal to 1 + if len(backendID) < 1 { + return nil, nil + } + + session, err := GetNewSession(ctx, d, "MANAGEMENT") + if err != nil { + plugin.Logger(ctx).Error("azure_api_management_backend.listAPIManagementBackends", "session_error", err) + return nil, err + } + subscriptionID := session.SubscriptionID + + apiManagementBackendClient := apimanagement.NewBackendClientWithBaseURI(session.ResourceManagerEndpoint, subscriptionID) + apiManagementBackendClient.Authorizer = session.Authorizer + + op, err := apiManagementBackendClient.Get(ctx, resourceGroup, serviceName, backendID) + if err != nil { + plugin.Logger(ctx).Error("azure_api_management_backend.listAPIManagementBackends", "api_error", err) + return nil, err + } + + return BackendWithServiceName{op, serviceName}, nil +} diff --git a/docs/tables/azure_api_management_backend.md b/docs/tables/azure_api_management_backend.md new file mode 100644 index 00000000..d0e73a5a --- /dev/null +++ b/docs/tables/azure_api_management_backend.md @@ -0,0 +1,118 @@ +--- +title: "Steampipe Table: azure_api_management_backend - Query Azure API Management Backends using SQL" +description: "Allows users to query Azure API Management Backends, specifically providing access to configuration details, service details, and backend settings." +--- + +# Table: azure_api_management_backend - Query API Management Backend Configurations using SQL + +In Azure API Management (APIM), a "Backend" represents the configuration of a web service or API that is the destination for API requests processed by APIM's policies. This includes various types of backends, such as services running on Azure App Service, virtual machines, or external APIs hosted outside of Azure. Understanding the backend configurations is crucial for managing the API request and response lifecycle within APIM. + +## Table Usage Guide + +The `azure_api_management_backend` table provides insights into the backend configurations within Azure API Management. Use this table to gain detailed information about how API requests are routed and managed, including the protocol used, service details, and security configurations. + +## Examples + +### Basic Info +Gain a general understanding of the backend configurations in your API Management service. This query is essential for a quick overview of how your APIs are set up in terms of endpoints and protocols. + +```sql+postgres +select + name, + id, + protocol, + service_name, + url, + type +from + azure_api_management_backend; +``` + +```sql+sqlite +select + name, + id, + protocol, + service_name, + url, + type +from + azure_api_management_backend; +``` + +### List the Backend Credential Contract Properties +Review the authorization credentials for backends. This is crucial for understanding and managing security configurations for your API backends. + +```sql+postgres +select + name, + id, + credentials -> 'authorization' ->> 'parameter' as parameter, + credentials -> 'authorization' ->> 'scheme' as scheme +from + azure_api_management_backend; +``` + +```sql+sqlite +select + name, + id, + json_extract(json_extract(credentials, '$.authorization'), '$.parameter') as parameter, + json_extract(json_extract(credentials, '$.authorization'), '$.scheme') as scheme +from + azure_api_management_backend; +``` + +### Get the TLS Configuration for a Particular Backend Service +Examine the TLS (Transport Layer Security) configurations for backend services. This query helps ensure that secure communication protocols are in place. + +```sql+postgres +select + name, + id, + tls -> 'validateCertificateChain' as tls_validate_certificate_chain, + tls -> 'validateCertificateName' as tls_validate_certificate_name +from + azure_api_management_backend; +``` + +```sql+sqlite +select + name, + id, + json_extract(tls, '$.validateCertificateChain') as tls_validate_certificate_chain, + json_extract(tls, '$.validateCertificateName') as tls_validate_certificate_name +from + azure_api_management_backend; +``` + +### List Backends That Follow HTTP Protocol +Identify backends using the HTTP protocol. This can be important for reviewing API security, as HTTP lacks the encryption of HTTPS. + +```sql+postgres +select + name, + id, + protocol, + service_name, + url, + type +from + azure_api_management_backend +where + protocol = 'http'; +``` + +```sql+sqlite +select + name, + id, + protocol, + service_name, + url, + type +from + azure_api_management_backend +where + protocol = 'http'; +```