github.com/Venafi/vcert/v5@v5.10.2/pkg/venafi/firefly/connector_test.go (about) 1 /* 2 * Copyright 2023 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 firefly 18 19 import ( 20 "crypto/tls" 21 "crypto/x509/pkix" 22 "fmt" 23 "net" 24 "net/http" 25 "net/url" 26 "strconv" 27 "testing" 28 29 "github.com/Venafi/vcert/v5/pkg/certificate" 30 "github.com/Venafi/vcert/v5/pkg/endpoint" 31 "github.com/stretchr/testify/assert" 32 "github.com/stretchr/testify/suite" 33 ) 34 35 type ConnectorSuite struct { 36 suite.Suite 37 idpServer *IdentityProviderMockServer 38 fireflyServer *FireflyMockServer 39 } 40 41 func (s *ConnectorSuite) SetupSuite() { 42 //setting the tls connection as insecure for testing purposes 43 http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{ 44 InsecureSkipVerify: true, 45 } 46 fmt.Println("mocking servers") 47 s.idpServer = newIdentityProviderMockServer() 48 s.fireflyServer = newFireflyMockServer() 49 } 50 51 func (s *ConnectorSuite) TearDownSuite() { 52 fmt.Println("closing mocked servers") 53 s.idpServer.server.Close() 54 s.fireflyServer.server.Close() 55 } 56 57 func (s *ConnectorSuite) createCredFlowAuth() *endpoint.Authentication { 58 return &endpoint.Authentication{ 59 Scope: TestingScope, 60 ClientId: TestingClientID, 61 ClientSecret: TestingClientSecret, 62 IdentityProvider: &endpoint.OAuthProvider{ 63 TokenURL: s.idpServer.idpURL + s.idpServer.tokenPath, 64 Audience: TestingAudience, 65 }, 66 } 67 } 68 69 func (s *ConnectorSuite) createPasswordFlowAuth() *endpoint.Authentication { 70 return &endpoint.Authentication{ 71 User: TestingUserName, 72 Password: TestingUserPassword, 73 Scope: TestingScope, 74 ClientId: TestingClientID, 75 IdentityProvider: &endpoint.OAuthProvider{ 76 TokenURL: s.idpServer.idpURL + s.idpServer.tokenPath, 77 Audience: TestingAudience, 78 }, 79 } 80 } 81 82 func (s *ConnectorSuite) createDevFlowAuth() *endpoint.Authentication { 83 return &endpoint.Authentication{ 84 Scope: TestingScope, 85 ClientId: TestingClientID, 86 IdentityProvider: &endpoint.OAuthProvider{ 87 DeviceURL: s.idpServer.idpURL + s.idpServer.devicePath, 88 TokenURL: s.idpServer.idpURL + s.idpServer.tokenPath, 89 Audience: TestingAudience, 90 }, 91 } 92 } 93 94 // In order for 'go test' to run this suite, we need to create 95 // a normal test function and pass our suite to suite.Run 96 func TestConnectorSuite(t *testing.T) { 97 suite.Run(t, new(ConnectorSuite)) 98 } 99 100 func (s *ConnectorSuite) TestNewConnector() { 101 102 s.Run("Success", func() { 103 fireflyConnector, err := NewConnector("my.firefly:8080", "", false, nil) 104 105 assert.Nil(s.T(), err) 106 assert.NotNil(s.T(), fireflyConnector) 107 assert.Equal(s.T(), "https://my.firefly:8080/", fireflyConnector.baseURL) 108 }) 109 } 110 111 func (s *ConnectorSuite) TestGetType() { 112 fireflyConnector, err := NewConnector(s.fireflyServer.serverURL, "", false, nil) 113 assert.Nil(s.T(), err, fmt.Errorf("error creating firefly connector: %w", err).Error()) 114 assert.Equal(s.T(), endpoint.ConnectorTypeFirefly, fireflyConnector.GetType()) 115 } 116 117 func (s *ConnectorSuite) TestAuthenticate() { 118 s.Run("AuthenticationConfNotProvided", func() { 119 fireflyConnector, err := NewConnector(s.fireflyServer.serverURL, "", false, nil) 120 assert.Nil(s.T(), err, fmt.Errorf("error creating firefly connector: %w", err).Error()) 121 122 err = fireflyConnector.Authenticate(nil) 123 124 if assert.Errorf(s.T(), err, "expected to get an error but was gotten the access_token") { 125 assert.Equal(s.T(), "failed to authenticate: no credentials provided", err.Error()) 126 } 127 assert.Equal(s.T(), "", fireflyConnector.accessToken) 128 }) 129 130 s.Run("Success", func() { 131 fireflyConnector, err := NewConnector(s.fireflyServer.serverURL, "", false, nil) 132 assert.Nil(s.T(), err, fmt.Errorf("error creating firefly connector: %w", err).Error()) 133 134 err = fireflyConnector.Authenticate(s.createCredFlowAuth()) 135 136 assert.Nil(s.T(), err, fmt.Errorf("error getting acccess token: %w", err).Error()) 137 assert.NotNil(s.T(), fireflyConnector.accessToken) 138 }) 139 } 140 141 func (s *ConnectorSuite) TestClientCredentialFlow() { 142 fireflyConnector, err := NewConnector(s.fireflyServer.serverURL, "", false, nil) 143 assert.Nil(s.T(), err, fmt.Errorf("error creating firefly connector: %w", err).Error()) 144 145 oauthToken, err := fireflyConnector.Authorize(s.createCredFlowAuth()) 146 147 assert.Nil(s.T(), err, fmt.Errorf("error getting acccess token: %w", err).Error()) 148 assert.NotNil(s.T(), oauthToken) 149 } 150 151 func (s *ConnectorSuite) TestClientCredentialFlow_Unauthorized() { 152 fireflyConnector, err := NewConnector(s.fireflyServer.serverURL, "", false, nil) 153 assert.Nil(s.T(), err, fmt.Errorf("error creating firefly connector: %w", err).Error()) 154 155 auth := s.createCredFlowAuth() 156 //changing the clientId 157 auth.ClientId = "unauthorized" 158 159 oauthToken, err := fireflyConnector.Authorize(auth) 160 161 assert.NotNil(s.T(), err, fmt.Errorf("error getting acccess token: %w", err).Error()) 162 assert.Nil(s.T(), oauthToken) 163 } 164 165 func (s *ConnectorSuite) TestClientPasswordFlow() { 166 fireflyConnector, err := NewConnector(s.fireflyServer.serverURL, "", false, nil) 167 assert.Nil(s.T(), err, fmt.Errorf("error creating firefly connector: %w", err).Error()) 168 169 oauthToken, err := fireflyConnector.Authorize(s.createPasswordFlowAuth()) 170 171 assert.Nil(s.T(), err, fmt.Errorf("error getting acccess token: %w", err).Error()) 172 assert.NotNil(s.T(), oauthToken) 173 } 174 175 func (s *ConnectorSuite) TestClientPasswordFlow_Unauthorized() { 176 fireflyConnector, err := NewConnector(s.fireflyServer.serverURL, "", false, nil) 177 assert.Nil(s.T(), err, fmt.Errorf("error creating firefly connector: %w", err).Error()) 178 179 auth := s.createPasswordFlowAuth() 180 auth.ClientId = "unauthorized" 181 182 oauthToken, err := fireflyConnector.Authorize(auth) 183 184 assert.NotNil(s.T(), err, fmt.Errorf("error getting acccess token: %w", err).Error()) 185 assert.Nil(s.T(), oauthToken) 186 } 187 188 func (s *ConnectorSuite) TestDeviceFlow() { 189 fireflyConnector, err := NewConnector(s.fireflyServer.serverURL, "", false, nil) 190 assert.Nil(s.T(), err, fmt.Errorf("error creating firefly connector: %w", err).Error()) 191 192 oauthToken, err := fireflyConnector.Authorize(s.createDevFlowAuth()) 193 194 assert.Nil(s.T(), err, fmt.Errorf("error getting acccess token: %w", err).Error()) 195 assert.NotNil(s.T(), oauthToken) 196 } 197 198 func (s *ConnectorSuite) TestDeviceFlow_AuthPending() { 199 fireflyConnector, err := NewConnector(s.fireflyServer.serverURL, "", false, nil) 200 assert.Nil(s.T(), err, fmt.Errorf("error creating firefly connector: %w", err).Error()) 201 202 auth := s.createDevFlowAuth() 203 auth.ClientId = TestingClientIDAuthPending 204 205 oauthToken, err := fireflyConnector.Authorize(auth) 206 207 assert.Nil(s.T(), err, fmt.Errorf("error getting acccess token: %w", err).Error()) 208 assert.NotNil(s.T(), oauthToken) 209 } 210 211 func (s *ConnectorSuite) TestDeviceFlow_SlowDown() { 212 fireflyConnector, err := NewConnector(s.fireflyServer.serverURL, "", false, nil) 213 assert.Nil(s.T(), err, fmt.Errorf("error creating firefly connector: %w", err).Error()) 214 215 auth := s.createDevFlowAuth() 216 auth.ClientId = TestingClientIDSlowDown 217 218 oauthToken, err := fireflyConnector.Authorize(auth) 219 220 assert.Nil(s.T(), err, fmt.Errorf("error getting acccess token: %w", err).Error()) 221 assert.NotNil(s.T(), oauthToken) 222 } 223 224 func (s *ConnectorSuite) TestDeviceFlow_AccessDenied() { 225 fireflyConnector, err := NewConnector(s.fireflyServer.serverURL, "", false, nil) 226 assert.Nil(s.T(), err, fmt.Errorf("error creating firefly connector: %w", err).Error()) 227 228 auth := s.createDevFlowAuth() 229 auth.ClientId = TestingClientIDAccessDenied 230 231 oauthToken, err := fireflyConnector.Authorize(auth) 232 233 if assert.Errorf(s.T(), err, "expected to get an error but was gotten the access_token") { 234 assert.Equal(s.T(), "vcert error: your data contains problems: auth error: the access from device was denied by the user", err.Error()) 235 } 236 assert.Nil(s.T(), oauthToken) 237 } 238 239 func (s *ConnectorSuite) TestDeviceFlow_ExpiredToken() { 240 fireflyConnector, err := NewConnector(s.fireflyServer.serverURL, "", false, nil) 241 assert.Nil(s.T(), err, fmt.Errorf("error creating firefly connector: %w", err).Error()) 242 243 auth := s.createDevFlowAuth() 244 auth.ClientId = TestingClientIDExpiredToken 245 246 oauthToken, err := fireflyConnector.Authorize(auth) 247 248 if assert.Errorf(s.T(), err, "expected to get an error but was gotten the access_token") { 249 assert.Equal(s.T(), "vcert error: your data contains problems: auth error: the device code expired", err.Error()) 250 } 251 assert.Nil(s.T(), oauthToken) 252 } 253 254 func (s *ConnectorSuite) TestSynchronousRequestCertificate_CSR_Service_Generated() { 255 fireflyConnector, err := NewConnector(s.fireflyServer.serverURL, TestingPolicyName, false, nil) 256 assert.Nil(s.T(), err, fmt.Errorf("error creating firefly connector: %w", err).Error()) 257 258 //connector authenticating 259 err = fireflyConnector.Authenticate(s.createDevFlowAuth()) 260 261 assert.Nil(s.T(), err, fmt.Errorf("error getting access token: %w", err).Error()) 262 assert.NotNil(s.T(), fireflyConnector.accessToken) 263 264 //creating the CertRequest 265 request := certificate.Request{ 266 Subject: pkix.Name{ 267 Country: []string{"MX"}, 268 Organization: []string{"Venafi"}, 269 Locality: []string{"Merida"}, 270 Province: []string{"Yucatan"}, 271 CommonName: "vcert.test.vfidev.com", 272 }, 273 KeyType: certificate.KeyTypeRSA, 274 KeyLength: certificate.DefaultRSAlength, 275 ValidityPeriod: "P90D", 276 } 277 s.Run("Success", func() { 278 pemCollection, err := fireflyConnector.SynchronousRequestCertificate(&request) 279 280 assert.Nil(s.T(), err, fmt.Errorf("error requesting the certificate: %w", err).Error()) 281 assert.NotNil(s.T(), pemCollection) 282 }) 283 s.Run("Failure_rsa_size_not_supported", func() { 284 //copying the request to keep the original without changes 285 requestRSASize1024 := request 286 requestRSASize1024.KeyLength = 1024 287 288 pemCollection, err := fireflyConnector.SynchronousRequestCertificate(&requestRSASize1024) 289 290 if assert.Errorf(s.T(), err, "expected to get an error but was gotten the certificate") { 291 assert.ErrorContains(s.T(), err, "key size 1024 is not supported. Valid RSA sizes for Firefly are ") 292 } 293 assert.Nil(s.T(), pemCollection) 294 }) 295 s.Run("Failure_request", func() { 296 //setting momentarily to work in secure mode to get an error managed by the request method 297 http.DefaultTransport.(*http.Transport).TLSClientConfig.InsecureSkipVerify = false 298 pemCollection, err := fireflyConnector.SynchronousRequestCertificate(&request) 299 //putting back to insecure mode 300 http.DefaultTransport.(*http.Transport).TLSClientConfig.InsecureSkipVerify = true 301 302 if assert.Errorf(s.T(), err, "expected to get an error but was gotten the certificate") { 303 assert.ErrorContains(s.T(), err, "tls: failed to verify certificate: x509: certificate signed by unknown authority") 304 } 305 assert.Nil(s.T(), pemCollection) 306 }) 307 s.Run("Failure_wrong_request", func() { 308 //copying the request to keep the original without changes 309 requestWithoutSubject := request 310 requestWithoutSubject.Subject = pkix.Name{} 311 312 pemCollection, err := fireflyConnector.SynchronousRequestCertificate(&requestWithoutSubject) 313 314 if assert.Errorf(s.T(), err, "expected to get an error but was gotten the certificate") { 315 assert.ErrorContains(s.T(), err, "unexpected status code on Venafi Firefly. Status: ") 316 } 317 assert.Nil(s.T(), pemCollection) 318 }) 319 s.Run("Failure_corrupted_cert_received", func() { 320 //copying the connector to keep the original without changes 321 fireflyConnectorFailingPolicy := fireflyConnector 322 fireflyConnectorFailingPolicy.zone = TestingFailingPolicyName 323 pemCollection, err := fireflyConnectorFailingPolicy.SynchronousRequestCertificate(&request) 324 325 if assert.Errorf(s.T(), err, "expected to get an error but was gotten the certificate") { 326 assert.ErrorContains(s.T(), err, "x509: malformed certificate") 327 } 328 assert.Nil(s.T(), pemCollection) 329 }) 330 } 331 332 func (s *ConnectorSuite) TestSynchronousRequestCertificate_CSR_Provided() { 333 fireflyConnector, err := NewConnector(s.fireflyServer.serverURL, TestingPolicyName, false, nil) 334 assert.Nil(s.T(), err, fmt.Errorf("error creating firefly connector: %w", err).Error()) 335 336 //connector authenticating 337 err = fireflyConnector.Authenticate(s.createDevFlowAuth()) 338 339 assert.Nil(s.T(), err, fmt.Errorf("error getting access token: %w", err).Error()) 340 assert.NotNil(s.T(), fireflyConnector.accessToken) 341 342 //creating the CertRequest 343 request := certificate.Request{ 344 CsrOrigin: certificate.UserProvidedCSR, 345 } 346 request.SetCSR([]byte(csr_test)) 347 348 s.Run("Success", func() { 349 pemCollection, err := fireflyConnector.SynchronousRequestCertificate(&request) 350 351 assert.Nil(s.T(), err, fmt.Errorf("error requesting the certificate: %w", err).Error()) 352 assert.NotNil(s.T(), pemCollection) 353 }) 354 s.Run("Failure_request", func() { 355 //setting momentarily to work in secure mode to get an error managed by the request method 356 http.DefaultTransport.(*http.Transport).TLSClientConfig.InsecureSkipVerify = false 357 pemCollection, err := fireflyConnector.SynchronousRequestCertificate(&request) 358 //putting back to insecure mode 359 http.DefaultTransport.(*http.Transport).TLSClientConfig.InsecureSkipVerify = true 360 361 if assert.Errorf(s.T(), err, "expected to get an error but was gotten the certificate") { 362 assert.ErrorContains(s.T(), err, "tls: failed to verify certificate: x509: certificate signed by unknown authority") 363 } 364 assert.Nil(s.T(), pemCollection) 365 }) 366 s.Run("Failure_wrong_request", func() { 367 //copying the request to keep the original without changes 368 requestWithoutSubject := certificate.Request{} 369 370 pemCollection, err := fireflyConnector.SynchronousRequestCertificate(&requestWithoutSubject) 371 372 if assert.Errorf(s.T(), err, "expected to get an error but was gotten the certificate") { 373 assert.ErrorContains(s.T(), err, "unexpected status code on Venafi Firefly. Status: ") 374 } 375 assert.Nil(s.T(), pemCollection) 376 }) 377 s.Run("Failure_corrupted_cert_received", func() { 378 //copying the connector to keep the original without changes 379 fireflyConnectorFailingPolicy := fireflyConnector 380 fireflyConnectorFailingPolicy.zone = TestingFailingPolicyName 381 pemCollection, err := fireflyConnectorFailingPolicy.SynchronousRequestCertificate(&request) 382 383 if assert.Errorf(s.T(), err, "expected to get an error but was gotten the certificate") { 384 assert.ErrorContains(s.T(), err, "x509: malformed certificate") 385 } 386 assert.Nil(s.T(), pemCollection) 387 }) 388 } 389 390 func (s *ConnectorSuite) TestGetCertificateRequestUrl() { 391 fireflyConnector, err := NewConnector(s.fireflyServer.serverURL, TestingPolicyName, false, nil) 392 assert.Nil(s.T(), err, fmt.Errorf("error creating firefly connector: %w", err).Error()) 393 s.Run("CertificateSigningRequestURL", func() { 394 urlRes := fireflyConnector.getCertificateRequestUrl(&certificate.Request{CsrOrigin: certificate.UserProvidedCSR}) 395 assert.Equal(s.T(), urlResourceCertificateRequestCSR, urlRes) 396 }) 397 s.Run("CertificateRequestURL", func() { 398 urlRes := fireflyConnector.getCertificateRequestUrl(&certificate.Request{CsrOrigin: certificate.ServiceGeneratedCSR}) 399 assert.Equal(s.T(), urlResourceCertificateRequest, urlRes) 400 }) 401 s.Run("DefaultCertificateRequestURL", func() { 402 urlRes := fireflyConnector.getCertificateRequestUrl(&certificate.Request{}) 403 assert.Equal(s.T(), urlResourceCertificateRequest, urlRes) 404 }) 405 } 406 407 func (s *ConnectorSuite) TestSupportSynchronousRequestCertificate() { 408 fireflyConnector, err := NewConnector(s.fireflyServer.serverURL, TestingPolicyName, false, nil) 409 assert.Nil(s.T(), err, fmt.Errorf("error creating firefly connector: %w", err).Error()) 410 assert.True(s.T(), fireflyConnector.SupportSynchronousRequestCertificate()) 411 } 412 413 func (s *ConnectorSuite) TestGetCertificateRequest() { 414 fireflyConnector, err := NewConnector(s.fireflyServer.serverURL, TestingPolicyName, false, nil) 415 assert.Nil(s.T(), err, fmt.Errorf("error creating firefly connector: %w", err).Error()) 416 417 s.Run("Success", func() { 418 exampleUrl, err := url.Parse("spiffe://rest.example.com") 419 420 //creating the CertRequest 421 request := certificate.Request{ 422 Subject: pkix.Name{ 423 CommonName: "vcert.test.vfidev.com", 424 Organization: []string{"Venafi"}, 425 OrganizationalUnit: []string{"Platform Engineering"}, 426 Locality: []string{"Merida"}, 427 Province: []string{"Yucatan"}, 428 Country: []string{"MX"}, 429 }, 430 DNSNames: []string{"vcert.test.vfidev.com"}, 431 IPAddresses: []net.IP{[]byte("10.20.30.40")}, 432 EmailAddresses: []string{"venafi.dev@example.com"}, 433 URIs: []*url.URL{exampleUrl}, 434 KeyType: certificate.KeyTypeRSA, 435 KeyLength: certificate.DefaultRSAlength, 436 ValidityPeriod: "P90D", 437 } 438 439 certReq, err := fireflyConnector.getCertificateRequest(&request) 440 assert.Nil(s.T(), err, fmt.Errorf("expected error nil but was gotten an error: %w", err).Error()) 441 assert.NotNil(s.T(), certReq) 442 443 assert.Equal(s.T(), TestingPolicyName, certReq.PolicyName) 444 //validating the subject 445 assert.Equal(s.T(), request.Subject.CommonName, certReq.Subject.CommonName) 446 assert.Equal(s.T(), request.Subject.Organization[0], certReq.Subject.Organization) 447 assert.Equal(s.T(), request.Subject.OrganizationalUnit[0], certReq.Subject.OrgUnits[0]) 448 assert.Equal(s.T(), request.Subject.Locality[0], certReq.Subject.Locality) 449 assert.Equal(s.T(), request.Subject.Province[0], certReq.Subject.State) 450 assert.Equal(s.T(), request.Subject.Country[0], certReq.Subject.Country) 451 452 //validating the AltNames 453 assert.Equal(s.T(), request.DNSNames, certReq.AlternativeName.DnsNames) 454 assert.Equal(s.T(), request.IPAddresses[0].String(), certReq.AlternativeName.IpAddresses[0]) 455 assert.Equal(s.T(), request.EmailAddresses, certReq.AlternativeName.EmailAddresses) 456 assert.Equal(s.T(), request.URIs[0].String(), certReq.AlternativeName.Uris[0]) 457 458 //validating KeyAlgorithm 459 assert.Equal(s.T(), request.KeyType.String()+"_"+strconv.Itoa(request.KeyLength), certReq.KeyAlgorithm) 460 //validating ValidityPeriod 461 assert.Equal(s.T(), request.ValidityPeriod, *certReq.ValidityPeriod) 462 }) 463 s.Run("Invalid_RSA_Size", func() { 464 //creating the CertRequest 465 request := certificate.Request{ 466 KeyType: certificate.KeyTypeRSA, 467 KeyLength: 1024, 468 } 469 470 certReq, err := fireflyConnector.getCertificateRequest(&request) 471 if assert.Errorf(s.T(), err, "expected to get an error but was gotten the certificate") { 472 assert.ErrorContains(s.T(), err, "key size 1024 is not supported. Valid RSA sizes for Firefly are ") 473 } 474 assert.Nil(s.T(), certReq) 475 476 }) 477 s.Run("EllipticCurve_not_set", func() { 478 //creating the CertRequest 479 request := certificate.Request{ 480 KeyType: certificate.KeyTypeECDSA, 481 } 482 483 certReq, err := fireflyConnector.getCertificateRequest(&request) 484 assert.Nil(s.T(), err, fmt.Errorf("expected error nil but was gotten an error: %w", err).Error()) 485 assert.NotNil(s.T(), certReq) 486 //validating KeyAlgorithm 487 curveDefault := certificate.EllipticCurveDefault 488 assert.Equal(s.T(), "EC_"+curveDefault.String(), certReq.KeyAlgorithm) 489 }) 490 s.Run("UserProvidedCSR", func() { 491 //creating the CertRequest 492 request := certificate.Request{ 493 CsrOrigin: certificate.UserProvidedCSR, 494 } 495 request.SetCSR([]byte(csr_test)) 496 497 certReq, err := fireflyConnector.getCertificateRequest(&request) 498 assert.Nil(s.T(), err, fmt.Errorf("expected error nil but was gotten an error: %w", err).Error()) 499 assert.NotNil(s.T(), certReq) 500 //validating CSR was set 501 assert.Equal(s.T(), csr_test, certReq.CSR) 502 }) 503 }