asserts,overlord/assertstate: auto refresh account-keys #3632

Closed
wants to merge 6 commits into
from
View
@@ -95,7 +95,7 @@ func checkPublicKey(ab *assertionBase, keyIDName string) (PublicKey, error) {
}
// Implement further consistency checks.
-func (ak *AccountKey) checkConsistency(db RODatabase, acck *AccountKey) error {
+func (ak *AccountKey) checkConsistency(db RODatabase, signingKey *AccountKey) error {
if !db.IsTrustedAccount(ak.AuthorityID()) {
return fmt.Errorf("account-key assertion for %q is not signed by a directly trusted authority: %s", ak.AccountID(), ak.AuthorityID())
}
@@ -108,6 +108,18 @@ func (ak *AccountKey) checkConsistency(db RODatabase, acck *AccountKey) error {
if err != nil {
return err
}
+
+ // key validity should be within signing key validity
+ if ak.Until().IsZero() {
+ if !signingKey.Until().IsZero() {
+ return fmt.Errorf("account-key assertion for %q has open-ended validity but its signing key %q does not", ak.AccountID(), signingKey.PublicKeyID())
+ }
+ } else {
+ if !signingKey.isKeyValidAt(ak.Until()) {
+ return fmt.Errorf("account-key assertion for %q validity is not within its signing key %q validity", ak.AccountID(), signingKey.PublicKeyID())
+ }
+ }
+
// XXX: Make this unconditional once account-key assertions are required to have a name.
if ak.Name() != "" {
// Check that we don't end up with multiple keys with
@@ -326,12 +326,14 @@ func (aks *accountKeySuite) TestAccountKeyCheckUntrustedAuthority(c *C) {
storeDB := assertstest.NewSigningDB("canonical", trustedKey)
otherDB := setup3rdPartySigning(c, "other", storeDB, db)
+ since := time.Now()
+ until := since.AddDate(1, 0, 0)
headers := map[string]interface{}{
"account-id": "acc-id1",
"name": "default",
"public-key-sha3-384": aks.keyID,
- "since": aks.since.Format(time.RFC3339),
- "until": aks.until.Format(time.RFC3339),
+ "since": since.Format(time.RFC3339),
+ "until": until.Format(time.RFC3339),
}
accKey, err := otherDB.Sign(asserts.AccountKeyType, headers, []byte(aks.pubKeyBody), "")
c.Assert(err, IsNil)
@@ -340,6 +342,29 @@ func (aks *accountKeySuite) TestAccountKeyCheckUntrustedAuthority(c *C) {
c.Assert(err, ErrorMatches, `account-key assertion for "acc-id1" is not signed by a directly trusted authority:.*`)
}
+func (aks *accountKeySuite) TestAccountKeyCheckSinceOutsideOfSigningKeyValidity(c *C) {
+ trustedKey := testPrivKey0
+
+ db := aks.openDB(c)
+ storeDB := assertstest.NewSigningDB("canonical", trustedKey)
+ otherDB := setup3rdPartySigning(c, "other", storeDB, db)
+
+ since := time.Now().Add(-24 * time.Hour)
+ until := since.AddDate(1, 0, 0)
+ headers := map[string]interface{}{
+ "account-id": "acc-id1",
+ "name": "default",
+ "public-key-sha3-384": aks.keyID,
+ "since": since.Format(time.RFC3339),
+ "until": until.Format(time.RFC3339),
+ }
+ accKey, err := otherDB.Sign(asserts.AccountKeyType, headers, []byte(aks.pubKeyBody), "")
+ c.Assert(err, IsNil)
+
+ err = db.Check(accKey)
+ c.Assert(err, ErrorMatches, `account-key assertion timestamp outside of signing key validity.*`)
+}
+
func (aks *accountKeySuite) TestAccountKeyCheckSameNameAndNewRevision(c *C) {
trustedKey := testPrivKey0
@@ -488,6 +513,47 @@ func (aks *accountKeySuite) TestAccountKeyCheckNameClash(c *C) {
c.Assert(err, ErrorMatches, fmt.Sprintf(`account-key assertion for "acc-id1" with ID %q has the same name "default" as existing ID %q`, newPubKey.ID(), aks.keyID))
}
+func (aks *accountKeySuite) TestAccountKeyCheckOpenEndedMismatchWithSigningKey(c *C) {
+ trustedKey := testPrivKey0
+
+ headers := map[string]interface{}{
+ "authority-id": "canonical",
+ "account-id": "acc-id1",
+ "name": "default",
+ "public-key-sha3-384": aks.keyID,
+ "since": aks.since.Format(time.RFC3339),
+ }
+ accKey, err := asserts.AssembleAndSignInTest(asserts.AccountKeyType, headers, []byte(aks.pubKeyBody), trustedKey)
+ c.Assert(err, IsNil)
+
+ db := aks.openDB(c)
+ aks.prereqAccount(c, db)
+
+ err = db.Add(accKey)
+ c.Assert(err, ErrorMatches, `account-key assertion for "acc-id1" has open-ended validity but its signing key ".*" does not`)
+}
+
+func (aks *accountKeySuite) TestAccountKeyCheckUntilOutOfSigningKeyValidaty(c *C) {
+ trustedKey := testPrivKey0
+
+ headers := map[string]interface{}{
+ "authority-id": "canonical",
+ "account-id": "acc-id1",
+ "name": "default",
+ "public-key-sha3-384": aks.keyID,
+ "since": time.Now().UTC().Format(time.RFC3339),
+ "until": time.Now().AddDate(4000, 0, 0).UTC().Format(time.RFC3339),
+ }
+ accKey, err := asserts.AssembleAndSignInTest(asserts.AccountKeyType, headers, []byte(aks.pubKeyBody), trustedKey)
+ c.Assert(err, IsNil)
+
+ db := aks.openDB(c)
+ aks.prereqAccount(c, db)
+
+ err = db.Add(accKey)
+ c.Assert(err, ErrorMatches, `account-key assertion for "acc-id1" validity is not within its signing key ".*" validity`)
+}
+
func (aks *accountKeySuite) TestAccountKeyAddAndFind(c *C) {
trustedKey := testPrivKey0
View
@@ -524,7 +524,11 @@ type timestamped interface {
Timestamp() time.Time
}
-// CheckTimestampVsSigningKeyValidity verifies that the timestamp of
+type sincestamped interface {
+ Since() time.Time
+}
+
+// CheckTimestampVsSigningKeyValidity verifies that the timestamp or since of
// the assertion is within the signing key validity.
func CheckTimestampVsSigningKeyValidity(assert Assertion, signingKey *AccountKey, roDB RODatabase, checkTime time.Time) error {
if signingKey == nil {
@@ -534,9 +538,20 @@ func CheckTimestampVsSigningKeyValidity(assert Assertion, signingKey *AccountKey
// (e.g. account-key-request)
return nil
}
+ // TODO: when to stop accepting/using keys that are expired since long?
+ // should there be a point at which obsolete === revoked?
+
+ var timestamp time.Time
+ check := false
if tstamped, ok := assert.(timestamped); ok {
- checkTime := tstamped.Timestamp()
- if !signingKey.isKeyValidAt(checkTime) {
+ check = true
+ timestamp = tstamped.Timestamp()
+ } else if tstamped, ok := assert.(sincestamped); ok {
+ check = true
+ timestamp = tstamped.Since()
+ }
+ if check {
+ if !signingKey.isKeyValidAt(timestamp) {
until := ""
if !signingKey.Until().IsZero() {
until = fmt.Sprintf(" until %q", signingKey.Until())
@@ -568,7 +583,6 @@ func CheckCrossConsistency(assert Assertion, signingKey *AccountKey, roDB ROData
// checkers used by Database if none are specified in the
// DatabaseConfig.Checkers.
var DefaultCheckers = []Checker{
- CheckSigningKeyIsNotExpired,
CheckSignature,
CheckTimestampVsSigningKeyValidity,
CheckCrossConsistency,
@@ -238,6 +238,7 @@ func (chks *checkSuite) TestCheckExpiredPubKey(c *C) {
cfg := &asserts.DatabaseConfig{
Backstore: chks.bs,
Trusted: []asserts.Assertion{asserts.ExpiredAccountKeyForTest("canonical", trustedKey.PublicKey())},
+ Checkers: []asserts.Checker{asserts.CheckSigningKeyIsNotExpired},
}
db, err := asserts.OpenDatabase(cfg)
c.Assert(err, IsNil)
@@ -729,6 +730,8 @@ func (safs *signAddFindSuite) TestFindFindsTrustedAccountKeys(c *C) {
acct1Key := assertstest.NewAccountKey(safs.signingDB, acct1, map[string]interface{}{
"authority-id": "canonical",
+ "since": time.Now().UTC().Format(time.RFC3339),
+ "until": time.Now().AddDate(5, 0, 0).UTC().Format(time.RFC3339),
}, pk1.PublicKey(), safs.signingKeyID)
err := safs.db.Add(acct1)
@@ -760,6 +763,8 @@ func (safs *signAddFindSuite) TestFindTrusted(c *C) {
acct1Key := assertstest.NewAccountKey(safs.signingDB, acct1, map[string]interface{}{
"authority-id": "canonical",
+ "since": time.Now().UTC().Format(time.RFC3339),
+ "until": time.Now().AddDate(5, 0, 0).UTC().Format(time.RFC3339),
}, pk1.PublicKey(), safs.signingKeyID)
err := safs.db.Add(acct1)
@@ -818,6 +823,8 @@ func (safs *signAddFindSuite) TestFindManyTrusted(c *C) {
acct1Key := assertstest.NewAccountKey(safs.signingDB, acct1, map[string]interface{}{
"authority-id": "canonical",
+ "since": time.Now().UTC().Format(time.RFC3339),
+ "until": time.Now().AddDate(5, 0, 0).UTC().Format(time.RFC3339),
}, pk1.PublicKey(), safs.signingKeyID)
err = db.Add(acct1)
View
@@ -79,7 +79,7 @@ func makeAccountKeyForTest(authorityID string, openPGPPubKey PublicKey, validYea
}
func BootstrapAccountKeyForTest(authorityID string, pubKey PublicKey) *AccountKey {
- return makeAccountKeyForTest(authorityID, pubKey, 9999)
+ return makeAccountKeyForTest(authorityID, pubKey, 2999)
}
func ExpiredAccountKeyForTest(authorityID string, pubKey PublicKey) *AccountKey {
@@ -620,7 +620,10 @@ func setup3rdPartySigning(c *C, username string, storeDB *assertstest.SigningDB,
acct := assertstest.NewAccount(storeDB, username, map[string]interface{}{
"account-id": username,
}, "")
- accKey := assertstest.NewAccountKey(storeDB, acct, nil, privKey.PublicKey(), "")
+ accKey := assertstest.NewAccountKey(storeDB, acct, map[string]interface{}{
+ "since": time.Now().UTC().Format(time.RFC3339),
+ "until": time.Now().AddDate(10, 0, 0).UTC().Format(time.RFC3339),
+ }, privKey.PublicKey(), "")
err := checkDB.Add(acct)
c.Assert(err, IsNil)
View
@@ -833,6 +833,7 @@ var (
snapstateRevertToRevision = snapstate.RevertToRevision
assertstateRefreshSnapDeclarations = assertstate.RefreshSnapDeclarations
+ assertstateAutoRefreshAssertions = assertstate.AutoRefreshAssertions
)
func ensureStateSoonImpl(st *state.State) {
@@ -905,7 +906,13 @@ func modeFlags(devMode, jailMode, classic bool) (snapstate.Flags, error) {
func snapUpdateMany(inst *snapInstruction, st *state.State) (msg string, updated []string, tasksets []*state.TaskSet, err error) {
// we need refreshed snap-declarations to enforce refresh-control as best as we can, this also ensures that snap-declarations and their prerequisite assertions are updated regularly
- if err := assertstateRefreshSnapDeclarations(st, inst.userID); err != nil {
+ // for refresh all we go a step further and refresh all assertions
+ // as scheduled auto refreshes do
+ refreshAssertions := assertstateRefreshSnapDeclarations
+ if len(inst.Snaps) == 0 {
+ refreshAssertions = assertstateAutoRefreshAssertions
+ }
+ if err := refreshAssertions(st, inst.userID); err != nil {
return "", nil, nil, err
}
View
@@ -214,6 +214,7 @@ func (s *apiBaseSuite) SetUpTest(c *check.C) {
s.storeSigning = assertstest.NewStoreStack("can0nical", rootPrivKey, storePrivKey)
s.trustedRestorer = sysdb.InjectTrusted(s.storeSigning.Trusted)
+ assertstateAutoRefreshAssertions = nil
assertstateRefreshSnapDeclarations = nil
snapstateCoreInfo = nil
snapstateInstall = nil
@@ -236,6 +237,7 @@ func (s *apiBaseSuite) TearDownTest(c *check.C) {
ensureStateSoon = ensureStateSoonImpl
dirs.SetRootDir("")
+ assertstateAutoRefreshAssertions = assertstate.AutoRefreshAssertions
assertstateRefreshSnapDeclarations = assertstate.RefreshSnapDeclarations
snapstateCoreInfo = snapstate.CoreInfo
snapstateInstall = snapstate.Install
@@ -615,6 +617,7 @@ func (s *apiSuite) TestListIncludesAll(c *check.C) {
"snapstateRevert",
"snapstateRevertToRevision",
"assertstateRefreshSnapDeclarations",
+ "assertstateAutoRefreshAssertions",
"unsafeReadSnapInfo",
"osutilAddUser",
"setupLocalUser",
@@ -2846,7 +2849,7 @@ func (s *apiSuite) TestRefreshIgnoreValidation(c *check.C) {
}
func (s *apiSuite) TestPostSnapsOp(c *check.C) {
- assertstateRefreshSnapDeclarations = func(*state.State, int) error { return nil }
+ assertstateAutoRefreshAssertions = func(*state.State, int) error { return nil }
snapstateUpdateMany = func(s *state.State, names []string, userID int) ([]string, []*state.TaskSet, error) {
c.Check(names, check.HasLen, 0)
t := s.NewTask("fake-refresh-all", "Refreshing everything")
@@ -2877,10 +2880,10 @@ func (s *apiSuite) TestPostSnapsOp(c *check.C) {
}
func (s *apiSuite) TestRefreshAll(c *check.C) {
- refreshSnapDecls := false
- assertstateRefreshSnapDeclarations = func(s *state.State, userID int) error {
- refreshSnapDecls = true
- return assertstate.RefreshSnapDeclarations(s, userID)
+ autoRefreshAssertions := false
+ assertstateAutoRefreshAssertions = func(st *state.State, userID int) error {
+ autoRefreshAssertions = true
+ return assertstate.AutoRefreshAssertions(st, userID)
}
d := s.daemon(c)
@@ -2892,7 +2895,7 @@ func (s *apiSuite) TestRefreshAll(c *check.C) {
{[]string{"fake"}, `Refresh snap "fake"`},
{[]string{"fake1", "fake2"}, `Refresh snaps "fake1", "fake2"`},
} {
- refreshSnapDecls = false
+ autoRefreshAssertions = false
snapstateUpdateMany = func(s *state.State, names []string, userID int) ([]string, []*state.TaskSet, error) {
c.Check(names, check.HasLen, 0)
@@ -2907,15 +2910,15 @@ func (s *apiSuite) TestRefreshAll(c *check.C) {
st.Unlock()
c.Assert(err, check.IsNil)
c.Check(summary, check.Equals, tst.msg)
- c.Check(refreshSnapDecls, check.Equals, true)
+ c.Check(autoRefreshAssertions, check.Equals, true)
}
}
func (s *apiSuite) TestRefreshAllNoChanges(c *check.C) {
- refreshSnapDecls := false
- assertstateRefreshSnapDeclarations = func(s *state.State, userID int) error {
- refreshSnapDecls = true
- return assertstate.RefreshSnapDeclarations(s, userID)
+ autoRefreshAssertions := false
+ assertstateAutoRefreshAssertions = func(st *state.State, userID int) error {
+ autoRefreshAssertions = true
+ return assertstate.AutoRefreshAssertions(st, userID)
}
snapstateUpdateMany = func(s *state.State, names []string, userID int) ([]string, []*state.TaskSet, error) {
@@ -2931,14 +2934,14 @@ func (s *apiSuite) TestRefreshAllNoChanges(c *check.C) {
st.Unlock()
c.Assert(err, check.IsNil)
c.Check(summary, check.Equals, `Refresh all snaps: no updates`)
- c.Check(refreshSnapDecls, check.Equals, true)
+ c.Check(autoRefreshAssertions, check.Equals, true)
}
func (s *apiSuite) TestRefreshMany(c *check.C) {
refreshSnapDecls := false
- assertstateRefreshSnapDeclarations = func(s *state.State, userID int) error {
+ assertstateRefreshSnapDeclarations = func(st *state.State, userID int) error {
refreshSnapDecls = true
- return nil
+ return assertstate.RefreshSnapDeclarations(st, userID)
}
snapstateUpdateMany = func(s *state.State, names []string, userID int) ([]string, []*state.TaskSet, error) {
@@ -2961,9 +2964,9 @@ func (s *apiSuite) TestRefreshMany(c *check.C) {
func (s *apiSuite) TestRefreshMany1(c *check.C) {
refreshSnapDecls := false
- assertstateRefreshSnapDeclarations = func(s *state.State, userID int) error {
+ assertstateRefreshSnapDeclarations = func(st *state.State, userID int) error {
refreshSnapDecls = true
- return nil
+ return assertstate.RefreshSnapDeclarations(st, userID)
}
snapstateUpdateMany = func(s *state.State, names []string, userID int) ([]string, []*state.TaskSet, error) {
@@ -4844,7 +4847,13 @@ func (s *postCreateUserSuite) makeSystemUsers(c *check.C, systemUsers []map[stri
assertAdd(st, model)
for _, suMap := range systemUsers {
- su, err := signers[suMap["authority-id"].(string)].Sign(asserts.SystemUserType, suMap, nil, "")
+ headers := make(map[string]interface{})
+ for k, v := range suMap {
+ headers[k] = v
+ }
+ headers["since"] = time.Now().Format(time.RFC3339)
+ headers["until"] = time.Now().Add(24 * 30 * time.Hour).Format(time.RFC3339)
+ su, err := signers[suMap["authority-id"].(string)].Sign(asserts.SystemUserType, headers, nil, "")
c.Assert(err, check.IsNil)
su = su.(*asserts.SystemUser)
// now add system-user assertion to the system
@@ -4869,8 +4878,6 @@ var goodUser = map[string]interface{}{
"name": "Boring Guy",
"username": "guy",
"password": "$6$salt$hash",
- "since": time.Now().Format(time.RFC3339),
- "until": time.Now().Add(24 * 30 * time.Hour).Format(time.RFC3339),
}
var partnerUser = map[string]interface{}{
@@ -4882,8 +4889,6 @@ var partnerUser = map[string]interface{}{
"name": "Partner Guy",
"username": "partnerguy",
"password": "$6$salt$hash",
- "since": time.Now().Format(time.RFC3339),
- "until": time.Now().Add(24 * 30 * time.Hour).Format(time.RFC3339),
}
var badUser = map[string]interface{}{
@@ -4896,8 +4901,6 @@ var badUser = map[string]interface{}{
"name": "Random Gal",
"username": "gal",
"password": "$6$salt$hash",
- "since": time.Now().Format(time.RFC3339),
- "until": time.Now().Add(24 * 30 * time.Hour).Format(time.RFC3339),
}
var unknownUser = map[string]interface{}{
Oops, something went wrong.