github.com/hashicorp/vault/sdk@v0.11.0/helper/identitytpl/templating_test.go (about) 1 // Copyright (c) HashiCorp, Inc. 2 // SPDX-License-Identifier: MPL-2.0 3 4 package identitytpl 5 6 import ( 7 "errors" 8 "fmt" 9 "math" 10 "strconv" 11 "testing" 12 "time" 13 14 "github.com/hashicorp/vault/sdk/logical" 15 ) 16 17 // intentionally != time.Now() to catch latent used of time.Now instead of 18 // passed in values 19 var testNow = time.Now().Add(100 * time.Hour) 20 21 func TestPopulate_Basic(t *testing.T) { 22 tests := []struct { 23 mode int 24 name string 25 input string 26 output string 27 err error 28 entityName string 29 metadata map[string]string 30 aliasAccessor string 31 aliasID string 32 aliasName string 33 nilEntity bool 34 validityCheckOnly bool 35 aliasMetadata map[string]string 36 aliasCustomMetadata map[string]string 37 groupName string 38 groupMetadata map[string]string 39 groupMemberships []string 40 now time.Time 41 }{ 42 // time.* tests. Keep tests with time.Now() at the front to avoid false 43 // positives due to the second changing during the test 44 { 45 name: "time now", 46 input: "{{time.now}}", 47 output: strconv.Itoa(int(testNow.Unix())), 48 now: testNow, 49 }, 50 { 51 name: "time plus", 52 input: "{{time.now.plus.1h}}", 53 output: strconv.Itoa(int(testNow.Unix() + (60 * 60))), 54 now: testNow, 55 }, 56 { 57 name: "time plus", 58 input: "{{time.now.minus.5m}}", 59 output: strconv.Itoa(int(testNow.Unix() - (5 * 60))), 60 now: testNow, 61 }, 62 { 63 name: "invalid operator", 64 input: "{{time.now.divide.5m}}", 65 err: errors.New("invalid time operator \"divide\""), 66 }, 67 { 68 name: "time missing operand", 69 input: "{{time.now.plus}}", 70 err: errors.New("missing time operand"), 71 }, 72 73 { 74 name: "no_templating", 75 input: "path foobar {", 76 output: "path foobar {", 77 }, 78 { 79 name: "only_closing", 80 input: "path foobar}} {", 81 err: ErrUnbalancedTemplatingCharacter, 82 }, 83 { 84 name: "closing_in_front", 85 input: "path }} {{foobar}} {", 86 err: ErrUnbalancedTemplatingCharacter, 87 }, 88 { 89 name: "closing_in_back", 90 input: "path {{foobar}} }}", 91 err: ErrUnbalancedTemplatingCharacter, 92 }, 93 { 94 name: "basic", 95 input: "path /{{identity.entity.id}}/ {", 96 output: "path /entityID/ {", 97 }, 98 { 99 name: "multiple", 100 input: "path {{identity.entity.name}} {\n\tval = {{identity.entity.metadata.foo}}\n}", 101 entityName: "entityName", 102 metadata: map[string]string{"foo": "bar"}, 103 output: "path entityName {\n\tval = bar\n}", 104 }, 105 { 106 name: "multiple_bad_name", 107 input: "path {{identity.entity.name}} {\n\tval = {{identity.entity.metadata.foo}}\n}", 108 metadata: map[string]string{"foo": "bar"}, 109 err: ErrTemplateValueNotFound, 110 }, 111 { 112 name: "unbalanced_close", 113 input: "path {{identity.entity.id}} {\n\tval = {{ent}}ity.metadata.foo}}\n}", 114 err: ErrUnbalancedTemplatingCharacter, 115 }, 116 { 117 name: "unbalanced_open", 118 input: "path {{identity.entity.id}} {\n\tval = {{ent{{ity.metadata.foo}}\n}", 119 err: ErrUnbalancedTemplatingCharacter, 120 }, 121 { 122 name: "no_entity_no_directives", 123 input: "path {{identity.entity.id}} {\n\tval = {{ent{{ity.metadata.foo}}\n}", 124 err: ErrNoEntityAttachedToToken, 125 nilEntity: true, 126 }, 127 { 128 name: "no_entity_no_diretives", 129 input: "path name {\n\tval = foo\n}", 130 output: "path name {\n\tval = foo\n}", 131 nilEntity: true, 132 }, 133 { 134 name: "alias_id_name", 135 input: "path {{ identity.entity.name}} {\n\tval = {{identity.entity.aliases.foomount.id}} nval = {{identity.entity.aliases.foomount.name}}\n}", 136 entityName: "entityName", 137 aliasAccessor: "foomount", 138 aliasID: "aliasID", 139 aliasName: "aliasName", 140 metadata: map[string]string{"foo": "bar"}, 141 output: "path entityName {\n\tval = aliasID nval = aliasName\n}", 142 }, 143 { 144 name: "alias_id_name_bad_selector", 145 input: "path foobar {\n\tval = {{identity.entity.aliases.foomount}}\n}", 146 aliasAccessor: "foomount", 147 err: errors.New("invalid alias selector"), 148 }, 149 { 150 name: "alias_id_name_bad_accessor", 151 input: "path \"foobar\" {\n\tval = {{identity.entity.aliases.barmount.id}}\n}", 152 aliasAccessor: "foomount", 153 err: errors.New("alias not found"), 154 }, 155 { 156 name: "alias_id_name", 157 input: "path \"{{identity.entity.name}}\" {\n\tval = {{identity.entity.aliases.foomount.metadata.zip}}\n}", 158 entityName: "entityName", 159 aliasAccessor: "foomount", 160 aliasID: "aliasID", 161 metadata: map[string]string{"foo": "bar"}, 162 aliasMetadata: map[string]string{"zip": "zap"}, 163 output: "path \"entityName\" {\n\tval = zap\n}", 164 }, 165 { 166 name: "group_name", 167 input: "path \"{{identity.groups.ids.groupID.name}}\" {\n\tval = {{identity.entity.name}}\n}", 168 entityName: "entityName", 169 groupName: "groupName", 170 output: "path \"groupName\" {\n\tval = entityName\n}", 171 }, 172 { 173 name: "group_bad_id", 174 input: "path \"{{identity.groups.ids.hroupID.name}}\" {\n\tval = {{identity.entity.name}}\n}", 175 entityName: "entityName", 176 groupName: "groupName", 177 err: errors.New("entity is not a member of group \"hroupID\""), 178 }, 179 { 180 name: "group_id", 181 input: "path \"{{identity.groups.names.groupName.id}}\" {\n\tval = {{identity.entity.name}}\n}", 182 entityName: "entityName", 183 groupName: "groupName", 184 output: "path \"groupID\" {\n\tval = entityName\n}", 185 }, 186 { 187 name: "group_bad_name", 188 input: "path \"{{identity.groups.names.hroupName.id}}\" {\n\tval = {{identity.entity.name}}\n}", 189 entityName: "entityName", 190 groupName: "groupName", 191 err: errors.New("entity is not a member of group \"hroupName\""), 192 }, 193 { 194 name: "metadata_object_disallowed", 195 input: "{{identity.entity.metadata}}", 196 metadata: map[string]string{"foo": "bar"}, 197 err: ErrTemplateValueNotFound, 198 }, 199 { 200 name: "alias_metadata_object_disallowed", 201 input: "{{identity.entity.aliases.foomount.metadata}}", 202 aliasAccessor: "foomount", 203 aliasMetadata: map[string]string{"foo": "bar"}, 204 err: ErrTemplateValueNotFound, 205 }, 206 { 207 name: "groups.names_disallowed", 208 input: "{{identity.entity.groups.names}}", 209 groupMemberships: []string{"foo", "bar"}, 210 err: ErrTemplateValueNotFound, 211 }, 212 { 213 name: "groups.ids_disallowed", 214 input: "{{identity.entity.groups.ids}}", 215 groupMemberships: []string{"foo", "bar"}, 216 err: ErrTemplateValueNotFound, 217 }, 218 219 // missing selector cases 220 { 221 mode: JSONTemplating, 222 name: "entity id", 223 input: "{{identity.entity.id}}", 224 output: `"entityID"`, 225 }, 226 { 227 mode: JSONTemplating, 228 name: "entity name", 229 input: "{{identity.entity.name}}", 230 entityName: "entityName", 231 output: `"entityName"`, 232 }, 233 { 234 mode: JSONTemplating, 235 name: "entity name missing", 236 input: "{{identity.entity.name}}", 237 output: `""`, 238 }, 239 { 240 mode: JSONTemplating, 241 name: "alias name/id", 242 input: "{{identity.entity.aliases.foomount.id}} {{identity.entity.aliases.foomount.name}}", 243 aliasAccessor: "foomount", 244 aliasID: "aliasID", 245 aliasName: "aliasName", 246 output: `"aliasID" "aliasName"`, 247 }, 248 { 249 mode: JSONTemplating, 250 name: "one metadata key", 251 input: "{{identity.entity.metadata.color}}", 252 metadata: map[string]string{"foo": "bar", "color": "green"}, 253 output: `"green"`, 254 }, 255 { 256 mode: JSONTemplating, 257 name: "one metadata key not found", 258 input: "{{identity.entity.metadata.size}}", 259 metadata: map[string]string{"foo": "bar", "color": "green"}, 260 output: `""`, 261 }, 262 { 263 mode: JSONTemplating, 264 name: "all entity metadata", 265 input: "{{identity.entity.metadata}}", 266 metadata: map[string]string{"foo": "bar", "color": "green"}, 267 output: `{"color":"green","foo":"bar"}`, 268 }, 269 { 270 mode: JSONTemplating, 271 name: "null entity metadata", 272 input: "{{identity.entity.metadata}}", 273 output: `{}`, 274 }, 275 { 276 mode: JSONTemplating, 277 name: "groups.names", 278 input: "{{identity.entity.groups.names}}", 279 groupMemberships: []string{"foo", "bar"}, 280 output: `["foo","bar"]`, 281 }, 282 { 283 mode: JSONTemplating, 284 name: "groups.ids", 285 input: "{{identity.entity.groups.ids}}", 286 groupMemberships: []string{"foo", "bar"}, 287 output: `["foo_0","bar_1"]`, 288 }, 289 { 290 mode: JSONTemplating, 291 name: "one alias metadata key", 292 input: "{{identity.entity.aliases.aws_123.metadata.color}}", 293 aliasAccessor: "aws_123", 294 aliasMetadata: map[string]string{"foo": "bar", "color": "green"}, 295 output: `"green"`, 296 }, 297 { 298 mode: JSONTemplating, 299 name: "one alias metadata key not found", 300 input: "{{identity.entity.aliases.aws_123.metadata.size}}", 301 aliasAccessor: "aws_123", 302 aliasMetadata: map[string]string{"foo": "bar", "color": "green"}, 303 output: `""`, 304 }, 305 { 306 mode: JSONTemplating, 307 name: "one alias metadata, accessor not found", 308 input: "{{identity.entity.aliases.aws_123.metadata.size}}", 309 aliasAccessor: "not_gonna_match", 310 aliasMetadata: map[string]string{"foo": "bar", "color": "green"}, 311 output: `""`, 312 }, 313 { 314 mode: JSONTemplating, 315 name: "all alias metadata", 316 input: "{{identity.entity.aliases.aws_123.metadata}}", 317 aliasAccessor: "aws_123", 318 aliasMetadata: map[string]string{"foo": "bar", "color": "green"}, 319 output: `{"color":"green","foo":"bar"}`, 320 }, 321 { 322 mode: JSONTemplating, 323 name: "null alias metadata", 324 input: "{{identity.entity.aliases.aws_123.metadata}}", 325 aliasAccessor: "aws_123", 326 output: `{}`, 327 }, 328 { 329 mode: JSONTemplating, 330 name: "all alias metadata, accessor not found", 331 input: "{{identity.entity.aliases.aws_123.metadata}}", 332 aliasAccessor: "not_gonna_match", 333 aliasMetadata: map[string]string{"foo": "bar", "color": "green"}, 334 output: `{}`, 335 }, 336 { 337 mode: JSONTemplating, 338 name: "one alias custom metadata key", 339 input: "{{identity.entity.aliases.aws_123.custom_metadata.foo}}", 340 aliasAccessor: "aws_123", 341 aliasCustomMetadata: map[string]string{"foo": "abc", "bar": "123"}, 342 output: `"abc"`, 343 }, 344 { 345 mode: JSONTemplating, 346 name: "one alias custom metadata key not found", 347 input: "{{identity.entity.aliases.aws_123.custom_metadata.size}}", 348 aliasAccessor: "aws_123", 349 aliasCustomMetadata: map[string]string{"foo": "abc", "bar": "123"}, 350 output: `""`, 351 }, 352 { 353 mode: JSONTemplating, 354 name: "one alias custom metadata, accessor not found", 355 input: "{{identity.entity.aliases.aws_123.custom_metadata.size}}", 356 aliasAccessor: "not_gonna_match", 357 aliasCustomMetadata: map[string]string{"foo": "abc", "bar": "123"}, 358 output: `""`, 359 }, 360 { 361 mode: JSONTemplating, 362 name: "all alias custom metadata", 363 input: "{{identity.entity.aliases.aws_123.custom_metadata}}", 364 aliasAccessor: "aws_123", 365 aliasCustomMetadata: map[string]string{"foo": "abc", "bar": "123"}, 366 output: `{"bar":"123","foo":"abc"}`, 367 }, 368 { 369 mode: JSONTemplating, 370 name: "null alias custom metadata", 371 input: "{{identity.entity.aliases.aws_123.custom_metadata}}", 372 aliasAccessor: "aws_123", 373 output: `{}`, 374 }, 375 { 376 mode: JSONTemplating, 377 name: "all alias custom metadata, accessor not found", 378 input: "{{identity.entity.aliases.aws_123.custom_metadata}}", 379 aliasAccessor: "not_gonna_match", 380 aliasCustomMetadata: map[string]string{"foo": "abc", "bar": "123"}, 381 output: `{}`, 382 }, 383 } 384 385 for _, test := range tests { 386 var entity *logical.Entity 387 if !test.nilEntity { 388 entity = &logical.Entity{ 389 ID: "entityID", 390 Name: test.entityName, 391 Metadata: test.metadata, 392 } 393 } 394 if test.aliasAccessor != "" { 395 entity.Aliases = []*logical.Alias{ 396 { 397 MountAccessor: test.aliasAccessor, 398 ID: test.aliasID, 399 Name: test.aliasName, 400 Metadata: test.aliasMetadata, 401 CustomMetadata: test.aliasCustomMetadata, 402 }, 403 } 404 } 405 var groups []*logical.Group 406 if test.groupName != "" { 407 groups = append(groups, &logical.Group{ 408 ID: "groupID", 409 Name: test.groupName, 410 Metadata: test.groupMetadata, 411 NamespaceID: "root", 412 }) 413 } 414 415 if test.groupMemberships != nil { 416 for i, groupName := range test.groupMemberships { 417 groups = append(groups, &logical.Group{ 418 ID: fmt.Sprintf("%s_%d", groupName, i), 419 Name: groupName, 420 }) 421 } 422 } 423 424 subst, out, err := PopulateString(PopulateStringInput{ 425 Mode: test.mode, 426 ValidityCheckOnly: test.validityCheckOnly, 427 String: test.input, 428 Entity: entity, 429 Groups: groups, 430 NamespaceID: "root", 431 Now: test.now, 432 }) 433 if err != nil { 434 if test.err == nil { 435 t.Fatalf("%s: expected success, got error: %v", test.name, err) 436 } 437 if err.Error() != test.err.Error() { 438 t.Fatalf("%s: got error: %v", test.name, err) 439 } 440 } 441 if out != test.output { 442 t.Fatalf("%s: bad output: %s, expected: %s", test.name, out, test.output) 443 } 444 if err == nil && !subst && out != test.input { 445 t.Fatalf("%s: bad subst flag", test.name) 446 } 447 } 448 } 449 450 func TestPopulate_CurrentTime(t *testing.T) { 451 now := time.Now() 452 453 // Test that an unset Now parameter results in current time 454 input := PopulateStringInput{ 455 Mode: JSONTemplating, 456 String: `{{time.now}}`, 457 } 458 459 _, out, err := PopulateString(input) 460 if err != nil { 461 t.Fatal(err) 462 } 463 464 nowPopulated, err := strconv.Atoi(out) 465 if err != nil { 466 t.Fatal(err) 467 } 468 469 diff := math.Abs(float64(int64(nowPopulated) - now.Unix())) 470 if diff > 1 { 471 t.Fatalf("expected time within 1 second. Got diff of: %f", diff) 472 } 473 } 474 475 func TestPopulate_FullObject(t *testing.T) { 476 testEntity := &logical.Entity{ 477 ID: "abc-123", 478 Name: "Entity Name", 479 Metadata: map[string]string{ 480 "color": "green", 481 "size": "small", 482 "non-printable": "\"\n\t", 483 }, 484 Aliases: []*logical.Alias{ 485 { 486 MountAccessor: "aws_123", 487 Metadata: map[string]string{ 488 "service": "ec2", 489 "region": "west", 490 }, 491 CustomMetadata: map[string]string{ 492 "foo": "abc", 493 "bar": "123", 494 }, 495 }, 496 }, 497 } 498 499 testGroups := []*logical.Group{ 500 {ID: "a08b0c02", Name: "g1"}, 501 {ID: "239bef91", Name: "g2"}, 502 } 503 504 template := ` 505 { 506 "id": {{identity.entity.id}}, 507 "name": {{identity.entity.name}}, 508 "all metadata": {{identity.entity.metadata}}, 509 "one metadata key": {{identity.entity.metadata.color}}, 510 "one metadata key not found": {{identity.entity.metadata.asldfk}}, 511 "alias metadata": {{identity.entity.aliases.aws_123.metadata}}, 512 "alias not found metadata": {{identity.entity.aliases.blahblah.metadata}}, 513 "one alias metadata key": {{identity.entity.aliases.aws_123.metadata.service}}, 514 "one not found alias metadata key": {{identity.entity.aliases.blahblah.metadata.service}}, 515 "group names": {{identity.entity.groups.names}}, 516 "group ids": {{identity.entity.groups.ids}}, 517 "repeated and": {"nested element": {{identity.entity.name}}}, 518 "alias custom metadata": {{identity.entity.aliases.aws_123.custom_metadata}}, 519 "alias not found custom metadata": {{identity.entity.aliases.blahblah.custom_metadata}}, 520 "one alias custom metadata key": {{identity.entity.aliases.aws_123.custom_metadata.foo}}, 521 "one not found alias custom metadata key": {{identity.entity.aliases.blahblah.custom_metadata.foo}}, 522 }` 523 524 expected := ` 525 { 526 "id": "abc-123", 527 "name": "Entity Name", 528 "all metadata": {"color":"green","non-printable":"\"\n\t","size":"small"}, 529 "one metadata key": "green", 530 "one metadata key not found": "", 531 "alias metadata": {"region":"west","service":"ec2"}, 532 "alias not found metadata": {}, 533 "one alias metadata key": "ec2", 534 "one not found alias metadata key": "", 535 "group names": ["g1","g2"], 536 "group ids": ["a08b0c02","239bef91"], 537 "repeated and": {"nested element": "Entity Name"}, 538 "alias custom metadata": {"bar":"123","foo":"abc"}, 539 "alias not found custom metadata": {}, 540 "one alias custom metadata key": "abc", 541 "one not found alias custom metadata key": "", 542 }` 543 544 input := PopulateStringInput{ 545 Mode: JSONTemplating, 546 String: template, 547 Entity: testEntity, 548 Groups: testGroups, 549 } 550 _, out, err := PopulateString(input) 551 if err != nil { 552 t.Fatal(err) 553 } 554 555 if out != expected { 556 t.Fatalf("expected:\n%s\n\ngot:\n%s", expected, out) 557 } 558 }