Skip to content

Commit 3bc38e7

Browse files
authored
Implement Audit API. (#23)
1 parent f9c86f0 commit 3bc38e7

18 files changed

Lines changed: 755 additions & 17 deletions

File tree

cmd/admin/v2/audit.go

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
package v2
2+
3+
import (
4+
"fmt"
5+
6+
adminv2 "github.com/metal-stack/api/go/metalstack/admin/v2"
7+
apiv2 "github.com/metal-stack/api/go/metalstack/api/v2"
8+
"github.com/metal-stack/cli/cmd/config"
9+
"github.com/metal-stack/cli/cmd/sorters"
10+
helpersaudit "github.com/metal-stack/cli/pkg/helpers/audit"
11+
"github.com/metal-stack/metal-lib/pkg/genericcli"
12+
"github.com/metal-stack/metal-lib/pkg/genericcli/printers"
13+
"github.com/metal-stack/metal-lib/pkg/pointer"
14+
"github.com/spf13/cobra"
15+
"github.com/spf13/viper"
16+
)
17+
18+
type adminAudit struct {
19+
c *config.Config
20+
}
21+
22+
func newAuditCmd(c *config.Config) *cobra.Command {
23+
a := &adminAudit{
24+
c: c,
25+
}
26+
27+
cmdsConfig := &genericcli.CmdsConfig[any, any, *apiv2.AuditTrace]{
28+
BinaryName: config.BinaryName,
29+
GenericCLI: genericcli.NewGenericCLI(a).WithFS(c.Fs),
30+
Singular: "audit",
31+
Plural: "audits",
32+
Description: "read api audit traces",
33+
Sorter: sorters.AuditSorter(),
34+
OnlyCmds: genericcli.OnlyCmds(genericcli.ListCmd, genericcli.DescribeCmd),
35+
DescribePrinter: func() printers.Printer { return c.DescribePrinter },
36+
ListPrinter: func() printers.Printer { return c.ListPrinter },
37+
ListCmdMutateFn: func(cmd *cobra.Command) {
38+
cmd.Flags().String("request-id", "", "request id of the audit trace.")
39+
40+
cmd.Flags().String("from", "", "start of range of the audit traces. e.g. 1h, 10m, 2006-01-02 15:04:05")
41+
cmd.Flags().String("to", "", "end of range of the audit traces. e.g. 1h, 10m, 2006-01-02 15:04:05")
42+
43+
cmd.Flags().String("user", "", "user of the audit trace.")
44+
cmd.Flags().String("tenant", "", "tenant of the audit trace.")
45+
46+
cmd.Flags().String("project", "", "project id of the audit trace")
47+
48+
cmd.Flags().String("phase", "", "the audit trace phase.")
49+
cmd.Flags().String("method", "", "api method of the audit trace.")
50+
cmd.Flags().Int32("result-code", 0, "gRPC result status code of the audit trace.")
51+
cmd.Flags().String("source-ip", "", "source-ip of the audit trace.")
52+
53+
cmd.Flags().String("body", "", "filters audit trace body payloads for the given text (full-text search).")
54+
55+
cmd.Flags().Int64("limit", 0, "limit the number of audit traces.")
56+
57+
cmd.Flags().Bool("prettify-body", true, "attempts to interpret the body as json and prettifies it.")
58+
59+
genericcli.Must(cmd.RegisterFlagCompletionFunc("phase", c.Completion.AuditPhaseListCompletion))
60+
genericcli.Must(cmd.RegisterFlagCompletionFunc("project", c.Completion.ProjectListCompletion))
61+
genericcli.Must(cmd.RegisterFlagCompletionFunc("tenant", c.Completion.TenantListCompletion))
62+
genericcli.Must(cmd.RegisterFlagCompletionFunc("result-code", c.Completion.AuditStatusCodesCompletion))
63+
},
64+
DescribeCmdMutateFn: func(cmd *cobra.Command) {
65+
cmd.Flags().String("tenant", "", "tenant of the audit trace.")
66+
67+
cmd.Flags().String("phase", "", "the audit trace phase.")
68+
69+
cmd.Flags().Bool("prettify-body", true, "attempts to interpret the body as json and prettifies it.")
70+
71+
genericcli.Must(cmd.RegisterFlagCompletionFunc("phase", c.Completion.AuditPhaseListCompletion))
72+
genericcli.Must(cmd.RegisterFlagCompletionFunc("tenant", c.Completion.TenantListCompletion))
73+
},
74+
}
75+
76+
return genericcli.NewCmds(cmdsConfig)
77+
}
78+
79+
func (a *adminAudit) Get(id string) (*apiv2.AuditTrace, error) {
80+
ctx, cancel := a.c.NewRequestContext()
81+
defer cancel()
82+
83+
resp, err := a.c.Client.Adminv2().Audit().Get(ctx, &adminv2.AuditServiceGetRequest{
84+
Uuid: id,
85+
Phase: helpersaudit.ToPhase(viper.GetString("phase")),
86+
})
87+
if err != nil {
88+
return nil, fmt.Errorf("failed to get audit trace: %w", err)
89+
}
90+
91+
if viper.GetBool("prettify-body") {
92+
helpersaudit.TryPrettifyBody(resp.Trace)
93+
}
94+
95+
return resp.Trace, nil
96+
}
97+
98+
func (a *adminAudit) List() ([]*apiv2.AuditTrace, error) {
99+
ctx, cancel := a.c.NewRequestContext()
100+
defer cancel()
101+
102+
fromDateTime, err := helpersaudit.RelativeDateTime(viper.GetString("from"))
103+
if err != nil {
104+
return nil, err
105+
}
106+
toDateTime, err := helpersaudit.RelativeDateTime(viper.GetString("to"))
107+
if err != nil {
108+
return nil, err
109+
}
110+
111+
var code *int32
112+
if viper.IsSet("result-code") {
113+
code = new(viper.GetInt32("result-code"))
114+
}
115+
116+
resp, err := a.c.Client.Adminv2().Audit().List(ctx, &adminv2.AuditServiceListRequest{
117+
Query: &apiv2.AuditQuery{
118+
Uuid: pointer.PointerOrNil(viper.GetString("request-id")),
119+
From: fromDateTime,
120+
To: toDateTime,
121+
User: pointer.PointerOrNil(viper.GetString("user")),
122+
Project: pointer.PointerOrNil(viper.GetString("project")),
123+
Method: pointer.PointerOrNil(viper.GetString("method")),
124+
ResultCode: code,
125+
Body: pointer.PointerOrNil(viper.GetString("body")),
126+
SourceIp: pointer.PointerOrNil(viper.GetString("source-ip")),
127+
Limit: pointer.PointerOrNil(viper.GetInt32("limit")),
128+
Phase: helpersaudit.ToPhase(viper.GetString("phase")),
129+
},
130+
})
131+
if err != nil {
132+
return nil, fmt.Errorf("failed to list audit traces: %w", err)
133+
}
134+
135+
if viper.GetBool("prettify-body") {
136+
for _, trace := range resp.Traces {
137+
helpersaudit.TryPrettifyBody(trace)
138+
}
139+
}
140+
141+
return resp.Traces, nil
142+
}
143+
144+
func (a *adminAudit) Convert(*apiv2.AuditTrace) (string, any, any, error) {
145+
panic("unimplemented")
146+
}
147+
148+
func (a *adminAudit) Delete(id string) (*apiv2.AuditTrace, error) {
149+
panic("unimplemented")
150+
}
151+
152+
func (a *adminAudit) Create(any) (*apiv2.AuditTrace, error) {
153+
panic("unimplemented")
154+
}
155+
156+
func (a *adminAudit) Update(any) (*apiv2.AuditTrace, error) {
157+
panic("unimplemented")
158+
}

cmd/admin/v2/commands.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,14 @@ func AddCmds(cmd *cobra.Command, c *config.Config) {
1414
Hidden: true,
1515
}
1616

17+
adminCmd.AddCommand(newAuditCmd(c))
1718
adminCmd.AddCommand(newComponentCmd(c))
1819
adminCmd.AddCommand(newImageCmd(c))
1920
adminCmd.AddCommand(newProjectCmd(c))
20-
adminCmd.AddCommand(newTenantCmd(c))
21-
adminCmd.AddCommand(newTokenCmd(c))
2221
adminCmd.AddCommand(newProjectCmd(c))
2322
adminCmd.AddCommand(newSwitchCmd(c))
23+
adminCmd.AddCommand(newTenantCmd(c))
24+
adminCmd.AddCommand(newTokenCmd(c))
2425

2526
cmd.AddCommand(adminCmd)
2627
}

cmd/api/v2/audit.go

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
package v2
2+
3+
import (
4+
"fmt"
5+
6+
apiv2 "github.com/metal-stack/api/go/metalstack/api/v2"
7+
"github.com/metal-stack/cli/cmd/config"
8+
"github.com/metal-stack/cli/cmd/sorters"
9+
helpersaudit "github.com/metal-stack/cli/pkg/helpers/audit"
10+
"github.com/metal-stack/metal-lib/pkg/genericcli"
11+
"github.com/metal-stack/metal-lib/pkg/genericcli/printers"
12+
"github.com/metal-stack/metal-lib/pkg/pointer"
13+
"github.com/spf13/cobra"
14+
"github.com/spf13/viper"
15+
)
16+
17+
type audit struct {
18+
c *config.Config
19+
}
20+
21+
func newAuditCmd(c *config.Config) *cobra.Command {
22+
w := &audit{
23+
c: c,
24+
}
25+
26+
cmdsConfig := &genericcli.CmdsConfig[any, any, *apiv2.AuditTrace]{
27+
BinaryName: config.BinaryName,
28+
GenericCLI: genericcli.NewGenericCLI(w).WithFS(c.Fs),
29+
Singular: "audit",
30+
Plural: "audits",
31+
Description: "read api audit traces of a tenant",
32+
Sorter: sorters.AuditSorter(),
33+
OnlyCmds: genericcli.OnlyCmds(genericcli.ListCmd, genericcli.DescribeCmd),
34+
DescribePrinter: func() printers.Printer { return c.DescribePrinter },
35+
ListPrinter: func() printers.Printer { return c.ListPrinter },
36+
DescribeCmdMutateFn: func(cmd *cobra.Command) {
37+
cmd.Flags().String("tenant", "", "tenant of the audit trace.")
38+
39+
cmd.Flags().String("phase", "", "the audit trace phase.")
40+
41+
cmd.Flags().Bool("prettify-body", true, "attempts to interpret the body as json and prettifies it.")
42+
43+
genericcli.Must(cmd.RegisterFlagCompletionFunc("phase", c.Completion.AuditPhaseListCompletion))
44+
genericcli.Must(cmd.RegisterFlagCompletionFunc("tenant", c.Completion.TenantListCompletion))
45+
},
46+
ListCmdMutateFn: func(cmd *cobra.Command) {
47+
cmd.Flags().String("request-id", "", "request id of the audit trace.")
48+
49+
cmd.Flags().String("from", "", "start of range of the audit traces. e.g. 1h, 10m, 2006-01-02 15:04:05")
50+
cmd.Flags().String("to", "", "end of range of the audit traces. e.g. 1h, 10m, 2006-01-02 15:04:05")
51+
52+
cmd.Flags().String("user", "", "user of the audit trace.")
53+
cmd.Flags().String("tenant", "", "tenant of the audit trace.")
54+
55+
cmd.Flags().String("project", "", "project id of the audit trace")
56+
57+
cmd.Flags().String("phase", "", "the audit trace phase.")
58+
cmd.Flags().String("method", "", "api method of the audit trace.")
59+
cmd.Flags().Int32("result-code", 0, "gRPC result status code of the audit trace.")
60+
cmd.Flags().String("source-ip", "", "source-ip of the audit trace.")
61+
62+
cmd.Flags().String("body", "", "filters audit trace body payloads for the given text (full-text search).")
63+
64+
cmd.Flags().Int64("limit", 0, "limit the number of audit traces.")
65+
66+
cmd.Flags().Bool("prettify-body", true, "attempts to interpret the body as json and prettifies it.")
67+
68+
genericcli.Must(cmd.RegisterFlagCompletionFunc("phase", c.Completion.AuditPhaseListCompletion))
69+
genericcli.Must(cmd.RegisterFlagCompletionFunc("project", c.Completion.ProjectListCompletion))
70+
genericcli.Must(cmd.RegisterFlagCompletionFunc("tenant", c.Completion.TenantListCompletion))
71+
genericcli.Must(cmd.RegisterFlagCompletionFunc("result-code", c.Completion.AuditStatusCodesCompletion))
72+
},
73+
}
74+
75+
return genericcli.NewCmds(cmdsConfig)
76+
}
77+
78+
func (c *audit) Get(id string) (*apiv2.AuditTrace, error) {
79+
ctx, cancel := c.c.NewRequestContext()
80+
defer cancel()
81+
82+
tenant, err := c.c.GetTenant()
83+
if err != nil {
84+
return nil, err
85+
}
86+
87+
resp, err := c.c.Client.Apiv2().Audit().Get(ctx, &apiv2.AuditServiceGetRequest{
88+
Login: tenant,
89+
Uuid: id,
90+
Phase: helpersaudit.ToPhase(viper.GetString("phase")),
91+
},
92+
)
93+
if err != nil {
94+
return nil, fmt.Errorf("failed to get audit trace: %w", err)
95+
}
96+
97+
if viper.GetBool("prettify-body") {
98+
helpersaudit.TryPrettifyBody(resp.Trace)
99+
}
100+
101+
return resp.Trace, nil
102+
}
103+
104+
func (c *audit) List() ([]*apiv2.AuditTrace, error) {
105+
ctx, cancel := c.c.NewRequestContext()
106+
defer cancel()
107+
108+
fromDateTime, err := helpersaudit.RelativeDateTime(viper.GetString("from"))
109+
if err != nil {
110+
return nil, err
111+
}
112+
toDateTime, err := helpersaudit.RelativeDateTime(viper.GetString("to"))
113+
if err != nil {
114+
return nil, err
115+
}
116+
117+
tenant, err := c.c.GetTenant()
118+
if err != nil {
119+
return nil, fmt.Errorf("tenant is required: %w", err)
120+
}
121+
122+
var code *int32
123+
if viper.IsSet("result-code") {
124+
code = new(viper.GetInt32("result-code"))
125+
}
126+
127+
resp, err := c.c.Client.Apiv2().Audit().List(ctx, &apiv2.AuditServiceListRequest{
128+
Login: tenant,
129+
Query: &apiv2.AuditQuery{
130+
Uuid: pointer.PointerOrNil(viper.GetString("request-id")),
131+
From: fromDateTime,
132+
To: toDateTime,
133+
User: pointer.PointerOrNil(viper.GetString("user")),
134+
Project: pointer.PointerOrNil(viper.GetString("project")),
135+
Method: pointer.PointerOrNil(viper.GetString("method")),
136+
ResultCode: code,
137+
Body: pointer.PointerOrNil(viper.GetString("body")),
138+
SourceIp: pointer.PointerOrNil(viper.GetString("source-ip")),
139+
Limit: pointer.PointerOrNil(viper.GetInt32("limit")),
140+
Phase: helpersaudit.ToPhase(viper.GetString("phase")),
141+
},
142+
})
143+
if err != nil {
144+
return nil, fmt.Errorf("failed to list audit traces: %w", err)
145+
}
146+
147+
if viper.GetBool("prettify-body") {
148+
for _, trace := range resp.Traces {
149+
helpersaudit.TryPrettifyBody(trace)
150+
}
151+
}
152+
153+
return resp.Traces, nil
154+
}
155+
156+
func (c *audit) Create(rq any) (*apiv2.AuditTrace, error) {
157+
panic("unimplemented")
158+
}
159+
160+
func (c *audit) Delete(id string) (*apiv2.AuditTrace, error) {
161+
panic("unimplemented")
162+
}
163+
164+
func (t *audit) Convert(r *apiv2.AuditTrace) (string, any, any, error) {
165+
panic("unimplemented")
166+
}
167+
168+
func (t *audit) Update(rq any) (*apiv2.AuditTrace, error) {
169+
panic("unimplemented")
170+
}

cmd/api/v2/commands.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,14 @@ import (
66
)
77

88
func AddCmds(cmd *cobra.Command, c *config.Config) {
9-
cmd.AddCommand(newVersionCmd(c))
9+
cmd.AddCommand(newAuditCmd(c))
1010
cmd.AddCommand(newHealthCmd(c))
11-
cmd.AddCommand(newTokenCmd(c))
12-
cmd.AddCommand(newIPCmd(c))
1311
cmd.AddCommand(newImageCmd(c))
12+
cmd.AddCommand(newIPCmd(c))
13+
cmd.AddCommand(newMethodsCmd(c))
1414
cmd.AddCommand(newProjectCmd(c))
1515
cmd.AddCommand(newTenantCmd(c))
16-
cmd.AddCommand(newMethodsCmd(c))
16+
cmd.AddCommand(newTokenCmd(c))
1717
cmd.AddCommand(newUserCmd(c))
18+
cmd.AddCommand(newVersionCmd(c))
1819
}

cmd/api/v2/project.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ func newProjectCmd(c *config.Config) *cobra.Command {
4141
cmd.Flags().String("name", "", "the name of the project to create")
4242
cmd.Flags().String("description", "", "the description of the project to create")
4343
cmd.Flags().String("tenant", "", "the tenant of this project, defaults to tenant of the default project")
44+
45+
genericcli.Must(cmd.RegisterFlagCompletionFunc("tenant", c.Completion.TenantListCompletion))
4446
},
4547
CreateRequestFromCLI: w.createRequestFromCLI,
4648
UpdateCmdMutateFn: func(cmd *cobra.Command) {

cmd/completion/audit.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package completion
2+
3+
import (
4+
"strconv"
5+
6+
apiv2 "github.com/metal-stack/api/go/metalstack/api/v2"
7+
"github.com/spf13/cobra"
8+
"google.golang.org/grpc/codes"
9+
)
10+
11+
func (c *Completion) AuditPhaseListCompletion(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
12+
return []string{apiv2.AuditPhase_AUDIT_PHASE_REQUEST.String(), apiv2.AuditPhase_AUDIT_PHASE_RESPONSE.String()}, cobra.ShellCompDirectiveNoFileComp
13+
}
14+
15+
func (c *Completion) AuditStatusCodesCompletion(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
16+
var result []string
17+
18+
for i := range 16 {
19+
result = append(result, strconv.Itoa(i)+"\t"+codes.Code(uint32(i)).String())
20+
}
21+
22+
return result, cobra.ShellCompDirectiveNoFileComp
23+
}

0 commit comments

Comments
 (0)