gitee.com/mysnapcore/mysnapd@v0.1.0/daemon/api_validate_test.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2020 Canonical Ltd 5 * 6 * This program is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License version 3 as 8 * published by the Free Software Foundation. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program. If not, see <http://www.gnu.org/licenses/>. 17 * 18 */ 19 20 package daemon_test 21 22 import ( 23 "fmt" 24 "net/http" 25 "net/url" 26 "sort" 27 "strings" 28 29 "gopkg.in/check.v1" 30 31 "gitee.com/mysnapcore/mysnapd/asserts" 32 "gitee.com/mysnapcore/mysnapd/asserts/assertstest" 33 "gitee.com/mysnapcore/mysnapd/asserts/snapasserts" 34 "gitee.com/mysnapcore/mysnapd/daemon" 35 "gitee.com/mysnapcore/mysnapd/overlord/assertstate" 36 "gitee.com/mysnapcore/mysnapd/overlord/assertstate/assertstatetest" 37 "gitee.com/mysnapcore/mysnapd/overlord/auth" 38 "gitee.com/mysnapcore/mysnapd/overlord/snapstate" 39 "gitee.com/mysnapcore/mysnapd/overlord/state" 40 "gitee.com/mysnapcore/mysnapd/snap" 41 "gitee.com/mysnapcore/mysnapd/snap/naming" 42 "gitee.com/mysnapcore/mysnapd/testutil" 43 ) 44 45 var _ = check.Suite(&apiValidationSetsSuite{}) 46 47 type apiValidationSetsSuite struct { 48 apiBaseSuite 49 50 storeSigning *assertstest.StoreStack 51 dev1Signing *assertstest.SigningDB 52 dev1acct *asserts.Account 53 acct1Key *asserts.AccountKey 54 mockSeqFormingAssertionFn func(assertType *asserts.AssertionType, sequenceKey []string, sequence int, user *auth.UserState) (asserts.Assertion, error) 55 } 56 57 type byName []*snapasserts.InstalledSnap 58 59 func (b byName) Len() int { return len(b) } 60 func (b byName) Swap(i, j int) { b[i], b[j] = b[j], b[i] } 61 func (b byName) Less(i, j int) bool { 62 return b[i].SnapName() < b[j].SnapName() 63 } 64 65 func (s *apiValidationSetsSuite) SetUpTest(c *check.C) { 66 s.apiBaseSuite.SetUpTest(c) 67 d := s.daemon(c) 68 69 s.expectAuthenticatedAccess() 70 71 restore := asserts.MockMaxSupportedFormat(asserts.ValidationSetType, 1) 72 s.AddCleanup(restore) 73 74 s.mockSeqFormingAssertionFn = nil 75 76 s.storeSigning = assertstest.NewStoreStack("can0nical", nil) 77 78 st := d.Overlord().State() 79 st.Lock() 80 snapstate.ReplaceStore(st, s) 81 assertstatetest.AddMany(st, s.storeSigning.StoreAccountKey("")) 82 st.Unlock() 83 84 s.dev1acct = assertstest.NewAccount(s.storeSigning, "developer1", nil, "") 85 c.Assert(s.storeSigning.Add(s.dev1acct), check.IsNil) 86 87 // developer signing 88 dev1PrivKey, _ := assertstest.GenerateKey(752) 89 s.acct1Key = assertstest.NewAccountKey(s.storeSigning, s.dev1acct, nil, dev1PrivKey.PublicKey(), "") 90 91 s.dev1Signing = assertstest.NewSigningDB(s.dev1acct.AccountID(), dev1PrivKey) 92 c.Assert(s.storeSigning.Add(s.acct1Key), check.IsNil) 93 94 d.Overlord().Loop() 95 s.AddCleanup(func() { d.Overlord().Stop() }) 96 } 97 98 func (s *apiValidationSetsSuite) mockValidationSetsTracking(st *state.State) { 99 st.Set("validation-sets", map[string]interface{}{ 100 fmt.Sprintf("%s/foo", s.dev1acct.AccountID()): map[string]interface{}{ 101 "account-id": s.dev1acct.AccountID(), 102 "name": "foo", 103 "mode": assertstate.Enforce, 104 "pinned-at": 9, 105 "current": 99, 106 }, 107 fmt.Sprintf("%s/baz", s.dev1acct.AccountID()): map[string]interface{}{ 108 "account-id": s.dev1acct.AccountID(), 109 "name": "baz", 110 "mode": assertstate.Monitor, 111 "pinned-at": 0, 112 "current": 2, 113 }, 114 }) 115 } 116 117 func (s *apiValidationSetsSuite) mockAssert(c *check.C, name, sequence string) asserts.Assertion { 118 snaps := []interface{}{map[string]interface{}{ 119 "id": "yOqKhntON3vR7kwEbVPsILm7bUViPDzz", 120 "name": "snap-b", 121 "presence": "required", 122 "revision": "1", 123 }} 124 headers := map[string]interface{}{ 125 "authority-id": s.dev1acct.AccountID(), 126 "account-id": s.dev1acct.AccountID(), 127 "name": name, 128 "series": "16", 129 "sequence": sequence, 130 "revision": "5", 131 "timestamp": "2030-11-06T09:16:26Z", 132 "snaps": snaps, 133 } 134 vs, err := s.dev1Signing.Sign(asserts.ValidationSetType, headers, nil, "") 135 c.Assert(err, check.IsNil) 136 return vs 137 } 138 139 func (s *apiValidationSetsSuite) SeqFormingAssertion(assertType *asserts.AssertionType, sequenceKey []string, sequence int, user *auth.UserState) (asserts.Assertion, error) { 140 s.pokeStateLock() 141 return s.mockSeqFormingAssertionFn(assertType, sequenceKey, sequence, user) 142 } 143 144 func (s *apiValidationSetsSuite) TestQueryValidationSetsErrors(c *check.C) { 145 s.mockSeqFormingAssertionFn = func(assertType *asserts.AssertionType, sequenceKey []string, sequence int, user *auth.UserState) (asserts.Assertion, error) { 146 return nil, &asserts.NotFoundError{ 147 Type: assertType, 148 } 149 } 150 151 st := s.d.Overlord().State() 152 st.Lock() 153 s.mockValidationSetsTracking(st) 154 st.Unlock() 155 156 for i, tc := range []struct { 157 validationSet string 158 // sequence is normally an int, use string for passing invalid ones. 159 sequence string 160 message string 161 status int 162 }{ 163 { 164 validationSet: "abc/Xfoo", 165 message: `invalid name "Xfoo"`, 166 status: 400, 167 }, 168 { 169 validationSet: "Xfoo/bar", 170 message: `invalid account ID "Xfoo"`, 171 status: 400, 172 }, 173 { 174 validationSet: "foo/foo", 175 message: "validation set not found", 176 status: 404, 177 }, 178 { 179 validationSet: "foo/bar", 180 sequence: "1999", 181 message: "validation set not found", 182 status: 404, 183 }, 184 { 185 validationSet: "foo/bar", 186 sequence: "x", 187 message: "invalid sequence argument", 188 status: 400, 189 }, 190 { 191 validationSet: "foo/bar", 192 sequence: "-2", 193 message: "invalid sequence argument: -2", 194 status: 400, 195 }, 196 } { 197 q := url.Values{} 198 if tc.sequence != "" { 199 q.Set("sequence", tc.sequence) 200 } 201 req, err := http.NewRequest("GET", fmt.Sprintf("/v2/validation-sets/%s?%s", tc.validationSet, q.Encode()), nil) 202 c.Assert(err, check.IsNil) 203 rspe := s.errorReq(c, req, nil) 204 c.Check(rspe.Status, check.Equals, tc.status, check.Commentf("case #%d", i)) 205 c.Check(rspe.Message, check.Matches, tc.message) 206 } 207 } 208 209 func (s *apiValidationSetsSuite) TestGetValidationSetsNone(c *check.C) { 210 req, err := http.NewRequest("GET", "/v2/validation-sets", nil) 211 c.Assert(err, check.IsNil) 212 213 rsp := s.syncReq(c, req, nil) 214 c.Assert(rsp.Status, check.Equals, 200) 215 res := rsp.Result.([]daemon.ValidationSetResult) 216 c.Check(res, check.HasLen, 0) 217 } 218 219 func (s *apiValidationSetsSuite) TestListValidationSets(c *check.C) { 220 st := s.d.Overlord().State() 221 st.Lock() 222 s.mockValidationSetsTracking(st) 223 assertstatetest.AddMany(st, s.dev1acct, s.acct1Key) 224 as := s.mockAssert(c, "foo", "9") 225 err := assertstate.Add(st, as) 226 c.Check(err, check.IsNil) 227 as = s.mockAssert(c, "baz", "2") 228 err = assertstate.Add(st, as) 229 st.Unlock() 230 c.Assert(err, check.IsNil) 231 232 req, err := http.NewRequest("GET", "/v2/validation-sets", nil) 233 c.Assert(err, check.IsNil) 234 rsp := s.syncReq(c, req, nil) 235 c.Assert(rsp.Status, check.Equals, 200) 236 res := rsp.Result.([]daemon.ValidationSetResult) 237 c.Check(res, check.DeepEquals, []daemon.ValidationSetResult{ 238 { 239 AccountID: s.dev1acct.AccountID(), 240 Name: "baz", 241 Mode: "monitor", 242 Sequence: 2, 243 Valid: false, 244 }, 245 { 246 AccountID: s.dev1acct.AccountID(), 247 Name: "foo", 248 PinnedAt: 9, 249 Mode: "enforce", 250 Sequence: 99, 251 Valid: false, 252 }, 253 }) 254 } 255 256 func (s *apiValidationSetsSuite) TestGetValidationSetOne(c *check.C) { 257 s.mockSeqFormingAssertionFn = func(assertType *asserts.AssertionType, sequenceKey []string, sequence int, user *auth.UserState) (asserts.Assertion, error) { 258 return nil, &asserts.NotFoundError{ 259 Type: assertType, 260 } 261 } 262 263 st := s.d.Overlord().State() 264 st.Lock() 265 as := s.mockAssert(c, "baz", "2") 266 assertstatetest.AddMany(st, s.dev1acct, s.acct1Key, as) 267 s.mockValidationSetsTracking(st) 268 st.Unlock() 269 270 req, err := http.NewRequest("GET", fmt.Sprintf("/v2/validation-sets/%s/baz", s.dev1acct.AccountID()), nil) 271 c.Assert(err, check.IsNil) 272 273 rsp := s.syncReq(c, req, nil) 274 c.Assert(rsp.Status, check.Equals, 200) 275 res := rsp.Result.(daemon.ValidationSetResult) 276 c.Check(res, check.DeepEquals, daemon.ValidationSetResult{ 277 AccountID: s.dev1acct.AccountID(), 278 Name: "baz", 279 Mode: "monitor", 280 Sequence: 2, 281 Valid: false, 282 }) 283 } 284 285 func (s *apiValidationSetsSuite) TestGetValidationSetPinned(c *check.C) { 286 q := url.Values{} 287 q.Set("sequence", "9") 288 req, err := http.NewRequest("GET", fmt.Sprintf("/v2/validation-sets/%s/foo?%s", s.dev1acct.AccountID(), q.Encode()), nil) 289 c.Assert(err, check.IsNil) 290 291 st := s.d.Overlord().State() 292 st.Lock() 293 as := s.mockAssert(c, "foo", "9") 294 assertstatetest.AddMany(st, s.dev1acct, s.acct1Key, as) 295 s.mockValidationSetsTracking(st) 296 st.Unlock() 297 c.Assert(err, check.IsNil) 298 299 rsp := s.syncReq(c, req, nil) 300 c.Assert(rsp.Status, check.Equals, 200) 301 res := rsp.Result.(daemon.ValidationSetResult) 302 c.Check(res, check.DeepEquals, daemon.ValidationSetResult{ 303 AccountID: s.dev1acct.AccountID(), 304 Name: "foo", 305 PinnedAt: 9, 306 Mode: "enforce", 307 Sequence: 99, 308 Valid: false, 309 }) 310 } 311 312 func (s *apiValidationSetsSuite) TestGetValidationSetNotFound(c *check.C) { 313 s.mockSeqFormingAssertionFn = func(assertType *asserts.AssertionType, sequenceKey []string, sequence int, user *auth.UserState) (asserts.Assertion, error) { 314 return nil, &asserts.NotFoundError{ 315 Type: assertType, 316 } 317 } 318 319 req, err := http.NewRequest("GET", "/v2/validation-sets/foo/other", nil) 320 c.Assert(err, check.IsNil) 321 322 st := s.d.Overlord().State() 323 st.Lock() 324 s.mockValidationSetsTracking(st) 325 st.Unlock() 326 327 rspe := s.errorReq(c, req, nil) 328 c.Assert(rspe.Status, check.Equals, 404) 329 c.Check(string(rspe.Kind), check.Equals, "validation-set-not-found") 330 c.Check(rspe.Value, check.DeepEquals, map[string]interface{}{ 331 "account-id": "foo", 332 "name": "other", 333 }) 334 } 335 336 var validationSetAssertion = []byte("type: validation-set\n" + 337 "format: 1\n" + 338 "authority-id: foo\n" + 339 "account-id: foo\n" + 340 "name: other\n" + 341 "sequence: 2\n" + 342 "revision: 5\n" + 343 "series: 16\n" + 344 "snaps:\n" + 345 " -\n" + 346 " id: yOqKhntON3vR7kwEbVPsILm7bUViPDzz\n" + 347 " name: snap-b\n" + 348 " presence: required\n" + 349 " revision: 1\n" + 350 "timestamp: 2020-11-06T09:16:26Z\n" + 351 "sign-key-sha3-384: 7bbncP0c4RcufwReeiylCe0S7IMCn-tHLNSCgeOVmV3K-7_MzpAHgJDYeOjldefE\n\n" + 352 "AXNpZw==") 353 354 func (s *apiValidationSetsSuite) TestGetValidationSetLatestFromRemote(c *check.C) { 355 s.mockSeqFormingAssertionFn = func(assertType *asserts.AssertionType, sequenceKey []string, sequence int, user *auth.UserState) (asserts.Assertion, error) { 356 c.Assert(assertType, check.NotNil) 357 c.Assert(assertType.Name, check.Equals, "validation-set") 358 // no sequence number element, querying the latest 359 c.Assert(sequenceKey, check.DeepEquals, []string{"16", "foo", "other"}) 360 c.Assert(sequence, check.Equals, 0) 361 as, err := asserts.Decode(validationSetAssertion) 362 c.Assert(err, check.IsNil) 363 // validity 364 c.Assert(as.Type().Name, check.Equals, "validation-set") 365 return as, nil 366 } 367 368 restore := daemon.MockCheckInstalledSnaps(func(vsets *snapasserts.ValidationSets, snaps []*snapasserts.InstalledSnap, ignoreValidation map[string]bool) error { 369 c.Assert(vsets, check.NotNil) 370 sort.Sort(byName(snaps)) 371 c.Assert(snaps, check.DeepEquals, []*snapasserts.InstalledSnap{ 372 { 373 SnapRef: naming.NewSnapRef("snap-a", "snapaid"), 374 Revision: snap.R(2), 375 }, 376 { 377 SnapRef: naming.NewSnapRef("snap-b", "snapbid"), 378 Revision: snap.R(4), 379 }, 380 }) 381 c.Assert(ignoreValidation, check.IsNil) 382 // nil indicates successful validation 383 return nil 384 }) 385 defer restore() 386 387 req, err := http.NewRequest("GET", "/v2/validation-sets/foo/other", nil) 388 c.Assert(err, check.IsNil) 389 390 st := s.d.Overlord().State() 391 st.Lock() 392 s.mockValidationSetsTracking(st) 393 394 snapstate.Set(st, "snap-a", &snapstate.SnapState{ 395 Active: true, 396 Sequence: []*snap.SideInfo{{RealName: "snap-a", Revision: snap.R(2), SnapID: "snapaid"}}, 397 Current: snap.R(2), 398 }) 399 snapstate.Set(st, "snap-b", &snapstate.SnapState{ 400 Active: true, 401 Sequence: []*snap.SideInfo{{RealName: "snap-b", Revision: snap.R(4), SnapID: "snapbid"}}, 402 Current: snap.R(4), 403 }) 404 405 st.Unlock() 406 407 rsp := s.syncReq(c, req, nil) 408 c.Assert(rsp.Status, check.Equals, 200) 409 res := rsp.Result.(daemon.ValidationSetResult) 410 c.Check(res, check.DeepEquals, daemon.ValidationSetResult{ 411 AccountID: "foo", 412 Name: "other", 413 Sequence: 2, 414 Valid: true, 415 }) 416 } 417 418 func (s *apiValidationSetsSuite) TestGetValidationSetLatestFromRemoteValidationFails(c *check.C) { 419 s.mockSeqFormingAssertionFn = func(assertType *asserts.AssertionType, sequenceKey []string, sequence int, user *auth.UserState) (asserts.Assertion, error) { 420 as, err := asserts.Decode(validationSetAssertion) 421 c.Assert(err, check.IsNil) 422 return as, nil 423 } 424 restore := daemon.MockCheckInstalledSnaps(func(vsets *snapasserts.ValidationSets, snaps []*snapasserts.InstalledSnap, ignoreValidation map[string]bool) error { 425 return &snapasserts.ValidationSetsValidationError{} 426 }) 427 defer restore() 428 429 req, err := http.NewRequest("GET", "/v2/validation-sets/foo/other", nil) 430 c.Assert(err, check.IsNil) 431 rsp := s.syncReq(c, req, nil) 432 c.Assert(rsp.Status, check.Equals, 200) 433 434 res := rsp.Result.(daemon.ValidationSetResult) 435 c.Check(res, check.DeepEquals, daemon.ValidationSetResult{ 436 AccountID: "foo", 437 Name: "other", 438 Sequence: 2, 439 Valid: false, 440 }) 441 } 442 443 func (s *apiValidationSetsSuite) TestGetValidationSetLatestFromRemoteRealValidation(c *check.C) { 444 s.mockSeqFormingAssertionFn = func(assertType *asserts.AssertionType, sequenceKey []string, sequence int, user *auth.UserState) (asserts.Assertion, error) { 445 as, err := asserts.Decode(validationSetAssertion) 446 c.Assert(err, check.IsNil) 447 return as, nil 448 } 449 450 st := s.d.Overlord().State() 451 452 for _, tc := range []struct { 453 revision snap.Revision 454 expectedValidationStatus bool 455 }{ 456 // required at revision 1 per validationSetAssertion, so it's valid 457 {snap.R(1), true}, 458 // but revision 2 is not valid 459 {snap.R(2), false}, 460 } { 461 st.Lock() 462 snapstate.Set(st, "snap-b", &snapstate.SnapState{ 463 Active: true, 464 Sequence: []*snap.SideInfo{{RealName: "snap-b", Revision: tc.revision, SnapID: "yOqKhntON3vR7kwEbVPsILm7bUViPDzz"}}, 465 Current: tc.revision, 466 }) 467 st.Unlock() 468 469 req, err := http.NewRequest("GET", "/v2/validation-sets/foo/other", nil) 470 c.Assert(err, check.IsNil) 471 rsp := s.syncReq(c, req, nil) 472 c.Assert(rsp.Status, check.Equals, 200) 473 474 res := rsp.Result.(daemon.ValidationSetResult) 475 c.Check(res, check.DeepEquals, daemon.ValidationSetResult{ 476 AccountID: "foo", 477 Name: "other", 478 Sequence: 2, 479 Valid: tc.expectedValidationStatus, 480 }) 481 } 482 } 483 484 func (s *apiValidationSetsSuite) TestGetValidationSetSpecificSequenceFromRemote(c *check.C) { 485 s.mockSeqFormingAssertionFn = func(assertType *asserts.AssertionType, sequenceKey []string, sequence int, user *auth.UserState) (asserts.Assertion, error) { 486 c.Assert(assertType, check.NotNil) 487 c.Assert(assertType.Name, check.Equals, "validation-set") 488 c.Assert(sequenceKey, check.DeepEquals, []string{"16", "foo", "other"}) 489 c.Assert(sequence, check.Equals, 2) 490 as, err := asserts.Decode(validationSetAssertion) 491 c.Assert(err, check.IsNil) 492 return as, nil 493 } 494 495 restore := daemon.MockCheckInstalledSnaps(func(vsets *snapasserts.ValidationSets, snaps []*snapasserts.InstalledSnap, ignoreValidation map[string]bool) error { 496 c.Assert(vsets, check.NotNil) 497 sort.Sort(byName(snaps)) 498 c.Assert(snaps, check.DeepEquals, []*snapasserts.InstalledSnap{ 499 { 500 SnapRef: naming.NewSnapRef("snap-a", "snapaid"), 501 Revision: snap.R(33), 502 }, 503 }) 504 c.Assert(ignoreValidation, check.IsNil) 505 // nil indicates successful validation 506 return nil 507 }) 508 defer restore() 509 510 q := url.Values{} 511 q.Set("sequence", "2") 512 req, err := http.NewRequest("GET", "/v2/validation-sets/foo/other?"+q.Encode(), nil) 513 c.Assert(err, check.IsNil) 514 515 st := s.d.Overlord().State() 516 st.Lock() 517 s.mockValidationSetsTracking(st) 518 519 snapstate.Set(st, "snap-a", &snapstate.SnapState{ 520 Active: true, 521 Sequence: []*snap.SideInfo{{RealName: "snap-a", Revision: snap.R(33), SnapID: "snapaid"}}, 522 Current: snap.R(33), 523 }) 524 525 st.Unlock() 526 527 rsp := s.syncReq(c, req, nil) 528 c.Assert(rsp.Status, check.Equals, 200) 529 res := rsp.Result.(daemon.ValidationSetResult) 530 c.Check(res, check.DeepEquals, daemon.ValidationSetResult{ 531 AccountID: "foo", 532 Name: "other", 533 Sequence: 2, 534 Valid: true, 535 }) 536 } 537 538 func (s *apiValidationSetsSuite) TestGetValidationSetFromRemoteFallbackToLocalAssertion(c *check.C) { 539 s.mockSeqFormingAssertionFn = func(assertType *asserts.AssertionType, sequenceKey []string, sequence int, user *auth.UserState) (asserts.Assertion, error) { 540 // not found in the store 541 return nil, &asserts.NotFoundError{ 542 Type: assertType, 543 } 544 } 545 restore := daemon.MockCheckInstalledSnaps(func(vsets *snapasserts.ValidationSets, snaps []*snapasserts.InstalledSnap, ignoreValidation map[string]bool) error { 546 // nil indicates successful validation 547 return nil 548 }) 549 defer restore() 550 551 st := s.d.Overlord().State() 552 st.Lock() 553 // assertion available in the local db (from snap ack) 554 vs := s.mockAssert(c, "bar", "2") 555 assertstatetest.AddMany(st, s.dev1acct, s.acct1Key, vs) 556 st.Unlock() 557 558 q := url.Values{} 559 q.Set("sequence", "2") 560 req, err := http.NewRequest("GET", fmt.Sprintf("/v2/validation-sets/%s/bar?%s", s.dev1acct.AccountID(), q.Encode()), nil) 561 c.Assert(err, check.IsNil) 562 563 rsp := s.syncReq(c, req, nil) 564 c.Assert(rsp.Status, check.Equals, 200) 565 res := rsp.Result.(daemon.ValidationSetResult) 566 c.Check(res, check.DeepEquals, daemon.ValidationSetResult{ 567 AccountID: s.dev1acct.AccountID(), 568 Name: "bar", 569 Sequence: 2, 570 Valid: true, 571 }) 572 } 573 574 func (s *apiValidationSetsSuite) TestGetValidationSetPinnedNotFound(c *check.C) { 575 s.mockSeqFormingAssertionFn = func(assertType *asserts.AssertionType, sequenceKey []string, sequence int, user *auth.UserState) (asserts.Assertion, error) { 576 return nil, &asserts.NotFoundError{ 577 Type: assertType, 578 } 579 } 580 581 q := url.Values{} 582 q.Set("sequence", "333") 583 req, err := http.NewRequest("GET", "/v2/validation-sets/foo/bar?"+q.Encode(), nil) 584 c.Assert(err, check.IsNil) 585 586 st := s.d.Overlord().State() 587 st.Lock() 588 s.mockValidationSetsTracking(st) 589 st.Unlock() 590 591 rspe := s.errorReq(c, req, nil) 592 c.Assert(rspe.Status, check.Equals, 404) 593 c.Check(string(rspe.Kind), check.Equals, "validation-set-not-found") 594 c.Check(rspe.Value, check.DeepEquals, map[string]interface{}{ 595 "account-id": "foo", 596 "name": "bar", 597 "sequence": 333, 598 }) 599 } 600 601 func (s *apiValidationSetsSuite) TestApplyValidationSetMonitorModePinnedLocalOnly(c *check.C) { 602 st := s.d.Overlord().State() 603 st.Lock() 604 s.mockValidationSetsTracking(st) 605 assertstatetest.AddMany(st, s.dev1acct, s.acct1Key) 606 as := s.mockAssert(c, "bar", "99") 607 err := assertstate.Add(st, as) 608 st.Unlock() 609 c.Assert(err, check.IsNil) 610 611 var called int 612 restore := daemon.MockAssertstateMonitorValidationSet(func(st *state.State, accountID, name string, sequence, userID int) (*assertstate.ValidationSetTracking, error) { 613 c.Assert(accountID, check.Equals, s.dev1acct.AccountID()) 614 c.Assert(name, check.Equals, "bar") 615 c.Assert(sequence, check.Equals, 99) 616 called++ 617 return &assertstate.ValidationSetTracking{AccountID: accountID, Name: name, PinnedAt: 99, Current: 99999}, nil 618 }) 619 defer restore() 620 621 restore = daemon.MockCheckInstalledSnaps(func(vsets *snapasserts.ValidationSets, snaps []*snapasserts.InstalledSnap, ignoreValidation map[string]bool) error { 622 // nil indicates successful validation 623 return nil 624 }) 625 defer restore() 626 627 body := `{"action":"apply","mode":"monitor", "sequence":99}` 628 req, err := http.NewRequest("POST", fmt.Sprintf("/v2/validation-sets/%s/bar", s.dev1acct.AccountID()), strings.NewReader(body)) 629 c.Assert(err, check.IsNil) 630 631 rsp := s.syncReq(c, req, nil) 632 c.Assert(rsp.Status, check.Equals, 200) 633 res := rsp.Result.(daemon.ValidationSetResult) 634 c.Check(res, check.DeepEquals, daemon.ValidationSetResult{ 635 AccountID: s.dev1acct.AccountID(), 636 Name: "bar", 637 Mode: "monitor", 638 PinnedAt: 99, 639 Sequence: 99999, 640 Valid: true, 641 }) 642 c.Check(called, check.Equals, 1) 643 } 644 645 func (s *apiValidationSetsSuite) TestApplyValidationSetMonitorModeError(c *check.C) { 646 restore := daemon.MockAssertstateMonitorValidationSet(func(st *state.State, accountID, name string, sequence, userID int) (*assertstate.ValidationSetTracking, error) { 647 return nil, fmt.Errorf("boom") 648 }) 649 defer restore() 650 651 body := `{"action":"apply","mode":"monitor"}` 652 req, err := http.NewRequest("POST", fmt.Sprintf("/v2/validation-sets/%s/bar", s.dev1acct.AccountID()), strings.NewReader(body)) 653 c.Assert(err, check.IsNil) 654 655 rspe := s.errorReq(c, req, nil) 656 c.Assert(rspe.Status, check.Equals, 400) 657 c.Check(rspe.Message, check.Equals, fmt.Sprintf(`cannot get validation set assertion for %s/bar: boom`, s.dev1acct.AccountID())) 658 } 659 660 func (s *apiValidationSetsSuite) TestForgetValidationSet(c *check.C) { 661 st := s.d.Overlord().State() 662 663 for i, sequence := range []int{0, 9} { 664 st.Lock() 665 s.mockValidationSetsTracking(st) 666 st.Unlock() 667 668 var body string 669 if sequence != 0 { 670 body = fmt.Sprintf(`{"action":"forget", "sequence":%d}`, sequence) 671 } else { 672 body = `{"action":"forget"}` 673 } 674 675 var tr assertstate.ValidationSetTracking 676 677 st.Lock() 678 // validity, it exists before removing 679 err := assertstate.GetValidationSet(st, s.dev1acct.AccountID(), "foo", &tr) 680 st.Unlock() 681 c.Assert(err, check.IsNil) 682 c.Check(tr.AccountID, check.Equals, s.dev1acct.AccountID()) 683 c.Check(tr.Name, check.Equals, "foo") 684 685 req, err := http.NewRequest("POST", fmt.Sprintf("/v2/validation-sets/%s/foo", s.dev1acct.AccountID()), strings.NewReader(body)) 686 c.Assert(err, check.IsNil) 687 rsp := s.syncReq(c, req, nil) 688 c.Assert(rsp.Status, check.Equals, 200, check.Commentf("case #%d", i)) 689 690 // after forget it's removed 691 st.Lock() 692 err = assertstate.GetValidationSet(st, s.dev1acct.AccountID(), "foo", &tr) 693 st.Unlock() 694 c.Assert(err, testutil.ErrorIs, state.ErrNoState) 695 696 // and forget again fails 697 req, err = http.NewRequest("POST", fmt.Sprintf("/v2/validation-sets/%s/foo", s.dev1acct.AccountID()), strings.NewReader(body)) 698 c.Assert(err, check.IsNil) 699 rspe := s.errorReq(c, req, nil) 700 c.Assert(rspe.Status, check.Equals, 404, check.Commentf("case #%d", i)) 701 } 702 } 703 704 func (s *apiValidationSetsSuite) TestApplyValidationSetsErrors(c *check.C) { 705 st := s.d.Overlord().State() 706 st.Lock() 707 s.mockValidationSetsTracking(st) 708 st.Unlock() 709 710 for i, tc := range []struct { 711 validationSet string 712 mode string 713 // sequence is normally an int, use string for passing invalid ones. 714 sequence string 715 message string 716 status int 717 }{ 718 { 719 validationSet: "0/zzz", 720 mode: "monitor", 721 message: `invalid account ID "0"`, 722 status: 400, 723 }, 724 { 725 validationSet: "Xfoo/bar", 726 mode: "monitor", 727 message: `invalid account ID "Xfoo"`, 728 status: 400, 729 }, 730 { 731 validationSet: "foo/Xabc", 732 mode: "monitor", 733 message: `invalid name "Xabc"`, 734 status: 400, 735 }, 736 { 737 validationSet: "foo/bar", 738 sequence: "x", 739 message: "cannot decode request body into validation set action: invalid character 'x' looking for beginning of value", 740 status: 400, 741 }, 742 { 743 validationSet: "foo/bar", 744 mode: "bad", 745 message: `invalid mode "bad"`, 746 status: 400, 747 }, 748 { 749 validationSet: "foo/bar", 750 sequence: "-1", 751 mode: "monitor", 752 message: `invalid sequence argument: -1`, 753 status: 400, 754 }, 755 } { 756 var body string 757 if tc.sequence != "" { 758 body = fmt.Sprintf(`{"action":"apply","mode":"%s", "sequence":%s}`, tc.mode, tc.sequence) 759 } else { 760 body = fmt.Sprintf(`{"action":"apply","mode":"%s"}`, tc.mode) 761 } 762 req, err := http.NewRequest("POST", fmt.Sprintf("/v2/validation-sets/%s", tc.validationSet), strings.NewReader(body)) 763 c.Assert(err, check.IsNil) 764 rspe := s.errorReq(c, req, nil) 765 c.Check(rspe.Status, check.Equals, tc.status, check.Commentf("case #%d", i)) 766 c.Check(rspe.Message, check.Matches, tc.message) 767 } 768 } 769 770 func (s *apiValidationSetsSuite) TestApplyValidationSetUnsupportedAction(c *check.C) { 771 body := `{"action":"baz","mode":"monitor"}` 772 773 req, err := http.NewRequest("POST", "/v2/validation-sets/foo/bar", strings.NewReader(body)) 774 c.Assert(err, check.IsNil) 775 776 rspe := s.errorReq(c, req, nil) 777 c.Check(rspe.Status, check.Equals, 400) 778 c.Check(rspe.Message, check.Matches, `unsupported action "baz"`) 779 } 780 781 func (s *apiValidationSetsSuite) TestApplyValidationSetEnforceMode(c *check.C) { 782 st := s.d.Overlord().State() 783 st.Lock() 784 defer st.Unlock() 785 786 s.mockValidationSetsTracking(st) 787 assertstatetest.AddMany(st, s.dev1acct, s.acct1Key) 788 as := s.mockAssert(c, "bar", "99") 789 err := assertstate.Add(st, as) 790 c.Assert(err, check.IsNil) 791 792 var called int 793 restore := daemon.MockAssertstateFetchEnforceValidationSet(func(st *state.State, accountID, name string, sequence int, userID int, snaps []*snapasserts.InstalledSnap, ignoreValidation map[string]bool) (*assertstate.ValidationSetTracking, error) { 794 c.Check(ignoreValidation, check.HasLen, 0) 795 c.Assert(accountID, check.Equals, s.dev1acct.AccountID()) 796 c.Assert(name, check.Equals, "bar") 797 c.Assert(sequence, check.Equals, 0) 798 c.Check(userID, check.Equals, 0) 799 called++ 800 return &assertstate.ValidationSetTracking{AccountID: accountID, Name: name, Mode: assertstate.Enforce, Current: 99}, nil 801 }) 802 defer restore() 803 804 snapstate.Set(st, "snap-b", &snapstate.SnapState{ 805 Active: true, 806 Sequence: []*snap.SideInfo{{RealName: "snap-b", Revision: snap.R(1), SnapID: "yOqKhntON3vR7kwEbVPsILm7bUViPDzz"}}, 807 Current: snap.R(1), 808 }) 809 810 assertstatetest.AddMany(st, s.dev1acct, s.acct1Key) 811 812 st.Unlock() 813 defer st.Lock() 814 body := `{"action":"apply","mode":"enforce"}` 815 req, err := http.NewRequest("POST", fmt.Sprintf("/v2/validation-sets/%s/bar", s.dev1acct.AccountID()), strings.NewReader(body)) 816 c.Assert(err, check.IsNil) 817 818 rsp := s.syncReq(c, req, nil) 819 c.Assert(rsp.Status, check.Equals, 200) 820 res := rsp.Result.(daemon.ValidationSetResult) 821 c.Check(res, check.DeepEquals, daemon.ValidationSetResult{ 822 AccountID: s.dev1acct.AccountID(), 823 Name: "bar", 824 Mode: "enforce", 825 Sequence: 99, 826 Valid: true, 827 }) 828 c.Check(called, check.Equals, 1) 829 } 830 831 func (s *apiValidationSetsSuite) TestApplyValidationSetEnforceModeIgnoreValidationOK(c *check.C) { 832 st := s.d.Overlord().State() 833 st.Lock() 834 defer st.Unlock() 835 836 s.mockValidationSetsTracking(st) 837 assertstatetest.AddMany(st, s.dev1acct, s.acct1Key) 838 as := s.mockAssert(c, "bar", "99") 839 err := assertstate.Add(st, as) 840 c.Assert(err, check.IsNil) 841 842 var called int 843 restore := daemon.MockAssertstateFetchEnforceValidationSet(func(st *state.State, accountID, name string, sequence int, userID int, snaps []*snapasserts.InstalledSnap, ignoreValidation map[string]bool) (*assertstate.ValidationSetTracking, error) { 844 c.Check(ignoreValidation, check.DeepEquals, map[string]bool{"snap-b": true}) 845 c.Check(snaps, testutil.DeepUnsortedMatches, []*snapasserts.InstalledSnap{ 846 snapasserts.NewInstalledSnap("snap-b", "yOqKhntON3vR7kwEbVPsILm7bUViPDzz", snap.R("1"))}) 847 c.Assert(accountID, check.Equals, s.dev1acct.AccountID()) 848 c.Assert(name, check.Equals, "bar") 849 c.Assert(sequence, check.Equals, 0) 850 c.Check(userID, check.Equals, 0) 851 called++ 852 return &assertstate.ValidationSetTracking{AccountID: accountID, Name: name, Mode: assertstate.Enforce, Current: 99}, nil 853 }) 854 defer restore() 855 856 snapstate.Set(st, "snap-b", &snapstate.SnapState{ 857 Active: true, 858 Sequence: []*snap.SideInfo{{RealName: "snap-b", Revision: snap.R(1), SnapID: "yOqKhntON3vR7kwEbVPsILm7bUViPDzz"}}, 859 Current: snap.R(1), 860 Flags: snapstate.Flags{IgnoreValidation: true}, 861 }) 862 863 assertstatetest.AddMany(st, s.dev1acct, s.acct1Key) 864 865 st.Unlock() 866 defer st.Lock() 867 body := `{"action":"apply","mode":"enforce"}` 868 req, err := http.NewRequest("POST", fmt.Sprintf("/v2/validation-sets/%s/bar", s.dev1acct.AccountID()), strings.NewReader(body)) 869 c.Assert(err, check.IsNil) 870 871 rsp := s.syncReq(c, req, nil) 872 c.Assert(rsp.Status, check.Equals, 200) 873 res := rsp.Result.(daemon.ValidationSetResult) 874 c.Check(res, check.DeepEquals, daemon.ValidationSetResult{ 875 AccountID: s.dev1acct.AccountID(), 876 Name: "bar", 877 Mode: "enforce", 878 Sequence: 99, 879 Valid: true, 880 }) 881 c.Check(called, check.Equals, 1) 882 } 883 884 func (s *apiValidationSetsSuite) TestApplyValidationSetEnforceModeSpecificSequence(c *check.C) { 885 st := s.d.Overlord().State() 886 st.Lock() 887 defer st.Unlock() 888 889 s.mockValidationSetsTracking(st) 890 assertstatetest.AddMany(st, s.dev1acct, s.acct1Key) 891 as := s.mockAssert(c, "bar", "5") 892 err := assertstate.Add(st, as) 893 c.Assert(err, check.IsNil) 894 895 var called int 896 restore := daemon.MockAssertstateFetchEnforceValidationSet(func(st *state.State, accountID, name string, sequence int, userID int, snaps []*snapasserts.InstalledSnap, ignoreValidation map[string]bool) (*assertstate.ValidationSetTracking, error) { 897 c.Assert(accountID, check.Equals, s.dev1acct.AccountID()) 898 c.Assert(name, check.Equals, "bar") 899 c.Assert(sequence, check.Equals, 5) 900 c.Check(userID, check.Equals, 0) 901 called++ 902 return &assertstate.ValidationSetTracking{AccountID: accountID, Name: name, Mode: assertstate.Enforce, PinnedAt: 5, Current: 5}, nil 903 }) 904 defer restore() 905 906 snapstate.Set(st, "snap-b", &snapstate.SnapState{ 907 Active: true, 908 Sequence: []*snap.SideInfo{{RealName: "snap-b", Revision: snap.R(1), SnapID: "yOqKhntON3vR7kwEbVPsILm7bUViPDzz"}}, 909 Current: snap.R(1), 910 }) 911 912 assertstatetest.AddMany(st, s.dev1acct, s.acct1Key) 913 914 st.Unlock() 915 defer st.Lock() 916 body := `{"action":"apply","mode":"enforce","sequence":5}` 917 req, err := http.NewRequest("POST", fmt.Sprintf("/v2/validation-sets/%s/bar", s.dev1acct.AccountID()), strings.NewReader(body)) 918 c.Assert(err, check.IsNil) 919 920 rsp := s.syncReq(c, req, nil) 921 c.Assert(rsp.Status, check.Equals, 200) 922 res := rsp.Result.(daemon.ValidationSetResult) 923 c.Check(res, check.DeepEquals, daemon.ValidationSetResult{ 924 AccountID: s.dev1acct.AccountID(), 925 Name: "bar", 926 Mode: "enforce", 927 PinnedAt: 5, 928 Sequence: 5, 929 Valid: true, 930 }) 931 c.Check(called, check.Equals, 1) 932 } 933 934 func (s *apiValidationSetsSuite) TestApplyValidationSetEnforceModeError(c *check.C) { 935 restore := daemon.MockAssertstateFetchEnforceValidationSet(func(st *state.State, accountID, name string, sequence int, userID int, snaps []*snapasserts.InstalledSnap, ignoreValidation map[string]bool) (*assertstate.ValidationSetTracking, error) { 936 return nil, fmt.Errorf("boom") 937 }) 938 defer restore() 939 940 st := s.d.Overlord().State() 941 st.Lock() 942 defer st.Unlock() 943 944 snapstate.Set(st, "snap-b", &snapstate.SnapState{ 945 Active: true, 946 Sequence: []*snap.SideInfo{{RealName: "snap-b", Revision: snap.R(1), SnapID: "yOqKhntON3vR7kwEbVPsILm7bUViPDzz"}}, 947 Current: snap.R(1), 948 }) 949 950 assertstatetest.AddMany(st, s.dev1acct, s.acct1Key) 951 952 st.Unlock() 953 defer st.Lock() 954 body := `{"action":"apply","mode":"enforce"}` 955 req, err := http.NewRequest("POST", fmt.Sprintf("/v2/validation-sets/%s/bar", s.dev1acct.AccountID()), strings.NewReader(body)) 956 c.Assert(err, check.IsNil) 957 958 rspe := s.errorReq(c, req, nil) 959 c.Assert(rspe.Status, check.Equals, 400) 960 c.Check(string(rspe.Message), check.Equals, "cannot enforce validation set: boom") 961 }