github.com/kubiko/snapd@v0.0.0-20201013125620-d4f3094d9ddf/asserts/store_asserts_test.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2017 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 asserts_test 21 22 import ( 23 "fmt" 24 "strings" 25 "time" 26 27 "github.com/snapcore/snapd/asserts" 28 "github.com/snapcore/snapd/asserts/assertstest" 29 . "gopkg.in/check.v1" 30 ) 31 32 var _ = Suite(&storeSuite{}) 33 34 type storeSuite struct { 35 ts time.Time 36 tsLine string 37 validExample string 38 } 39 40 func (s *storeSuite) SetUpSuite(c *C) { 41 s.ts = time.Now().Truncate(time.Second).UTC() 42 s.tsLine = "timestamp: " + s.ts.Format(time.RFC3339) + "\n" 43 s.validExample = "type: store\n" + 44 "authority-id: canonical\n" + 45 "store: store1\n" + 46 "operator-id: op-id1\n" + 47 "url: https://store.example.com\n" + 48 "location: upstairs\n" + 49 s.tsLine + 50 "sign-key-sha3-384: Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij\n" + 51 "\n" + 52 "AXNpZw==" 53 } 54 55 func (s *storeSuite) TestDecodeOK(c *C) { 56 a, err := asserts.Decode([]byte(s.validExample)) 57 c.Assert(err, IsNil) 58 c.Check(a.Type(), Equals, asserts.StoreType) 59 store := a.(*asserts.Store) 60 61 c.Check(store.OperatorID(), Equals, "op-id1") 62 c.Check(store.Store(), Equals, "store1") 63 c.Check(store.URL().String(), Equals, "https://store.example.com") 64 c.Check(store.Location(), Equals, "upstairs") 65 c.Check(store.Timestamp().Equal(s.ts), Equals, true) 66 c.Check(store.FriendlyStores(), HasLen, 0) 67 } 68 69 var storeErrPrefix = "assertion store: " 70 71 func (s *storeSuite) TestDecodeInvalidHeaders(c *C) { 72 tests := []struct{ original, invalid, expectedErr string }{ 73 {"store: store1\n", "", `"store" header is mandatory`}, 74 {"store: store1\n", "store: \n", `"store" header should not be empty`}, 75 {"operator-id: op-id1\n", "", `"operator-id" header is mandatory`}, 76 {"operator-id: op-id1\n", "operator-id: \n", `"operator-id" header should not be empty`}, 77 {"url: https://store.example.com\n", "url:\n - foo\n", `"url" header must be a string`}, 78 {"location: upstairs\n", "location:\n - foo\n", `"location" header must be a string`}, 79 {s.tsLine, "", `"timestamp" header is mandatory`}, 80 {s.tsLine, "timestamp: \n", `"timestamp" header should not be empty`}, 81 {s.tsLine, "timestamp: 12:30\n", `"timestamp" header is not a RFC3339 date: .*`}, 82 {"url: https://store.example.com\n", "friendly-stores: foo\n", `"friendly-stores" header must be a list of strings`}, 83 } 84 85 for _, test := range tests { 86 invalid := strings.Replace(s.validExample, test.original, test.invalid, 1) 87 _, err := asserts.Decode([]byte(invalid)) 88 c.Check(err, ErrorMatches, storeErrPrefix+test.expectedErr) 89 } 90 } 91 92 func (s *storeSuite) TestURLOptional(c *C) { 93 tests := []string{"", "url: \n"} 94 for _, test := range tests { 95 encoded := strings.Replace(s.validExample, "url: https://store.example.com\n", test, 1) 96 assert, err := asserts.Decode([]byte(encoded)) 97 c.Assert(err, IsNil) 98 store := assert.(*asserts.Store) 99 c.Check(store.URL(), IsNil) 100 } 101 } 102 103 func (s *storeSuite) TestURL(c *C) { 104 tests := []struct { 105 url string 106 err string 107 }{ 108 // Valid URLs. 109 {"http://example.com/", ""}, 110 {"https://example.com/", ""}, 111 {"https://example.com/some/path/", ""}, 112 {"https://example.com:443/", ""}, 113 {"https://example.com:1234/", ""}, 114 {"https://user:pass@example.com/", ""}, 115 {"https://token@example.com/", ""}, 116 117 // Invalid URLs. 118 {"://example.com", `"url" header must be a valid URL`}, 119 {"example.com", `"url" header scheme must be "https" or "http"`}, 120 {"//example.com", `"url" header scheme must be "https" or "http"`}, 121 {"ftp://example.com", `"url" header scheme must be "https" or "http"`}, 122 {"mailto:someone@example.com", `"url" header scheme must be "https" or "http"`}, 123 {"https://", `"url" header must have a host`}, 124 {"https:///", `"url" header must have a host`}, 125 {"https:///some/path", `"url" header must have a host`}, 126 {"https://example.com/?foo=bar", `"url" header must not have a query`}, 127 {"https://example.com/#fragment", `"url" header must not have a fragment`}, 128 } 129 130 for _, test := range tests { 131 encoded := strings.Replace( 132 s.validExample, "url: https://store.example.com\n", 133 fmt.Sprintf("url: %s\n", test.url), 1) 134 assert, err := asserts.Decode([]byte(encoded)) 135 if test.err != "" { 136 c.Assert(err, NotNil) 137 c.Check(err.Error(), Equals, storeErrPrefix+test.err+": "+test.url) 138 } else { 139 c.Assert(err, IsNil) 140 c.Check(assert.(*asserts.Store).URL().String(), Equals, test.url) 141 } 142 } 143 } 144 145 func (s *storeSuite) TestLocationOptional(c *C) { 146 encoded := strings.Replace(s.validExample, "location: upstairs\n", "", 1) 147 _, err := asserts.Decode([]byte(encoded)) 148 c.Check(err, IsNil) 149 } 150 151 func (s *storeSuite) TestLocation(c *C) { 152 for _, test := range []string{"foo", "bar", ""} { 153 encoded := strings.Replace( 154 s.validExample, "location: upstairs\n", 155 fmt.Sprintf("location: %s\n", test), 1) 156 assert, err := asserts.Decode([]byte(encoded)) 157 c.Assert(err, IsNil) 158 store := assert.(*asserts.Store) 159 c.Check(store.Location(), Equals, test) 160 } 161 } 162 163 func (s *storeSuite) TestCheckAuthority(c *C) { 164 storeDB, db := makeStoreAndCheckDB(c) 165 166 // Add account for operator. 167 operator := assertstest.NewAccount(storeDB, "op-id1", nil, "") 168 err := db.Add(operator) 169 c.Assert(err, IsNil) 170 171 otherDB := setup3rdPartySigning(c, "other", storeDB, db) 172 173 storeHeaders := map[string]interface{}{ 174 "store": "store1", 175 "operator-id": operator.HeaderString("account-id"), 176 "timestamp": time.Now().Format(time.RFC3339), 177 } 178 179 // store signed by some other account fails. 180 store, err := otherDB.Sign(asserts.StoreType, storeHeaders, nil, "") 181 c.Assert(err, IsNil) 182 err = db.Check(store) 183 c.Assert(err, ErrorMatches, `store assertion "store1" is not signed by a directly trusted authority: other`) 184 185 // but succeeds when signed by a trusted authority. 186 store, err = storeDB.Sign(asserts.StoreType, storeHeaders, nil, "") 187 c.Assert(err, IsNil) 188 err = db.Check(store) 189 c.Assert(err, IsNil) 190 } 191 192 func (s *storeSuite) TestFriendlyStores(c *C) { 193 encoded := strings.Replace(s.validExample, "url: https://store.example.com\n", `friendly-stores: 194 - store1 195 - store2 196 - store3 197 `, 1) 198 assert, err := asserts.Decode([]byte(encoded)) 199 c.Assert(err, IsNil) 200 store := assert.(*asserts.Store) 201 c.Check(store.URL(), IsNil) 202 c.Check(store.FriendlyStores(), DeepEquals, []string{"store1", "store2", "store3"}) 203 } 204 205 func (s *storeSuite) TestCheckOperatorAccount(c *C) { 206 storeDB, db := makeStoreAndCheckDB(c) 207 208 store, err := storeDB.Sign(asserts.StoreType, map[string]interface{}{ 209 "store": "store1", 210 "operator-id": "op-id1", 211 "timestamp": time.Now().Format(time.RFC3339), 212 }, nil, "") 213 c.Assert(err, IsNil) 214 215 // No account for operator op-id1 yet, so Check fails. 216 err = db.Check(store) 217 c.Assert(err, ErrorMatches, `store assertion "store1" does not have a matching account assertion for the operator "op-id1"`) 218 219 // Add the op-id1 account. 220 operator := assertstest.NewAccount(storeDB, "op-id1", map[string]interface{}{"account-id": "op-id1"}, "") 221 err = db.Add(operator) 222 c.Assert(err, IsNil) 223 224 // Now the operator exists so Check succeeds. 225 err = db.Check(store) 226 c.Assert(err, IsNil) 227 } 228 229 func (s *storeSuite) TestPrerequisites(c *C) { 230 assert, err := asserts.Decode([]byte(s.validExample)) 231 c.Assert(err, IsNil) 232 c.Assert(assert.Prerequisites(), DeepEquals, []*asserts.Ref{ 233 {Type: asserts.AccountType, PrimaryKey: []string{"op-id1"}}, 234 }) 235 }