2023-04-12 16:26:26 +00:00
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
//go:build testonly
package vault
import (
"context"
2023-05-23 09:58:51 +00:00
"fmt"
2023-05-25 16:55:55 +00:00
"sync"
"time"
2023-04-12 16:26:26 +00:00
2023-05-23 09:58:51 +00:00
"github.com/hashicorp/go-uuid"
"github.com/hashicorp/vault/helper/namespace"
2023-05-25 16:55:55 +00:00
"github.com/hashicorp/vault/helper/timeutil"
2023-04-12 16:26:26 +00:00
"github.com/hashicorp/vault/sdk/framework"
"github.com/hashicorp/vault/sdk/logical"
2023-05-23 09:58:51 +00:00
"github.com/hashicorp/vault/vault/activity"
2023-04-12 16:26:26 +00:00
"github.com/hashicorp/vault/vault/activity/generation"
"google.golang.org/protobuf/encoding/protojson"
)
const helpText = "Create activity log data for testing purposes"
func ( b * SystemBackend ) activityWritePath ( ) * framework . Path {
return & framework . Path {
Pattern : "internal/counters/activity/write$" ,
HelpDescription : helpText ,
HelpSynopsis : helpText ,
Fields : map [ string ] * framework . FieldSchema {
"input" : {
Type : framework . TypeString ,
Description : "JSON input for generating mock data" ,
} ,
} ,
Operations : map [ logical . Operation ] framework . OperationHandler {
logical . CreateOperation : & framework . PathOperation {
Callback : b . handleActivityWriteData ,
Summary : "Write activity log data" ,
} ,
} ,
}
}
func ( b * SystemBackend ) handleActivityWriteData ( ctx context . Context , request * logical . Request , data * framework . FieldData ) ( * logical . Response , error ) {
json := data . Get ( "input" )
input := & generation . ActivityLogMockInput { }
err := protojson . Unmarshal ( [ ] byte ( json . ( string ) ) , input )
if err != nil {
return logical . ErrorResponse ( "Invalid input data: %s" , err ) , logical . ErrInvalidRequest
}
if len ( input . Write ) == 0 {
return logical . ErrorResponse ( "Missing required \"write\" values" ) , logical . ErrInvalidRequest
}
if len ( input . Data ) == 0 {
return logical . ErrorResponse ( "Missing required \"data\" values" ) , logical . ErrInvalidRequest
}
2023-05-25 16:55:55 +00:00
numMonths := 0
for _ , month := range input . Data {
if int ( month . GetMonthsAgo ( ) ) > numMonths {
numMonths = int ( month . GetMonthsAgo ( ) )
}
}
generated := newMultipleMonthsActivityClients ( numMonths + 1 )
for _ , month := range input . Data {
err := generated . processMonth ( ctx , b . Core , month )
if err != nil {
return logical . ErrorResponse ( "failed to process data for month %d" , month . GetMonthsAgo ( ) ) , err
}
}
opts := make ( map [ generation . WriteOptions ] struct { } , len ( input . Write ) )
for _ , opt := range input . Write {
opts [ opt ] = struct { } { }
}
paths , err := generated . write ( ctx , opts , b . Core . activityLog )
if err != nil {
return logical . ErrorResponse ( "failed to write data" ) , err
}
return & logical . Response {
Data : map [ string ] interface { } {
"paths" : paths ,
} ,
} , nil
2023-04-12 16:26:26 +00:00
}
2023-05-23 09:58:51 +00:00
// singleMonthActivityClients holds a single month's client IDs, in the order they were seen
type singleMonthActivityClients struct {
// clients are indexed by ID
clients [ ] * activity . EntityRecord
2023-05-24 08:42:00 +00:00
// predefinedSegments map from the segment number to the client's index in
// the clients slice
predefinedSegments map [ int ] [ ] int
// generationParameters holds the generation request
generationParameters * generation . Data
2023-05-23 09:58:51 +00:00
}
// multipleMonthsActivityClients holds multiple month's data
type multipleMonthsActivityClients struct {
// months are in order, with month 0 being the current month and index 1 being 1 month ago
months [ ] * singleMonthActivityClients
}
2023-05-24 08:42:00 +00:00
func ( s * singleMonthActivityClients ) addEntityRecord ( record * activity . EntityRecord , segmentIndex * int ) {
s . clients = append ( s . clients , record )
if segmentIndex != nil {
index := len ( s . clients ) - 1
s . predefinedSegments [ * segmentIndex ] = append ( s . predefinedSegments [ * segmentIndex ] , index )
}
}
// populateSegments converts a month of clients into a segmented map. The map's
// keys are the segment index, and the value are the clients that were seen in
// that index. If the value is an empty slice, then it's an empty index. If the
// value is nil, then it's a skipped index
func ( s * singleMonthActivityClients ) populateSegments ( ) ( map [ int ] [ ] * activity . EntityRecord , error ) {
segments := make ( map [ int ] [ ] * activity . EntityRecord )
ignoreIndexes := make ( map [ int ] struct { } )
skipIndexes := s . generationParameters . SkipSegmentIndexes
emptyIndexes := s . generationParameters . EmptySegmentIndexes
for _ , i := range skipIndexes {
segments [ int ( i ) ] = nil
ignoreIndexes [ int ( i ) ] = struct { } { }
}
for _ , i := range emptyIndexes {
segments [ int ( i ) ] = make ( [ ] * activity . EntityRecord , 0 , 0 )
ignoreIndexes [ int ( i ) ] = struct { } { }
}
// if we have predefined segments, then we can construct the map using those
if len ( s . predefinedSegments ) > 0 {
for segment , clientIndexes := range s . predefinedSegments {
clientsInSegment := make ( [ ] * activity . EntityRecord , 0 , len ( clientIndexes ) )
for _ , idx := range clientIndexes {
clientsInSegment = append ( clientsInSegment , s . clients [ idx ] )
}
segments [ segment ] = clientsInSegment
}
return segments , nil
}
totalSegmentCount := 1
if s . generationParameters . GetNumSegments ( ) > 0 {
totalSegmentCount = int ( s . generationParameters . GetNumSegments ( ) )
}
numNonUsable := len ( skipIndexes ) + len ( emptyIndexes )
usableSegmentCount := totalSegmentCount - numNonUsable
if usableSegmentCount <= 0 {
return nil , fmt . Errorf ( "num segments %d is too low, it must be greater than %d (%d skipped indexes + %d empty indexes)" , totalSegmentCount , numNonUsable , len ( skipIndexes ) , len ( emptyIndexes ) )
}
// determine how many clients should be in each segment
segmentSizes := len ( s . clients ) / usableSegmentCount
if len ( s . clients ) % usableSegmentCount != 0 {
segmentSizes ++
}
clientIndex := 0
for i := 0 ; i < totalSegmentCount ; i ++ {
if clientIndex >= len ( s . clients ) {
break
}
if _ , ok := ignoreIndexes [ i ] ; ok {
continue
}
for len ( segments [ i ] ) < segmentSizes && clientIndex < len ( s . clients ) {
segments [ i ] = append ( segments [ i ] , s . clients [ clientIndex ] )
clientIndex ++
}
}
return segments , nil
}
2023-05-23 09:58:51 +00:00
// addNewClients generates clients according to the given parameters, and adds them to the month
// the client will always have the mountAccessor as its mount accessor
2023-05-24 08:42:00 +00:00
func ( s * singleMonthActivityClients ) addNewClients ( c * generation . Client , mountAccessor string , segmentIndex * int ) error {
2023-05-23 09:58:51 +00:00
count := 1
if c . Count > 1 {
count = int ( c . Count )
}
clientType := entityActivityType
if c . NonEntity {
clientType = nonEntityTokenActivityType
}
for i := 0 ; i < count ; i ++ {
record := & activity . EntityRecord {
ClientID : c . Id ,
NamespaceID : c . Namespace ,
NonEntity : c . NonEntity ,
MountAccessor : mountAccessor ,
ClientType : clientType ,
}
if record . ClientID == "" {
var err error
record . ClientID , err = uuid . GenerateUUID ( )
if err != nil {
return err
}
}
2023-05-24 08:42:00 +00:00
s . addEntityRecord ( record , segmentIndex )
2023-05-23 09:58:51 +00:00
}
return nil
}
// processMonth populates a month of client data
func ( m * multipleMonthsActivityClients ) processMonth ( ctx context . Context , core * Core , month * generation . Data ) error {
// default to using the root namespace and the first mount on the root namespace
mounts , err := core . ListMounts ( )
if err != nil {
return err
}
defaultMountAccessorRootNS := ""
for _ , mount := range mounts {
if mount . NamespaceID == namespace . RootNamespaceID {
defaultMountAccessorRootNS = mount . Accessor
break
}
}
2023-05-24 08:42:00 +00:00
m . months [ month . GetMonthsAgo ( ) ] . generationParameters = month
add := func ( c [ ] * generation . Client , segmentIndex * int ) error {
for _ , clients := range c {
2023-05-23 09:58:51 +00:00
2023-05-24 08:42:00 +00:00
if clients . Namespace == "" {
clients . Namespace = namespace . RootNamespaceID
}
2023-05-23 09:58:51 +00:00
2023-05-24 08:42:00 +00:00
// verify that the namespace exists
ns , err := core . NamespaceByID ( ctx , clients . Namespace )
if err != nil {
return err
}
2023-05-23 09:58:51 +00:00
2023-05-24 08:42:00 +00:00
// verify that the mount exists
if clients . Mount != "" {
nctx := namespace . ContextWithNamespace ( ctx , ns )
mountEntry := core . router . MatchingMountEntry ( nctx , clients . Mount )
if mountEntry == nil {
return fmt . Errorf ( "unable to find matching mount in namespace %s" , clients . Namespace )
}
2023-05-23 09:58:51 +00:00
}
2023-05-24 08:42:00 +00:00
mountAccessor := defaultMountAccessorRootNS
if clients . Namespace != namespace . RootNamespaceID && clients . Mount == "" {
// if we're not using the root namespace, find a mount on the namespace that we are using
found := false
for _ , mount := range mounts {
if mount . NamespaceID == clients . Namespace {
mountAccessor = mount . Accessor
found = true
break
}
}
if ! found {
return fmt . Errorf ( "unable to find matching mount in namespace %s" , clients . Namespace )
2023-05-23 09:58:51 +00:00
}
}
2023-05-24 08:42:00 +00:00
err = m . addClientToMonth ( month . GetMonthsAgo ( ) , clients , mountAccessor , segmentIndex )
if err != nil {
return err
2023-05-23 09:58:51 +00:00
}
}
2023-05-24 08:42:00 +00:00
return nil
}
if month . GetAll ( ) != nil {
return add ( month . GetAll ( ) . GetClients ( ) , nil )
}
predefinedSegments := month . GetSegments ( )
for i , segment := range predefinedSegments . GetSegments ( ) {
index := i
if segment . SegmentIndex != nil {
index = int ( * segment . SegmentIndex )
}
err = add ( segment . GetClients ( ) . GetClients ( ) , & index )
2023-05-23 09:58:51 +00:00
if err != nil {
return err
}
}
2023-05-24 08:42:00 +00:00
return nil
}
func ( m * multipleMonthsActivityClients ) addClientToMonth ( monthsAgo int32 , c * generation . Client , mountAccessor string , segmentIndex * int ) error {
if c . Repeated || c . RepeatedFromMonth > 0 {
return m . addRepeatedClients ( monthsAgo , c , mountAccessor , segmentIndex )
}
return m . months [ monthsAgo ] . addNewClients ( c , mountAccessor , segmentIndex )
}
func ( m * multipleMonthsActivityClients ) addRepeatedClients ( monthsAgo int32 , c * generation . Client , mountAccessor string , segmentIndex * int ) error {
addingTo := m . months [ monthsAgo ]
repeatedFromMonth := monthsAgo + 1
if c . RepeatedFromMonth > 0 {
repeatedFromMonth = c . RepeatedFromMonth
}
repeatedFrom := m . months [ repeatedFromMonth ]
numClients := 1
if c . Count > 0 {
numClients = int ( c . Count )
}
for _ , client := range repeatedFrom . clients {
if c . NonEntity == client . NonEntity && mountAccessor == client . MountAccessor && c . Namespace == client . NamespaceID {
addingTo . addEntityRecord ( client , segmentIndex )
numClients --
if numClients == 0 {
break
}
}
}
if numClients > 0 {
return fmt . Errorf ( "missing repeated %d clients matching given parameters" , numClients )
}
2023-05-23 09:58:51 +00:00
return nil
}
2023-05-25 16:55:55 +00:00
func ( m * multipleMonthsActivityClients ) write ( ctx context . Context , opts map [ generation . WriteOptions ] struct { } , activityLog * ActivityLog ) ( [ ] string , error ) {
now := timeutil . StartOfMonth ( time . Now ( ) . UTC ( ) )
paths := [ ] string { }
for i , month := range m . months {
var timestamp time . Time
if i > 0 {
timestamp = timeutil . StartOfMonth ( timeutil . MonthsPreviousTo ( i , now ) )
} else {
timestamp = now
}
segments , err := month . populateSegments ( )
if err != nil {
return nil , err
}
for segmentIndex , segment := range segments {
if _ , ok := opts [ generation . WriteOptions_WRITE_ENTITIES ] ; ok {
if segment == nil {
// skip the index
continue
}
entityPath , err := activityLog . saveSegmentEntitiesInternal ( ctx , segmentInfo {
startTimestamp : timestamp . Unix ( ) ,
currentClients : & activity . EntityActivityLog { Clients : segment } ,
clientSequenceNumber : uint64 ( segmentIndex ) ,
tokenCount : & activity . TokenCount { } ,
} , true )
if err != nil {
return nil , err
}
paths = append ( paths , entityPath )
}
}
}
wg := sync . WaitGroup { }
err := activityLog . refreshFromStoredLog ( ctx , & wg , now )
if err != nil {
return nil , err
}
return paths , nil
}
2023-05-23 09:58:51 +00:00
func newMultipleMonthsActivityClients ( numberOfMonths int ) * multipleMonthsActivityClients {
m := & multipleMonthsActivityClients {
months : make ( [ ] * singleMonthActivityClients , numberOfMonths ) ,
}
for i := 0 ; i < numberOfMonths ; i ++ {
2023-05-24 08:42:00 +00:00
m . months [ i ] = & singleMonthActivityClients {
predefinedSegments : make ( map [ int ] [ ] int ) ,
}
2023-05-23 09:58:51 +00:00
}
return m
}