github.com/ilhicas/nomad@v0.11.8/acl/acl_test.go (about) 1 package acl 2 3 import ( 4 "testing" 5 6 "github.com/stretchr/testify/assert" 7 ) 8 9 func TestCapabilitySet(t *testing.T) { 10 var cs capabilitySet = make(map[string]struct{}) 11 12 // Check no capabilities by default 13 if cs.Check(PolicyDeny) { 14 t.Fatalf("unexpected check") 15 } 16 17 // Do a set and check 18 cs.Set(PolicyDeny) 19 if !cs.Check(PolicyDeny) { 20 t.Fatalf("missing check") 21 } 22 23 // Clear and check 24 cs.Clear() 25 if cs.Check(PolicyDeny) { 26 t.Fatalf("unexpected check") 27 } 28 } 29 30 func TestMaxPrivilege(t *testing.T) { 31 type tcase struct { 32 Privilege string 33 PrecedenceOver []string 34 } 35 tcases := []tcase{ 36 { 37 PolicyDeny, 38 []string{PolicyDeny, PolicyWrite, PolicyRead, ""}, 39 }, 40 { 41 PolicyWrite, 42 []string{PolicyWrite, PolicyRead, ""}, 43 }, 44 { 45 PolicyRead, 46 []string{PolicyRead, ""}, 47 }, 48 } 49 50 for idx1, tc := range tcases { 51 for idx2, po := range tc.PrecedenceOver { 52 if maxPrivilege(tc.Privilege, po) != tc.Privilege { 53 t.Fatalf("failed %d %d", idx1, idx2) 54 } 55 if maxPrivilege(po, tc.Privilege) != tc.Privilege { 56 t.Fatalf("failed %d %d", idx1, idx2) 57 } 58 } 59 } 60 } 61 62 func TestACLManagement(t *testing.T) { 63 assert := assert.New(t) 64 65 // Create management ACL 66 acl, err := NewACL(true, nil) 67 assert.Nil(err) 68 69 // Check default namespace rights 70 assert.True(acl.AllowNamespaceOperation("default", NamespaceCapabilityListJobs)) 71 assert.True(acl.AllowNamespaceOperation("default", NamespaceCapabilitySubmitJob)) 72 assert.True(acl.AllowNamespace("default")) 73 74 // Check non-specified namespace 75 assert.True(acl.AllowNamespaceOperation("foo", NamespaceCapabilityListJobs)) 76 assert.True(acl.AllowNamespace("foo")) 77 78 // Check the other simpler operations 79 assert.True(acl.IsManagement()) 80 assert.True(acl.AllowAgentRead()) 81 assert.True(acl.AllowAgentWrite()) 82 assert.True(acl.AllowNodeRead()) 83 assert.True(acl.AllowNodeWrite()) 84 assert.True(acl.AllowOperatorRead()) 85 assert.True(acl.AllowOperatorWrite()) 86 assert.True(acl.AllowQuotaRead()) 87 assert.True(acl.AllowQuotaWrite()) 88 } 89 90 func TestACLMerge(t *testing.T) { 91 assert := assert.New(t) 92 93 // Merge read + write policy 94 p1, err := Parse(readAll) 95 assert.Nil(err) 96 p2, err := Parse(writeAll) 97 assert.Nil(err) 98 acl, err := NewACL(false, []*Policy{p1, p2}) 99 assert.Nil(err) 100 101 // Check default namespace rights 102 assert.True(acl.AllowNamespaceOperation("default", NamespaceCapabilityListJobs)) 103 assert.True(acl.AllowNamespaceOperation("default", NamespaceCapabilitySubmitJob)) 104 assert.True(acl.AllowNamespace("default")) 105 106 // Check non-specified namespace 107 assert.False(acl.AllowNamespaceOperation("foo", NamespaceCapabilityListJobs)) 108 assert.False(acl.AllowNamespace("foo")) 109 110 // Check the other simpler operations 111 assert.False(acl.IsManagement()) 112 assert.True(acl.AllowAgentRead()) 113 assert.True(acl.AllowAgentWrite()) 114 assert.True(acl.AllowNodeRead()) 115 assert.True(acl.AllowNodeWrite()) 116 assert.True(acl.AllowOperatorRead()) 117 assert.True(acl.AllowOperatorWrite()) 118 assert.True(acl.AllowQuotaRead()) 119 assert.True(acl.AllowQuotaWrite()) 120 121 // Merge read + blank 122 p3, err := Parse("") 123 assert.Nil(err) 124 acl, err = NewACL(false, []*Policy{p1, p3}) 125 assert.Nil(err) 126 127 // Check default namespace rights 128 assert.True(acl.AllowNamespaceOperation("default", NamespaceCapabilityListJobs)) 129 assert.False(acl.AllowNamespaceOperation("default", NamespaceCapabilitySubmitJob)) 130 131 // Check non-specified namespace 132 assert.False(acl.AllowNamespaceOperation("foo", NamespaceCapabilityListJobs)) 133 134 // Check the other simpler operations 135 assert.False(acl.IsManagement()) 136 assert.True(acl.AllowAgentRead()) 137 assert.False(acl.AllowAgentWrite()) 138 assert.True(acl.AllowNodeRead()) 139 assert.False(acl.AllowNodeWrite()) 140 assert.True(acl.AllowOperatorRead()) 141 assert.False(acl.AllowOperatorWrite()) 142 assert.True(acl.AllowQuotaRead()) 143 assert.False(acl.AllowQuotaWrite()) 144 145 // Merge read + deny 146 p4, err := Parse(denyAll) 147 assert.Nil(err) 148 acl, err = NewACL(false, []*Policy{p1, p4}) 149 assert.Nil(err) 150 151 // Check default namespace rights 152 assert.False(acl.AllowNamespaceOperation("default", NamespaceCapabilityListJobs)) 153 assert.False(acl.AllowNamespaceOperation("default", NamespaceCapabilitySubmitJob)) 154 155 // Check non-specified namespace 156 assert.False(acl.AllowNamespaceOperation("foo", NamespaceCapabilityListJobs)) 157 158 // Check the other simpler operations 159 assert.False(acl.IsManagement()) 160 assert.False(acl.AllowAgentRead()) 161 assert.False(acl.AllowAgentWrite()) 162 assert.False(acl.AllowNodeRead()) 163 assert.False(acl.AllowNodeWrite()) 164 assert.False(acl.AllowOperatorRead()) 165 assert.False(acl.AllowOperatorWrite()) 166 assert.False(acl.AllowQuotaRead()) 167 assert.False(acl.AllowQuotaWrite()) 168 } 169 170 var readAll = ` 171 namespace "default" { 172 policy = "read" 173 } 174 agent { 175 policy = "read" 176 } 177 node { 178 policy = "read" 179 } 180 operator { 181 policy = "read" 182 } 183 quota { 184 policy = "read" 185 } 186 ` 187 188 var writeAll = ` 189 namespace "default" { 190 policy = "write" 191 } 192 agent { 193 policy = "write" 194 } 195 node { 196 policy = "write" 197 } 198 operator { 199 policy = "write" 200 } 201 quota { 202 policy = "write" 203 } 204 ` 205 206 var denyAll = ` 207 namespace "default" { 208 policy = "deny" 209 } 210 agent { 211 policy = "deny" 212 } 213 node { 214 policy = "deny" 215 } 216 operator { 217 policy = "deny" 218 } 219 quota { 220 policy = "deny" 221 } 222 ` 223 224 func TestAllowNamespace(t *testing.T) { 225 tests := []struct { 226 Policy string 227 Allow bool 228 }{ 229 { 230 Policy: `namespace "foo" {}`, 231 Allow: false, 232 }, 233 { 234 Policy: `namespace "foo" { policy = "deny" }`, 235 Allow: false, 236 }, 237 { 238 Policy: `namespace "foo" { capabilities = ["deny"] }`, 239 Allow: false, 240 }, 241 { 242 Policy: `namespace "foo" { capabilities = ["list-jobs"] }`, 243 Allow: true, 244 }, 245 { 246 Policy: `namespace "foo" { policy = "read" }`, 247 Allow: true, 248 }, 249 } 250 251 for _, tc := range tests { 252 t.Run(tc.Policy, func(t *testing.T) { 253 assert := assert.New(t) 254 255 policy, err := Parse(tc.Policy) 256 assert.Nil(err) 257 258 acl, err := NewACL(false, []*Policy{policy}) 259 assert.Nil(err) 260 261 assert.Equal(tc.Allow, acl.AllowNamespace("foo")) 262 }) 263 } 264 } 265 266 func TestWildcardNamespaceMatching(t *testing.T) { 267 tests := []struct { 268 Policy string 269 Allow bool 270 }{ 271 { // Wildcard matches 272 Policy: `namespace "prod-api-*" { policy = "write" }`, 273 Allow: true, 274 }, 275 { // Non globbed namespaces are not wildcards 276 Policy: `namespace "prod-api" { policy = "write" }`, 277 Allow: false, 278 }, 279 { // Concrete matches take precedence 280 Policy: `namespace "prod-api-services" { policy = "deny" } 281 namespace "prod-api-*" { policy = "write" }`, 282 Allow: false, 283 }, 284 { 285 Policy: `namespace "prod-api-*" { policy = "deny" } 286 namespace "prod-api-services" { policy = "write" }`, 287 Allow: true, 288 }, 289 { // The closest character match wins 290 Policy: `namespace "*-api-services" { policy = "deny" } 291 namespace "prod-api-*" { policy = "write" }`, // 4 vs 8 chars 292 Allow: false, 293 }, 294 { 295 Policy: `namespace "prod-api-*" { policy = "write" } 296 namespace "*-api-services" { policy = "deny" }`, // 4 vs 8 chars 297 Allow: false, 298 }, 299 } 300 301 for _, tc := range tests { 302 t.Run(tc.Policy, func(t *testing.T) { 303 assert := assert.New(t) 304 305 policy, err := Parse(tc.Policy) 306 assert.NoError(err) 307 assert.NotNil(policy.Namespaces) 308 309 acl, err := NewACL(false, []*Policy{policy}) 310 assert.Nil(err) 311 312 assert.Equal(tc.Allow, acl.AllowNamespace("prod-api-services")) 313 }) 314 } 315 } 316 317 func TestWildcardHostVolumeMatching(t *testing.T) { 318 tests := []struct { 319 Policy string 320 Allow bool 321 }{ 322 { // Wildcard matches 323 Policy: `host_volume "prod-api-*" { policy = "write" }`, 324 Allow: true, 325 }, 326 { // Non globbed volumes are not wildcards 327 Policy: `host_volume "prod-api" { policy = "write" }`, 328 Allow: false, 329 }, 330 { // Concrete matches take precedence 331 Policy: `host_volume "prod-api-services" { policy = "deny" } 332 host_volume "prod-api-*" { policy = "write" }`, 333 Allow: false, 334 }, 335 { 336 Policy: `host_volume "prod-api-*" { policy = "deny" } 337 host_volume "prod-api-services" { policy = "write" }`, 338 Allow: true, 339 }, 340 { // The closest character match wins 341 Policy: `host_volume "*-api-services" { policy = "deny" } 342 host_volume "prod-api-*" { policy = "write" }`, // 4 vs 8 chars 343 Allow: false, 344 }, 345 { 346 Policy: `host_volume "prod-api-*" { policy = "write" } 347 host_volume "*-api-services" { policy = "deny" }`, // 4 vs 8 chars 348 Allow: false, 349 }, 350 } 351 352 for _, tc := range tests { 353 t.Run(tc.Policy, func(t *testing.T) { 354 assert := assert.New(t) 355 356 policy, err := Parse(tc.Policy) 357 assert.NoError(err) 358 assert.NotNil(policy.HostVolumes) 359 360 acl, err := NewACL(false, []*Policy{policy}) 361 assert.Nil(err) 362 363 assert.Equal(tc.Allow, acl.AllowHostVolume("prod-api-services")) 364 }) 365 } 366 } 367 func TestACL_matchingCapabilitySet_returnsAllMatches(t *testing.T) { 368 tests := []struct { 369 Policy string 370 NS string 371 MatchingGlobs []string 372 }{ 373 { 374 Policy: `namespace "production-*" { policy = "write" }`, 375 NS: "production-api", 376 MatchingGlobs: []string{"production-*"}, 377 }, 378 { 379 Policy: `namespace "prod-*" { policy = "write" }`, 380 NS: "production-api", 381 MatchingGlobs: nil, 382 }, 383 { 384 Policy: `namespace "production-*" { policy = "write" } 385 namespace "production-*-api" { policy = "deny" }`, 386 387 NS: "production-admin-api", 388 MatchingGlobs: []string{"production-*", "production-*-api"}, 389 }, 390 } 391 392 for _, tc := range tests { 393 t.Run(tc.Policy, func(t *testing.T) { 394 assert := assert.New(t) 395 396 policy, err := Parse(tc.Policy) 397 assert.NoError(err) 398 assert.NotNil(policy.Namespaces) 399 400 acl, err := NewACL(false, []*Policy{policy}) 401 assert.Nil(err) 402 403 var namespaces []string 404 for _, cs := range findAllMatchingWildcards(acl.wildcardNamespaces, tc.NS) { 405 namespaces = append(namespaces, cs.name) 406 } 407 408 assert.Equal(tc.MatchingGlobs, namespaces) 409 }) 410 } 411 } 412 413 func TestACL_matchingCapabilitySet_difference(t *testing.T) { 414 tests := []struct { 415 Policy string 416 NS string 417 Difference int 418 }{ 419 { 420 Policy: `namespace "production-*" { policy = "write" }`, 421 NS: "production-api", 422 Difference: 3, 423 }, 424 { 425 Policy: `namespace "production-*" { policy = "write" }`, 426 NS: "production-admin-api", 427 Difference: 9, 428 }, 429 { 430 Policy: `namespace "production-**" { policy = "write" }`, 431 NS: "production-admin-api", 432 Difference: 9, 433 }, 434 { 435 Policy: `namespace "*" { policy = "write" }`, 436 NS: "production-admin-api", 437 Difference: 20, 438 }, 439 { 440 Policy: `namespace "*admin*" { policy = "write" }`, 441 NS: "production-admin-api", 442 Difference: 15, 443 }, 444 } 445 446 for _, tc := range tests { 447 t.Run(tc.Policy, func(t *testing.T) { 448 assert := assert.New(t) 449 450 policy, err := Parse(tc.Policy) 451 assert.NoError(err) 452 assert.NotNil(policy.Namespaces) 453 454 acl, err := NewACL(false, []*Policy{policy}) 455 assert.Nil(err) 456 457 matches := findAllMatchingWildcards(acl.wildcardNamespaces, tc.NS) 458 assert.Equal(tc.Difference, matches[0].difference) 459 }) 460 } 461 462 }