github.com/Venafi/vcert/v5@v5.10.2/pkg/venafi/tpp/search_test.go (about) 1 /* 2 * Copyright 2018 Venafi, Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package tpp 18 19 import ( 20 "crypto/x509" 21 "encoding/pem" 22 "fmt" 23 "net/url" 24 "regexp" 25 "strings" 26 "testing" 27 "time" 28 29 "github.com/Venafi/vcert/v5/pkg/certificate" 30 "github.com/Venafi/vcert/v5/pkg/endpoint" 31 "github.com/Venafi/vcert/v5/test" 32 ) 33 34 func TestParseCertificateSearchResponse(t *testing.T) { 35 body := ` 36 { 37 "Certificates": [ 38 { 39 "CreatedOn": "2018-06-06T12:49:11.4795797Z", 40 "DN": "\\VED\\Policy\\devops\\vcert\\renx3.venafi.example.com", 41 "Guid": "{f32c5cd0-9b77-47ab-bf27-65a1159ff98e}", 42 "Name": "renx3.venafi.example.com", 43 "ParentDn": "\\VED\\Policy\\devops\\vcert", 44 "SchemaClass": "X509 Server Certificate", 45 "_links": [ 46 { 47 "Details": "/vedsdk/certificates/%7bf32c5cd0-9b77-47ab-bf27-65a1159ff98e%7d" 48 } 49 ] 50 } 51 ], 52 "DataRange": "Certificates 1 - 1", 53 "TotalCount": 1 54 }` 55 56 res, err := ParseCertificateSearchResponse(200, []byte(body)) 57 if err != nil { 58 t.Fatal(err) 59 } 60 61 if res.Certificates[0].CertificateRequestId != "\\VED\\Policy\\devops\\vcert\\renx3.venafi.example.com" { 62 t.Fatal("failed to parse cert DN") 63 } 64 } 65 66 func TestParseCertificateDetailsResponse(t *testing.T) { 67 body := ` 68 { 69 "CertificateAuthorityDN": "\\VED\\Policy\\devops\\msca_template", 70 "CertificateDetails": { 71 "AIACAIssuerURL": [ 72 "0:http://qavenafica.venqa.venafi.com/CertEnroll/qavenafica.venqa.venafi.com_QA%20Venafi%20CA.crt", 73 "1:ldap:///CN=QA%20Venafi%20CA,CN=AIA,CN=Public%20Key%20Services,CN=Services,CN=Configuration,DC=venqa,DC=venafi,DC=com?cACertificate?base?objectClass=certificationAuthority" 74 ], 75 "AIAKeyIdentifier": "3CAC9CA60DA130D456A73D78BC231BECB47B4D75", 76 "C": "US", 77 "CDPURI": "0::False:http://qavenafica.venqa.venafi.com/CertEnroll/QA%20Venafi%20CA.crl", 78 "CN": "t1579099443-xiel.venafi.example.com", 79 "EnhancedKeyUsage": "Server Authentication(1.3.6.1.5.5.7.3.1)", 80 "Issuer": "CN=QA Venafi CA, DC=venqa, DC=venafi, DC=com", 81 "KeyAlgorithm": "RSA", 82 "KeySize": 8192, 83 "KeyUsage": "KeyEncipherment, DigitalSignature", 84 "L": "Las Vegas", 85 "O": "Venafi, Inc.", 86 "OU": [ 87 "Automated Tests" 88 ], 89 "PublicKeyHash": "8637C052479F9C4A01CC0CEE600769597DF69DA8", 90 "S": "Nevada", 91 "SKIKeyIdentifier": "C65C994B38A5B17841C536A8C8189C6613B02C44", 92 "Serial": "6D007AAF80B115C1BE51B6F94E0000007AAF80", 93 "SignatureAlgorithm": "sha256RSA", 94 "SignatureAlgorithmOID": "1.2.840.113549.1.1.11", 95 "StoreAdded": "2020-01-15T14:47:02.0862587Z", 96 "Subject": "CN=t1579099443-xiel.venafi.example.com, OU=Automated Tests, O=\"Venafi, Inc.\", L=Las Vegas, S=Nevada, C=US", 97 "SubjectAltNameDNS": [ 98 "t1579099443-xiel.venafi.example.com" 99 ], 100 "SubjectAltNameURI": [ 101 "https://example.com/test" 102 ], 103 "TemplateMajorVersion": "100", 104 "TemplateMinorVersion": "4", 105 "TemplateName": "WebServer-2008(8years)", 106 "TemplateOID": "1.3.6.1.4.1.311.21.8.2344178.8460394.1920656.15056892.1115285.96.9686371.12506947", 107 "Thumbprint": "D9F8A14D6687824D2F25D1BE1C2A24697B84CF68", 108 "ValidFrom": "2020-01-15T14:36:29.0000000Z", 109 "ValidTo": "2028-01-13T14:36:29.0000000Z" 110 }, 111 "Contact": [ 112 "local:{f47ab62f-65d4-4a7f-8a8a-cd5440ce2d60}" 113 ], 114 "CreatedBy": [ 115 "Web SDK" 116 ], 117 "CreatedOn": "2020-01-15T14:46:53.2296661Z", 118 "CustomFields": [ 119 { 120 "Name": "custom", 121 "Type": "Text", 122 "Value": [ 123 "2019-10-10" 124 ] 125 } 126 ], 127 "DN": "\\VED\\Policy\\devops\\vcert\\t1579099443-xiel.venafi.example.com", 128 "Guid": "{d1542a81-9268-4c62-af7e-8090fac5194d}", 129 "ManagementType": "Enrollment", 130 "Name": "t1579099443-xiel.venafi.example.com", 131 "ParentDn": "\\VED\\Policy\\devops\\vcert", 132 "ProcessingDetails": {}, 133 "RenewalDetails": { 134 "City": "Las Vegas", 135 "Country": "US", 136 "KeySize": 8192, 137 "Organization": "Venafi, Inc.", 138 "OrganizationalUnit": [ 139 "Automated Tests" 140 ], 141 "State": "Nevada", 142 "Subject": "t1579099443-xiel.venafi.example.com", 143 "SubjectAltNameURI": [ 144 "https://example.com/test" 145 ] 146 }, 147 "SchemaClass": "X509 Server Certificate", 148 "ValidationDetails": { 149 "LastValidationStateUpdate": "0001-01-01T00:00:00.0000000Z" 150 } 151 }` 152 153 res, err := parseCertificateDetailsResponse(200, []byte(body)) 154 if err != nil { 155 t.Fatal(err) 156 } 157 158 if res.CustomFields[0].Value[0] != "2019-10-10" { 159 t.Fatal("invalid custom field value") 160 } 161 } 162 163 func TestRequestAndSearchCertificate(t *testing.T) { 164 tpp, err := getTestConnector(ctx.TPPurl, ctx.TPPZone) 165 if err != nil { 166 t.Fatalf("err is not nil, err: %s url: %s", err, expectedURL) 167 } 168 169 if tpp.apiKey == "" { 170 err = tpp.Authenticate(&endpoint.Authentication{AccessToken: ctx.TPPaccessToken}) 171 if err != nil { 172 t.Fatalf("err is not nil, err: %s", err) 173 } 174 } 175 176 config, err := tpp.ReadZoneConfiguration() 177 if err != nil { 178 t.Fatalf("err is not nil, err: %s", err) 179 } 180 181 cn := test.RandCN() 182 appInfo := "APP Info " + cn 183 workload := fmt.Sprintf("workload-%d", time.Now().Unix()) 184 instance := "devops-instance" 185 cfValue := cn 186 req := &certificate.Request{Timeout: time.Second * 30} 187 req.Subject.CommonName = cn 188 req.Subject.Organization = []string{"Venafi, Inc."} 189 req.Subject.OrganizationalUnit = []string{"Automated Tests"} 190 req.Subject.Locality = []string{"Las Vegas"} 191 req.Subject.Province = []string{"Nevada"} 192 req.Subject.Country = []string{"US"} 193 u := url.URL{Scheme: "https", Host: "example.com", Path: "/test"} 194 req.URIs = []*url.URL{&u} 195 req.FriendlyName = cn 196 req.CustomFields = []certificate.CustomField{ 197 {Name: "custom", Value: cfValue}, 198 {Type: certificate.CustomFieldOrigin, Value: appInfo}, 199 } 200 req.Location = &certificate.Location{ 201 Instance: instance, 202 Workload: workload, 203 TLSAddress: "wwww.example.com:443", 204 } 205 206 req.KeyLength = 1024 207 208 err = tpp.GenerateRequest(config, req) 209 if err != nil { 210 t.Fatalf("err is not nil, err: %s", err) 211 } 212 213 req.PickupID, err = tpp.RequestCertificate(req) 214 if err != nil { 215 t.Fatalf("err is not nil, err: %s", err) 216 } 217 certCollections, err := tpp.RetrieveCertificate(req) 218 if err != nil { 219 t.Fatal(err) 220 } 221 p, _ := pem.Decode([]byte(certCollections.Certificate)) 222 cert, err := x509.ParseCertificate(p.Bytes) 223 if err != nil { 224 t.Fatalf("err is not nil, err: %s", err) 225 } 226 if cert.Subject.CommonName != cn { 227 t.Fatalf("mismatched common names: %v and %v", cn, cert.Subject.CommonName) 228 } 229 if cert.URIs[0].String() != u.String() { 230 t.Fatalf("mismatched URIs: %v and %v", u.String(), cert.URIs[0].String()) 231 } 232 233 thumbprint := calcThumbprint(certCollections.Certificate) 234 searchResult, err := tpp.searchCertificatesByFingerprint(thumbprint) 235 if err != nil { 236 t.Fatal(err) 237 } 238 239 guid := searchResult.Certificates[0].CertificateRequestGuid 240 details, err := tpp.searchCertificateDetails(guid) 241 if err != nil { 242 t.Fatal(err) 243 } 244 245 //check custom fields 246 if details.CustomFields[0].Value[0] != cfValue { 247 t.Fatalf("mismtached custom field valud: want %s but got %s", details.CustomFields[0].Value[0], cfValue) 248 } 249 250 //check installed location device 251 if !strings.HasSuffix(details.Consumers[0], instance+"\\"+workload) { 252 t.Fatalf("Consumer %s should end on %s", details.Consumers[0], instance+"\\"+workload) 253 } 254 255 configReq := ConfigReadDNRequest{ 256 ObjectDN: getCertificateDN(ctx.TPPZone, "", cn), 257 AttributeName: "Origin", 258 } 259 260 configResp, err := tpp.configReadDN(configReq) 261 if err != nil { 262 t.Fatal(err) 263 } 264 if configResp.Values[0] != appInfo { 265 t.Fatalf("Origin attribute value should be %s, but it is %s", appInfo, configResp.Values[0]) 266 } 267 268 //add one more device 269 req.Location = &certificate.Location{ 270 Instance: instance, 271 Workload: workload + "-1", 272 TLSAddress: "wwww.example.com:443", 273 } 274 275 err = tpp.GenerateRequest(config, req) 276 if err != nil { 277 t.Fatalf("err is not nil, err: %s", err) 278 } 279 280 req.PickupID, err = tpp.RequestCertificate(req) 281 if err != nil { 282 t.Fatalf("err is not nil, err: %s", err) 283 } 284 285 //to wait until cert will be aprooved so we can check list of devices 286 _, err = tpp.RetrieveCertificate(req) 287 if err != nil { 288 t.Fatal(err) 289 } 290 291 details, err = tpp.searchCertificateDetails(guid) 292 if err != nil { 293 t.Fatal(err) 294 } 295 296 if len(details.Consumers) < 1 { 297 t.Fatal("There should be at least two devices in consumers") 298 } 299 //check installed location device 300 if !strings.HasSuffix(details.Consumers[1], instance+"\\"+workload+"-1") { 301 t.Fatalf("Consumer %s should end on %s", details.Consumers[1], instance+"\\"+workload+"-1") 302 } 303 304 //replace first device, second must be kept 305 req.Location = &certificate.Location{ 306 Instance: instance, 307 Workload: workload, 308 TLSAddress: "wwww.example.com:443", 309 Replace: true, 310 } 311 312 err = tpp.GenerateRequest(config, req) 313 if err != nil { 314 t.Fatalf("err is not nil, err: %s", err) 315 } 316 317 req.PickupID, err = tpp.RequestCertificate(req) 318 if err != nil { 319 t.Fatalf("err is not nil, err: %s", err) 320 } 321 322 //to wait until cert will be aprooved so we can check list of devices 323 _, err = tpp.RetrieveCertificate(req) 324 if err != nil { 325 t.Fatal(err) 326 } 327 328 details, err = tpp.searchCertificateDetails(guid) 329 if err != nil { 330 t.Fatal(err) 331 } 332 333 if len(details.Consumers) < 1 { 334 t.Fatal("There should be at least two devices in consumers") 335 } 336 337 //check installed location device 338 if !strings.HasSuffix(details.Consumers[0], instance+"\\"+workload+"-1") { 339 t.Fatalf("Consumer %s should end on %s", details.Consumers[0], instance+"\\"+workload+"-1") 340 } 341 } 342 343 func TestRequestAndSearchCertificateWithFriendlyName(t *testing.T) { 344 tpp, err := getTestConnector(ctx.TPPurl, ctx.TPPZone) 345 if err != nil { 346 t.Fatalf("err is not nil, err: %s url: %s", err, expectedURL) 347 } 348 349 if tpp.apiKey == "" { 350 err = tpp.Authenticate(&endpoint.Authentication{AccessToken: ctx.TPPaccessToken}) 351 if err != nil { 352 t.Fatalf("err is not nil, err: %s", err) 353 } 354 } 355 356 config, err := tpp.ReadZoneConfiguration() 357 if err != nil { 358 t.Fatalf("err is not nil, err: %s", err) 359 } 360 361 cn := test.RandCN() 362 appInfo := "APP Info " + cn 363 workload := fmt.Sprintf("workload-%d", time.Now().Unix()) 364 instance := "devops-instance" 365 cfValue := cn 366 req := &certificate.Request{Timeout: time.Second * 30} 367 req.Subject.CommonName = cn 368 req.Subject.Organization = []string{"Venafi, Inc."} 369 req.Subject.OrganizationalUnit = []string{"Automated Tests"} 370 req.Subject.Locality = []string{"Las Vegas"} 371 req.Subject.Province = []string{"Nevada"} 372 req.Subject.Country = []string{"US"} 373 u := url.URL{Scheme: "https", Host: "example.com", Path: "/test"} 374 req.URIs = []*url.URL{&u} 375 req.FriendlyName = fmt.Sprintf("friendly.%s", cn) 376 req.CustomFields = []certificate.CustomField{ 377 {Name: "custom", Value: cfValue}, 378 {Type: certificate.CustomFieldOrigin, Value: appInfo}, 379 } 380 req.Location = &certificate.Location{ 381 Instance: instance, 382 Workload: workload, 383 TLSAddress: "wwww.example.com:443", 384 } 385 386 req.KeyLength = 1024 387 388 err = tpp.GenerateRequest(config, req) 389 if err != nil { 390 t.Fatalf("err is not nil, err: %s", err) 391 } 392 393 req.PickupID, err = tpp.RequestCertificate(req) 394 if err != nil { 395 t.Fatalf("err is not nil, err: %s", err) 396 } 397 certCollections, err := tpp.RetrieveCertificate(req) 398 if err != nil { 399 t.Fatal(err) 400 } 401 p, _ := pem.Decode([]byte(certCollections.Certificate)) 402 cert, err := x509.ParseCertificate(p.Bytes) 403 if err != nil { 404 t.Fatalf("err is not nil, err: %s", err) 405 } 406 if cert.Subject.CommonName != cn { 407 t.Fatalf("mismatched common names: %v and %v", cn, cert.Subject.CommonName) 408 } 409 if cert.URIs[0].String() != u.String() { 410 t.Fatalf("mismatched URIs: %v and %v", u.String(), cert.URIs[0].String()) 411 } 412 413 thumbprint := calcThumbprint(certCollections.Certificate) 414 searchResult, err := tpp.searchCertificatesByFingerprint(thumbprint) 415 if err != nil { 416 t.Fatal(err) 417 } 418 419 guid := searchResult.Certificates[0].CertificateRequestGuid 420 details, err := tpp.searchCertificateDetails(guid) 421 if err != nil { 422 t.Fatal(err) 423 } 424 425 //check custom fields 426 if details.CustomFields[0].Value[0] != cfValue { 427 t.Fatalf("mismtached custom field valud: want %s but got %s", details.CustomFields[0].Value[0], cfValue) 428 } 429 430 //check installed location device 431 if !strings.HasSuffix(details.Consumers[0], instance+"\\"+workload) { 432 t.Fatalf("Consumer %s should end on %s", details.Consumers[0], instance+"\\"+workload) 433 } 434 435 configReq := ConfigReadDNRequest{ 436 ObjectDN: getCertificateDN(ctx.TPPZone, req.FriendlyName, cn), 437 AttributeName: "Origin", 438 } 439 440 configResp, err := tpp.configReadDN(configReq) 441 if err != nil { 442 t.Fatal(err) 443 } 444 if configResp.Values[0] != appInfo { 445 t.Fatalf("Origin attribute value should be %s, but it is %s", appInfo, configResp.Values[0]) 446 } 447 448 //add one more device 449 req.Location = &certificate.Location{ 450 Instance: instance, 451 Workload: workload + "-1", 452 TLSAddress: "wwww.example.com:443", 453 } 454 455 err = tpp.GenerateRequest(config, req) 456 if err != nil { 457 t.Fatalf("err is not nil, err: %s", err) 458 } 459 460 req.PickupID, err = tpp.RequestCertificate(req) 461 if err != nil { 462 t.Fatalf("err is not nil, err: %s", err) 463 } 464 465 //to wait until cert will be aprooved so we can check list of devices 466 _, err = tpp.RetrieveCertificate(req) 467 if err != nil { 468 t.Fatal(err) 469 } 470 471 details, err = tpp.searchCertificateDetails(guid) 472 if err != nil { 473 t.Fatal(err) 474 } 475 476 if len(details.Consumers) < 1 { 477 t.Fatal("There should be at least two devices in consumers") 478 } 479 //check installed location device 480 if !strings.HasSuffix(details.Consumers[1], instance+"\\"+workload+"-1") { 481 t.Fatalf("Consumer %s should end on %s", details.Consumers[1], instance+"\\"+workload+"-1") 482 } 483 484 //replace first device, second must be kept 485 req.Location = &certificate.Location{ 486 Instance: instance, 487 Workload: workload, 488 TLSAddress: "wwww.example.com:443", 489 Replace: true, 490 } 491 492 err = tpp.GenerateRequest(config, req) 493 if err != nil { 494 t.Fatalf("err is not nil, err: %s", err) 495 } 496 497 req.PickupID, err = tpp.RequestCertificate(req) 498 if err != nil { 499 t.Fatalf("err is not nil, err: %s", err) 500 } 501 502 //to wait until cert will be aprooved so we can check list of devices 503 _, err = tpp.RetrieveCertificate(req) 504 if err != nil { 505 t.Fatal(err) 506 } 507 508 details, err = tpp.searchCertificateDetails(guid) 509 if err != nil { 510 t.Fatal(err) 511 } 512 513 if len(details.Consumers) < 1 { 514 t.Fatal("There should be at least two devices in consumers") 515 } 516 517 //check installed location device 518 if !strings.HasSuffix(details.Consumers[0], instance+"\\"+workload+"-1") { 519 t.Fatalf("Consumer %s should end on %s", details.Consumers[0], instance+"\\"+workload+"-1") 520 } 521 } 522 523 func TestSearchDevice(t *testing.T) { 524 t.Skip() //we don't use this method now, keep this test for future usage 525 526 tpp, err := getTestConnector(ctx.TPPurl, ctx.TPPZone) 527 if err != nil { 528 t.Fatalf("err is not nil, err: %s url: %s", err, expectedURL) 529 } 530 531 authResp, err := tpp.GetRefreshToken(&endpoint.Authentication{ 532 User: ctx.TPPuser, Password: ctx.TPPPassword, 533 Scope: "configuration:read"}) 534 if err != nil { 535 panic(err) 536 } 537 538 err = tpp.Authenticate(&endpoint.Authentication{ 539 AccessToken: authResp.Access_token, 540 }) 541 542 if err != nil { 543 t.Fatalf("err is not nil, err: %s", err) 544 } 545 546 req := ConfigReadDNRequest{ 547 ObjectDN: "\\VED\\Policy\\devops\\vcert\\kube-worker-1\\nginx_246", 548 AttributeName: "Certificate", 549 } 550 551 resp, err := tpp.configReadDN(req) 552 if err != nil { 553 t.Fatal(err) 554 } 555 fmt.Println(resp) 556 } 557 558 type FormatSearchCertificateArgumentsMock struct { 559 zone string 560 cn string 561 sans *certificate.Sans 562 certMinTimeLeft time.Duration 563 } 564 565 // TODO: find a way to test the correct time 566 func TestFormatSearchCertificateArguments(t *testing.T) { 567 timeRegex := "((?:(\\d{4}-\\d{2}-\\d{2})T(\\d{2}%3A\\d{2}%3A\\d{2}(?:\\.\\d+)?))(Z|[\\+-]\\d{2}%3A\\d{2})?)$" 568 testCases := []struct { 569 name string 570 input FormatSearchCertificateArgumentsMock 571 expected string 572 }{ 573 { 574 // test empty arguments, should return just the ValidToGreater 575 // argument 576 name: "Empty", 577 input: FormatSearchCertificateArgumentsMock{}, 578 expected: "^ValidToGreater=" + timeRegex, 579 }, 580 { 581 // test with just CN, should return Common Name and ValidToGreater 582 // arguments 583 name: "CN", 584 input: FormatSearchCertificateArgumentsMock{ 585 cn: "test.example.com", 586 }, 587 expected: "^CN=test\\.example\\.com&ValidToGreater=" + timeRegex, 588 }, 589 { 590 // test with just 1 DNS, should return SAN-DNS and ValidToGreater 591 // arguments 592 name: "SANS_1", 593 input: FormatSearchCertificateArgumentsMock{ 594 sans: &certificate.Sans{DNS: []string{"one.example.com"}}, 595 }, 596 expected: "^SAN-DNS=one\\.example\\.com&ValidToGreater=" + timeRegex, 597 }, 598 { 599 // test with 2 DNS, should return both SAN-DNS and ValidToGreater 600 // arguments 601 name: "SANS_2", 602 input: FormatSearchCertificateArgumentsMock{ 603 sans: &certificate.Sans{DNS: []string{"one.example.com", "two.example.com"}}, 604 }, 605 expected: "^SAN-DNS=one\\.example\\.com,two\\.example\\.com&ValidToGreater=" + timeRegex, 606 }, 607 { 608 // test with CN and 1 DNS, should return the Common Name, DNS and 609 // ValidToGreater arguments 610 name: "CN SANS_1", 611 input: FormatSearchCertificateArgumentsMock{ 612 cn: "test.example.com", 613 sans: &certificate.Sans{DNS: []string{"one.example.com"}}, 614 }, 615 expected: "^CN=test\\.example\\.com&SAN-DNS=one\\.example\\.com&ValidToGreater=" + timeRegex, 616 }, 617 { 618 // test with CN and 2 DNS, should return the Common Name, 2 DNS and 619 // ValidToGreater arguments 620 name: "CN SANS_2", 621 input: FormatSearchCertificateArgumentsMock{ 622 cn: "test.example.com", 623 sans: &certificate.Sans{DNS: []string{"one.example.com", "two.example.com"}}, 624 }, 625 expected: "^CN=test\\.example\\.com&SAN-DNS=one\\.example\\.com,two\\.example\\.com&ValidToGreater=" + timeRegex, 626 }, 627 } 628 629 for _, testCase := range testCases { 630 t.Run(testCase.name, func(t *testing.T) { 631 req := formatSearchCertificateArguments(testCase.input.cn, testCase.input.sans, testCase.input.certMinTimeLeft) 632 matches, err := regexp.MatchString(testCase.expected, req) 633 if err != nil { 634 t.Fatal(err) 635 } 636 if !matches { 637 // might want to send a better error message in case of failure 638 t.Errorf("unmatched regexp\nExpected:\n%v\nGot:\n%v", testCase.expected, req) 639 } 640 }) 641 } 642 }