github.com/keybase/client/go@v0.0.0-20240520164431-4f512a4c85a3/client/cmd_audit.go (about) 1 // Copyright 2019 Keybase, Inc. All rights reserved. Use of 2 // this source code is governed by the included BSD license. 3 4 package client 5 6 import ( 7 "fmt" 8 "strings" 9 10 "github.com/keybase/cli" 11 "github.com/keybase/client/go/libcmdline" 12 "github.com/keybase/client/go/libkb" 13 "github.com/keybase/client/go/protocol/keybase1" 14 "golang.org/x/net/context" 15 ) 16 17 func NewCmdAudit(cl *libcmdline.CommandLine, g *libkb.GlobalContext) cli.Command { 18 commands := []cli.Command{ 19 NewCmdAuditBox(cl, g), 20 } 21 22 return cli.Command{ 23 Name: "audit", 24 // No 'Usage' makes this hidden 25 Description: "Perform security audits", 26 Subcommands: commands, 27 } 28 } 29 30 type CmdAuditBox struct { 31 libkb.Contextified 32 AuditAllKnownTeams bool 33 IsInJail bool 34 Audit bool 35 Attempt bool 36 RotateBeforeAttempt bool 37 Ls bool 38 TeamID keybase1.TeamID 39 TeamName string 40 } 41 42 func NewCmdAuditBox(cl *libcmdline.CommandLine, g *libkb.GlobalContext) cli.Command { 43 cmd := &CmdAuditBox{ 44 Contextified: libkb.NewContextified(g), 45 } 46 return cli.Command{ 47 Name: "box", 48 Usage: `A team box audit makes sure a team's secrets are encrypted for 49 the right members in the team, and that when members revoke devices or 50 reset their accounts, the team's secret keys are rotated accordingly.`, 51 Flags: []cli.Flag{ 52 cli.BoolFlag{ 53 Name: "audit-all-known-teams", 54 Usage: `Audit all known teams. If an audit fails, the team will 55 be rotated and the audit will be retried immediately . If it fails again, 56 this error will be reported. This operation may take a long time if you are 57 in many teams.`, 58 }, 59 cli.StringFlag{ 60 Name: "team-id", 61 Usage: "Team ID, required (or team name) except for list-known-team-ids/audit-all-known-teams", 62 }, 63 cli.StringFlag{ 64 Name: "team", 65 Usage: "Team name, required (or team ID) except for list-known-team-ids/audit-all-known-teams", 66 }, 67 cli.BoolFlag{ 68 Name: "is-in-jail", 69 Usage: "Check if a team id is in the box audit jail", 70 }, 71 cli.BoolFlag{ 72 Name: "audit", 73 Usage: "Audit a team id, storing result to disk and scheduling additional background reaudits if it failed", 74 }, 75 cli.BoolFlag{ 76 Name: "attempt", 77 Usage: "Audit a team id without persisting results anywhere", 78 }, 79 cli.BoolFlag{ 80 Name: "rotate-before-attempt", 81 Usage: "Only valid with --attempt; rotate the team's keys first when given.", 82 }, 83 cli.BoolFlag{ 84 Name: "list-known-team-ids", 85 Usage: "List all known team ids", 86 }, 87 }, 88 ArgumentHelp: "", 89 Action: func(c *cli.Context) { 90 cl.ChooseCommand(cmd, "box", c) 91 }, 92 } 93 } 94 95 func b2i(x bool) int { 96 if x { 97 return 1 98 } 99 return 0 100 } 101 102 func (c *CmdAuditBox) ParseArgv(ctx *cli.Context) error { 103 c.AuditAllKnownTeams = ctx.Bool("audit-all-known-teams") 104 c.IsInJail = ctx.Bool("is-in-jail") 105 c.Audit = ctx.Bool("audit") 106 c.Attempt = ctx.Bool("attempt") 107 c.Ls = ctx.Bool("list-known-team-ids") 108 if b2i(c.AuditAllKnownTeams)+b2i(c.IsInJail)+b2i(c.Audit)+b2i(c.Attempt)+b2i(c.Ls) != 1 { 109 return fmt.Errorf("need a single command: audit-all-known-teams, is-in-jail, audit, attempt, or list-known-team-ids") 110 } 111 c.RotateBeforeAttempt = ctx.Bool("rotate-before-attempt") 112 if c.RotateBeforeAttempt && !c.Attempt { 113 return fmt.Errorf("can only use --rotate-before-attempt with --attempt") 114 } 115 116 c.TeamID = keybase1.TeamID(ctx.String("team-id")) 117 c.TeamName = ctx.String("team") 118 if len(c.TeamID) != 0 && len(c.TeamName) != 0 { 119 return fmt.Errorf("cannot provide both team id and team name") 120 } 121 gaveTeam := c.TeamID != "" || c.TeamName != "" 122 123 if c.Ls || c.AuditAllKnownTeams { 124 if gaveTeam { 125 return fmt.Errorf("cannot provide team with this option") 126 } 127 } else { 128 if !gaveTeam { 129 return fmt.Errorf("need team id or team name") 130 } 131 } 132 133 return nil 134 } 135 136 func (c *CmdAuditBox) Run() error { 137 cli, err := GetAuditClient(c.G()) 138 if err != nil { 139 return err 140 } 141 142 ctx := context.Background() 143 144 if c.TeamName != "" { 145 cli, err := GetTeamsClient(c.G()) 146 if err != nil { 147 return err 148 } 149 teamID, err := cli.GetTeamID(ctx, c.TeamName) 150 if err != nil { 151 return err 152 } 153 c.TeamID = teamID 154 } 155 156 var failedTeamIDs []keybase1.TeamID 157 switch { 158 case c.AuditAllKnownTeams: 159 knownTeamIDs, err := cli.KnownTeamIDs(ctx, 0) 160 if err != nil { 161 return err 162 } 163 164 // team name, 1/... 165 for idx, teamID := range knownTeamIDs { 166 arg := keybase1.BoxAuditTeamArg{TeamID: teamID} 167 168 var err error 169 var attempt *keybase1.BoxAuditAttempt 170 attempt, err = cli.BoxAuditTeam(ctx, arg) 171 prefix := fmt.Sprintf("(%d/%d) %s", idx+1, len(knownTeamIDs), teamID) 172 describeAttempt(c.G(), attempt, prefix) 173 if err != nil { 174 attempt, err = cli.BoxAuditTeam(ctx, arg) 175 describeAttempt(c.G(), attempt, "(retry) "+prefix) 176 } 177 if err != nil { 178 c.G().Log.Error("Audit failed for %s: %s", teamID, err) 179 failedTeamIDs = append(failedTeamIDs, teamID) 180 } 181 } 182 183 if failedTeamIDs != nil { 184 var teamIDStrs []string 185 for _, teamID := range failedTeamIDs { 186 teamIDStrs = append(teamIDStrs, teamID.String()) 187 } 188 return fmt.Errorf("The following teams failed to pass an audit. This does not necessarily mean something is wrong, unless you are a member of those teams.\n%s", strings.Join(teamIDStrs, ", ")) 189 } 190 return nil 191 case c.IsInJail: 192 ok, err := cli.IsInJail(ctx, keybase1.IsInJailArg{TeamID: c.TeamID}) 193 if err != nil { 194 return err 195 } 196 fmt.Println(ok) 197 return nil 198 case c.Audit: 199 attempt, err := cli.BoxAuditTeam(ctx, keybase1.BoxAuditTeamArg{TeamID: c.TeamID}) 200 describeAttempt(c.G(), attempt, "") 201 return err 202 case c.Attempt: 203 arg := keybase1.AttemptBoxAuditArg{TeamID: c.TeamID, RotateBeforeAudit: c.RotateBeforeAttempt} 204 audit, err := cli.AttemptBoxAudit(ctx, arg) 205 if err != nil { 206 return err 207 } 208 fmt.Printf("%s\n", audit.Ctime.Time()) 209 fmt.Printf("Result: %s\n", audit.Result) 210 if audit.Generation != nil { 211 fmt.Printf("Team generation: %d\n", *audit.Generation) 212 } 213 if audit.Error != nil { 214 c.G().Log.Error("Box audit attempt failed: %s\n", *audit.Error) 215 } 216 return nil 217 case c.Ls: 218 ids, err := cli.KnownTeamIDs(ctx, 0) 219 if err != nil { 220 return err 221 } 222 for _, id := range ids { 223 fmt.Println(id) 224 } 225 return nil 226 default: 227 return fmt.Errorf("no command given") 228 } 229 } 230 231 func (c *CmdAuditBox) GetUsage() libkb.Usage { 232 return libkb.Usage{ 233 Config: true, 234 API: true, 235 } 236 } 237 238 func describeAttempt(g *libkb.GlobalContext, attempt *keybase1.BoxAuditAttempt, info string) { 239 tui := g.UI.GetTerminalUI() 240 prefix := "" 241 if info != "" { 242 prefix = info + " " 243 } 244 if attempt == nil { 245 _, _ = tui.PrintfUnescaped("%s\n", ColorString(g, "red", prefix+"Audit not attempted.")) 246 } else if attempt.Error == nil { 247 _, _ = tui.PrintfUnescaped("%s\n", ColorString(g, "green", prefix+attempt.String())) 248 } else { 249 _, _ = tui.PrintfUnescaped("%s\n", ColorString(g, "red", prefix+attempt.String())) 250 } 251 }