github.com/Venafi/vcert/v5@v5.10.2/pkg/venafi/firefly/connector.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 "context" 21 "crypto/x509" 22 "errors" 23 "fmt" 24 "net/http" 25 "net/url" 26 "strings" 27 "time" 28 29 "github.com/sosodev/duration" 30 "go.uber.org/zap" 31 "golang.org/x/oauth2" 32 "golang.org/x/oauth2/clientcredentials" 33 34 "github.com/Venafi/vcert/v5/pkg/certificate" 35 "github.com/Venafi/vcert/v5/pkg/domain" 36 "github.com/Venafi/vcert/v5/pkg/endpoint" 37 "github.com/Venafi/vcert/v5/pkg/policy" 38 "github.com/Venafi/vcert/v5/pkg/util" 39 "github.com/Venafi/vcert/v5/pkg/venafi" 40 "github.com/Venafi/vcert/v5/pkg/verror" 41 ) 42 43 var ( 44 fieldPlatform = zap.String("platform", venafi.Firefly.String()) 45 ) 46 47 // Connector contains the base data needed to communicate with a Firefly Server 48 type Connector struct { 49 baseURL string 50 accessToken string 51 verbose bool 52 trust *x509.CertPool 53 client *http.Client 54 zone string // holds the policyName 55 userAgent string 56 } 57 58 // NewConnector creates a new Firefly Connector object used to communicate with Firefly 59 func NewConnector(url string, zone string, verbose bool, trust *x509.CertPool) (*Connector, error) { 60 if url != "" { 61 var err error 62 url, err = normalizeURL(url) 63 if err != nil { 64 return nil, fmt.Errorf("%w: failed to normalize URL: %v", verror.UserDataError, err) 65 } 66 } 67 return &Connector{baseURL: url, zone: zone, verbose: verbose, trust: trust, userAgent: util.DefaultUserAgent}, nil 68 } 69 70 // normalizeURL normalizes the base URL used to communicate with Firefly 71 func normalizeURL(url string) (normalizedURL string, err error) { 72 normalizedURL = util.NormalizeUrl(url) 73 return normalizedURL, err 74 } 75 76 func (c *Connector) SetZone(zone string) { 77 //for now the zone refers to the policyName 78 c.zone = zone 79 } 80 81 func (c *Connector) SetUserAgent(userAgent string) { 82 c.userAgent = userAgent 83 } 84 85 func (c *Connector) GetType() endpoint.ConnectorType { 86 return endpoint.ConnectorTypeFirefly 87 } 88 89 func (c *Connector) Authenticate(auth *endpoint.Authentication) error { 90 if auth == nil { 91 msg := "failed to authenticate: no credentials provided" 92 zap.L().Error(msg, fieldPlatform) 93 return errors.New(msg) 94 } 95 96 if auth.AccessToken == "" { 97 zap.L().Info("no access token provided. Authorization needed", fieldPlatform) 98 var token *oauth2.Token 99 token, err := c.Authorize(auth) 100 if err != nil { 101 return err 102 } 103 auth.AccessToken = token.AccessToken 104 } 105 106 zap.L().Info("successfully authenticated", fieldPlatform) 107 //setting the accessToken to the connector 108 c.accessToken = auth.AccessToken 109 return nil 110 } 111 112 // Authorize Get an OAuth access token 113 func (c *Connector) Authorize(auth *endpoint.Authentication) (token *oauth2.Token, err error) { 114 defer func() { 115 if err != nil { 116 err = fmt.Errorf("%w: %s", verror.AuthError, err) 117 } 118 }() 119 120 zap.L().Info("authorizing to OAuth2 server", fieldPlatform) 121 122 if auth == nil { 123 msg := "failed to authenticate: missing credentials" 124 zap.L().Error(msg, fieldPlatform) 125 return nil, errors.New(msg) 126 } 127 128 successMsg := "successfully authorized to OAuth2 server" 129 failureMsg := "authorization flow failed" 130 131 // if it's a client credentials flow grant 132 if auth.ClientSecret != "" && auth.IdentityProvider.DeviceURL == "" { 133 zap.L().Info("authorizing using credentials flow", fieldPlatform) 134 135 config := clientcredentials.Config{ 136 ClientID: auth.ClientId, 137 ClientSecret: auth.ClientSecret, 138 TokenURL: auth.IdentityProvider.TokenURL, 139 Scopes: strings.Split(auth.Scope, scopesSeparator), 140 } 141 //if the audience was provided, then it's required to set it to the config. 142 if auth.IdentityProvider.Audience != "" { 143 config.EndpointParams = url.Values{ 144 "audience": []string{auth.IdentityProvider.Audience}, 145 } 146 } 147 148 token, err = config.Token(context.Background()) 149 if err != nil { 150 zap.L().Error(failureMsg, fieldPlatform, zap.Error(err)) 151 return token, err 152 } 153 154 zap.L().Info(successMsg, fieldPlatform) 155 return 156 } 157 158 // if it's a password flow grant 159 if auth.User != "" && auth.Password != "" { 160 zap.L().Info("authorizing using password flow", fieldPlatform) 161 162 config := oauth2.Config{ 163 ClientID: auth.ClientId, 164 ClientSecret: auth.ClientSecret, 165 Scopes: strings.Split(auth.Scope, scopesSeparator), 166 //RedirectURL: "http://localhost:9094/oauth2", 167 // This points to our Authorization Server 168 // if our Client ID and Client Secret are valid 169 // it will attempt to authorize our user 170 Endpoint: oauth2.Endpoint{ 171 //AuthURL: "http://localhost:9096/authorize", 172 TokenURL: auth.IdentityProvider.TokenURL, 173 }, 174 } 175 176 token, err = config.PasswordCredentialsToken(context.Background(), auth.User, auth.Password) 177 if err != nil { 178 zap.L().Error(failureMsg, fieldPlatform, zap.Error(err)) 179 return token, err 180 } 181 182 zap.L().Info(successMsg, fieldPlatform) 183 return 184 } 185 186 // if it's a device flow grant 187 if auth.IdentityProvider.DeviceURL != "" { 188 zap.L().Info("authorizing using device flow", fieldPlatform) 189 190 token, err = c.getDeviceAccessToken(auth) 191 if err != nil { 192 zap.L().Error(failureMsg, fieldPlatform, zap.Error(err)) 193 return token, err 194 } 195 196 zap.L().Info(successMsg, fieldPlatform) 197 return 198 } 199 200 errMsg := "authorization failed: cannot determine the authorization flow required for the credentials provided" 201 zap.L().Error(errMsg, fieldPlatform) 202 return token, errors.New(errMsg) 203 } 204 205 // SynchronousRequestCertificate It's not supported yet in VaaS 206 func (c *Connector) SynchronousRequestCertificate(req *certificate.Request) (certificates *certificate.PEMCollection, err error) { 207 208 zap.L().Info("requesting certificate", zap.String("cn", req.Subject.CommonName), fieldPlatform) 209 //creating the request object 210 certReq, err := c.getCertificateRequest(req) 211 if err != nil { 212 zap.L().Error("HTTP request failed", fieldPlatform, zap.Error(err)) 213 return nil, err 214 } 215 216 zap.L().Info("sending HTTP request", fieldPlatform) 217 statusCode, status, body, err := c.request("POST", c.getCertificateRequestUrl(req), certReq) 218 if err != nil { 219 zap.L().Error("HTTP request failed", fieldPlatform, zap.Error(err)) 220 return nil, err 221 } 222 223 //parsing the result 224 cr, err := parseCertificateRequestResult(statusCode, status, body) 225 if err != nil { 226 zap.L().Error("failed to request a certificate", fieldPlatform, zap.Error(err)) 227 return nil, err 228 } 229 230 //converting to PEMCollection 231 certificates, err = certificate.PEMCollectionFromBytes([]byte(cr.CertificateChain), req.ChainOption) 232 if err != nil { 233 zap.L().Error("failed to create pem collection", fieldPlatform, zap.Error(err)) 234 return nil, err 235 } 236 237 certificates.PrivateKey = cr.PrivateKey 238 zap.L().Info("successfully requested certificate", fieldPlatform) 239 return certificates, nil 240 } 241 242 func (c *Connector) getCertificateRequest(req *certificate.Request) (*certificateRequest, error) { 243 zap.L().Info("building certificate request", fieldPlatform) 244 fireflyCertRequest := &certificateRequest{} 245 246 if req.CsrOrigin == certificate.UserProvidedCSR { 247 fireflyCertRequest.CSR = string(req.GetCSR()) 248 } else { // it's considered as a ServiceGeneratedCSR 249 //getting the subject 250 subject := Subject{ 251 CommonName: req.Subject.CommonName, 252 } 253 254 if len(req.Subject.Organization) > 0 { 255 subject.Organization = req.Subject.Organization[0] 256 } 257 258 if len(req.Subject.OrganizationalUnit) > 0 { 259 subject.OrgUnits = req.Subject.OrganizationalUnit 260 } 261 262 if len(req.Subject.Locality) > 0 { 263 subject.Locality = req.Subject.Locality[0] 264 } 265 266 if len(req.Subject.Province) > 0 { 267 subject.State = req.Subject.Province[0] 268 } 269 270 if len(req.Subject.Country) > 0 { 271 subject.Country = req.Subject.Country[0] 272 } 273 274 fireflyCertRequest.Subject = subject 275 276 //getting the altnames 277 if len(req.DNSNames) > 0 || len(req.IPAddresses) > 0 || len(req.EmailAddresses) > 0 || len(req.URIs) > 0 { 278 altNames := &AlternativeNames{} 279 if len(req.DNSNames) > 0 { 280 altNames.DnsNames = req.DNSNames 281 } 282 283 if len(req.IPAddresses) > 0 { 284 sIPAddresses := make([]string, 0) 285 for _, address := range req.IPAddresses { 286 sIPAddresses = append(sIPAddresses, address.String()) 287 } 288 289 altNames.IpAddresses = sIPAddresses 290 } 291 292 if len(req.EmailAddresses) > 0 { 293 altNames.EmailAddresses = req.EmailAddresses 294 } 295 296 if len(req.URIs) > 0 { 297 sUris := make([]string, 0) 298 for _, uri := range req.URIs { 299 sUris = append(sUris, uri.String()) 300 } 301 altNames.Uris = sUris 302 } 303 304 fireflyCertRequest.AlternativeName = altNames 305 } 306 } 307 308 if req.ValidityPeriod != "" { 309 fireflyCertRequest.ValidityPeriod = &req.ValidityPeriod 310 } else { 311 if req.ValidityDuration != nil { //if the validityDuration was set then it will convert to ISO 8601 312 validityPeriod := duration.Format(*req.ValidityDuration) 313 fireflyCertRequest.ValidityPeriod = &validityPeriod 314 } 315 } 316 317 fireflyCertRequest.PolicyName = c.zone 318 319 //getting the keyAlgorithm 320 keyAlgorithm := "" 321 switch req.KeyType { 322 case certificate.KeyTypeRSA: 323 keySize, err := GetRSASize(req.KeyLength) 324 if err != nil { 325 return nil, err 326 } 327 keyAlgorithm = fmt.Sprintf("RSA_%d", keySize) 328 case certificate.KeyTypeECDSA, certificate.KeyTypeED25519: 329 keyCurve := req.KeyCurve 330 if keyCurve == certificate.EllipticCurveNotSet { 331 keyCurve = certificate.EllipticCurveDefault 332 } 333 keyAlgorithm = fmt.Sprintf("EC_%s", keyCurve.String()) 334 } 335 fireflyCertRequest.KeyAlgorithm = keyAlgorithm 336 337 zap.L().Info("successfully built certificate request", fieldPlatform) 338 return fireflyCertRequest, nil 339 } 340 341 func (c *Connector) getCertificateRequestUrl(req *certificate.Request) urlResource { 342 if req.CsrOrigin == certificate.UserProvidedCSR { 343 return urlResourceCertificateRequestCSR 344 } 345 346 return urlResourceCertificateRequest 347 } 348 349 // SupportSynchronousRequestCertificate returns if the connector support synchronous calls to request a certificate. 350 func (c *Connector) SupportSynchronousRequestCertificate() bool { 351 return true 352 } 353 354 type ErrCertNotFound struct { 355 error 356 } 357 358 func (e *ErrCertNotFound) Error() string { 359 return e.error.Error() 360 } 361 362 func (e *ErrCertNotFound) Unwrap() error { 363 return e.error 364 } 365 366 func (c *Connector) Ping() (err error) { 367 panic("operation is not supported yet") 368 } 369 370 func (c *Connector) RetrieveSystemVersion() (string, error) { 371 panic("operation is not supported yet") 372 } 373 374 // RequestCertificate submits the CSR to the Venafi Firefly API for processing 375 func (c *Connector) RequestCertificate(_ *certificate.Request) (requestID string, err error) { 376 panic("operation is not supported yet") 377 } 378 379 func (c *Connector) IsCSRServiceGenerated(_ *certificate.Request) (bool, error) { 380 panic("operation is not supported yet") 381 } 382 383 func (c *Connector) RetrieveSshConfig(_ *certificate.SshCaTemplateRequest) (*certificate.SshConfig, error) { 384 panic("operation is not supported yet") 385 } 386 387 func (c *Connector) RetrieveAvailableSSHTemplates() (response []certificate.SshAvaliableTemplate, err error) { 388 panic("operation is not supported yet") 389 } 390 391 func (c *Connector) ResetCertificate(_ *certificate.Request, _ bool) (err error) { 392 panic("operation is not supported yet") 393 } 394 395 func (c *Connector) GetPolicy(_ string) (*policy.PolicySpecification, error) { 396 panic("operation is not supported yet") 397 } 398 399 func (c *Connector) SetPolicy(_ string, _ *policy.PolicySpecification) (string, error) { 400 panic("operation is not supported yet") 401 } 402 403 func (c *Connector) RetrieveCertificate(_ *certificate.Request) (certificates *certificate.PEMCollection, err error) { 404 panic("operation is not supported yet") 405 } 406 407 func (c *Connector) RenewCertificate(_ *certificate.RenewalRequest) (requestID string, err error) { 408 panic("operation is not supported yet") 409 } 410 411 func (c *Connector) RevokeCertificate(_ *certificate.RevocationRequest) (err error) { 412 panic("operation is not supported yet") 413 } 414 415 func (c *Connector) ReadPolicyConfiguration() (policy *endpoint.Policy, err error) { 416 panic("operation is not supported yet") 417 } 418 419 func (c *Connector) ReadZoneConfiguration() (config *endpoint.ZoneConfiguration, err error) { 420 return nil, nil 421 } 422 423 func (c *Connector) ImportCertificate(_ *certificate.ImportRequest) (*certificate.ImportResponse, error) { 424 panic("operation is not supported yet") 425 } 426 427 func (c *Connector) SearchCertificates(_ *certificate.SearchRequest) (*certificate.CertSearchResponse, error) { 428 panic("operation is not supported yet") 429 } 430 431 func (c *Connector) SearchCertificate(_ string, _ string, _ *certificate.Sans, _ time.Duration) (certificateInfo *certificate.CertificateInfo, err error) { 432 panic("operation is not supported yet") 433 } 434 435 func (c *Connector) SetHTTPClient(client *http.Client) { 436 c.client = client 437 } 438 439 func (c *Connector) WriteLog(_ *endpoint.LogRequest) error { 440 panic("operation is not supported yet") 441 } 442 443 func (c *Connector) ListCertificates(_ endpoint.Filter) ([]certificate.CertificateInfo, error) { 444 panic("operation is not supported yet") 445 } 446 447 func (c *Connector) GetZonesByParent(_ string) ([]string, error) { 448 panic("operation is not supported yet") 449 } 450 451 func (c *Connector) RequestSSHCertificate(_ *certificate.SshCertRequest) (response *certificate.SshCertificateObject, err error) { 452 panic("operation is not supported yet") 453 } 454 455 func (c *Connector) RetrieveSSHCertificate(_ *certificate.SshCertRequest) (response *certificate.SshCertificateObject, err error) { 456 panic("operation is not supported yet") 457 } 458 459 func (c *Connector) ProvisionCertificate(_ *domain.ProvisioningRequest, _ *domain.ProvisioningOptions) (*domain.ProvisioningMetadata, error) { 460 panic("operation is not supported yet") 461 } 462 463 func (c *Connector) RetrieveCertificateMetaData(_ string) (*certificate.CertificateMetaData, error) { 464 panic("operation is not supported yet") 465 } 466 467 func (c *Connector) RetireCertificate(_ *certificate.RetireRequest) error { 468 panic("operation is not supported yet") 469 }