Tilak Sharma | 3ed3024 | 2014-08-11 11:45:55 -0700 | [diff] [blame] | 1 | package security |
| 2 | |
| 3 | import ( |
| 4 | "io/ioutil" |
| 5 | "os" |
| 6 | "runtime" |
| 7 | "testing" |
| 8 | "time" |
| 9 | |
| 10 | "veyron2/naming" |
| 11 | "veyron2/security" |
| 12 | ) |
| 13 | |
| 14 | type authMap map[security.PublicID]security.LabelSet |
| 15 | |
| 16 | // context implements Context. |
| 17 | type context struct { |
| 18 | localID, remoteID security.PublicID |
| 19 | discharges security.CaveatDischargeMap |
| 20 | method, name, suffix string |
| 21 | label security.Label |
| 22 | } |
| 23 | |
| 24 | func (c *context) Method() string { return c.method } |
| 25 | func (c *context) Name() string { return c.name } |
| 26 | func (c *context) Suffix() string { return c.suffix } |
| 27 | func (c *context) Label() security.Label { return c.label } |
| 28 | func (c *context) CaveatDischarges() security.CaveatDischargeMap { return c.discharges } |
| 29 | func (c *context) LocalID() security.PublicID { return c.localID } |
| 30 | func (c *context) RemoteID() security.PublicID { return c.remoteID } |
| 31 | func (c *context) LocalEndpoint() naming.Endpoint { return nil } |
| 32 | func (c *context) RemoteEndpoint() naming.Endpoint { return nil } |
| 33 | |
| 34 | func saveACLToTempFile(acl security.ACL) string { |
| 35 | f, err := ioutil.TempFile("", "saved_acl") |
| 36 | if err != nil { |
| 37 | panic(err) |
| 38 | } |
| 39 | defer f.Close() |
Tilak Sharma | d6ade0e | 2014-08-20 16:28:32 -0700 | [diff] [blame] | 40 | if err := SaveACL(f, acl); err != nil { |
Tilak Sharma | 3ed3024 | 2014-08-11 11:45:55 -0700 | [diff] [blame] | 41 | defer os.Remove(f.Name()) |
| 42 | panic(err) |
| 43 | } |
| 44 | return f.Name() |
| 45 | } |
| 46 | |
| 47 | func updateACLInFile(fileName string, acl security.ACL) { |
| 48 | f, err := os.OpenFile(fileName, os.O_WRONLY, 0600) |
| 49 | if err != nil { |
| 50 | panic(err) |
| 51 | } |
| 52 | defer f.Close() |
Tilak Sharma | d6ade0e | 2014-08-20 16:28:32 -0700 | [diff] [blame] | 53 | if err := SaveACL(f, acl); err != nil { |
Tilak Sharma | 3ed3024 | 2014-08-11 11:45:55 -0700 | [diff] [blame] | 54 | panic(err) |
| 55 | } |
| 56 | } |
| 57 | |
| 58 | func bless(blessee security.PublicID, blesser security.PrivateID, name string) security.PublicID { |
| 59 | blessed, err := blesser.Bless(blessee, name, 5*time.Minute, nil) |
| 60 | if err != nil { |
| 61 | panic(err) |
| 62 | } |
| 63 | return blessed |
| 64 | } |
| 65 | |
| 66 | func derive(pub security.PublicID, priv security.PrivateID) security.PrivateID { |
| 67 | d, err := priv.Derive(pub) |
| 68 | if err != nil { |
| 69 | panic(err) |
| 70 | } |
| 71 | return d |
| 72 | } |
| 73 | |
| 74 | func testSelfRPCs(t *testing.T, authorizer security.Authorizer) { |
| 75 | _, file, line, _ := runtime.Caller(1) |
| 76 | var ( |
| 77 | veyron = security.FakePrivateID("veyron") |
| 78 | alice = security.FakePrivateID("alice") |
| 79 | veyronAlice = bless(alice.PublicID(), veyron, "alice") |
| 80 | ) |
| 81 | testData := []struct { |
| 82 | localID, remoteID security.PublicID |
| 83 | isAuthorized bool |
| 84 | }{ |
| 85 | {alice.PublicID(), alice.PublicID(), true}, |
| 86 | {veyron.PublicID(), veyron.PublicID(), true}, |
| 87 | {veyron.PublicID(), alice.PublicID(), false}, |
| 88 | {veyronAlice, veyronAlice, true}, |
| 89 | {veyronAlice, alice.PublicID(), false}, |
| 90 | {veyronAlice, veyron.PublicID(), false}, |
| 91 | } |
| 92 | for _, d := range testData { |
| 93 | ctx := &context{localID: d.localID, remoteID: d.remoteID} |
| 94 | if got, want := authorizer.Authorize(ctx), d.isAuthorized; (got == nil) != want { |
| 95 | t.Errorf("%s:%d: %+v.Authorize(&context{localID: %v, remoteID: %v}) returned error: %v, want error: %v", file, line, authorizer, d.localID, d.remoteID, got, !want) |
| 96 | } |
| 97 | } |
| 98 | } |
| 99 | |
| 100 | func testAuthorizations(t *testing.T, authorizer security.Authorizer, authorizations authMap) { |
| 101 | _, file, line, _ := runtime.Caller(1) |
| 102 | for user, labels := range authorizations { |
| 103 | for _, l := range security.ValidLabels { |
| 104 | ctx := &context{remoteID: user, label: l} |
| 105 | if got, want := authorizer.Authorize(ctx), labels.HasLabel(l); (got == nil) != want { |
| 106 | t.Errorf("%s:%d: %+v.Authorize(&context{remoteID: %v, label: %v}) returned error: %v, want error: %v", file, line, authorizer, user, l, got, !want) |
| 107 | } |
| 108 | } |
| 109 | } |
| 110 | } |
| 111 | |
| 112 | func testNothingPermitted(t *testing.T, authorizer security.Authorizer) { |
| 113 | _, file, line, _ := runtime.Caller(1) |
| 114 | var ( |
| 115 | veyronPrivateID = security.FakePrivateID("veyron") |
| 116 | alicePrivateID = security.FakePrivateID("alice") |
| 117 | randomPrivateID = security.FakePrivateID("random") |
| 118 | veyron = veyronPrivateID.PublicID() |
| 119 | alice = alicePrivateID.PublicID() |
| 120 | random = randomPrivateID.PublicID() |
| 121 | veyronAlice = bless(alice, veyronPrivateID, "alice") |
| 122 | veyronAliceFriend = bless(random, derive(veyronAlice, alicePrivateID), "friend") |
| 123 | veyronBob = bless(random, veyronPrivateID, "bob") |
| 124 | ) |
| 125 | users := []security.PublicID{ |
| 126 | veyron, |
| 127 | random, |
| 128 | alice, |
| 129 | |
| 130 | // Blessed principals |
| 131 | veyronAlice, |
| 132 | veyronAliceFriend, |
| 133 | veyronBob, |
| 134 | } |
| 135 | // No principal (whether the identity provider is trusted or not) |
| 136 | // should have access to any valid or invalid label. |
| 137 | for _, u := range users { |
| 138 | for _, l := range security.ValidLabels { |
| 139 | ctx := &context{remoteID: u, label: l} |
| 140 | if got := authorizer.Authorize(ctx); got == nil { |
| 141 | t.Errorf("%s:%d: %+v.Authorize(%v) returns nil, want error", file, line, authorizer, ctx) |
| 142 | } |
| 143 | } |
| 144 | invalidLabel := security.Label(3) |
| 145 | ctx := &context{remoteID: u, label: invalidLabel} |
| 146 | if got := authorizer.Authorize(ctx); got == nil { |
| 147 | t.Errorf("%s:%d: %+v.Authorize(%v) returns nil, want error", file, line, authorizer, ctx) |
| 148 | } |
| 149 | } |
| 150 | } |
| 151 | |
| 152 | func TestACLAuthorizer(t *testing.T) { |
| 153 | const ( |
| 154 | // Shorthands |
| 155 | R = security.ReadLabel |
| 156 | W = security.WriteLabel |
| 157 | A = security.AdminLabel |
| 158 | D = security.DebugLabel |
| 159 | M = security.MonitoringLabel |
| 160 | ) |
| 161 | // Principals to test |
| 162 | var ( |
| 163 | veyronPrivateID = security.FakePrivateID("veyron") |
| 164 | alicePrivateID = security.FakePrivateID("alice") |
Tilak Sharma | b88a111 | 2014-08-15 17:17:12 -0700 | [diff] [blame] | 165 | bobPrivateID = security.FakePrivateID("bob") |
| 166 | chePrivateID = security.FakePrivateID("che") |
Tilak Sharma | 3ed3024 | 2014-08-11 11:45:55 -0700 | [diff] [blame] | 167 | veyron = veyronPrivateID.PublicID() |
| 168 | alice = alicePrivateID.PublicID() |
Tilak Sharma | b88a111 | 2014-08-15 17:17:12 -0700 | [diff] [blame] | 169 | bob = bobPrivateID.PublicID() |
| 170 | che = chePrivateID.PublicID() |
Tilak Sharma | 3ed3024 | 2014-08-11 11:45:55 -0700 | [diff] [blame] | 171 | |
| 172 | // Blessed principals |
| 173 | veyronAlice = bless(alice, veyronPrivateID, "alice") |
| 174 | veyronBob = bless(bob, veyronPrivateID, "bob") |
Tilak Sharma | b88a111 | 2014-08-15 17:17:12 -0700 | [diff] [blame] | 175 | veyronChe = bless(che, veyronPrivateID, "che") |
Tilak Sharma | 3ed3024 | 2014-08-11 11:45:55 -0700 | [diff] [blame] | 176 | veyronAliceFriend = bless(bob, derive(veyronAlice, alicePrivateID), "friend") |
Tilak Sharma | b88a111 | 2014-08-15 17:17:12 -0700 | [diff] [blame] | 177 | veyronCheFriend = bless(che, derive(veyronChe, chePrivateID), "friend") |
Tilak Sharma | 3ed3024 | 2014-08-11 11:45:55 -0700 | [diff] [blame] | 178 | ) |
| 179 | // Convenience function for combining Labels into a LabelSet. |
| 180 | LS := func(labels ...security.Label) security.LabelSet { |
| 181 | var ret security.LabelSet |
| 182 | for _, l := range labels { |
| 183 | ret = ret | security.LabelSet(l) |
| 184 | } |
| 185 | return ret |
| 186 | } |
| 187 | |
| 188 | // ACL for testing |
Tilak Sharma | b88a111 | 2014-08-15 17:17:12 -0700 | [diff] [blame] | 189 | acl := security.ACL{} |
Asim Shankar | 9f6db08 | 2014-08-27 16:44:03 -0700 | [diff] [blame] | 190 | acl.In = map[security.BlessingPattern]security.LabelSet{ |
| 191 | "...": LS(R), |
| 192 | "fake/veyron/alice/...": LS(W, R), |
| 193 | "fake/veyron/alice": LS(A, D, M), |
| 194 | "fake/veyron/bob": LS(D, M), |
| 195 | "fake/veyron/che/...": LS(W, R), |
| 196 | "fake/veyron/che": LS(W, R), |
Tilak Sharma | b88a111 | 2014-08-15 17:17:12 -0700 | [diff] [blame] | 197 | } |
Asim Shankar | 9f6db08 | 2014-08-27 16:44:03 -0700 | [diff] [blame] | 198 | acl.NotIn = map[string]security.LabelSet{ |
Tilak Sharma | b88a111 | 2014-08-15 17:17:12 -0700 | [diff] [blame] | 199 | "fake/veyron/che/friend": LS(W), |
Tilak Sharma | 3ed3024 | 2014-08-11 11:45:55 -0700 | [diff] [blame] | 200 | } |
| 201 | |
| 202 | // Authorizations for the above ACL. |
| 203 | authorizations := authMap{ |
Asim Shankar | 9f6db08 | 2014-08-27 16:44:03 -0700 | [diff] [blame] | 204 | // alice and bob have only what "..." has. |
Tilak Sharma | 3ed3024 | 2014-08-11 11:45:55 -0700 | [diff] [blame] | 205 | alice: LS(R), |
| 206 | bob: LS(R), |
Tilak Sharma | b88a111 | 2014-08-15 17:17:12 -0700 | [diff] [blame] | 207 | che: LS(R), |
Tilak Sharma | 3ed3024 | 2014-08-11 11:45:55 -0700 | [diff] [blame] | 208 | // veyron and veyronAlice have R, W, A, D, M from the "veyron/alice" and |
Asim Shankar | 9f6db08 | 2014-08-27 16:44:03 -0700 | [diff] [blame] | 209 | // "veyron/alice/..." ACL entries. |
Tilak Sharma | 3ed3024 | 2014-08-11 11:45:55 -0700 | [diff] [blame] | 210 | veyron: LS(R, W, A, D, M), |
| 211 | veyronAlice: LS(R, W, A, D, M), |
Asim Shankar | 9f6db08 | 2014-08-27 16:44:03 -0700 | [diff] [blame] | 212 | // veyronBob has R, D, M from "..." and "veyron/bob" ACL entries. |
Tilak Sharma | 3ed3024 | 2014-08-11 11:45:55 -0700 | [diff] [blame] | 213 | veyronBob: LS(R, D, M), |
Asim Shankar | 9f6db08 | 2014-08-27 16:44:03 -0700 | [diff] [blame] | 214 | // veyronAliceFriend has W, R from the "veyron/alice/..." ACL entry. |
Tilak Sharma | 3ed3024 | 2014-08-11 11:45:55 -0700 | [diff] [blame] | 215 | veyronAliceFriend: LS(W, R), |
Tilak Sharma | b88a111 | 2014-08-15 17:17:12 -0700 | [diff] [blame] | 216 | // veyronChe has W, R from the "veyron/che" entry. |
| 217 | veyronChe: LS(W, R), |
Asim Shankar | 9f6db08 | 2014-08-27 16:44:03 -0700 | [diff] [blame] | 218 | // veyronCheFriend has W, R from the "veyron/che/..." entry, but loses W |
Tilak Sharma | b88a111 | 2014-08-15 17:17:12 -0700 | [diff] [blame] | 219 | // from the blacklist entry "veyron/che/friend". |
| 220 | veyronCheFriend: LS(R), |
Tilak Sharma | 3ed3024 | 2014-08-11 11:45:55 -0700 | [diff] [blame] | 221 | // nil PublicIDs are not authorized. |
| 222 | nil: LS(), |
| 223 | } |
| 224 | // Create an aclAuthorizer based on the ACL and verify the authorizations. |
| 225 | authorizer := NewACLAuthorizer(acl) |
| 226 | testAuthorizations(t, authorizer, authorizations) |
| 227 | testSelfRPCs(t, authorizer) |
| 228 | |
| 229 | // Create a fileACLAuthorizer by saving the ACL in a file, and verify the |
| 230 | // authorizations. |
| 231 | fileName := saveACLToTempFile(acl) |
| 232 | defer os.Remove(fileName) |
| 233 | fileAuthorizer := NewFileACLAuthorizer(fileName) |
| 234 | testAuthorizations(t, fileAuthorizer, authorizations) |
| 235 | testSelfRPCs(t, fileAuthorizer) |
| 236 | |
| 237 | // Modify the ACL stored in the file and verify that the authorizations appropriately |
| 238 | // change for the fileACLAuthorizer. |
Asim Shankar | 9f6db08 | 2014-08-27 16:44:03 -0700 | [diff] [blame] | 239 | acl.In["fake/veyron/bob"] = LS(R, W, A, D, M) |
Tilak Sharma | 3ed3024 | 2014-08-11 11:45:55 -0700 | [diff] [blame] | 240 | updateACLInFile(fileName, acl) |
| 241 | |
| 242 | authorizations[veyronBob] = LS(R, W, A, D, M) |
| 243 | testAuthorizations(t, fileAuthorizer, authorizations) |
| 244 | testSelfRPCs(t, fileAuthorizer) |
| 245 | |
| 246 | // Update the ACL file with invalid contents and verify that no requests are |
| 247 | // authorized. |
| 248 | f, err := os.OpenFile(fileName, os.O_WRONLY, 0600) |
| 249 | if err != nil { |
| 250 | panic(err) |
| 251 | } |
| 252 | f.Write([]byte("invalid ACL")) |
| 253 | f.Close() |
| 254 | testNothingPermitted(t, fileAuthorizer) |
| 255 | |
| 256 | // Verify that a fileACLAuthorizer based on a nonexistent file does not authorize any |
| 257 | // requests. |
| 258 | fileAuthorizer = NewFileACLAuthorizer("fileDoesNotExist") |
| 259 | testNothingPermitted(t, fileAuthorizer) |
| 260 | } |
| 261 | |
| 262 | func TestNilACLAuthorizer(t *testing.T) { |
Tilak Sharma | b88a111 | 2014-08-15 17:17:12 -0700 | [diff] [blame] | 263 | authorizer := NewACLAuthorizer(nullACL) |
Tilak Sharma | 3ed3024 | 2014-08-11 11:45:55 -0700 | [diff] [blame] | 264 | testNothingPermitted(t, authorizer) |
| 265 | testSelfRPCs(t, authorizer) |
| 266 | } |