github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/teams/box_audit_test.go (about) 1 package teams 2 3 import ( 4 "context" 5 "encoding/hex" 6 "errors" 7 "regexp" 8 "sync" 9 "testing" 10 11 lru "github.com/hashicorp/golang-lru" 12 "github.com/keybase/client/go/kbtest" 13 "github.com/keybase/client/go/libkb" 14 "github.com/keybase/client/go/protocol/keybase1" 15 "github.com/stretchr/testify/require" 16 ) 17 18 func toErr(attempt keybase1.BoxAuditAttempt) error { 19 if attempt.Error != nil { 20 return errors.New(*attempt.Error) 21 } 22 return nil 23 } 24 25 func mustGetJailLRU(tc *libkb.TestContext, a libkb.TeamBoxAuditor) *lru.Cache { 26 b := a.(*BoxAuditor) 27 return b.jailLRU 28 } 29 30 func countTrues(t *testing.T, cache *lru.Cache) int { 31 c := 0 32 for _, k := range cache.Keys() { 33 v, ok := cache.Get(k) 34 require.True(t, ok) 35 vBool := v.(bool) 36 if vBool { 37 c++ 38 } 39 } 40 return c 41 } 42 43 func mustGetBoxState(tc *libkb.TestContext, a libkb.TeamBoxAuditor, mctx libkb.MetaContext, teamID keybase1.TeamID) (*BoxAuditLog, *BoxAuditQueue, *BoxAuditJail) { 44 b := a.(*BoxAuditor) 45 log, err := b.maybeGetLog(mctx, teamID) 46 require.NoError(tc.T, err) 47 queue, err := b.maybeGetQueue(mctx) 48 require.NoError(tc.T, err) 49 jail, err := b.maybeGetJail(mctx) 50 require.NoError(tc.T, err) 51 return log, queue, jail 52 } 53 54 func TestBoxAuditAttempt(t *testing.T) { 55 fus, tcs, cleanup := setupNTests(t, 3) 56 defer cleanup() 57 58 _, bU, cU := fus[0], fus[1], fus[2] 59 aTc, bTc, cTc := tcs[0], tcs[1], tcs[2] 60 aM, bM, cM := libkb.NewMetaContextForTest(*aTc), libkb.NewMetaContextForTest(*bTc), libkb.NewMetaContextForTest(*cTc) 61 aA, bA, cA := aTc.G.GetTeamBoxAuditor(), bTc.G.GetTeamBoxAuditor(), cTc.G.GetTeamBoxAuditor() 62 63 t.Logf("A creates team") 64 teamName, teamID := createTeam2(*aTc) 65 66 t.Logf("adding B as admin") 67 _, err := AddMember(aM.Ctx(), aTc.G, teamName.String(), bU.Username, keybase1.TeamRole_ADMIN, nil) 68 require.NoError(t, err) 69 70 t.Logf("adding C as reader") 71 _, err = AddMember(aM.Ctx(), aTc.G, teamName.String(), cU.Username, keybase1.TeamRole_READER, nil) 72 require.NoError(t, err) 73 74 require.NoError(t, toErr(aA.Attempt(aM, teamID, false)), "A can attempt") 75 require.NoError(t, toErr(bA.Attempt(aM, teamID, false)), "B can attempt") 76 77 attempt := aA.Attempt(aM, teamID, false) 78 require.NoError(t, toErr(attempt)) 79 require.Equal(t, attempt.Result, keybase1.BoxAuditAttemptResult_OK_VERIFIED, "owner can attempt") 80 require.Equal(t, *attempt.Generation, keybase1.PerTeamKeyGeneration(1)) 81 82 attempt = bA.Attempt(bM, teamID, false) 83 require.NoError(t, toErr(attempt)) 84 require.Equal(t, attempt.Result, keybase1.BoxAuditAttemptResult_OK_VERIFIED, "admins can attempt") 85 require.Equal(t, *attempt.Generation, keybase1.PerTeamKeyGeneration(1)) 86 87 attempt = cA.Attempt(cM, teamID, false) 88 require.NoError(t, toErr(attempt)) 89 require.Equal(t, attempt.Result, keybase1.BoxAuditAttemptResult_OK_NOT_ATTEMPTED_ROLE, "readers can attempt but don't verify") 90 require.Equal(t, *attempt.Generation, keybase1.PerTeamKeyGeneration(1)) 91 92 kbtest.RotatePaper(*cTc, cU) 93 attempt = aA.Attempt(aM, teamID, false) 94 require.Error(t, toErr(attempt), "team not rotated after puk rotate so attempt fails") 95 team, err := Load(context.TODO(), aTc.G, keybase1.LoadTeamArg{Name: teamName.String(), ForceRepoll: true}) 96 require.NoError(t, err) 97 err = team.Rotate(aM.Ctx(), keybase1.RotationType_VISIBLE) 98 require.NoError(t, err) 99 attempt = aA.Attempt(aM, teamID, false) 100 require.NoError(t, toErr(attempt), "team rotated, so audit works") 101 102 t.Logf("check rotate-before-attempt option") 103 kbtest.RotatePaper(*cTc, cU) 104 attempt = aA.Attempt(aM, teamID, false) 105 require.Error(t, toErr(attempt), "team not rotated after puk rotate so attempt fails") 106 attempt = aA.Attempt(aM, teamID, true) 107 require.NoError(t, toErr(attempt), "rotate-before-attempt option works") 108 109 t.Logf("check after reset") 110 kbtest.ResetAccount(*cTc, cU) 111 attempt = aA.Attempt(aM, teamID, false) 112 require.Error(t, toErr(attempt), "team not rotated after reset") 113 attempt = aA.Attempt(aM, teamID, true) 114 require.NoError(t, toErr(attempt), "attempt OK after rotate") 115 116 attempt = cA.Attempt(cM, teamID, false) 117 require.Error(t, toErr(attempt), "check that someone not in a team cannot audit") 118 119 t.Logf("C provisions and A adds C back after account reset") 120 err = cU.Login(cTc.G) 121 require.NoError(t, err) 122 _, err = AddMember(aM.Ctx(), aTc.G, teamName.String(), cU.Username, keybase1.TeamRole_READER, nil) 123 require.NoError(t, err) 124 125 attempt = cA.Attempt(cM, teamID, false) 126 require.NoError(t, toErr(attempt), "check that attempt is OK after allow reset member back in without another rotate") 127 128 t.Logf("check after delete") 129 kbtest.DeleteAccount(*cTc, cU) 130 attempt = aA.Attempt(aM, teamID, false) 131 require.Error(t, toErr(attempt), "team not rotated after delete") 132 attempt = aA.Attempt(aM, teamID, true) 133 require.NoError(t, toErr(attempt), "attempt OK after rotate") 134 } 135 136 func requireFatalError(t *testing.T, err error, args ...interface{}) { 137 _, ok := err.(FatalBoxAuditError) 138 require.True(t, ok, args...) 139 } 140 141 func requireNonfatalError(t *testing.T, err error, args ...interface{}) { 142 _, ok := err.(NonfatalBoxAuditError) 143 require.True(t, ok, args...) 144 } 145 146 func auditTeam(a libkb.TeamBoxAuditor, mctx libkb.MetaContext, teamID keybase1.TeamID) error { 147 _, err := a.BoxAuditTeam(mctx, teamID) 148 return err 149 } 150 151 func TestBoxAuditAudit(t *testing.T) { 152 fus, tcs, cleanup := setupNTests(t, 5) 153 defer cleanup() 154 155 _, bU, cU, dU, eU := fus[0], fus[1], fus[2], fus[3], fus[4] 156 aTc, bTc, cTc, dTc, eTc := tcs[0], tcs[1], tcs[2], tcs[3], tcs[4] 157 aM, bM, cM, dM, eM := libkb.NewMetaContextForTest(*aTc), libkb.NewMetaContextForTest(*bTc), libkb.NewMetaContextForTest(*cTc), libkb.NewMetaContextForTest(*dTc), libkb.NewMetaContextForTest(*eTc) 158 aA, bA, cA, dA, eA := aTc.G.GetTeamBoxAuditor(), bTc.G.GetTeamBoxAuditor(), cTc.G.GetTeamBoxAuditor(), dTc.G.GetTeamBoxAuditor(), eTc.G.GetTeamBoxAuditor() 159 160 t.Logf("A creates team") 161 teamName, teamID := createTeam2(*aTc) 162 163 t.Logf("adding B as admin") 164 _, err := AddMember(aM.Ctx(), aTc.G, teamName.String(), bU.Username, keybase1.TeamRole_ADMIN, nil) 165 require.NoError(t, err) 166 167 t.Logf("adding C as reader") 168 _, err = AddMember(aM.Ctx(), aTc.G, teamName.String(), cU.Username, keybase1.TeamRole_READER, nil) 169 require.NoError(t, err) 170 171 t.Logf("adding D as bot") 172 _, err = AddMember(aM.Ctx(), aTc.G, teamName.String(), dU.Username, keybase1.TeamRole_BOT, nil) 173 require.NoError(t, err) 174 175 t.Logf("adding E as restricted bot") 176 _, err = AddMember(aM.Ctx(), aTc.G, teamName.String(), eU.Username, keybase1.TeamRole_RESTRICTEDBOT, &keybase1.TeamBotSettings{}) 177 require.NoError(t, err) 178 179 require.NoError(t, auditTeam(aA, aM, teamID), "A can audit") 180 require.NoError(t, auditTeam(bA, bM, teamID), "B can audit") 181 require.NoError(t, auditTeam(cA, cM, teamID), "C can audit (this is vacuous, since C is a reader)") 182 require.NoError(t, auditTeam(dA, dM, teamID), "D can audit (this is vacuous, since D is a bot)") 183 require.NoError(t, auditTeam(eA, eM, teamID), "E can audit (this is vacuous, since E is a restricted bot)") 184 185 var nullstring *string 186 g1 := keybase1.PerTeamKeyGeneration(1) 187 188 t.Logf("check A's view of the successful audit in db") 189 log, queue, jail := mustGetBoxState(aTc, aA, aM, teamID) 190 log.Audits[0].ID = nil 191 log.Audits[0].Attempts[0].Ctime = 0 192 require.Equal(t, *log, BoxAuditLog{ 193 Audits: []BoxAudit{ 194 { 195 ID: nil, 196 Attempts: []keybase1.BoxAuditAttempt{ 197 { 198 Ctime: 0, 199 Error: nullstring, 200 Result: keybase1.BoxAuditAttemptResult_OK_VERIFIED, 201 Generation: &g1, 202 }, 203 }, 204 }, 205 }, 206 InProgress: false, 207 Version: CurrentBoxAuditVersion, 208 }) 209 require.Nil(t, queue) 210 require.Equal(t, *jail, BoxAuditJail{ 211 TeamIDs: map[keybase1.TeamID]bool{}, 212 Version: CurrentBoxAuditVersion, 213 }) 214 215 t.Logf("check B's view of the successful audit in db") 216 log, queue, jail = mustGetBoxState(bTc, bA, bM, teamID) 217 log.Audits[0].ID = nil 218 log.Audits[0].Attempts[0].Ctime = 0 219 require.Equal(t, *log, BoxAuditLog{ 220 Audits: []BoxAudit{ 221 { 222 ID: nil, 223 Attempts: []keybase1.BoxAuditAttempt{ 224 { 225 Ctime: 0, 226 Error: nullstring, 227 Result: keybase1.BoxAuditAttemptResult_OK_VERIFIED, 228 Generation: &g1, 229 }, 230 }, 231 }, 232 }, 233 InProgress: false, 234 Version: CurrentBoxAuditVersion, 235 }) 236 require.Nil(t, queue) 237 require.Equal(t, *jail, BoxAuditJail{ 238 TeamIDs: map[keybase1.TeamID]bool{}, 239 Version: CurrentBoxAuditVersion, 240 }) 241 242 t.Logf("check C's & D's view of the successful no-op audit in db") 243 vacuousLog := BoxAuditLog{ 244 Audits: []BoxAudit{ 245 { 246 ID: nil, 247 Attempts: []keybase1.BoxAuditAttempt{ 248 { 249 Ctime: 0, 250 Error: nullstring, 251 Result: keybase1.BoxAuditAttemptResult_OK_NOT_ATTEMPTED_ROLE, 252 Generation: &g1, 253 }, 254 }, 255 }, 256 }, 257 InProgress: false, 258 Version: CurrentBoxAuditVersion, 259 } 260 log, queue, jail = mustGetBoxState(cTc, cA, cM, teamID) 261 log.Audits[0].ID = nil 262 log.Audits[0].Attempts[0].Ctime = 0 263 require.Equal(t, *log, vacuousLog) 264 require.Nil(t, queue) 265 require.Equal(t, *jail, BoxAuditJail{ 266 TeamIDs: map[keybase1.TeamID]bool{}, 267 Version: CurrentBoxAuditVersion, 268 }) 269 log, queue, jail = mustGetBoxState(dTc, dA, dM, teamID) 270 log.Audits[0].ID = nil 271 log.Audits[0].Attempts[0].Ctime = 0 272 require.Equal(t, *log, vacuousLog) 273 require.Nil(t, queue) 274 require.Equal(t, *jail, BoxAuditJail{ 275 TeamIDs: map[keybase1.TeamID]bool{}, 276 Version: CurrentBoxAuditVersion, 277 }) 278 log, queue, jail = mustGetBoxState(eTc, eA, eM, teamID) 279 log.Audits[0].ID = nil 280 log.Audits[0].Attempts[0].Ctime = 0 281 require.Equal(t, *log, vacuousLog) 282 require.Nil(t, queue) 283 require.Equal(t, *jail, BoxAuditJail{ 284 TeamIDs: map[keybase1.TeamID]bool{}, 285 Version: CurrentBoxAuditVersion, 286 }) 287 288 require.Equal(t, countTrues(t, mustGetJailLRU(aTc, aA)), 0) 289 require.Equal(t, countTrues(t, mustGetJailLRU(bTc, bA)), 0) 290 require.Equal(t, countTrues(t, mustGetJailLRU(cTc, cA)), 0) 291 require.Equal(t, countTrues(t, mustGetJailLRU(cTc, dA)), 0) 292 require.Equal(t, countTrues(t, mustGetJailLRU(cTc, eA)), 0) 293 294 t.Logf("checking state after failed attempts") 295 t.Logf("disable autorotate on retry") 296 aTc.G.TestOptions.NoAutorotateOnBoxAuditRetry = true 297 t.Logf("c rotates and a check's state") 298 kbtest.RotatePaper(*cTc, cU) 299 err = auditTeam(aA, aM, teamID) 300 requireNonfatalError(t, err, "audit failure on unrotated puk") 301 _, ok := err.(NonfatalBoxAuditError) 302 require.True(t, ok) 303 log, queue, _ = mustGetBoxState(aTc, aA, aM, teamID) 304 require.Equal(t, len(log.Audits), 2) 305 require.True(t, log.InProgress, "failed audit causes it to be in progress") 306 require.Equal(t, len(queue.Items), 1) 307 require.Equal(t, queue.Items[0].TeamID, teamID) 308 require.Equal(t, queue.Version, CurrentBoxAuditVersion) 309 err = auditTeam(aA, aM, teamID) 310 requireNonfatalError(t, err, "another audit failure on unrotated puk") 311 _, queue, _ = mustGetBoxState(aTc, aA, aM, teamID) 312 require.Equal(t, len(queue.Items), 1, "no duplicates in retry queue") 313 314 t.Logf("checking that we can load a team in retry queue, but that is not jailed yet") 315 _, err = Load(context.TODO(), aTc.G, keybase1.LoadTeamArg{Name: teamName.String(), ForceRepoll: true}) 316 require.NoError(t, err) 317 318 t.Logf("rotate until we hit max retry attempts; should result in fatal error") 319 for i := 0; i < MaxBoxAuditRetryAttempts-3; i++ { 320 err = auditTeam(aA, aM, teamID) 321 requireNonfatalError(t, err, "another audit failure on unrotated puk") 322 } 323 err = auditTeam(aA, aM, teamID) 324 requireFatalError(t, err) 325 log, queue, jail = mustGetBoxState(aTc, aA, aM, teamID) 326 require.Equal(t, len(log.Last().Attempts), MaxBoxAuditRetryAttempts) 327 require.True(t, log.InProgress, "fatal state still counts as in progress even though it won't be retried") 328 require.Equal(t, len(queue.Items), 0, "jailed teams not in retry queue") 329 require.Equal(t, *jail, BoxAuditJail{ 330 TeamIDs: map[keybase1.TeamID]bool{ 331 teamID: true, 332 }, 333 Version: CurrentBoxAuditVersion, 334 }) 335 require.Equal(t, 1, countTrues(t, mustGetJailLRU(aTc, aA))) 336 337 // NOTE We may eventually cause the jailed team load that did not pass 338 // reaudit to fail entirely instead of just putting up a black bar in the 339 // GUI. 340 t.Logf("checking that we can load a jailed team that won't pass auto-reaudit") 341 _, err = Load(context.TODO(), aTc.G, keybase1.LoadTeamArg{Name: teamName.String(), ForceRepoll: true}) 342 require.NoError(t, err) 343 344 inJail, err := aA.IsInJail(aM, teamID) 345 require.NoError(t, err) 346 require.True(t, inJail) 347 348 t.Logf("reenable autorotate on retry") 349 aTc.G.TestOptions.NoAutorotateOnBoxAuditRetry = false 350 351 // Not in queue anymore so this is a noop 352 _, err = aA.RetryNextBoxAudit(aM) 353 require.NoError(t, err) 354 355 // We are jailed, but reaudit passes 356 didReaudit, err := aA.AssertUnjailedOrReaudit(aM, teamID) 357 require.NoError(t, err) 358 require.True(t, didReaudit) 359 360 require.NoError(t, err, "no error since we rotate on retry now") 361 log, queue, jail = mustGetBoxState(aTc, aA, aM, teamID) 362 require.False(t, log.InProgress) 363 attempts := log.Last().Attempts 364 require.Equal(t, attempts[len(attempts)-1].Result, keybase1.BoxAuditAttemptResult_OK_VERIFIED) 365 require.Equal(t, len(queue.Items), 0, "not in queue") 366 require.Equal(t, len(jail.TeamIDs), 0, "unjailed") 367 368 // Just check these public methods are ok 369 teamIDs, err := KnownTeamIDs(aM) 370 require.NoError(t, err) 371 require.Equal(t, len(teamIDs), 1) 372 require.Equal(t, teamIDs[0], teamID) 373 374 randomID, err := randomKnownTeamID(aM) 375 require.NoError(t, err) 376 require.NotNil(t, randomID) 377 require.Equal(t, teamID, *randomID) 378 379 _, err = aA.BoxAuditRandomTeam(aM) 380 require.NoError(t, err) 381 } 382 383 // TestBoxAuditRaces makes 3 users, 3 teams with all 3 users, and audits all 384 // of them many times at the same time in separate goroutines. If tested with 385 // the -race option, it will fail if there's any data races. Also, we check 386 // that all the routines eventually finish, which might catch some deadlocks. 387 // Note that the race detector only catches memory races, so it doesn't really 388 // mean there are no data races in the code even if it passes the detector, i.e., 389 // one goroutine could have overwritten a queue add of another goroutine, and this 390 // would not be caught by the detector. 391 // However, we *do* check that the error is Nonfatal, not Client, which means there 392 // were no errors in the leveldb or lru operations, but rather the audit itself. 393 func TestBoxAuditRaces(t *testing.T) { 394 fus, tcs, cleanup := setupNTests(t, 3) 395 defer cleanup() 396 397 aU, bU, cU := fus[0], fus[1], fus[2] 398 aTc, bTc, cTc := tcs[0], tcs[1], tcs[2] 399 aM, bM, cM := libkb.NewMetaContextForTest(*aTc), libkb.NewMetaContextForTest(*bTc), libkb.NewMetaContextForTest(*cTc) 400 aA, bA, cA := aTc.G.GetTeamBoxAuditor(), bTc.G.GetTeamBoxAuditor(), cTc.G.GetTeamBoxAuditor() 401 402 aTeamName, aTeamID := createTeam2(*aTc) 403 _, err := AddMember(aM.Ctx(), aTc.G, aTeamName.String(), bU.Username, keybase1.TeamRole_ADMIN, nil) 404 require.NoError(t, err) 405 _, err = AddMember(aM.Ctx(), aTc.G, aTeamName.String(), cU.Username, keybase1.TeamRole_ADMIN, nil) 406 require.NoError(t, err) 407 408 bTeamName, bTeamID := createTeam2(*bTc) 409 _, err = AddMember(bM.Ctx(), bTc.G, bTeamName.String(), aU.Username, keybase1.TeamRole_ADMIN, nil) 410 require.NoError(t, err) 411 _, err = AddMember(bM.Ctx(), bTc.G, bTeamName.String(), cU.Username, keybase1.TeamRole_ADMIN, nil) 412 require.NoError(t, err) 413 414 cTeamName, cTeamID := createTeam2(*cTc) 415 _, err = AddMember(cM.Ctx(), cTc.G, cTeamName.String(), aU.Username, keybase1.TeamRole_ADMIN, nil) 416 require.NoError(t, err) 417 _, err = AddMember(cM.Ctx(), cTc.G, cTeamName.String(), bU.Username, keybase1.TeamRole_ADMIN, nil) 418 require.NoError(t, err) 419 420 // We do this so the audits will access the shared jail and queue data 421 // structures, not just the logs. 422 t.Logf("Turning off autorotate on retry and putting teams in failing audit state") 423 aTc.G.TestOptions.NoAutorotateOnBoxAuditRetry = true 424 kbtest.RotatePaper(*aTc, aU) 425 kbtest.RotatePaper(*bTc, bU) 426 kbtest.RotatePaper(*cTc, cU) 427 428 auditors := []libkb.TeamBoxAuditor{aA, bA, cA} 429 metacontexts := []libkb.MetaContext{aM, bM, cM} 430 teamIDs := []keybase1.TeamID{aTeamID, bTeamID, cTeamID} 431 var wg sync.WaitGroup 432 total := 9 433 errCh := make(chan error, total) 434 wg.Add(total) 435 for i := 0; i < 3; i++ { 436 for j := 0; j < 3; j++ { 437 go func(i, j int) { 438 _, auditErr := auditors[i].BoxAuditTeam(metacontexts[i], teamIDs[j]) 439 errCh <- auditErr 440 wg.Done() 441 }(i, j) 442 } 443 } 444 wg.Wait() 445 i := 0 446 for err := range errCh { 447 require.NotNil(t, err) 448 boxErr := err.(NonfatalBoxAuditError) 449 require.Regexp(t, regexp.MustCompile(`.*box summary hash mismatch.*`), boxErr.inner.Error()) 450 // stop reading after 9 handled errors, otherwise the for loop goes 451 // forever since we don't close errCh 452 i++ 453 if i >= total { 454 break 455 } 456 } 457 } 458 459 // TestBoxAuditCalculation makes sure we calculate summaries at different 460 // merkle seqnos properly, regardless of users who are reset or who rotate 461 // their devices. 462 func TestBoxAuditCalculation(t *testing.T) { 463 fus, tcs, cleanup := setupNTests(t, 3) 464 defer cleanup() 465 466 aU, bU, cU := fus[0], fus[1], fus[2] 467 aTc, bTc, cTc := tcs[0], tcs[1], tcs[2] 468 aM, _, cM := libkb.NewMetaContextForTest(*aTc), libkb.NewMetaContextForTest(*bTc), libkb.NewMetaContextForTest(*cTc) 469 470 aTeamName, aTeamID := createTeam2(*aTc) 471 _, err := AddMember(aM.Ctx(), aTc.G, aTeamName.String(), bU.Username, keybase1.TeamRole_ADMIN, nil) 472 require.NoError(t, err) 473 _, err = AddMember(aM.Ctx(), aTc.G, aTeamName.String(), cU.Username, keybase1.TeamRole_ADMIN, nil) 474 require.NoError(t, err) 475 476 puk := aU.User.GetComputedKeyFamily().GetLatestPerUserKey() 477 initSeqno := puk.Seqno 478 479 load := func(mctx libkb.MetaContext, teamID keybase1.TeamID) (team *Team, chainSummary, currentSummary *boxPublicSummary) { 480 team, err := loadTeamForBoxAudit(mctx, teamID) 481 require.NoError(t, err) 482 chainSummary, err = calculateChainSummary(mctx, team) 483 require.NoError(t, err) 484 currentSummary, err = calculateCurrentSummary(mctx, team) 485 require.NoError(t, err) 486 return team, chainSummary, currentSummary 487 } 488 489 _, chainSummary, currentSummary := load(aM, aTeamID) 490 expected := boxPublicSummaryTable{ 491 aU.User.GetUID(): initSeqno, 492 bU.User.GetUID(): initSeqno, 493 cU.User.GetUID(): initSeqno, 494 } 495 require.Equal(t, expected, chainSummary.table) 496 require.Equal(t, expected, currentSummary.table) 497 498 t.Logf("B rotates PUK") 499 kbtest.RotatePaper(*bTc, bU) 500 501 team, chainSummary, currentSummary := load(cM, aTeamID) 502 newExpected := boxPublicSummaryTable{ 503 aU.User.GetUID(): initSeqno, 504 bU.User.GetUID(): initSeqno + 3, 505 cU.User.GetUID(): initSeqno, 506 } 507 require.Equal(t, expected, chainSummary.table) 508 require.Equal(t, newExpected, currentSummary.table) 509 510 t.Logf("A rotates team") 511 err = team.Rotate(aM.Ctx(), keybase1.RotationType_VISIBLE) 512 require.NoError(t, err) 513 514 t.Logf("C checks summary") 515 _, chainSummary, currentSummary = load(cM, aTeamID) 516 require.Equal(t, newExpected, chainSummary.table) 517 require.Equal(t, newExpected, currentSummary.table) 518 519 t.Logf("check after reset") 520 kbtest.ResetAccount(*cTc, cU) 521 newerExpected := boxPublicSummaryTable{ 522 aU.User.GetUID(): initSeqno, 523 bU.User.GetUID(): initSeqno + 3, 524 } 525 _, chainSummary, currentSummary = load(aM, aTeamID) 526 require.Equal(t, newExpected, chainSummary.table) 527 require.Equal(t, newerExpected, currentSummary.table) 528 529 t.Logf("make some dummy links to test historical chain summary") 530 err = EditMember(aM.Ctx(), aTc.G, aTeamName.String(), bU.Username, keybase1.TeamRole_WRITER, nil) 531 require.NoError(t, err) 532 err = EditMember(aM.Ctx(), aTc.G, aTeamName.String(), bU.Username, keybase1.TeamRole_ADMIN, nil) 533 require.NoError(t, err) 534 err = EditMember(aM.Ctx(), aTc.G, aTeamName.String(), bU.Username, keybase1.TeamRole_WRITER, nil) 535 require.NoError(t, err) 536 err = EditMember(aM.Ctx(), aTc.G, aTeamName.String(), bU.Username, keybase1.TeamRole_ADMIN, nil) 537 require.NoError(t, err) 538 _, chainSummary, currentSummary = load(aM, aTeamID) 539 require.Equal(t, newExpected, chainSummary.table) 540 require.Equal(t, newerExpected, currentSummary.table) 541 542 t.Logf("make some more dummy links") 543 err = EditMember(aM.Ctx(), aTc.G, aTeamName.String(), bU.Username, keybase1.TeamRole_WRITER, nil) 544 require.NoError(t, err) 545 err = EditMember(aM.Ctx(), aTc.G, aTeamName.String(), bU.Username, keybase1.TeamRole_ADMIN, nil) 546 require.NoError(t, err) 547 548 team, _, _ = load(aM, aTeamID) 549 err = team.Rotate(aM.Ctx(), keybase1.RotationType_VISIBLE) 550 require.NoError(t, err) 551 _, chainSummary, currentSummary = load(aM, aTeamID) 552 require.Equal(t, newerExpected, chainSummary.table) 553 require.Equal(t, newerExpected, currentSummary.table) 554 } 555 556 func TestBoxAuditSubteamCalculation(t *testing.T) { 557 fus, tcs, cleanup := setupNTests(t, 3) 558 defer cleanup() 559 560 aU, bU, cU := fus[0], fus[1], fus[2] 561 aTc, bTc, cTc := tcs[0], tcs[1], tcs[2] 562 aM, _, _ := libkb.NewMetaContextForTest(*aTc), libkb.NewMetaContextForTest(*bTc), libkb.NewMetaContextForTest(*cTc) 563 564 parentName, parentID := createTeam2(*tcs[0]) 565 // A is not in subteam 566 subteamID, err := CreateSubteam(aM.Ctx(), aTc.G, "abc", parentName, keybase1.TeamRole_NONE /* addSelfAs */) 567 require.NoError(t, err) 568 subteamName, err := parentName.Append("abc") 569 require.NoError(t, err) 570 t.Logf("adding B as writer of team") 571 _, err = AddMember(aM.Ctx(), aTc.G, parentName.String(), bU.Username, keybase1.TeamRole_WRITER, nil) 572 require.NoError(t, err) 573 t.Logf("adding C as writer of subteam") 574 _, err = AddMember(aM.Ctx(), aTc.G, subteamName.String(), cU.Username, keybase1.TeamRole_WRITER, nil) 575 require.NoError(t, err) 576 577 puk := aU.User.GetComputedKeyFamily().GetLatestPerUserKey() 578 initSeqno := puk.Seqno 579 load := func(mctx libkb.MetaContext, teamID keybase1.TeamID) (team *Team, chainSummary, currentSummary *boxPublicSummary) { 580 team, err := loadTeamForBoxAudit(mctx, teamID) 581 require.NoError(t, err) 582 chainSummary, err = calculateChainSummary(mctx, team) 583 require.NoError(t, err) 584 currentSummary, err = calculateCurrentSummary(mctx, team) 585 require.NoError(t, err) 586 return team, chainSummary, currentSummary 587 } 588 589 _, chainSummary, currentSummary := load(aM, parentID) 590 teamTable1 := boxPublicSummaryTable{ 591 aU.User.GetUID(): initSeqno, 592 bU.User.GetUID(): initSeqno, 593 } 594 require.Equal(t, teamTable1, chainSummary.table) 595 require.Equal(t, teamTable1, currentSummary.table) 596 597 _, chainSummary, currentSummary = load(aM, *subteamID) 598 subteamTable1 := boxPublicSummaryTable{ 599 aU.User.GetUID(): initSeqno, 600 cU.User.GetUID(): initSeqno, 601 } 602 require.Equal(t, subteamTable1, chainSummary.table) 603 require.Equal(t, subteamTable1, currentSummary.table) 604 605 t.Logf("make b an admin of parent, giving him boxes") 606 err = EditMember(aM.Ctx(), aTc.G, parentName.String(), bU.Username, keybase1.TeamRole_ADMIN, nil) 607 require.NoError(t, err) 608 _, chainSummary, currentSummary = load(aM, *subteamID) 609 t.Logf("check do not need to rotate to know about new admin in chainsummary") 610 subteamTable2 := boxPublicSummaryTable{ 611 aU.User.GetUID(): initSeqno, 612 bU.User.GetUID(): initSeqno, 613 cU.User.GetUID(): initSeqno, 614 } 615 require.Equal(t, subteamTable2, chainSummary.table) 616 require.Equal(t, subteamTable2, currentSummary.table) 617 } 618 619 func TestBoxAuditOpen(t *testing.T) { 620 _, tcs, cleanup := setupNTests(t, 1) 621 defer cleanup() 622 tc := tcs[0] 623 624 b, err := libkb.RandBytes(4) 625 auditor := tc.G.GetTeamBoxAuditor() 626 require.NoError(t, err) 627 name := hex.EncodeToString(b) 628 _, err = CreateRootTeam(context.Background(), tc.G, name, keybase1.TeamSettings{ 629 Open: true, 630 JoinAs: keybase1.TeamRole_WRITER, 631 }) 632 require.NoError(t, err) 633 634 mctx := libkb.NewMetaContextForTest(*tc) 635 teamname, err := keybase1.TeamNameFromString(name) 636 require.NoError(t, err) 637 attempt := auditor.Attempt(mctx, teamname.ToPrivateTeamID(), false) 638 639 require.Equal(t, attempt.Result, keybase1.BoxAuditAttemptResult_OK_NOT_ATTEMPTED_OPENTEAM) 640 } 641 642 func TestBoxAuditImplicit(t *testing.T) { 643 fus, tcs, cleanup := setupNTests(t, 2) 644 defer cleanup() 645 646 var teamIDs []keybase1.TeamID 647 648 for idx, isPublic := range []bool{false, true} { 649 t.Logf("testing audit implicit with public=%t", isPublic) 650 team1, _, _, err := LookupOrCreateImplicitTeam(context.TODO(), tcs[idx].G, "t_tracy,"+fus[idx].Username, isPublic) 651 require.NoError(t, err) 652 auditor := tcs[idx].G.GetTeamBoxAuditor() 653 mctx := libkb.NewMetaContextForTest(*tcs[idx]) 654 attempt := auditor.Attempt(mctx, team1.ID, false) 655 require.Equal(t, attempt.Result, keybase1.BoxAuditAttemptResult_OK_VERIFIED) 656 require.NoError(t, auditTeam(auditor, mctx, team1.ID)) 657 teamIDs = append(teamIDs, team1.ID) 658 } 659 660 // Check we keep track of public and private implicit teams too 661 mctx := libkb.NewMetaContextForTest(*tcs[0]) 662 knownTeamIDs, err := KnownTeamIDs(mctx) 663 require.NoError(t, err) 664 require.Equal(t, len(knownTeamIDs), 1) 665 require.Equal(t, teamIDs[0], knownTeamIDs[0]) 666 randomID, err := randomKnownTeamID(mctx) 667 require.NoError(t, err) 668 require.NotNil(t, randomID) 669 require.True(t, teamIDs[0] == *randomID) 670 671 mctx = libkb.NewMetaContextForTest(*tcs[1]) 672 knownTeamIDs, err = KnownTeamIDs(mctx) 673 require.NoError(t, err) 674 require.Equal(t, len(knownTeamIDs), 1) 675 require.Equal(t, teamIDs[1], knownTeamIDs[0]) 676 randomID, err = randomKnownTeamID(mctx) 677 require.NoError(t, err) 678 require.NotNil(t, randomID) 679 require.True(t, teamIDs[1] == *randomID) 680 } 681 682 func TestBoxAuditSubteamWithImplicitAdmins(t *testing.T) { 683 fus, tcs, cleanup := setupNTests(t, 3) 684 _, bU := fus[0], fus[1] 685 aTc, bTc := tcs[0], tcs[1] 686 aM, bM := libkb.NewMetaContextForTest(*aTc), libkb.NewMetaContextForTest(*bTc) 687 aA, bA := aTc.G.GetTeamBoxAuditor(), bTc.G.GetTeamBoxAuditor() 688 defer cleanup() 689 690 parentName, _ := createTeam2(*tcs[0]) 691 // A is not in subteam 692 subteamID, err := CreateSubteam(aM.Ctx(), aTc.G, "abc", parentName, keybase1.TeamRole_NONE /* addSelfAs */) 693 require.NoError(t, err) 694 subTeamName, err := parentName.Append("abc") 695 require.NoError(t, err) 696 697 t.Logf("adding B as admin to subteam") 698 _, err = AddMember(aM.Ctx(), aTc.G, subTeamName.String(), bU.Username, keybase1.TeamRole_WRITER, nil) 699 require.NoError(t, err) 700 701 // Even though A is not in subteam, A has a box because of implicit 702 // adminship. Check that B can still audit successfully. 703 require.NoError(t, auditTeam(bA, bM, *subteamID)) 704 705 // Add third user as an admin to parent team. They will be boxed for 706 // subteam as well. 707 _, err = AddMember(aM.Ctx(), aM.G(), parentName.String(), fus[2].Username, keybase1.TeamRole_ADMIN, nil) 708 require.NoError(t, err) 709 710 // Audit both teams again 711 require.NoError(t, auditTeam(aA, aM, parentName.RootID())) 712 713 require.NoError(t, auditTeam(bA, bM, *subteamID)) 714 } 715 716 func TestBoxAuditTransactionsWithBoxSummaries(t *testing.T) { 717 tc, owner, otherA, otherB, name := memberSetupMultiple(t) 718 defer tc.Cleanup() 719 720 otherC, err := kbtest.CreateAndSignupFakeUser("t", tc.G) 721 require.NoError(t, err) 722 723 kbtest.Logout(tc) 724 require.NoError(t, owner.Login(tc.G)) 725 726 t.Logf("Team name is %s\n", name) 727 728 team, err := Load(context.Background(), tc.G, keybase1.LoadTeamArg{ 729 Name: name, 730 NeedAdmin: true, 731 }) 732 require.NoError(t, err) 733 734 tx := CreateAddMemberTx(team) 735 for _, otherUser := range []*kbtest.FakeUser{otherA, otherB, otherC} { 736 val := &keybase1.TeamChangeReq{} 737 err = val.AddUVWithRole(otherUser.GetUserVersion(), keybase1.TeamRole_WRITER, nil) 738 require.NoError(t, err) 739 payload := txPayload{ 740 Tag: txPayloadTagCryptomembers, 741 Val: val, 742 } 743 tx.payloads = append(tx.payloads, payload) 744 } 745 746 err = tx.Post(libkb.NewMetaContextForTest(tc)) 747 require.NoError(t, err) 748 749 auditor := tc.G.GetTeamBoxAuditor() 750 attempt := auditor.Attempt(libkb.NewMetaContextForTest(tc), team.ID, false /* rotateBeforeAudit */) 751 require.Nil(t, attempt.Error) 752 require.Equal(t, attempt.Result, keybase1.BoxAuditAttemptResult_OK_VERIFIED) 753 } 754 755 func TestBoxAuditVersionBump(t *testing.T) { 756 _, tcs, cleanup := setupNTests(t, 1) 757 defer cleanup() 758 aTc := tcs[0] 759 aM := libkb.NewMetaContextForTest(*aTc) 760 761 teamID := keybase1.TeamID("YELLOW_SUBMARINE") 762 763 a1 := newBoxAuditorWithVersion(aM.G(), 5) 764 765 err := a1.jail(aM, teamID) 766 require.NoError(t, err) 767 768 jailed, err := a1.IsInJail(aM, teamID) 769 require.NoError(t, err) 770 require.True(t, jailed) 771 772 jailed, err = a1.IsInJail(aM, teamID) 773 require.NoError(t, err) 774 require.True(t, jailed) 775 776 a2 := newBoxAuditorWithVersion(aM.G(), 6) 777 jailed, err = a2.IsInJail(aM, teamID) 778 require.NoError(t, err) 779 require.False(t, jailed) 780 781 jailed, err = a1.IsInJail(aM, teamID) 782 require.NoError(t, err) 783 require.True(t, jailed) 784 } 785 786 type timeoutAPI struct { 787 *libkb.APIArgRecorder 788 } 789 790 var errFakeNetworkTimeout = errors.New("fake network timeout in test") 791 792 func (r *timeoutAPI) GetDecode(mctx libkb.MetaContext, arg libkb.APIArg, w libkb.APIResponseWrapper) error { 793 return libkb.APINetError{Err: errFakeNetworkTimeout} 794 } 795 func (r *timeoutAPI) PostDecode(mctx libkb.MetaContext, arg libkb.APIArg, w libkb.APIResponseWrapper) error { 796 return libkb.APINetError{Err: errFakeNetworkTimeout} 797 } 798 799 func (r *timeoutAPI) Get(mctx libkb.MetaContext, arg libkb.APIArg) (*libkb.APIRes, error) { 800 return nil, libkb.APINetError{Err: errFakeNetworkTimeout} 801 } 802 803 func TestBoxAuditRetryBehavior(t *testing.T) { 804 _, tcs, cleanup := setupNTests(t, 1) 805 defer cleanup() 806 807 aTc := tcs[0] 808 aM := libkb.NewMetaContextForTest(*aTc) 809 aA := aTc.G.GetTeamBoxAuditor() 810 811 t.Logf("A creates team") 812 teamName, teamID := createTeam2(*aTc) 813 814 require.NoError(t, auditTeam(aA, aM, teamID), "A can audit") 815 816 origAPI := aTc.G.API 817 aTc.G.API = &timeoutAPI{} 818 err := auditTeam(aA, aM, teamID) 819 require.Error(t, err, "A can't audit when offline") 820 require.Contains(t, err.Error(), "fake network timeout in test") 821 822 aTc.G.API = origAPI 823 require.NoError(t, auditTeam(aA, aM, teamID), "A can audit") 824 team, err := Load(context.TODO(), aTc.G, keybase1.LoadTeamArg{Name: teamName.String(), ForceRepoll: true}) 825 require.NoError(t, err) 826 require.Equal(t, keybase1.PerTeamKeyGeneration(1), team.Generation()) 827 }