Go audit query client¶
go get github.com/truvity/sluis/audit/[email protected]
auditv1connect.QueryServiceClient reads audit.v1.QueryService. It ships in the same module as the emitter. This page follows examples/read. The API is on pkg.go.dev. Access rules are in read the trail and the wire contract in the API reference.
Make a client¶
import (
"connectrpc.com/connect/v2"
"connectrpc.com/connect/v2/connecthttp"
auditv1 "github.com/truvity/sluis/audit/sdk/gen/audit/v1"
"github.com/truvity/sluis/audit/sdk/gen/audit/v1/auditv1connect"
)
client := auditv1connect.NewQueryServiceClient(connect.NewClient(
connecthttp.NewTransport(httpClient, "https://audit-query.example.com")))
Authentication is yours. Wrap the transport and set Authorization: Bearer <token> on every request. A program gets the token by exchange. The service ANDs the caller's grants into every query.
type bearer struct{ token string }
func (b bearer) RoundTrip(r *http.Request) (*http.Response, error) {
r = r.Clone(r.Context())
r.Header.Set("Authorization", "Bearer "+b.token)
return http.DefaultTransport.RoundTrip(r)
}
A filtered query¶
A SearchRequest names a profile and a filter. The filter is an OR-joined list of conjunctions of typed predicates. There is no free text. This one finds failed and denied actions of the last day under one prefix:
query := &auditv1.SearchRequest{
Profile: "security",
Filter: []*auditv1.Filter{{
OccurredAt: &auditv1.TimePredicate{Operator: &auditv1.TimePredicate_Between{Between: &auditv1.TimeRange{
From: timestamppb.New(time.Now().Add(-24 * time.Hour)),
To: timestamppb.Now(),
}}},
Action: &auditv1.StringPredicate{Operator: &auditv1.StringPredicate_Prefix{Prefix: "shop.order."}},
Outcome: &auditv1.StringPredicate{Operator: &auditv1.StringPredicate_In{In: &auditv1.StringList{Values: []string{"failure", "denied"}}}},
}},
Limit: 100,
}
page, err := client.Search(ctx, query)
Records in page.GetItems() have typed getters such as GetOccurredAt() and GetActor().GetId().
Paging¶
Set the page cursor on the next request until a page comes back short or without one:
for {
page, err := client.Search(ctx, query)
if err != nil {
return err
}
// ... use page.GetItems() ...
next := page.GetCursors().GetNext()
if len(page.GetItems()) < int(query.GetLimit()) || next == "" {
break
}
query.Cursor = next
}
Keep the last next cursor. Asked later, it returns what was recorded since.
One record and where it came from¶
got, err := client.Get(ctx, &auditv1.GetRequest{Profile: "security", Id: id})
p := got.GetProvenance() // p.GetObjectKey(), p.GetLine(), p.GetDigestId(), p.GetVerifiedAt(), p.GetSealUnknown()
Provenance names the archived copy and whether a seal vouches for it:
| getter | value |
|---|---|
GetDigestId |
the key of the seal covering the record's hour; empty until sealed |
GetVerifiedAt |
empty until a verifier sets it |
GetSealUnknown |
true when the service cannot tell whether the hour is sealed |
The service records every read, and a refused one as a failure.