11package secrethub
22
33import (
4+ "fmt"
5+ "strings"
6+ "text/tabwriter"
7+
48 "github.com/secrethub/secrethub-cli/internals/cli/ui"
59 "github.com/secrethub/secrethub-cli/internals/secrethub/command"
10+ "github.com/secrethub/secrethub-go/pkg/secrethub"
11+ "github.com/secrethub/secrethub-go/pkg/secrethub/iterator"
612
713 "github.com/secrethub/secrethub-go/internals/api"
814)
@@ -12,7 +18,9 @@ type AuditCommand struct {
1218 io ui.IO
1319 path api.Path
1420 useTimestamps bool
21+ timeFormatter TimeFormatter
1522 newClient newClientFunc
23+ perPage int
1624}
1725
1826// NewAuditCommand creates a new audit command.
@@ -27,34 +35,190 @@ func NewAuditCommand(io ui.IO, newClient newClientFunc) *AuditCommand {
2735func (cmd * AuditCommand ) Register (r command.Registerer ) {
2836 clause := r .Command ("audit" , "Show the audit log." )
2937 clause .Arg ("repo-path or secret-path" , "Path to the repository or the secret to audit " + repoPathPlaceHolder + " or " + secretPathPlaceHolder ).SetValue (& cmd .path )
38+ clause .Flag ("per-page" , "number of audit events shown per page" ).Default ("20" ).IntVar (& cmd .perPage )
3039 registerTimestampFlag (clause ).BoolVar (& cmd .useTimestamps )
3140
3241 command .BindAction (clause , cmd .Run )
3342}
3443
3544// Run prints all audit events for the given repository or secret.
3645func (cmd * AuditCommand ) Run () error {
46+ cmd .beforeRun ()
47+ return cmd .run ()
48+ }
49+
50+ // beforeRun configures the command using the flag values.
51+ func (cmd * AuditCommand ) beforeRun () {
52+ cmd .timeFormatter = NewTimeFormatter (cmd .useTimestamps )
53+ }
54+
55+ // Run prints all audit events for the given repository or secret.
56+ func (cmd * AuditCommand ) run () error {
57+ if cmd .perPage < 1 {
58+ return fmt .Errorf ("per-page should be positive, got %d" , cmd .perPage )
59+ }
60+
61+ iter , auditTable , err := cmd .iterAndAuditTable ()
62+ if err != nil {
63+ return err
64+ }
65+
66+ tabWriter := tabwriter .NewWriter (cmd .io .Stdout (), 0 , 4 , 4 , ' ' , 0 )
67+ header := strings .Join (auditTable .header (), "\t " ) + "\n "
68+ fmt .Fprint (tabWriter , header )
69+
70+ i := 0
71+ for {
72+ i ++
73+ event , err := iter .Next ()
74+ if err == iterator .Done {
75+ break
76+ } else if err != nil {
77+ return err
78+ }
79+
80+ row , err := auditTable .row (event )
81+ if err != nil {
82+ return err
83+ }
84+
85+ fmt .Fprint (tabWriter , strings .Join (row , "\t " )+ "\n " )
86+
87+ if i == cmd .perPage {
88+ err = tabWriter .Flush ()
89+ if err != nil {
90+ return err
91+ }
92+ i = 0
93+
94+ // wait for <ENTER> to continue.
95+ _ , err := ui .Ask (cmd .io , "Press <ENTER> to show more results. Press <CTRL+C> to exit." )
96+ if err != nil {
97+ return err
98+ }
99+ fmt .Fprint (tabWriter , header )
100+ }
101+ }
102+
103+ err = tabWriter .Flush ()
104+ if err != nil {
105+ return err
106+ }
107+
108+ return nil
109+ }
110+
111+ func (cmd * AuditCommand ) iterAndAuditTable () (secrethub.AuditEventIterator , auditTable , error ) {
37112 repoPath , err := cmd .path .ToRepoPath ()
38113 if err == nil {
39- auditRepoCommand := AuditRepoCommand {
40- io : cmd .io ,
41- path : repoPath ,
42- useTimestamps : cmd .useTimestamps ,
43- newClient : cmd .newClient ,
114+ client , err := cmd .newClient ()
115+ if err != nil {
116+ return nil , nil , err
117+ }
118+ tree , err := client .Dirs ().GetTree (repoPath .GetDirPath ().Value (), - 1 , false )
119+ if err != nil {
120+ return nil , nil , err
44121 }
45- return auditRepoCommand .Run ()
122+
123+ iter := client .Repos ().EventIterator (repoPath .Value (), & secrethub.AuditEventIteratorParams {})
124+ auditTable := newRepoAuditTable (tree , cmd .timeFormatter )
125+ return iter , auditTable , nil
126+
46127 }
47128
48129 secretPath , err := cmd .path .ToSecretPath ()
49130 if err == nil {
50- auditSecretCommand := AuditSecretCommand {
51- io : cmd .io ,
52- path : secretPath ,
53- useTimestamps : cmd .useTimestamps ,
54- newClient : cmd .newClient ,
131+ if cmd .path .HasVersion () {
132+ return nil , nil , ErrCannotAuditSecretVersion
133+ }
134+
135+ client , err := cmd .newClient ()
136+ if err != nil {
137+ return nil , nil , err
55138 }
56- return auditSecretCommand .Run ()
139+
140+ isDir , err := client .Dirs ().Exists (secretPath .Value ())
141+ if err == nil && isDir {
142+ return nil , nil , ErrCannotAuditDir
143+ }
144+
145+ iter := client .Secrets ().EventIterator (secretPath .Value (), & secrethub.AuditEventIteratorParams {})
146+ auditTable := newSecretAuditTable (cmd .timeFormatter )
147+ return iter , auditTable , nil
148+ }
149+
150+ return nil , nil , ErrNoValidRepoOrSecretPath
151+ }
152+
153+ type auditTable interface {
154+ header () []string
155+ row (event api.Audit ) ([]string , error )
156+ }
157+
158+ func newBaseAuditTable (timeFormatter TimeFormatter ) baseAuditTable {
159+ return baseAuditTable {
160+ timeFormatter : timeFormatter ,
161+ }
162+ }
163+
164+ type baseAuditTable struct {
165+ timeFormatter TimeFormatter
166+ }
167+
168+ func (table baseAuditTable ) header (content ... string ) []string {
169+ res := append ([]string {"AUTHOR" , "EVENT" }, content ... )
170+ return append (res , "IP ADDRESS" , "DATE" )
171+ }
172+
173+ func (table baseAuditTable ) row (event api.Audit , content ... string ) ([]string , error ) {
174+ actor , err := getAuditActor (event )
175+ if err != nil {
176+ return nil , err
177+ }
178+
179+ res := append ([]string {actor , getEventAction (event )}, content ... )
180+ return append (res , event .IPAddress , table .timeFormatter .Format (event .LoggedAt )), nil
181+ }
182+
183+ func newSecretAuditTable (timeFormatter TimeFormatter ) secretAuditTable {
184+ return secretAuditTable {
185+ baseAuditTable : newBaseAuditTable (timeFormatter ),
186+ }
187+ }
188+
189+ type secretAuditTable struct {
190+ baseAuditTable
191+ }
192+
193+ func (table secretAuditTable ) header () []string {
194+ return table .baseAuditTable .header ()
195+ }
196+
197+ func (table secretAuditTable ) row (event api.Audit ) ([]string , error ) {
198+ return table .baseAuditTable .row (event )
199+ }
200+
201+ func newRepoAuditTable (tree * api.Tree , timeFormatter TimeFormatter ) repoAuditTable {
202+ return repoAuditTable {
203+ baseAuditTable : newBaseAuditTable (timeFormatter ),
204+ tree : tree ,
205+ }
206+ }
207+
208+ type repoAuditTable struct {
209+ baseAuditTable
210+ tree * api.Tree
211+ }
212+
213+ func (table repoAuditTable ) header () []string {
214+ return table .baseAuditTable .header ("EVENT SUBJECT" )
215+ }
216+
217+ func (table repoAuditTable ) row (event api.Audit ) ([]string , error ) {
218+ subject , err := getAuditSubject (event , table .tree )
219+ if err != nil {
220+ return nil , err
57221 }
58222
59- return ErrNoValidRepoOrSecretPath
223+ return table . baseAuditTable . row ( event , subject )
60224}
0 commit comments