detective

package
v1.3.5 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 19, 2026 License: MIT Imports: 23 Imported by: 0

README

Detective

Parity grade: A · SDK aws-sdk-go-v2/service/detective@v1.41.4 · last audited 2026-08-10 (40f059288a40c1d9b7956624bb288861e2e0651d)

Coverage

Metric Value
Operations audited 29 (28 ok, 1 partial)
Feature families 2 (2 ok)
Known gaps 2
Deferred items 2
Resource leaks clean
Known gaps
  • StartMonitoringMember's precondition (member status ACCEPTED_BUT_DISABLED) is unreachable through normal API flow: AcceptInvitation transitions INVITED straight to ENABLED, mirroring the AWS happy path, but real Detective can also land a member in ACCEPTED_BUT_DISABLED (data-volume-too-high / volume-unknown edge cases per MemberDisabledReason) which this emulator does not model. Not fixed this pass: real AWS determines this state via internal GuardDuty volume telemetry with no documented client-controllable trigger, so modeling a way to reach it would mean inventing a control surface that does not exist in the real API rather than emulating one -- a larger, speculative feature, not a wire/state bug fix. Re-verified gopherstack-c902: ACCEPTED_BUT_DISABLED IS present in the pinned SDK's MemberStatus enum (types/enums.go, aws-sdk-go-v2/service/detective@v1.41.4, line 186), so this is not a wire gap -- the value exists in the model, it is just unreachable through any legitimate client action. Also re-verified the precondition itself is NOT missing: administrator.go's StartMonitoringMember already rejects any member whose status isn't ACCEPTED_BUT_DISABLED with ValidationException (see TestDetective_StartMonitoringMember's "member not ACCEPTED_BUT_DISABLED returns 400" case), so there was nothing left to fix here.
  • MemberDetail still omits DisabledReason, VolumeUsageInBytes (deprecated), VolumeUsageUpdatedTime (deprecated), PercentOfGraphUtilization (deprecated), PercentOfGraphUtilizationUpdatedTime (deprecated), and VolumeUsageByDatasourcePackage. InvitationType and DatasourcePackageIngestStates were fixed this pass (see CreateMembers/GetMembers/ListMembers/ListInvitations notes). The remaining fields are volume/analytics telemetry this emulator does not model (no real data-ingest pipeline), and DisabledReason has no valid state to populate since ACCEPTED_BUT_DISABLED is unreachable (see the StartMonitoringMember gap above) -- all are optional fields real clients already treat as absent-safe, so omitting them is wire-legal, just incomplete. Low priority. Re-verified gopherstack-c902: DisabledReason and the volume metrics were deliberately split per the follow-up issue's instruction -- but the split does not change the verdict here. DisabledReason would be trivially serialisable IF the backend ever transitioned a member into ACCEPTED_BUT_DISABLED (storedMember already has a Status field to key off of), but grep confirms nothing in this codebase ever assigns memberStatusAcceptedDisabled to a member -- StartMonitoringMember only reads it as a precondition, never writes it. So there is no disabled-state instance anywhere in the backend for DisabledReason to be derived from; inventing a value would mean fabricating data with no backing state, which is worse than omitting the field. VolumeUsage*/PercentOfGraphUtilization genuinely need ingest-volume telemetry this emulator has no model for -- left absent rather than invented, matching this campaign's 'absent beats plausible-but-wrong' rule.
Deferred
  • Detective Organizations edge cases beyond the base Enable/Disable/List/Describe/Update surface (delegated-admin-account transfer, cross-region graph semantics) — out of scope for a single-region single-account emulator.
  • UpdateOrganizationConfiguration's AutoEnable flag has no side effect: real AWS auto-enables Detective for new Organizations member accounts as they join the org. This emulator has no Organizations-service integration to source account-join events from, so AutoEnable is stored and returned correctly (DescribeOrganizationConfiguration) but never drives member auto-creation. Out of scope for a single-account emulator with no cross-service org simulation. Re-verified gopherstack-c902: services/organizations exists and other services (grafana, mgn) do reach it via a siblingServices/GetOrganizationsHandler cross_service.go pattern -- but only for synchronous reads (DescribeOrganizationalUnit, DescribeOrganization, ListDelegatedAdministrators, ListAccounts), never as an event source. Checked every other gopherstack service that models an AutoEnable-shaped org config (guardduty, inspector2, macie2, securityhub, all found via grep -rl AutoEnable services): every one of them stores and echoes AutoEnable identically, with zero side effect -- none has solved 'new account joins org' as a trigger. services/organizations' AcceptHandshake/InviteAccountToOrganization add an account to the org's own account list but publish no event or callback any sibling service subscribes to. This is a genuine cross-cutting gap (not a stale 'already solved elsewhere' claim like the codedeploy/EC2 case) -- AutoEnable is stored-and-echoed with no trigger to hook into anywhere in this codebase, which is the honest half of the stored-vs-ignored distinction, not the negligent half.

More

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrGraphNotFound is returned when a behavior graph does not exist.
	ErrGraphNotFound = awserr.New(errResourceNotFound, awserr.ErrNotFound)
	// ErrAlreadyHasGraph is returned when account already has a graph in region.
	ErrAlreadyHasGraph = awserr.New(errConflictException, awserr.ErrAlreadyExists)
	// ErrMemberNotFound is returned when a member does not exist.
	ErrMemberNotFound = awserr.New(errResourceNotFound, awserr.ErrNotFound)
	// ErrValidation is returned on invalid input.
	ErrValidation = awserr.New(errValidation, awserr.ErrInvalidParameter)
)
View Source
var ErrNilAppContext = errors.New("detective: nil app context")

ErrNilAppContext is returned when Init is called with a nil AppContext.

Functions

This section is empty.

Types

type Account

type Account struct {
	AccountID    string
	EmailAddress string
}

Account is an AWS account for member operations.

type DatasourcePackageIngestDetail

type DatasourcePackageIngestDetail struct {
	IngestState string
}

DatasourcePackageIngestDetail holds the ingest state for a datasource package.

type FlaggedIPAddressDetail added in v1.2.0

type FlaggedIPAddressDetail struct {
	IPAddress string
	Reason    string
}

FlaggedIPAddressDetail describes a threat-intelligence-flagged IP address.

type Graph

type Graph struct {
	CreatedTime time.Time
	Tags        map[string]string
	Arn         string
}

Graph represents a Detective behavior graph. CreatedTime is first so its non-pointer prefix (wall, ext) reduces GC pointer bytes.

type Handler

type Handler struct {
	Backend StorageBackend
	// contains filtered or unexported fields
}

Handler handles Detective HTTP requests.

func NewHandler

func NewHandler(b StorageBackend) *Handler

NewHandler constructs a new Handler.

func (*Handler) ExtractOperation

func (h *Handler) ExtractOperation(c *echo.Context) string

ExtractOperation extracts the operation name from the request.

func (*Handler) ExtractResource

func (h *Handler) ExtractResource(c *echo.Context) string

ExtractResource extracts the resource identifier from the request.

func (*Handler) GetSupportedOperations

func (h *Handler) GetSupportedOperations() []string

GetSupportedOperations returns the list of supported operations.

func (*Handler) Handler

func (h *Handler) Handler() echo.HandlerFunc

Handler returns the Echo handler function.

func (*Handler) MatchPriority

func (h *Handler) MatchPriority() int

MatchPriority returns the routing priority.

func (*Handler) Name

func (h *Handler) Name() string

Name returns the service name.

func (*Handler) Reset

func (h *Handler) Reset()

Reset resets the backend.

func (*Handler) Restore

func (h *Handler) Restore(ctx context.Context, data []byte) error

Restore implements persistence.Persistable by delegating to the backend.

func (*Handler) RouteMatcher

func (h *Handler) RouteMatcher() service.Matcher

RouteMatcher returns a matcher that accepts Detective REST paths.

func (*Handler) Snapshot

func (h *Handler) Snapshot(ctx context.Context) []byte

Snapshot implements persistence.Persistable by delegating to the backend. Without this, cli.go's generic setupPersistence (which type-asserts the registered service.Registerable, i.e. *Handler, for Snapshot/Restore) never finds a persistable Detective service even though InMemoryBackend itself has always implemented Snapshot/Restore -- the backend methods were dead wiring until Handler delegated to them.

type ImpossibleTravelDetail added in v1.2.0

type ImpossibleTravelDetail struct {
	StartingIPAddress string
	StartingLocation  string
	EndingIPAddress   string
	EndingLocation    string
	HourlyTimeDelta   int32
}

ImpossibleTravelDetail describes geographically impossible activity.

type InMemoryBackend

type InMemoryBackend struct {
	// contains filtered or unexported fields
}

InMemoryBackend implements StorageBackend using in-memory maps.

graphs, members, and investigations are store.Table-backed (see store_setup.go); tags, datasources, and orgConfigs remain plain maps since their values are not *T; orgAdmins remains a plain order-sensitive slice.

func NewInMemoryBackend

func NewInMemoryBackend(accountID, region string) *InMemoryBackend

NewInMemoryBackend constructs a new InMemoryBackend.

func (*InMemoryBackend) AcceptInvitation

func (b *InMemoryBackend) AcceptInvitation(graphARN string) error

AcceptInvitation accepts a graph invitation on behalf of the member account.

func (*InMemoryBackend) AccountID

func (b *InMemoryBackend) AccountID() string

AccountID returns the account ID.

func (*InMemoryBackend) BatchGetGraphMemberDatasources

func (b *InMemoryBackend) BatchGetGraphMemberDatasources(
	graphARN string,
	accountIDs []string,
) ([]MembershipDatasources, []UnprocessedAccount, error)

BatchGetGraphMemberDatasources returns datasource package info for member accounts of a graph.

func (*InMemoryBackend) BatchGetMembershipDatasources

func (b *InMemoryBackend) BatchGetMembershipDatasources(
	graphARNs []string,
) ([]MembershipDatasources, []UnprocessedGraph, error)

BatchGetMembershipDatasources returns datasource history for the account across graphs.

func (*InMemoryBackend) CreateGraph

func (b *InMemoryBackend) CreateGraph(tags map[string]string) (*Graph, error)

CreateGraph creates a new behavior graph. Returns existing one if already created (idempotent).

func (*InMemoryBackend) CreateMembers

func (b *InMemoryBackend) CreateMembers(
	graphARN string,
	accounts []Account,
	_ string,
) ([]*MemberDetail, []UnprocessedAccount, error)

CreateMembers creates or invites member accounts to a behavior graph.

func (*InMemoryBackend) DeleteGraph

func (b *InMemoryBackend) DeleteGraph(graphARN string) error

DeleteGraph deletes a behavior graph.

func (*InMemoryBackend) DeleteMembers

func (b *InMemoryBackend) DeleteMembers(
	graphARN string,
	accountIDs []string,
) ([]string, []UnprocessedAccount, error)

DeleteMembers removes member accounts from a behavior graph.

func (*InMemoryBackend) DescribeOrganizationConfiguration

func (b *InMemoryBackend) DescribeOrganizationConfiguration(graphARN string) (bool, error)

DescribeOrganizationConfiguration returns AutoEnable setting for a graph.

func (*InMemoryBackend) DisableOrganizationAdminAccount

func (b *InMemoryBackend) DisableOrganizationAdminAccount() error

DisableOrganizationAdminAccount removes the Detective administrator account. AWS docs: "Removes the Detective administrator account in the current Region. Deletes the organization behavior graph." -- so every graph referenced by the current org admin(s) is deleted along with its dependent state (members, investigations, tags, datasources), not just the orgAdmins record.

func (*InMemoryBackend) DisassociateMembership

func (b *InMemoryBackend) DisassociateMembership(graphARN string) error

DisassociateMembership removes the calling account from a graph it belongs to.

func (*InMemoryBackend) EnableOrganizationAdminAccount

func (b *InMemoryBackend) EnableOrganizationAdminAccount(accountID string) error

EnableOrganizationAdminAccount designates a Detective administrator account.

func (*InMemoryBackend) GetInvestigation

func (b *InMemoryBackend) GetInvestigation(graphARN, investigationID string) (*Investigation, error)

GetInvestigation returns an investigation by graph ARN and investigation ID.

func (*InMemoryBackend) GetMembers

func (b *InMemoryBackend) GetMembers(
	graphARN string,
	accountIDs []string,
) ([]*MemberDetail, []UnprocessedAccount, error)

GetMembers returns member details for the given account IDs.

func (*InMemoryBackend) ListDatasourcePackages

func (b *InMemoryBackend) ListDatasourcePackages(
	graphARN string,
	maxResults int32,
	nextToken string,
) (map[string]DatasourcePackageIngestDetail, string, error)

ListDatasourcePackages returns datasource package ingest details for a graph.

func (*InMemoryBackend) ListGraphs

func (b *InMemoryBackend) ListGraphs(maxResults int32, nextToken string) ([]*Graph, string, error)

ListGraphs returns behavior graphs for the admin account.

func (*InMemoryBackend) ListIndicators

func (b *InMemoryBackend) ListIndicators(
	graphARN, investigationID, indicatorType string,
	maxResults int32,
	nextToken string,
) ([]*Indicator, string, error)

ListIndicators returns indicators for an investigation.

func (*InMemoryBackend) ListInvestigations

func (b *InMemoryBackend) ListInvestigations(
	graphARN string,
	maxResults int32,
	nextToken string,
) ([]*InvestigationDetail, string, error)

ListInvestigations returns investigations for a graph.

func (*InMemoryBackend) ListInvitations

func (b *InMemoryBackend) ListInvitations(maxResults int32, nextToken string) ([]*MemberDetail, string, error)

ListInvitations returns graphs where this account has an open or accepted invitation.

func (*InMemoryBackend) ListMembers

func (b *InMemoryBackend) ListMembers(
	graphARN string,
	maxResults int32,
	nextToken string,
) ([]*MemberDetail, string, error)

ListMembers returns member accounts for a behavior graph.

func (*InMemoryBackend) ListOrganizationAdminAccounts

func (b *InMemoryBackend) ListOrganizationAdminAccounts(
	maxResults int32,
	nextToken string,
) ([]*OrgAdmin, string, error)

ListOrganizationAdminAccounts returns Detective organization administrator accounts.

func (*InMemoryBackend) ListTagsForResource

func (b *InMemoryBackend) ListTagsForResource(resourceARN string) (map[string]string, error)

ListTagsForResource returns tags for a resource.

func (*InMemoryBackend) Region

func (b *InMemoryBackend) Region() string

Region returns the region.

func (*InMemoryBackend) RejectInvitation

func (b *InMemoryBackend) RejectInvitation(graphARN string) error

RejectInvitation rejects a graph invitation on behalf of the member account.

func (*InMemoryBackend) Reset

func (b *InMemoryBackend) Reset()

Reset clears all state.

func (*InMemoryBackend) Restore

func (b *InMemoryBackend) Restore(ctx context.Context, data []byte) error

Restore deserializes backend state from a snapshot.

func (*InMemoryBackend) Snapshot

func (b *InMemoryBackend) Snapshot(ctx context.Context) []byte

Snapshot serializes the backend state to JSON.

func (*InMemoryBackend) StartInvestigation

func (b *InMemoryBackend) StartInvestigation(
	graphARN, entityARN string,
	scopeStart, scopeEnd time.Time,
) (string, error)

StartInvestigation creates a new investigation for an entity within a graph.

func (*InMemoryBackend) StartMonitoringMember

func (b *InMemoryBackend) StartMonitoringMember(graphARN, accountID string) error

StartMonitoringMember enables monitoring for a member in ACCEPTED_BUT_DISABLED state.

func (*InMemoryBackend) TagResource

func (b *InMemoryBackend) TagResource(resourceARN string, tags map[string]string) error

TagResource adds or updates tags on a resource.

func (*InMemoryBackend) TaggedResources added in v1.3.1

func (b *InMemoryBackend) TaggedResources() []TaggedEntry

TaggedResources returns every Detective graph ARN that currently has at least one tag applied via TagResource.

func (*InMemoryBackend) UntagResource

func (b *InMemoryBackend) UntagResource(resourceARN string, tagKeys []string) error

UntagResource removes tags from a resource.

func (*InMemoryBackend) UpdateDatasourcePackages

func (b *InMemoryBackend) UpdateDatasourcePackages(graphARN string, packages []string) error

UpdateDatasourcePackages enables datasource packages on a graph.

func (*InMemoryBackend) UpdateInvestigationState

func (b *InMemoryBackend) UpdateInvestigationState(graphARN, investigationID, state string) error

UpdateInvestigationState updates the state of an investigation.

func (*InMemoryBackend) UpdateOrganizationConfiguration

func (b *InMemoryBackend) UpdateOrganizationConfiguration(graphARN string, autoEnable bool) error

UpdateOrganizationConfiguration sets the AutoEnable flag for a graph.

type Indicator

type Indicator struct {
	Detail        IndicatorDetail
	IndicatorType string
}

Indicator is a compromise indicator within an investigation. The real IndicatorDetail shape has no "Title" member; it is a union-like struct with one type-specific sub-detail populated per IndicatorType.

type IndicatorDetail added in v1.2.0

type IndicatorDetail struct {
	FlaggedIPAddress    *FlaggedIPAddressDetail
	ImpossibleTravel    *ImpossibleTravelDetail
	NewASO              *NewASODetail
	NewGeolocation      *NewGeolocationDetail
	NewUserAgent        *NewUserAgentDetail
	RelatedFinding      *RelatedFindingDetail
	RelatedFindingGroup *RelatedFindingGroupDetail
	TTPsObserved        *TTPsObservedDetail
}

IndicatorDetail holds type-specific detail for a compromise indicator. Exactly one field is populated, selected by the sibling IndicatorType, mirroring the real (union-like) aws-sdk-go-v2 IndicatorDetail shape.

type Investigation

type Investigation struct {
	CreatedTime     time.Time
	ScopeStartTime  time.Time
	ScopeEndTime    time.Time
	GraphARN        string
	InvestigationID string
	EntityARN       string
	EntityType      string
	Severity        string
	State           string
	Status          string
}

Investigation holds investigation report data. time.Time fields are first so their non-pointer prefix reduces GC pointer bytes.

type InvestigationDetail

type InvestigationDetail struct {
	CreatedTime     time.Time
	EntityARN       string
	EntityType      string
	InvestigationID string
	Severity        string
	State           string
	Status          string
}

InvestigationDetail is a summary of an investigation used in list responses. time.Time is first so its non-pointer prefix reduces GC pointer bytes.

type MemberDetail

type MemberDetail struct {
	InvitedTime                   time.Time
	UpdatedTime                   time.Time
	DatasourcePackageIngestStates map[string]string
	AccountID                     string
	AdministratorID               string
	EmailAddress                  string
	GraphARN                      string
	InvitationType                string
	Status                        string
}

MemberDetail is the detail of a behavior graph member. time.Time fields are first so their non-pointer prefix reduces GC pointer bytes.

type MembershipDatasources

type MembershipDatasources struct {
	IngestHistory map[string]map[string]time.Time
	AccountID     string
	GraphARN      string
}

MembershipDatasources holds datasource package info for a member or graph. IngestHistory mirrors the real SDK's DatasourcePackageIngestHistory: package name -> ingest state -> the time that state began (aws-sdk-go-v2's map[string]map[string]TimestampForCollection), not a flat current-state map.

type NewASODetail added in v1.2.0

type NewASODetail struct {
	ASO                   string
	IsNewForEntireAccount bool
}

NewASODetail describes a newly observed Autonomous System Organization.

type NewGeolocationDetail added in v1.2.0

type NewGeolocationDetail struct {
	IPAddress             string
	Location              string
	IsNewForEntireAccount bool
}

NewGeolocationDetail describes a newly observed access geolocation.

type NewUserAgentDetail added in v1.2.0

type NewUserAgentDetail struct {
	UserAgent             string
	IsNewForEntireAccount bool
}

NewUserAgentDetail describes a newly observed client user agent.

type OrgAdmin

type OrgAdmin struct {
	DelegationTime time.Time
	AccountID      string
	GraphARN       string
}

OrgAdmin represents a Detective organization administrator account. DelegationTime is first so its non-pointer prefix reduces GC pointer bytes.

type Provider

type Provider struct{}

Provider implements service.Provider for Amazon Detective.

func (*Provider) Init

Init initializes the Detective service backend and handler.

func (*Provider) Name

func (p *Provider) Name() string

Name returns the provider name.

type RelatedFindingDetail added in v1.2.0

type RelatedFindingDetail struct {
	Arn       string
	Type      string
	IPAddress string
}

RelatedFindingDetail describes a GuardDuty finding related to the entity.

type RelatedFindingGroupDetail added in v1.2.0

type RelatedFindingGroupDetail struct {
	ID string
}

RelatedFindingGroupDetail describes a cluster of related findings.

type StorageBackend

type StorageBackend interface {
	CreateGraph(tags map[string]string) (*Graph, error)
	DeleteGraph(graphARN string) error
	ListGraphs(maxResults int32, nextToken string) ([]*Graph, string, error)

	CreateMembers(graphARN string, accounts []Account, message string) ([]*MemberDetail, []UnprocessedAccount, error)
	DeleteMembers(graphARN string, accountIDs []string) ([]string, []UnprocessedAccount, error)
	GetMembers(graphARN string, accountIDs []string) ([]*MemberDetail, []UnprocessedAccount, error)
	ListMembers(graphARN string, maxResults int32, nextToken string) ([]*MemberDetail, string, error)

	TagResource(resourceARN string, tags map[string]string) error
	UntagResource(resourceARN string, tagKeys []string) error
	ListTagsForResource(resourceARN string) (map[string]string, error)

	AcceptInvitation(graphARN string) error
	RejectInvitation(graphARN string) error
	DisassociateMembership(graphARN string) error
	ListInvitations(maxResults int32, nextToken string) ([]*MemberDetail, string, error)

	StartInvestigation(graphARN, entityARN string, scopeStart, scopeEnd time.Time) (string, error)
	GetInvestigation(graphARN, investigationID string) (*Investigation, error)
	ListInvestigations(graphARN string, maxResults int32, nextToken string) ([]*InvestigationDetail, string, error)
	UpdateInvestigationState(graphARN, investigationID, state string) error
	ListIndicators(
		graphARN, investigationID, indicatorType string,
		maxResults int32,
		nextToken string,
	) ([]*Indicator, string, error)

	ListDatasourcePackages(
		graphARN string,
		maxResults int32,
		nextToken string,
	) (map[string]DatasourcePackageIngestDetail, string, error)
	UpdateDatasourcePackages(graphARN string, packages []string) error
	BatchGetGraphMemberDatasources(
		graphARN string,
		accountIDs []string,
	) ([]MembershipDatasources, []UnprocessedAccount, error)
	BatchGetMembershipDatasources(graphARNs []string) ([]MembershipDatasources, []UnprocessedGraph, error)

	EnableOrganizationAdminAccount(accountID string) error
	DisableOrganizationAdminAccount() error
	ListOrganizationAdminAccounts(maxResults int32, nextToken string) ([]*OrgAdmin, string, error)
	DescribeOrganizationConfiguration(graphARN string) (bool, error)
	UpdateOrganizationConfiguration(graphARN string, autoEnable bool) error

	StartMonitoringMember(graphARN, accountID string) error

	AccountID() string
	Region() string
	Reset()
	Snapshot(ctx context.Context) []byte
	Restore(ctx context.Context, data []byte) error
}

StorageBackend is the interface for Detective storage operations.

type TTPsObservedDetail added in v1.2.0

type TTPsObservedDetail struct {
	Tactic          string
	Procedure       string
	APIName         string
	IPAddress       string
	APISuccessCount int64
	APIFailureCount int64
}

TTPsObservedDetail describes an observed tactic/technique/procedure.

type TaggedEntry added in v1.3.1

type TaggedEntry struct {
	Tags map[string]string
	ARN  string
}

TaggedEntry pairs a resource ARN with its tag map, for cross-service tag enumeration by the Resource Groups Tagging API (see cli.go's wireTaggingDetective).

type UnprocessedAccount

type UnprocessedAccount struct {
	AccountID string
	Reason    string
}

UnprocessedAccount is an account that could not be processed.

type UnprocessedGraph

type UnprocessedGraph struct {
	GraphArn string
	Reason   string
}

UnprocessedGraph is a graph that could not be processed.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL