func (as *authSuite) TestAuthContextUpdateDeviceAuthOtherUpdate(c *C) { as.state.Lock() device, _ := auth.Device(as.state) otherUpdateDevice := *device otherUpdateDevice.SessionMacaroon = "othe-session-macaroon" otherUpdateDevice.KeyID = "KEYID" err := auth.SetDevice(as.state, &otherUpdateDevice) as.state.Unlock() c.Check(err, IsNil) sessionMacaroon := "the-device-macaroon" authContext := auth.NewAuthContext(as.state, nil) curDevice, err := authContext.UpdateDeviceAuth(device, sessionMacaroon) c.Assert(err, IsNil) as.state.Lock() deviceFromState, err := auth.Device(as.state) as.state.Unlock() c.Check(err, IsNil) c.Check(deviceFromState, DeepEquals, curDevice) c.Check(curDevice, DeepEquals, &auth.DeviceState{ KeyID: "KEYID", SessionMacaroon: sessionMacaroon, }) }
func (s *deviceMgrSuite) TestDoRequestSerialIdempotentAfterGotSerial(c *C) { privKey, _ := assertstest.GenerateKey(1024) mockServer := s.mockServer(c, "REQID-1") defer mockServer.Close() mockRequestIDURL := mockServer.URL + "/identity/api/v1/request-id" restore := devicestate.MockRequestIDURL(mockRequestIDURL) defer restore() mockSerialRequestURL := mockServer.URL + "/identity/api/v1/devices" restore = devicestate.MockSerialRequestURL(mockSerialRequestURL) defer restore() restore = devicestate.MockRepeatRequestSerial("after-got-serial") defer restore() s.state.Lock() defer s.state.Unlock() // setup state as done by first-boot/Ensure/doGenerateDeviceKey auth.SetDevice(s.state, &auth.DeviceState{ Brand: "canonical", Model: "pc", KeyID: privKey.PublicKey().ID(), }) s.mgr.KeypairManager().Put(privKey) t := s.state.NewTask("request-serial", "test") chg := s.state.NewChange("become-operational", "...") chg.AddTask(t) s.state.Unlock() s.mgr.Ensure() s.mgr.Wait() s.state.Lock() c.Check(chg.Status(), Equals, state.DoingStatus) device, err := auth.Device(s.state) c.Check(err, IsNil) _, err = s.db.Find(asserts.SerialType, map[string]string{ "brand-id": "canonical", "model": "pc", "serial": "9999", }) c.Assert(err, Equals, asserts.ErrNotFound) s.state.Unlock() s.mgr.Ensure() s.mgr.Wait() s.state.Lock() // Repeated handler run but set original serial. c.Check(chg.Status(), Equals, state.DoneStatus) device, err = auth.Device(s.state) c.Check(err, IsNil) c.Check(device.Serial, Equals, "9999") }
func (as *authSuite) TestSetDevice(c *C) { as.state.Lock() device, err := auth.Device(as.state) as.state.Unlock() c.Check(err, IsNil) c.Check(device, DeepEquals, &auth.DeviceState{}) as.state.Lock() err = auth.SetDevice(as.state, &auth.DeviceState{Brand: "some-brand"}) c.Check(err, IsNil) device, err = auth.Device(as.state) as.state.Unlock() c.Check(err, IsNil) c.Check(device, DeepEquals, &auth.DeviceState{Brand: "some-brand"}) }
func (m *DeviceManager) doGenerateDeviceKey(t *state.Task, _ *tomb.Tomb) error { st := t.State() st.Lock() defer st.Unlock() device, err := auth.Device(st) if err != nil { return err } if device.KeyID != "" { // nothing to do return nil } keyPair, err := rsa.GenerateKey(rand.Reader, keyLength) if err != nil { return fmt.Errorf("cannot generate device key pair: %v", err) } privKey := asserts.RSAPrivateKey(keyPair) err = m.keypairMgr.Put(privKey) if err != nil { return fmt.Errorf("cannot store device key pair: %v", err) } device.KeyID = privKey.PublicKey().ID() err = auth.SetDevice(st, device) if err != nil { return err } t.SetStatus(state.DoneStatus) return nil }
func importAssertionsFromSeed(st *state.State) error { device, err := auth.Device(st) if err != nil { return err } // set device,model from the model assertion assertSeedDir := filepath.Join(dirs.SnapSeedDir, "assertions") dc, err := ioutil.ReadDir(assertSeedDir) if err != nil { return fmt.Errorf("cannot read assert seed dir: %s", err) } // FIXME: remove this check once asserts are mandatory if len(dc) == 0 { return nil } // collect var modelRef *asserts.Ref batch := assertstate.NewBatch() for _, fi := range dc { fn := filepath.Join(assertSeedDir, fi.Name()) refs, err := readAsserts(fn, batch) if err != nil { return fmt.Errorf("cannot read assertions: %s", err) } for _, ref := range refs { if ref.Type == asserts.ModelType { if modelRef != nil && modelRef.Unique() != ref.Unique() { return fmt.Errorf("cannot add more than one model assertion") } modelRef = ref } } } // verify we have one model assertion if modelRef == nil { return fmt.Errorf("need a model assertion") } if err := batch.Commit(st); err != nil { return err } a, err := modelRef.Resolve(assertstate.DB(st).Find) if err != nil { return fmt.Errorf("internal error: cannot find just added assertion %v: %v", modelRef, err) } modelAssertion := a.(*asserts.Model) // set device,model from the model assertion device.Brand = modelAssertion.BrandID() device.Model = modelAssertion.Model() if err := auth.SetDevice(st, device); err != nil { return err } return nil }
func (s *FirstBootTestSuite) TestImportAssertionsFromSeedHappy(c *C) { ovld, err := overlord.New() c.Assert(err, IsNil) st := ovld.State() // add a bunch of assert files assertsChain := s.makeModelAssertionChain(c) for i, as := range assertsChain { fn := filepath.Join(dirs.SnapSeedDir, "assertions", strconv.Itoa(i)) err := ioutil.WriteFile(fn, asserts.Encode(as), 0644) c.Assert(err, IsNil) } // import them err = boot.ImportAssertionsFromSeed(st) c.Assert(err, IsNil) // verify that the model was added st.Lock() defer st.Unlock() db := assertstate.DB(st) as, err := db.Find(asserts.ModelType, map[string]string{ "series": "16", "brand-id": "my-brand", "model": "my-model", }) c.Assert(err, IsNil) _, ok := as.(*asserts.Model) c.Check(ok, Equals, true) ds, err := auth.Device(st) c.Assert(err, IsNil) c.Check(ds.Brand, Equals, "my-brand") c.Check(ds.Model, Equals, "my-model") }
func (m *DeviceManager) ensureOperational() error { m.state.Lock() defer m.state.Unlock() device, err := auth.Device(m.state) if err != nil { return err } if device.Serial != "" { // serial is set, we are all set return nil } if device.Brand == "" || device.Model == "" { // need first-boot, loading of model assertion info if release.OnClassic { // TODO: are we going to have model assertions on classic or need will need to cheat here? return nil } // cannot proceed yet, once first boot is done these will be set // and we can pick up from there return nil } for _, chg := range m.state.Changes() { if chg.Kind() == "become-operational" && !chg.Status().Ready() { // change already in motion return nil } } if serialRequestURL == "" { // cannot do anything actually return nil } // XXX: some of these will need to be split and use hooks // retries might need to embrace more than one "task" then, // need to be careful genKey := m.state.NewTask("generate-device-key", i18n.G("Generate device key")) requestSerial := m.state.NewTask("request-serial", i18n.G("Request device serial")) requestSerial.WaitFor(genKey) chg := m.state.NewChange("become-operational", i18n.G("Initialize device")) chg.AddAll(state.NewTaskSet(genKey, requestSerial)) return nil }
func (m *DeviceManager) keyPair() (asserts.PrivateKey, error) { device, err := auth.Device(m.state) if err != nil { return nil, err } if device.KeyID == "" { return nil, state.ErrNoState } privKey, err := m.keypairMgr.Get(device.KeyID) if err != nil { return nil, fmt.Errorf("cannot read device key pair: %v", err) } return privKey, nil }
func (as *authSuite) TestAuthContextUpdateDeviceAuth(c *C) { as.state.Lock() device, err := auth.Device(as.state) as.state.Unlock() c.Check(err, IsNil) c.Check(device, DeepEquals, &auth.DeviceState{}) sessionMacaroon := "the-device-macaroon" authContext := auth.NewAuthContext(as.state, nil) device, err = authContext.UpdateDeviceAuth(device, sessionMacaroon) c.Check(err, IsNil) deviceFromState, err := authContext.Device() c.Check(err, IsNil) c.Check(deviceFromState, DeepEquals, device) c.Check(deviceFromState.SessionMacaroon, DeepEquals, sessionMacaroon) }
// alreadyFirstbooted recovers already first booted devices with the old method appropriately func (m *DeviceManager) alreadyFirstbooted() error { device, err := auth.Device(m.state) if err != nil { return err } // recover key-id if device.Brand != "" && device.Model != "" { serials, err := assertstate.DB(m.state).FindMany(asserts.SerialType, map[string]string{ "brand-id": device.Brand, "model": device.Model, }) if err != nil && err != asserts.ErrNotFound { return err } if len(serials) == 1 { // we can recover the key id from the assertion serial := serials[0].(*asserts.Serial) keyID := serial.DeviceKey().ID() device.KeyID = keyID device.Serial = serial.Serial() err := auth.SetDevice(m.state, device) if err != nil { return err } // best effort to cleanup abandoned keys pat := filepath.Join(dirs.SnapDeviceDir, "private-keys-v1", "*") keyFns, err := filepath.Glob(pat) if err != nil { panic(fmt.Sprintf("invalid glob for device keys: %v", err)) } for _, keyFn := range keyFns { if filepath.Base(keyFn) == keyID { continue } os.Remove(keyFn) } } } m.state.Set("seeded", true) return nil }
// Serial returns the device serial assertion. func Serial(st *state.State) (*asserts.Serial, error) { device, err := auth.Device(st) if err != nil { return nil, err } if device.Serial == "" { return nil, state.ErrNoState } a, err := assertstate.DB(st).Find(asserts.SerialType, map[string]string{ "brand-id": device.Brand, "model": device.Model, "serial": device.Serial, }) if err == asserts.ErrNotFound { return nil, state.ErrNoState } if err != nil { return nil, err } return a.(*asserts.Serial), nil }
func (m *DeviceManager) doRequestSerial(t *state.Task, _ *tomb.Tomb) error { st := t.State() st.Lock() defer st.Unlock() cfg, err := getSerialRequestConfig(t) if err != nil { return err } device, err := auth.Device(st) if err != nil { return err } privKey, err := m.keyPair() if err == state.ErrNoState { return fmt.Errorf("internal error: cannot find device key pair") } if err != nil { return err } // make this idempotent, look if we have already a serial assertion // for privKey serials, err := assertstate.DB(st).FindMany(asserts.SerialType, map[string]string{ "brand-id": device.Brand, "model": device.Model, "device-key-sha3-384": privKey.PublicKey().ID(), }) if err != nil && err != asserts.ErrNotFound { return err } if len(serials) == 1 { // means we saved the assertion but didn't get to the end of the task device.Serial = serials[0].(*asserts.Serial).Serial() err := auth.SetDevice(st, device) if err != nil { return err } t.SetStatus(state.DoneStatus) return nil } if len(serials) > 1 { return fmt.Errorf("internal error: multiple serial assertions for the same device key") } serial, err := getSerial(t, privKey, device, cfg) if err == errPoll { t.Logf("Will poll for device serial assertion in 60 seconds") return &state.Retry{After: retryInterval} } if err != nil { // errors & retries return err } sto := snapstate.Store(st) // try to fetch the signing key of the serial st.Unlock() a, errAcctKey := sto.Assertion(asserts.AccountKeyType, []string{serial.SignKeyID()}, nil) st.Lock() if errAcctKey == nil { err := assertstate.Add(st, a) if err != nil { if !asserts.IsUnaccceptedUpdate(err) { return err } } } // add the serial assertion to the system assertion db err = assertstate.Add(st, serial) if err != nil { // if we had failed to fetch the signing key, retry in a bit if errAcctKey != nil { t.Errorf("cannot fetch signing key for the serial: %v", errAcctKey) return &state.Retry{After: retryInterval} } return err } if repeatRequestSerial == "after-add-serial" { // For testing purposes, ensure a crash in this state works. return &state.Retry{} } device.Serial = serial.Serial() err = auth.SetDevice(st, device) if err != nil { return err } t.SetStatus(state.DoneStatus) return nil }
func (m *DeviceManager) ensureOperational() error { m.state.Lock() defer m.state.Unlock() device, err := auth.Device(m.state) if err != nil { return err } if device.Serial != "" { // serial is set, we are all set return nil } if device.Brand == "" || device.Model == "" { // need first-boot, loading of model assertion info if release.OnClassic { // TODO: are we going to have model assertions on classic or need will need to cheat here? return nil } // cannot proceed yet, once first boot is done these will be set // and we can pick up from there return nil } if m.changeInFlight("become-operational") { return nil } if serialRequestURL == "" { // cannot do anything actually return nil } gadgetInfo, err := snapstate.GadgetInfo(m.state) if err == state.ErrNoState { // no gadget installed yet, cannot proceed return nil } if err != nil { return err } // XXX: some of these will need to be split and use hooks // retries might need to embrace more than one "task" then, // need to be careful tasks := []*state.Task{} var prepareDevice *state.Task if gadgetInfo.Hooks["prepare-device"] != nil { summary := i18n.G("Run prepare-device hook") hooksup := &hookstate.HookSetup{ Snap: gadgetInfo.Name(), Hook: "prepare-device", } prepareDevice = hookstate.HookTask(m.state, summary, hooksup, nil) tasks = append(tasks, prepareDevice) } genKey := m.state.NewTask("generate-device-key", i18n.G("Generate device key")) if prepareDevice != nil { genKey.WaitFor(prepareDevice) } tasks = append(tasks, genKey) requestSerial := m.state.NewTask("request-serial", i18n.G("Request device serial")) requestSerial.WaitFor(genKey) tasks = append(tasks, requestSerial) chg := m.state.NewChange("become-operational", i18n.G("Initialize device")) chg.AddAll(state.NewTaskSet(tasks...)) return nil }
func (s *deviceMgrSuite) TestDeviceManagerEnsureSeedYamlRecover(c *C) { release.OnClassic = false restore := devicestate.MockPopulateStateFromSeed(func(*state.State) (ts []*state.TaskSet, err error) { return nil, errors.New("should not be called") }) defer restore() s.state.Lock() defer s.state.Unlock() s.setupCore(c, "ubuntu-core", ` name: ubuntu-core type: os version: ubuntu-core `, "") // have a model assertion model, err := s.storeSigning.Sign(asserts.ModelType, map[string]interface{}{ "series": "16", "brand-id": "canonical", "model": "pc", "gadget": "pc", "kernel": "kernel", "architecture": "amd64", "timestamp": time.Now().Format(time.RFC3339), }, nil, "") c.Assert(err, IsNil) err = assertstate.Add(s.state, model) c.Assert(err, IsNil) // have a serial assertion devKey, _ := assertstest.GenerateKey(752) encDevKey, err := asserts.EncodePublicKey(devKey.PublicKey()) keyID := devKey.PublicKey().ID() c.Assert(err, IsNil) serial, err := s.storeSigning.Sign(asserts.SerialType, map[string]interface{}{ "brand-id": "canonical", "model": "pc", "serial": "8989", "device-key": string(encDevKey), "device-key-sha3-384": keyID, "timestamp": time.Now().Format(time.RFC3339), }, nil, "") c.Assert(err, IsNil) err = assertstate.Add(s.state, serial) c.Assert(err, IsNil) // forgotten key id and serial auth.SetDevice(s.state, &auth.DeviceState{ Brand: "canonical", Model: "pc", }) // put key on disk err = s.mgr.KeypairManager().Put(devKey) c.Assert(err, IsNil) // extra unused stuff junk1 := filepath.Join(dirs.SnapDeviceDir, "private-keys-v1", "junkjunk1") err = ioutil.WriteFile(junk1, nil, 0644) c.Assert(err, IsNil) junk2 := filepath.Join(dirs.SnapDeviceDir, "private-keys-v1", "junkjunk2") err = ioutil.WriteFile(junk2, nil, 0644) c.Assert(err, IsNil) // double check pat := filepath.Join(dirs.SnapDeviceDir, "private-keys-v1", "*") onDisk, err := filepath.Glob(pat) c.Assert(err, IsNil) c.Check(onDisk, HasLen, 3) s.state.Unlock() err = s.mgr.EnsureSeedYaml() s.state.Lock() c.Assert(err, IsNil) c.Check(s.state.Changes(), HasLen, 0) var seeded bool err = s.state.Get("seeded", &seeded) c.Assert(err, IsNil) c.Check(seeded, Equals, true) device, err := auth.Device(s.state) c.Assert(err, IsNil) c.Check(device, DeepEquals, &auth.DeviceState{ Brand: "canonical", Model: "pc", KeyID: keyID, Serial: "8989", }) // key is still there _, err = s.mgr.KeypairManager().Get(keyID) c.Assert(err, IsNil) onDisk, err = filepath.Glob(pat) c.Assert(err, IsNil) // junk was removed c.Check(onDisk, HasLen, 1) }
func (s *deviceMgrSuite) TestFullDeviceRegistrationHappyPrepareDeviceHook(c *C) { r1 := devicestate.MockKeyLength(752) defer r1() mockServer := s.mockServer(c, "REQID-1") defer mockServer.Close() r2 := hookstate.MockRunHook(func(ctx *hookstate.Context, _ *tomb.Tomb) ([]byte, error) { c.Assert(ctx.HookName(), Equals, "prepare-device") // snapctl set the registration params _, _, err := ctlcmd.Run(ctx, []string{"set", fmt.Sprintf("device-service.url=%q", mockServer.URL+"/identity/api/v1/")}) c.Assert(err, IsNil) h, err := json.Marshal(map[string]string{ "x-extra-header": "extra", }) c.Assert(err, IsNil) _, _, err = ctlcmd.Run(ctx, []string{"set", fmt.Sprintf("device-service.headers=%s", string(h))}) c.Assert(err, IsNil) _, _, err = ctlcmd.Run(ctx, []string{"set", fmt.Sprintf("registration.proposed-serial=%q", "Y9999")}) c.Assert(err, IsNil) d, err := yaml.Marshal(map[string]string{ "mac": "00:00:00:00:ff:00", }) c.Assert(err, IsNil) _, _, err = ctlcmd.Run(ctx, []string{"set", fmt.Sprintf("registration.body=%q", d)}) c.Assert(err, IsNil) return nil, nil }) defer r2() // setup state as will be done by first-boot // & have a gadget with a prepare-device hook s.state.Lock() defer s.state.Unlock() s.setupGadget(c, ` name: gadget type: gadget version: gadget hooks: prepare-device: `, "") auth.SetDevice(s.state, &auth.DeviceState{ Brand: "canonical", Model: "pc", }) // runs the whole device registration process s.state.Unlock() s.settle() s.state.Lock() var becomeOperational *state.Change for _, chg := range s.state.Changes() { if chg.Kind() == "become-operational" { becomeOperational = chg break } } c.Assert(becomeOperational, NotNil) c.Check(becomeOperational.Status().Ready(), Equals, true) c.Check(becomeOperational.Err(), IsNil) device, err := auth.Device(s.state) c.Assert(err, IsNil) c.Check(device.Brand, Equals, "canonical") c.Check(device.Model, Equals, "pc") c.Check(device.Serial, Equals, "Y9999") a, err := s.db.Find(asserts.SerialType, map[string]string{ "brand-id": "canonical", "model": "pc", "serial": "Y9999", }) c.Assert(err, IsNil) serial := a.(*asserts.Serial) var details map[string]interface{} err = yaml.Unmarshal(serial.Body(), &details) c.Assert(err, IsNil) c.Check(details, DeepEquals, map[string]interface{}{ "mac": "00:00:00:00:ff:00", }) privKey, err := s.mgr.KeypairManager().Get(serial.DeviceKey().ID()) c.Assert(err, IsNil) c.Check(privKey, NotNil) c.Check(device.KeyID, Equals, privKey.PublicKey().ID()) }
func (s *deviceMgrSuite) TestFullDeviceRegistrationPollHappy(c *C) { r1 := devicestate.MockKeyLength(752) defer r1() mockServer := s.mockServer(c, "REQID-POLL") defer mockServer.Close() mockRequestIDURL := mockServer.URL + "/identity/api/v1/request-id" r2 := devicestate.MockRequestIDURL(mockRequestIDURL) defer r2() mockSerialRequestURL := mockServer.URL + "/identity/api/v1/devices" r3 := devicestate.MockSerialRequestURL(mockSerialRequestURL) defer r3() // immediately r4 := devicestate.MockRetryInterval(0) defer r4() // setup state as will be done by first-boot s.state.Lock() defer s.state.Unlock() s.setupGadget(c, ` name: gadget type: gadget version: gadget `, "") auth.SetDevice(s.state, &auth.DeviceState{ Brand: "canonical", Model: "pc", }) // runs the whole device registration process with polling s.state.Unlock() s.settle() s.state.Lock() var becomeOperational *state.Change for _, chg := range s.state.Changes() { if chg.Kind() == "become-operational" { becomeOperational = chg break } } c.Assert(becomeOperational, NotNil) c.Check(becomeOperational.Status().Ready(), Equals, true) c.Check(becomeOperational.Err(), IsNil) device, err := auth.Device(s.state) c.Assert(err, IsNil) c.Check(device.Brand, Equals, "canonical") c.Check(device.Model, Equals, "pc") c.Check(device.Serial, Equals, "10002") a, err := s.db.Find(asserts.SerialType, map[string]string{ "brand-id": "canonical", "model": "pc", "serial": "10002", }) c.Assert(err, IsNil) serial := a.(*asserts.Serial) privKey, err := s.mgr.KeypairManager().Get(serial.DeviceKey().ID()) c.Assert(err, IsNil) c.Check(privKey, NotNil) c.Check(device.KeyID, Equals, privKey.PublicKey().ID()) }