diff --git a/changelog/28042.txt b/changelog/28042.txt new file mode 100644 index 000000000000..f49cad8ac597 --- /dev/null +++ b/changelog/28042.txt @@ -0,0 +1,3 @@ +```release-note:bug +activity: The sys/internal/counters/activity endpoint will return current month data when the end_date parameter is set to a future date. +``` diff --git a/vault/activity_log.go b/vault/activity_log.go index b063e853b014..a5cb76ffa92a 100644 --- a/vault/activity_log.go +++ b/vault/activity_log.go @@ -1769,12 +1769,20 @@ func (a *ActivityLog) handleQuery(ctx context.Context, startTime, endTime time.T startTime = timeutil.StartOfMonth(startTime) endTime = timeutil.EndOfMonth(endTime) + // At the max, we only want to return data up until the end of the current month. + // Adjust the end time be the current month if a future date has been provided. + endOfCurrentMonth := timeutil.EndOfMonth(a.clock.Now().UTC()) + adjustedEndTime := endTime + if endTime.After(endOfCurrentMonth) { + adjustedEndTime = endOfCurrentMonth + } + // If the endTime of the query is the current month, request data from the queryStore // with the endTime equal to the end of the last month, and add in the current month // data. - precomputedQueryEndTime := endTime - if timeutil.IsCurrentMonth(endTime, a.clock.Now().UTC()) { - precomputedQueryEndTime = timeutil.EndOfMonth(timeutil.MonthsPreviousTo(1, timeutil.StartOfMonth(endTime))) + precomputedQueryEndTime := adjustedEndTime + if timeutil.IsCurrentMonth(adjustedEndTime, a.clock.Now().UTC()) { + precomputedQueryEndTime = timeutil.EndOfMonth(timeutil.MonthsPreviousTo(1, timeutil.StartOfMonth(adjustedEndTime))) computePartial = true } @@ -1817,7 +1825,7 @@ func (a *ActivityLog) handleQuery(ctx context.Context, startTime, endTime time.T // Estimate the current month totals. These record contains is complete with all the // current month data, grouped by namespace and mounts - currentMonth, err := a.computeCurrentMonthForBillingPeriod(ctx, partialByMonth, startTime, endTime) + currentMonth, err := a.computeCurrentMonthForBillingPeriod(ctx, partialByMonth, startTime, adjustedEndTime) if err != nil { return nil, err } @@ -1867,7 +1875,7 @@ func (a *ActivityLog) handleQuery(ctx context.Context, startTime, endTime time.T a.sortActivityLogMonthsResponse(months) // Modify the final month output to make response more consumable based on API request - months = a.modifyResponseMonths(months, startTime, endTime) + months = a.modifyResponseMonths(months, startTime, adjustedEndTime) responseData["months"] = months return responseData, nil diff --git a/vault/external_tests/activity_testonly/activity_testonly_test.go b/vault/external_tests/activity_testonly/activity_testonly_test.go index 251be638a0f1..29a8675d3a6b 100644 --- a/vault/external_tests/activity_testonly/activity_testonly_test.go +++ b/vault/external_tests/activity_testonly/activity_testonly_test.go @@ -227,6 +227,45 @@ func Test_ActivityLog_EmptyDataMonths(t *testing.T) { } } +// Test_ActivityLog_FutureEndDate queries a start time from the past +// and an end date in the future. The test +// verifies that the current month is returned in the response. +func Test_ActivityLog_FutureEndDate(t *testing.T) { + t.Parallel() + cluster := minimal.NewTestSoloCluster(t, nil) + client := cluster.Cores[0].Client + _, err := client.Logical().Write("sys/internal/counters/config", map[string]interface{}{ + "enabled": "enable", + }) + require.NoError(t, err) + _, err = clientcountutil.NewActivityLogData(client). + NewPreviousMonthData(1). + NewClientsSeen(10). + NewCurrentMonthData(). + NewClientsSeen(10). + Write(context.Background(), generation.WriteOptions_WRITE_PRECOMPUTED_QUERIES, generation.WriteOptions_WRITE_ENTITIES) + require.NoError(t, err) + + now := time.Now().UTC() + // query from the beginning of 3 months ago to beginning of next month + resp, err := client.Logical().ReadWithData("sys/internal/counters/activity", map[string][]string{ + "end_time": {timeutil.StartOfNextMonth(now).Format(time.RFC3339)}, + "start_time": {timeutil.StartOfMonth(timeutil.MonthsPreviousTo(3, now)).Format(time.RFC3339)}, + }) + require.NoError(t, err) + monthsResponse := getMonthsData(t, resp) + + require.Len(t, monthsResponse, 4) + + // Get the last month of data in the slice + expectedCurrentMonthData := monthsResponse[3] + expectedTime, err := time.Parse(time.RFC3339, expectedCurrentMonthData.Timestamp) + require.NoError(t, err) + if !timeutil.IsCurrentMonth(expectedTime, now) { + t.Fatalf("final month data is not current month") + } +} + func getMonthsData(t *testing.T, resp *api.Secret) []vault.ResponseMonth { t.Helper() monthsRaw, ok := resp.Data["months"]