AzureMonitorDatasource: Add bounds check to fix panics (#110879)
(azure-monitor-datasource): add bounds check to fix panics
This commit is contained in:
@@ -573,6 +573,10 @@ func addTraceDataLinksToFields(query *AzureLogAnalyticsQuery, azurePortalBaseUrl
|
||||
return err
|
||||
}
|
||||
|
||||
if len(queryJSONModel.AzureTraces.Resources) == 0 {
|
||||
return fmt.Errorf("no resources specified for Azure traces data link")
|
||||
}
|
||||
|
||||
traceIdVariable := "${__data.fields.traceID}"
|
||||
resultFormat := dataquery.ResultFormatTrace
|
||||
queryJSONModel.AzureTraces.ResultFormat = &resultFormat
|
||||
@@ -668,6 +672,9 @@ func (e *AzureLogAnalyticsDatasource) createRequest(ctx context.Context, queryUR
|
||||
if query.AppInsightsQuery {
|
||||
// If the query type is traces then we only need the first resource as the rest are specified in the query
|
||||
if query.QueryType == dataquery.AzureQueryTypeAzureTraces {
|
||||
if len(query.Resources) == 0 {
|
||||
return nil, fmt.Errorf("no resources specified for Azure traces Application Insights query")
|
||||
}
|
||||
body["applications"] = []string{query.Resources[0]}
|
||||
} else {
|
||||
body["applications"] = query.Resources
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend/httpclient"
|
||||
"github.com/grafana/grafana-plugin-sdk-go/data"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/grafana/grafana/pkg/tsdb/azuremonitor/kinds/dataquery"
|
||||
@@ -710,6 +711,19 @@ func TestLogAnalyticsCreateRequest(t *testing.T) {
|
||||
t.Errorf("Unexpected Body: %v", cmp.Diff(string(body), expectedBody))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("returns error for AppInsights traces query with empty resources", func(t *testing.T) {
|
||||
ds := AzureLogAnalyticsDatasource{}
|
||||
_, err := ds.createRequest(ctx, url, &AzureLogAnalyticsQuery{
|
||||
Resources: []string{}, // Empty resources
|
||||
Query: "traces",
|
||||
QueryType: dataquery.AzureQueryTypeAzureTraces,
|
||||
AppInsightsQuery: true,
|
||||
DashboardTime: false,
|
||||
})
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), "no resources specified for Azure traces Application Insights query")
|
||||
})
|
||||
}
|
||||
|
||||
func Test_executeQueryErrorWithDifferentLogAnalyticsCreds(t *testing.T) {
|
||||
@@ -826,3 +840,62 @@ func Test_exemplarsFeatureToggle(t *testing.T) {
|
||||
require.Error(t, err, "query type unsupported as azureMonitorPrometheusExemplars feature toggle is not enabled")
|
||||
})
|
||||
}
|
||||
|
||||
func TestAddTraceDataLinksToFields_EmptyResources(t *testing.T) {
|
||||
dsInfo := types.DatasourceInfo{
|
||||
Services: map[string]types.DatasourceService{
|
||||
"Azure Monitor": {},
|
||||
},
|
||||
JSONData: map[string]any{
|
||||
"azureLogAnalyticsSameAs": false,
|
||||
},
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
queryJSON string
|
||||
expectedErrorString string
|
||||
}{
|
||||
{
|
||||
name: "empty resources array should return error",
|
||||
queryJSON: `{
|
||||
"queryType": "Azure Traces",
|
||||
"azureTraces": {
|
||||
"resources": [],
|
||||
"resultFormat": "table",
|
||||
"traceTypes": ["trace"]
|
||||
}
|
||||
}`,
|
||||
expectedErrorString: "no resources specified for Azure traces data link",
|
||||
},
|
||||
{
|
||||
name: "missing resources field should return error",
|
||||
queryJSON: `{
|
||||
"queryType": "Azure Traces",
|
||||
"azureTraces": {
|
||||
"resultFormat": "table",
|
||||
"traceTypes": ["trace"]
|
||||
}
|
||||
}`,
|
||||
expectedErrorString: "no resources specified for Azure traces data link",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
query := &AzureLogAnalyticsQuery{
|
||||
JSON: []byte(tt.queryJSON),
|
||||
QueryType: dataquery.AzureQueryTypeAzureTraces,
|
||||
ResultFormat: dataquery.ResultFormatTable,
|
||||
}
|
||||
|
||||
// Create a mock data frame
|
||||
frame := data.NewFrame("test")
|
||||
|
||||
err := addTraceDataLinksToFields(query, "https://portal.azure.com", frame, dsInfo)
|
||||
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), tt.expectedErrorString)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -207,6 +207,10 @@ func buildAppInsightsQuery(ctx context.Context, query backend.DataQuery, dsInfo
|
||||
resources = []string{fmt.Sprintf("/subscriptions/%s", subscription)}
|
||||
}
|
||||
|
||||
if len(resources) == 0 {
|
||||
return nil, fmt.Errorf("no resources specified for Azure traces query")
|
||||
}
|
||||
|
||||
resourceOrWorkspace := resources[0]
|
||||
appInsightsQuery := appInsightsRegExp.Match([]byte(resourceOrWorkspace))
|
||||
resourcesMap := make(map[string]bool, 0)
|
||||
@@ -236,6 +240,9 @@ func buildAppInsightsQuery(ctx context.Context, query backend.DataQuery, dsInfo
|
||||
|
||||
if query.QueryType == string(dataquery.AzureQueryTypeTraceExemplar) {
|
||||
resources = queryResources
|
||||
if len(resources) == 0 {
|
||||
return nil, fmt.Errorf("no correlation resources found for trace exemplar query with operation ID: %s", operationId)
|
||||
}
|
||||
resourceOrWorkspace = resources[0]
|
||||
}
|
||||
|
||||
|
||||
@@ -1184,3 +1184,118 @@ func TestBuildAppInsightsQuery(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildAppInsightsQuery_EmptyResources(t *testing.T) {
|
||||
fromStart := time.Date(2018, 3, 15, 13, 0, 0, 0, time.UTC).In(time.Local)
|
||||
timeRange := backend.TimeRange{From: fromStart, To: fromStart.Add(34 * time.Minute)}
|
||||
ctx := context.Background()
|
||||
|
||||
// Create a mock HTTP server that returns empty correlation resources
|
||||
svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
// Return empty correlation response
|
||||
correlationRes := AzureCorrelationAPIResponse{
|
||||
ID: "/subscriptions/test-sub/resourceGroups/test-rg/providers/Microsoft.Insights/components/r1",
|
||||
Name: "guid-1",
|
||||
Type: "microsoft.insights/transactions",
|
||||
Properties: AzureCorrelationAPIResponseProperties{
|
||||
Resources: []string{}, // Empty resources array
|
||||
NextLink: nil,
|
||||
},
|
||||
}
|
||||
err := json.NewEncoder(w).Encode(correlationRes)
|
||||
if err != nil {
|
||||
t.Errorf("failed to encode correlation API response")
|
||||
}
|
||||
}))
|
||||
defer svr.Close()
|
||||
|
||||
provider := httpclient.NewProvider(httpclient.ProviderOptions{Timeout: &httpclient.DefaultTimeoutOptions})
|
||||
client, err := provider.New()
|
||||
require.NoError(t, err)
|
||||
|
||||
dsInfo := types.DatasourceInfo{
|
||||
Services: map[string]types.DatasourceService{
|
||||
"Azure Monitor": {URL: svr.URL, HTTPClient: client},
|
||||
},
|
||||
JSONData: map[string]any{
|
||||
"azureLogAnalyticsSameAs": false,
|
||||
},
|
||||
Settings: types.AzureMonitorSettings{
|
||||
SubscriptionId: "test-sub-id",
|
||||
},
|
||||
}
|
||||
appInsightsRegExp, err := regexp.Compile("providers/Microsoft.Insights/components")
|
||||
require.NoError(t, err)
|
||||
|
||||
logger := log.NewNullLogger()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
queryModel backend.DataQuery
|
||||
expectedErrorString string
|
||||
}{
|
||||
{
|
||||
name: "empty resources array should return error",
|
||||
queryModel: backend.DataQuery{
|
||||
JSON: []byte(fmt.Sprintf(`{
|
||||
"queryType": "Azure Traces",
|
||||
"azureTraces": {
|
||||
"resources": [],
|
||||
"resultFormat": "%s",
|
||||
"traceTypes": ["trace"]
|
||||
}
|
||||
}`, dataquery.ResultFormatTable)),
|
||||
RefID: "A",
|
||||
TimeRange: timeRange,
|
||||
QueryType: string(dataquery.AzureQueryTypeAzureTraces),
|
||||
},
|
||||
expectedErrorString: "no resources specified for Azure traces query",
|
||||
},
|
||||
{
|
||||
name: "missing resources field should return error",
|
||||
queryModel: backend.DataQuery{
|
||||
JSON: []byte(fmt.Sprintf(`{
|
||||
"queryType": "Azure Traces",
|
||||
"azureTraces": {
|
||||
"resultFormat": "%s",
|
||||
"traceTypes": ["trace"]
|
||||
}
|
||||
}`, dataquery.ResultFormatTable)),
|
||||
RefID: "A",
|
||||
TimeRange: timeRange,
|
||||
QueryType: string(dataquery.AzureQueryTypeAzureTraces),
|
||||
},
|
||||
expectedErrorString: "no resources specified for Azure traces query",
|
||||
},
|
||||
{
|
||||
name: "trace exemplar with empty correlation resources should return error",
|
||||
queryModel: backend.DataQuery{
|
||||
JSON: []byte(fmt.Sprintf(`{
|
||||
"queryType": "Azure Traces",
|
||||
"azureTraces": {
|
||||
"resources": ["/subscriptions/test-sub/resourceGroups/test-rg/providers/Microsoft.Insights/components/r1"],
|
||||
"resultFormat": "%s",
|
||||
"traceTypes": ["trace"],
|
||||
"operationId": "missing-op-id"
|
||||
}
|
||||
}`, dataquery.ResultFormatTable)),
|
||||
RefID: "A",
|
||||
TimeRange: timeRange,
|
||||
QueryType: string(dataquery.AzureQueryTypeTraceExemplar),
|
||||
},
|
||||
expectedErrorString: "no correlation resources found for trace exemplar query with operation ID: missing-op-id",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
query, err := buildAppInsightsQuery(ctx, tt.queryModel, dsInfo, appInsightsRegExp, logger)
|
||||
|
||||
require.Error(t, err)
|
||||
require.Nil(t, query)
|
||||
require.Contains(t, err.Error(), tt.expectedErrorString)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user