From 4e1149d6a5b30da373b4510ab1f306e9442b94af Mon Sep 17 00:00:00 2001 From: Russel Vela Date: Wed, 20 Mar 2024 11:57:36 -0600 Subject: [PATCH 1/2] feat(svc-account-auth): Fixes required to support service account authentication Closes VC-31642 --- pkg/endpoint/endpoint.go | 40 +- pkg/venafi/cloud/connector.go | 1810 +++++++++++---------------- pkg/venafi/cloud/connectorPolicy.go | 419 +++++++ pkg/venafi/cloud/connectorSSH.go | 19 + pkg/venafi/cloud/connector_test.go | 2 +- pkg/venafi/cloud/search.go | 4 +- 6 files changed, 1175 insertions(+), 1119 deletions(-) create mode 100644 pkg/venafi/cloud/connectorPolicy.go create mode 100644 pkg/venafi/cloud/connectorSSH.go diff --git a/pkg/endpoint/endpoint.go b/pkg/endpoint/endpoint.go index d0976c07..a98b7d20 100644 --- a/pkg/endpoint/endpoint.go +++ b/pkg/endpoint/endpoint.go @@ -80,26 +80,26 @@ type Connector interface { GetType() ConnectorType // SetZone sets a zone (by name) for requests with this connector. SetZone(z string) - // GetZonesByParent returns a list of valid zones specified by parent - GetZonesByParent(parent string) ([]string, error) + // SetHTTPClient allows to set custom http.Client to this Connector. + SetHTTPClient(client *http.Client) Ping() (err error) // Authenticate is usually called by NewClient and it is not required that you manually call it. Authenticate(auth *Authentication) (err error) + // ReadPolicyConfiguration returns information about zone policies. It can be used for checking request compatibility with policies. ReadPolicyConfiguration() (policy *Policy, err error) // ReadZoneConfiguration returns the zone configuration. A zone configuration includes zone policy and additional zone information. ReadZoneConfiguration() (config *ZoneConfiguration, err error) + // GetZonesByParent returns a list of valid zones specified by parent + GetZonesByParent(parent string) ([]string, error) // GenerateRequest update certificate.Request with data from zone configuration. GenerateRequest(config *ZoneConfiguration, req *certificate.Request) (err error) + // ResetCertificate resets the state of a certificate. // This function is idempotent, i.e., it won't fail if there is nothing to be reset. ResetCertificate(req *certificate.Request, restart bool) (err error) // RequestCertificate makes a request to the server with data for enrolling the certificate. RequestCertificate(req *certificate.Request) (requestID string, err error) - // SynchronousRequestCertificate makes a request to the server with data for enrolling the certificate and returns the enrolled certificate. - SynchronousRequestCertificate(req *certificate.Request) (certificates *certificate.PEMCollection, err error) - // SupportSynchronousRequestCertificate returns if the connector support synchronous calls to request a certificate. - SupportSynchronousRequestCertificate() bool // RetrieveCertificate immediately returns an enrolled certificate. Otherwise, RetrieveCertificate waits and retries during req.Timeout. RetrieveCertificate(req *certificate.Request) (certificates *certificate.PEMCollection, err error) IsCSRServiceGenerated(req *certificate.Request) (bool, error) @@ -108,29 +108,35 @@ type Connector interface { RetireCertificate(req *certificate.RetireRequest) error // ImportCertificate adds an existing certificate to Venafi Platform even if the certificate was not issued by Venafi Cloud or Venafi Platform. For information purposes. ImportCertificate(req *certificate.ImportRequest) (*certificate.ImportResponse, error) - // SetHTTPClient allows to set custom http.Client to this Connector. - SetHTTPClient(client *http.Client) - // ListCertificates + // ListCertificates returns a list of certificates from inventory that matches the filter ListCertificates(filter Filter) ([]certificate.CertificateInfo, error) - SetPolicy(name string, ps *policy.PolicySpecification) (string, error) - GetPolicy(name string) (*policy.PolicySpecification, error) - RequestSSHCertificate(req *certificate.SshCertRequest) (response *certificate.SshCertificateObject, err error) - RetrieveSSHCertificate(req *certificate.SshCertRequest) (response *certificate.SshCertificateObject, err error) - RetrieveSshConfig(ca *certificate.SshCaTemplateRequest) (*certificate.SshConfig, error) SearchCertificates(req *certificate.SearchRequest) (*certificate.CertSearchResponse, error) - // Returns a valid certificate + // SearchCertificate returns a valid certificate // // If it returns no error, the certificate returned should be the latest [1] // exact matching zone [2], CN and sans.DNS [3] provided, with a minimum // validity of `certMinTimeLeft` // - // [1] the one with longest validity; field named ValidTo for TPP and + // [1] the one with the longest validity; field named ValidTo for TPP and // validityEnd for VaaS // [2] application name for VaaS // [3] an array of strings representing the DNS names SearchCertificate(zone string, cn string, sans *certificate.Sans, certMinTimeLeft time.Duration) (*certificate.CertificateInfo, error) - RetrieveAvailableSSHTemplates() ([]certificate.SshAvaliableTemplate, error) RetrieveCertificateMetaData(dn string) (*certificate.CertificateMetaData, error) + + SetPolicy(name string, ps *policy.PolicySpecification) (string, error) + GetPolicy(name string) (*policy.PolicySpecification, error) + + RequestSSHCertificate(req *certificate.SshCertRequest) (response *certificate.SshCertificateObject, err error) + RetrieveSSHCertificate(req *certificate.SshCertRequest) (response *certificate.SshCertificateObject, err error) + RetrieveSshConfig(ca *certificate.SshCaTemplateRequest) (*certificate.SshConfig, error) + RetrieveAvailableSSHTemplates() ([]certificate.SshAvaliableTemplate, error) + + // SynchronousRequestCertificate makes a request to the server with data for enrolling the certificate and returns the enrolled certificate. + SynchronousRequestCertificate(req *certificate.Request) (certificates *certificate.PEMCollection, err error) + // SupportSynchronousRequestCertificate returns if the connector support synchronous calls to request a certificate. + SupportSynchronousRequestCertificate() bool + RetrieveSystemVersion() (string, error) WriteLog(req *LogRequest) error } diff --git a/pkg/venafi/cloud/connector.go b/pkg/venafi/cloud/connector.go index 1b5458fa..9b886580 100644 --- a/pkg/venafi/cloud/connector.go +++ b/pkg/venafi/cloud/connector.go @@ -94,61 +94,182 @@ type Connector struct { client *http.Client } -func (c *Connector) RetrieveCertificateMetaData(_ string) (*certificate.CertificateMetaData, error) { - panic("operation is not supported yet") +// NewConnector creates a new Venafi Cloud Connector object used to communicate with Venafi Cloud +func NewConnector(url string, zone string, verbose bool, trust *x509.CertPool) (*Connector, error) { + cZone := cloudZone{zone: zone} + c := Connector{verbose: verbose, trust: trust, zone: cZone} + + var err error + c.baseURL, err = normalizeURL(url) + if err != nil { + return nil, err + } + return &c, nil } -func (c *Connector) SearchCertificates(_ *certificate.SearchRequest) (*certificate.CertSearchResponse, error) { - panic("operation is not supported yet") +func (c *Connector) GetType() endpoint.ConnectorType { + return endpoint.ConnectorTypeCloud } -func (c *Connector) SearchCertificate(zone string, cn string, sans *certificate.Sans, certMinTimeLeft time.Duration) (certificateInfo *certificate.CertificateInfo, err error) { - // retrieve application name from zone - appName := getAppNameFromZone(zone) - // get application id from name - app, _, err := c.getAppDetailsByName(appName) +func (c *Connector) SetZone(z string) { + cZone := cloudZone{zone: z} + c.zone = cZone +} + +func (c *Connector) SetHTTPClient(client *http.Client) { + c.client = client +} + +// Ping attempts to connect to the Venafi Cloud API and returns an error if it cannot +func (c *Connector) Ping() (err error) { + return nil +} + +// Authenticate authenticates the user with Venafi Cloud using the provided API Key +func (c *Connector) Authenticate(auth *endpoint.Authentication) (err error) { + if auth == nil { + return fmt.Errorf("failed to authenticate: missing credentials") + } + + //1. Access token. Assign it to connector and return + if auth.AccessToken != "" { + c.accessToken = auth.AccessToken + return + } + + //2. JWT and tenantID. use it to request new access token + if auth.TenantID != "" && auth.ExternalIdPJWT != "" { + c.tenantID = auth.TenantID + c.jwt = auth.ExternalIdPJWT + err = c.getServiceAccountToken() + if err != nil { + return + } + auth.AccessToken = c.accessToken + return + } + + // 3. API key. Get user to test authentication + c.apiKey = auth.APIKey + url := c.getURL(urlResourceUserAccounts) + statusCode, status, body, err := c.request("GET", url, nil, true) if err != nil { - return nil, err + return + } + ud, err := parseUserDetailsResult(http.StatusOK, statusCode, status, body) + if err != nil { + return } + c.user = ud + return +} - // format arguments for request - req := formatSearchCertificateArguments(cn, sans, certMinTimeLeft) +func (c *Connector) ReadPolicyConfiguration() (policy *endpoint.Policy, err error) { + if !c.isAuthenticated() { + return nil, fmt.Errorf("must be autheticated to request a certificate") - // perform request - searchResult, err := c.searchCertificates(req) + } + config, err := c.ReadZoneConfiguration() if err != nil { return nil, err } + policy = &config.Policy + return +} - // fail if no certificate is returned from api - if searchResult.Count == 0 { - return nil, verror.NoCertificateFoundError +// ReadZoneConfiguration reads the Zone information needed for generating and requesting a certificate from Venafi Cloud +func (c *Connector) ReadZoneConfiguration() (config *endpoint.ZoneConfiguration, err error) { + if !c.isAuthenticated() { + return nil, fmt.Errorf("must be autheticated to request a certificate") } - // map (convert) response to an array of CertificateInfo - certificates := make([]*certificate.CertificateInfo, 0) - n := 0 - for _, cert := range searchResult.Certificates { - if util.ArrayContainsString(cert.ApplicationIds, app.ApplicationId) { - match := cert.ToCertificateInfo() - certificates = append(certificates, &match) - n = n + 1 + var template *certificateTemplate + var statusCode int + + // to fully support the "headless registration" use case... + // if application does not exist and is for the default CIT, create the application + citAlias := c.zone.getTemplateAlias() + if citAlias == "Default" { + appName := c.zone.getApplicationName() + _, statusCode, err = c.getAppDetailsByName(appName) + if err != nil && statusCode == 404 { + log.Printf("creating application %s for issuing template %s", appName, citAlias) + + ps := policy.PolicySpecification{} + template, err = getCit(c, citAlias) + if err != nil { + return + } + _, err = c.createApplication(appName, &ps, template) + if err != nil { + return + } + } + } + if template == nil { + template, err = c.getTemplateByID() + if err != nil { + return } } + config = getZoneConfiguration(template) + return config, nil +} + +// GetZonesByParent returns a list of valid zones for a VaaS application specified by parent +func (c *Connector) GetZonesByParent(parent string) ([]string, error) { + if !c.isAuthenticated() { + return nil, fmt.Errorf("must be autheticated to request a certificate") + } - // fail if no certificates found with matching zone - if n == 0 { - return nil, verror.NoCertificateWithMatchingZoneFoundError + var zones []string + appDetails, _, err := c.getAppDetailsByName(parent) + if err != nil { + return nil, err } - // at this point all certificates belong to our zone, the next step is - // finding the newest valid certificate matching the provided sans - return certificate.FindNewestCertificateWithSans(certificates, sans) + for citAlias := range appDetails.CitAliasToIdMap { + zone := fmt.Sprintf("%s\\%s", parent, citAlias) + zones = append(zones, zone) + } + return zones, nil } -func (c *Connector) IsCSRServiceGenerated(req *certificate.Request) (bool, error) { - if c.user == nil || c.user.Company == nil { - return false, fmt.Errorf("must be autheticated to retieve certificate") +// ResetCertificate resets the state of a certificate. +func (c *Connector) ResetCertificate(_ *certificate.Request, _ bool) (err error) { + return fmt.Errorf("not supported by endpoint") +} + +// RequestCertificate submits the CSR to the Venafi Cloud API for processing +func (c *Connector) RequestCertificate(req *certificate.Request) (requestID string, err error) { + if !c.isAuthenticated() { + return "", fmt.Errorf("must be autheticated to request a certificate") + } + + url := c.getURL(urlResourceCertificateRequests) + cloudReq, err := c.getCloudRequest(req) + if err != nil { + return "", err + } + + statusCode, status, body, err := c.request("POST", url, cloudReq) + + if err != nil { + return "", err + } + cr, err := parseCertificateRequestResult(statusCode, status, body) + if err != nil { + return "", err + } + requestID = cr.CertificateRequests[0].ID + req.PickupID = requestID + return requestID, nil +} + +// RetrieveCertificate retrieves the certificate for the specified ID +func (c *Connector) RetrieveCertificate(req *certificate.Request) (certificates *certificate.PEMCollection, err error) { + if !c.isAuthenticated() { + return nil, fmt.Errorf("must be autheticated to request a certificate") } if req.PickupID == "" && req.CertID == "" && req.Thumbprint != "" { @@ -156,17 +277,18 @@ func (c *Connector) IsCSRServiceGenerated(req *certificate.Request) (bool, error var certificateRequestId string searchResult, err := c.searchCertificatesByFingerprint(req.Thumbprint) if err != nil { - return false, fmt.Errorf("failed to retrieve certificate: %s", err) + return nil, fmt.Errorf("failed to retrieve certificate: %s", err) } if len(searchResult.Certificates) == 0 { - return false, fmt.Errorf("no certificate found using fingerprint %s", req.Thumbprint) + return nil, fmt.Errorf("no certificate found using fingerprint %s", req.Thumbprint) } var reqIds []string + isOnlyOneCertificateRequestId := true for _, c := range searchResult.Certificates { reqIds = append(reqIds, c.CertificateRequestId) if certificateRequestId != "" && certificateRequestId != c.CertificateRequestId { - return false, fmt.Errorf("more than one CertificateRequestId was found with the same Fingerprint: %s", reqIds) + isOnlyOneCertificateRequestId = false } if c.CertificateRequestId != "" { certificateRequestId = c.CertificateRequestId @@ -175,559 +297,569 @@ func (c *Connector) IsCSRServiceGenerated(req *certificate.Request) (bool, error req.CertID = c.Id } } + if !isOnlyOneCertificateRequestId { + return nil, fmt.Errorf("more than one CertificateRequestId was found with the same Fingerprint: %s", reqIds) + } + req.PickupID = certificateRequestId } + startTime := time.Now() + //Wait for certificate to be issued by checking its PickupID + //If certID is filled then certificate should be already issued. + var certificateId string + if req.CertID == "" { + for { + if req.PickupID == "" { + break + } + certStatus, err := c.getCertificateStatus(req.PickupID) + if err != nil { + return nil, fmt.Errorf("unable to retrieve: %s", err) + } + if certStatus.Status == "ISSUED" { + certificateId = certStatus.CertificateIdsList[0] + break // to fetch the cert itself + } else if certStatus.Status == "FAILED" { + return nil, fmt.Errorf("failed to retrieve certificate. Status: %v", certStatus) + } + // status.Status == "REQUESTED" || status.Status == "PENDING" + if req.Timeout == 0 { + return nil, endpoint.ErrCertificatePending{CertificateID: req.PickupID, Status: certStatus.Status} + } else { + log.Println("Issuance of certificate is pending...") + } + if time.Now().After(startTime.Add(req.Timeout)) { + return nil, endpoint.ErrRetrieveCertificateTimeout{CertificateID: req.PickupID} + } + // fmt.Printf("pending... %s\n", status.Status) + time.Sleep(2 * time.Second) + } + } else { + certificateId = req.CertID + } + + url := c.getURL(urlResourceCertificateRetrievePem) + url = fmt.Sprintf(url, certificateId) + var dekInfo *EdgeEncryptionKey var currentId string - var err error if req.CertID != "" { dekInfo, err = getDekInfo(c, req.CertID) - } else { - var certificateId string - certificateId, err = getCertificateId(c, req) - if err == nil && certificateId != "" { - dekInfo, err = getDekInfo(c, certificateId) - } + currentId = req.CertID + } else if certificateId != "" { + dekInfo, err = getDekInfo(c, certificateId) + currentId = certificateId } - if err == nil && dekInfo.Key != "" { req.CertID = currentId - return true, err + return retrieveServiceGeneratedCertData(c, req, dekInfo) } - return false, nil -} -func getCertificateId(c *Connector, req *certificate.Request) (string, error) { - startTime := time.Now() - //Wait for certificate to be issued by checking its PickupID - //If certID is filled then certificate should be already issued. - for { - if req.PickupID == "" { - break - } - certStatus, err := c.getCertificateStatus(req.PickupID) - if err != nil { - return "", fmt.Errorf("unable to retrieve: %s", err) + switch { + case req.CertID != "": + statusCode, status, body, err := c.waitForCertificate(url, req) //c.request("GET", url, nil) + if err != nil { + return nil, err } - if certStatus.Status == "ISSUED" { - return certStatus.CertificateIdsList[0], nil - } else if certStatus.Status == "FAILED" { - return "", fmt.Errorf("failed to retrieve certificate. Status: %v", certStatus) + if statusCode != http.StatusOK { + return nil, fmt.Errorf("failed to retrieve certificate. StatusCode: %d -- Status: %s -- Server Data: %s", statusCode, status, body) } - if req.Timeout == 0 { - return "", endpoint.ErrCertificatePending{CertificateID: req.PickupID, Status: certStatus.Status} - } else { - log.Println("Issuance of certificate is pending...") + return newPEMCollectionFromResponse(body, certificate.ChainOptionIgnore) + case req.PickupID != "": + url += "?chainOrder=%s&format=PEM" + switch req.ChainOption { + case certificate.ChainOptionRootFirst: + url = fmt.Sprintf(url, condorChainOptionRootFirst) + default: + url = fmt.Sprintf(url, condorChainOptionRootLast) } - if time.Now().After(startTime.Add(req.Timeout)) { - return "", endpoint.ErrRetrieveCertificateTimeout{CertificateID: req.PickupID} + statusCode, status, body, err := c.waitForCertificate(url, req) //c.request("GET", url, nil) + if err != nil { + return nil, err + } + if statusCode == http.StatusOK { + certificates, err = newPEMCollectionFromResponse(body, req.ChainOption) + if err != nil { + return nil, err + } + err = req.CheckCertificate(certificates.Certificate) + return certificates, err + } else if statusCode == http.StatusConflict { // Http Status Code 409 means the certificate has not been signed by the ca yet. + return nil, endpoint.ErrCertificatePending{CertificateID: req.PickupID} + } else { + return nil, fmt.Errorf("failed to retrieve certificate. StatusCode: %d -- Status: %s", statusCode, status) } - time.Sleep(2 * time.Second) } - - return "", endpoint.ErrRetrieveCertificateTimeout{CertificateID: req.PickupID} -} - -func (c *Connector) RetrieveSshConfig(_ *certificate.SshCaTemplateRequest) (*certificate.SshConfig, error) { - panic("operation is not supported yet") -} - -func (c *Connector) RetrieveSSHCertificate(_ *certificate.SshCertRequest) (response *certificate.SshCertificateObject, err error) { - panic("operation is not supported yet") -} - -func (c *Connector) RequestSSHCertificate(_ *certificate.SshCertRequest) (response *certificate.SshCertificateObject, err error) { - panic("operation is not supported yet") -} - -func (c *Connector) RetrieveAvailableSSHTemplates() (response []certificate.SshAvaliableTemplate, err error) { - panic("operation is not supported yet") + return nil, fmt.Errorf("couldn't retrieve certificate because both PickupID and CertId are empty") } -func (c *Connector) RetrieveSystemVersion() (response string, err error) { - panic("operation is not supported yet") -} +// RenewCertificate attempts to renew the certificate +func (c *Connector) RenewCertificate(renewReq *certificate.RenewalRequest) (requestID string, err error) { + if !c.isAuthenticated() { + return "", fmt.Errorf("must be autheticated to request a certificate") + } -func (c *Connector) GetPolicyWithRegex(name string) (*policy.PolicySpecification, error) { + /* 1st step is to get CertificateRequestId which is required to lookup managedCertificateId and zoneId */ + var certificateRequestId string - cit, err := retrievePolicySpecification(c, name) + if renewReq.Thumbprint != "" { + // by Thumbprint (aka Fingerprint) + searchResult, err := c.searchCertificatesByFingerprint(renewReq.Thumbprint) + if err != nil { + return "", fmt.Errorf("failed to create renewal request: %s", err) + } + if len(searchResult.Certificates) == 0 { + return "", fmt.Errorf("no certificate found using fingerprint %s", renewReq.Thumbprint) + } - if err != nil { - return nil, err + var reqIds []string + isOnlyOneCertificateRequestId := true + for _, c := range searchResult.Certificates { + reqIds = append(reqIds, c.CertificateRequestId) + if certificateRequestId != "" && certificateRequestId != c.CertificateRequestId { + isOnlyOneCertificateRequestId = false + } + certificateRequestId = c.CertificateRequestId + } + if !isOnlyOneCertificateRequestId { + return "", fmt.Errorf("error: more than one CertificateRequestId was found with the same Fingerprint: %s", reqIds) + } + } else if renewReq.CertificateDN != "" { + // by CertificateDN (which is the same as CertificateRequestId for current implementation) + certificateRequestId = renewReq.CertificateDN + } else { + return "", fmt.Errorf("failed to create renewal request: CertificateDN or Thumbprint required") } - info, err := getCertificateAuthorityInfoFromCloud(cit.CertificateAuthority, cit.CertificateAuthorityAccountId, cit.CertificateAuthorityProductOptionId, c) - + /* 2nd step is to get ManagedCertificateId & ZoneId by looking up certificate request record */ + previousRequest, err := c.getCertificateStatus(certificateRequestId) if err != nil { - return nil, err + return "", fmt.Errorf("certificate renew failed: %s", err) } + applicationId := previousRequest.ApplicationId + templateId := previousRequest.TemplateId + certificateId := previousRequest.CertificateIdsList[0] - log.Println("Building policy") - ps := buildPolicySpecification(cit, info, false) - - return ps, nil -} - -func retrievePolicySpecification(c *Connector, name string) (*certificateTemplate, error) { - appName := policy.GetApplicationName(name) - if appName != "" { - c.zone.appName = appName - } else { - return nil, fmt.Errorf("application name is not valid, please provide a valid zone name in the format: appName\\CitName") + emptyField := "" + if certificateId == "" { + emptyField = "certificateId" + } else if applicationId == "" { + emptyField = "applicationId" + } else if templateId == "" { + emptyField = "templateId" } - citName := policy.GetCitName(name) - if citName != "" { - c.zone.templateAlias = citName - } else { - return nil, fmt.Errorf("cit name is not valid, please provide a valid zone name in the format: appName\\CitName") + if emptyField != "" { + return "", fmt.Errorf("failed to submit renewal request for certificate: %s is empty, certificate status is %s", emptyField, previousRequest.Status) } - log.Println("Getting CIT") - cit, err := c.getTemplateByID() - + /* 3rd step is to get Certificate Object by id + and check if latestCertificateRequestId there equals to certificateRequestId from 1st step */ + managedCertificate, err := c.getCertificate(certificateId) if err != nil { - return nil, err + return "", fmt.Errorf("failed to renew certificate: %s", err) + } + if managedCertificate.CertificateRequestId != certificateRequestId { + withThumbprint := "" + if renewReq.Thumbprint != "" { + withThumbprint = fmt.Sprintf("with thumbprint %s ", renewReq.Thumbprint) + } + return "", fmt.Errorf( + "certificate under requestId %s %s is not the latest under CertificateId %s."+ + "The latest request is %s. This error may happen when revoked certificate is requested to be renewed", + certificateRequestId, withThumbprint, certificateId, managedCertificate.CertificateRequestId) } - return cit, nil - -} - -func (c *Connector) GetPolicy(name string) (*policy.PolicySpecification, error) { + /* 4th step is to send renewal request */ + url := c.getURL(urlResourceCertificateRequests) - cit, err := retrievePolicySpecification(c, name) - if err != nil { - return nil, err + req := certificateRequest{ + ExistingCertificateId: certificateId, + ApplicationId: applicationId, + TemplateId: templateId, } - info, err := getCertificateAuthorityInfoFromCloud(cit.CertificateAuthority, cit.CertificateAuthorityAccountId, cit.CertificateAuthorityProductOptionId, c) + if renewReq.CertificateRequest.Location != nil { + workload := renewReq.CertificateRequest.Location.Workload + if workload == "" { + workload = defaultAppName + } + nodeName := renewReq.CertificateRequest.Location.Instance + appName := workload - if err != nil { - return nil, err + req.CertificateUsageMetadata = []certificateUsageMetadata{ + { + AppName: appName, + NodeName: nodeName, + }, + } } - log.Println("Building policy") - ps := buildPolicySpecification(cit, info, true) - - // getting the users to set to the PolicySpecification - policyUsers, err := c.getUsers() + if renewReq.CertificateRequest != nil && len(renewReq.CertificateRequest.GetCSR()) != 0 { + req.CSR = string(renewReq.CertificateRequest.GetCSR()) + req.ReuseCSR = false + } else { + req.ReuseCSR = true + return "", fmt.Errorf("reuseCSR option is not currently available for Renew Certificate operation. A new CSR must be provided in the request") + } + statusCode, status, body, err := c.request("POST", url, req) if err != nil { - return nil, err + return } - ps.Users = policyUsers - return ps, nil -} - -func (c *Connector) getUsers() ([]string, error) { - var usersList []string - appDetails, _, err := c.getAppDetailsByName(c.zone.getApplicationName()) + cr, err := parseCertificateRequestResult(statusCode, status, body) if err != nil { - return nil, err + return "", fmt.Errorf("failed to renew certificate: %s", err) } - var teamsList *teams - for _, owner := range appDetails.OwnerIdType { - if owner.OwnerType == UserType.String() { - retrievedUser, userErr := c.retrieveUser(owner.OwnerId) - if userErr != nil { - return nil, userErr - } - usersList = append(usersList, retrievedUser.Username) - } else if owner.OwnerType == TeamType.String() { - if teamsList == nil { - teamsList, err = c.retrieveTeams() - if err != nil { - return nil, err - } - } - if teamsList != nil { - for _, t := range teamsList.Teams { - if t.ID == owner.OwnerId { - usersList = append(usersList, t.Name) - break - } - } - } - } + return cr.CertificateRequests[0].ID, nil +} +// RetireCertificate attempts to retire the certificate +func (c *Connector) RetireCertificate(retireReq *certificate.RetireRequest) error { + if !c.isAuthenticated() { + return fmt.Errorf("must be autheticated to request a certificate") } - return usersList, nil -} -func PolicyExist(policyName string, c *Connector) (bool, error) { + url := c.getURL(urlResourceCertificatesRetirement) + /* 1st step is to get CertificateRequestId which is required to retire certificate */ + var certificateRequestId string + if retireReq.Thumbprint != "" { + // by Thumbprint (aka Fingerprint) + searchResult, err := c.searchCertificatesByFingerprint(retireReq.Thumbprint) + if err != nil { + return fmt.Errorf("failed to create retire request: %s", err) + } + if len(searchResult.Certificates) == 0 { + return fmt.Errorf("no certificate found using fingerprint %s", retireReq.Thumbprint) + } - c.zone.appName = policy.GetApplicationName(policyName) - citName := policy.GetCitName(policyName) - if citName != "" { - c.zone.templateAlias = citName + var reqIds []string + isOnlyOneCertificateRequestId := true + for _, c := range searchResult.Certificates { + reqIds = append(reqIds, c.CertificateRequestId) + if certificateRequestId != "" && certificateRequestId != c.CertificateRequestId { + isOnlyOneCertificateRequestId = false + } + certificateRequestId = c.CertificateRequestId + } + if !isOnlyOneCertificateRequestId { + return fmt.Errorf("error: more than one CertificateRequestId was found with the same Fingerprint: %s", reqIds) + } + } else if retireReq.CertificateDN != "" { + // by CertificateDN (which is the same as CertificateRequestId for current implementation) + certificateRequestId = retireReq.CertificateDN } else { - return false, fmt.Errorf("cit name is not valid, please provide a valid zone name in the format: appName\\CitName") + return fmt.Errorf("failed to create retire request: CertificateDN or Thumbprint required") } - _, err := c.getTemplateByID() - return err == nil, nil -} + /* 2nd step is to get ManagedCertificateId & ZoneId by looking up certificate request record */ + previousRequest, err := c.getCertificateStatus(certificateRequestId) + if err != nil { + if strings.Contains(err.Error(), "Unable to find certificateRequest") { + return fmt.Errorf("invalid thumbprint or certificate ID. No certificates were retired") + } + return fmt.Errorf("certificate retirement failed: error on getting Certificate ID: %s", err) + } + certificateId := previousRequest.CertificateIdsList[0] -func (c *Connector) SetPolicy(name string, ps *policy.PolicySpecification) (string, error) { + /* Now we do retirement*/ + retRequest := certificateRetireRequest{ + CertificateIds: []string{certificateId}, + } - err := policy.ValidateCloudPolicySpecification(ps) + statusCode, status, response, err := c.request("POST", url, retRequest) if err != nil { - return "", err + return err } - log.Printf("policy specification is valid") + err = checkCertificateRetireResults(statusCode, status, response) + if err != nil { + return err + } - var status string + return nil +} - //validate if zone name is set and if zone already exist on Venafi cloud if not create it. - citName := policy.GetCitName(name) +// RevokeCertificate attempts to revoke the certificate +func (c *Connector) RevokeCertificate(_ *certificate.RevocationRequest) (err error) { + return fmt.Errorf("not supported by endpoint") +} - if citName == "" { - return "", fmt.Errorf("cit name is empty, please provide zone in the format: app_name\\cit_name") +func (c *Connector) ImportCertificate(req *certificate.ImportRequest) (*certificate.ImportResponse, error) { + if !c.isAuthenticated() { + return nil, fmt.Errorf("must be autheticated to request a certificate") } - //get certificate authority product option io - var caDetails *policy.CADetails - - if ps.Policy != nil && ps.Policy.CertificateAuthority != nil && *(ps.Policy.CertificateAuthority) != "" { - caDetails, err = getCertificateAuthorityDetails(*(ps.Policy.CertificateAuthority), c) - + pBlock, _ := pem.Decode([]byte(req.CertificateData)) + if pBlock == nil { + return nil, fmt.Errorf("%w can`t parse certificate", verror.UserDataError) + } + zone := req.PolicyDN + if zone == "" { + appDetails, _, err := c.getAppDetailsByName(c.zone.getApplicationName()) if err != nil { - return "", err + return nil, err } - - } else { - if ps.Policy != nil { - - defaultCA := policy.DefaultCA - ps.Policy.CertificateAuthority = &defaultCA - - caDetails, err = getCertificateAuthorityDetails(*(ps.Policy.CertificateAuthority), c) - if err != nil { - return "", err - } - - } else { - //policy is not specified so we get the default CA - caDetails, err = getCertificateAuthorityDetails(policy.DefaultCA, c) - if err != nil { - return "", err - } + zone = appDetails.ApplicationId + } + ipAddr := endpoint.LocalIP + origin := endpoint.SDKName + for _, f := range req.CustomFields { + if f.Type == certificate.CustomFieldOrigin { + origin = f.Value } } - - //at this moment we know that ps.Policy.CertificateAuthority is valid. - - req, err := policy.BuildCloudCitRequest(ps, caDetails) + base64.StdEncoding.EncodeToString(pBlock.Bytes) + fingerprint := certThumbprint(pBlock.Bytes) + request := importRequest{ + Certificates: []importRequestCertInfo{ + { + Certificate: base64.StdEncoding.EncodeToString(pBlock.Bytes), + ApplicationIds: []string{zone}, + ApiClientInformation: apiClientInformation{ + Type: origin, + Identifier: ipAddr, + }, + }, + }, + } + + url := c.getURL(urlResourceCertificates) + statusCode, status, body, err := c.request("POST", url, request) + if err != nil { + return nil, fmt.Errorf("%w: %v", verror.ServerTemporaryUnavailableError, err) + } + var r importResponse + switch statusCode { + case http.StatusOK, http.StatusCreated, http.StatusAccepted: + case http.StatusBadRequest, http.StatusForbidden, http.StatusConflict: + return nil, fmt.Errorf("%w: certificate can`t be imported. %d %s %s", verror.ServerBadDataResponce, statusCode, status, string(body)) + case http.StatusInternalServerError, http.StatusBadGateway, http.StatusServiceUnavailable: + return nil, verror.ServerTemporaryUnavailableError + default: + return nil, verror.ServerError + } + err = json.Unmarshal(body, &r) if err != nil { - return "", err + return nil, fmt.Errorf("%w: can`t unmarshal json response %s", verror.ServerError, err) + } else if !(len(r.CertificateInformations) == 1) { + return nil, fmt.Errorf("%w: certificate was not imported on unknown reason", verror.ServerBadDataResponce) } - req.Name = citName - - url := c.getURL(urlIssuingTemplate) - - cit, err := getCit(c, citName) - + time.Sleep(time.Second) + foundCert, err := c.searchCertificatesByFingerprint(fingerprint) if err != nil { - return "", err + return nil, err } + if len(foundCert.Certificates) != 1 { + return nil, fmt.Errorf("%w certificate has been imported but could not be found on platform after that", verror.ServerError) + } + cert := foundCert.Certificates[0] + resp := &certificate.ImportResponse{CertificateDN: cert.SubjectCN[0], CertId: cert.Id} + return resp, nil +} - if cit != nil { - log.Printf("updating issuing template: %s", citName) - //update cit using the new values - url = fmt.Sprint(url, "/", cit.ID) - statusCode, status, body, err := c.request("PUT", url, req) - - if err != nil { - return "", err - } - - cit, err = parseCitResult(http.StatusOK, statusCode, status, body) +func (c *Connector) ListCertificates(filter endpoint.Filter) ([]certificate.CertificateInfo, error) { + if !c.isAuthenticated() { + return nil, fmt.Errorf("must be autheticated to request a certificate") + } - if err != nil { - return status, err + if c.zone.String() == "" { + return nil, fmt.Errorf("empty zone") + } + const batchSize = 50 + limit := 100000000 + if filter.Limit != nil { + limit = *filter.Limit + } + var buf [][]certificate.CertificateInfo + for page := 0; limit > 0; limit, page = limit-batchSize, page+1 { + var b []certificate.CertificateInfo + var err error + b, err = c.getCertsBatch(page, batchSize, filter.WithExpired) + if limit < batchSize && len(b) > limit { + b = b[:limit] } - - } else { - log.Printf("creating issuing template: %s", citName) - //var body []byte - statusCode, status, body, err := c.request("POST", url, req) - if err != nil { - return "", err + return nil, err } - - cit, err = parseCitResult(http.StatusCreated, statusCode, status, body) - - if err != nil { - return status, err + buf = append(buf, b) + if len(b) < batchSize { + break } - } - - //validate if appName is set and if app already exist on Venafi cloud if not create it - //and as final steps link the app with the cit. - appName := policy.GetApplicationName(name) - - if appName == "" { - return "", fmt.Errorf("application name is empty, please provide zone in the format: app_name\\cit_name") + sumLen := 0 + for _, b := range buf { + sumLen += len(b) } - - appDetails, statusCode, err := c.getAppDetailsByName(appName) - - if err != nil && statusCode == 404 { //means application was not found. - log.Printf("creating application: %s", appName) - - _, err = c.createApplication(appName, ps, cit) - if err != nil { - return "", err - } - - } else { //determine if the application needs to be updated - log.Printf("updating application: %s", appName) - err = c.updateApplication(name, ps, cit, appDetails) - if err != nil { - return "", err - } + infos := make([]certificate.CertificateInfo, sumLen) + offset := 0 + for _, b := range buf { + copy(infos[offset:], b[:]) + offset += len(b) } - - log.Printf("policy successfully applied to %s", name) - - return status, nil + return infos, nil } -func (c *Connector) createApplication(appName string, ps *policy.PolicySpecification, cit *certificateTemplate) (*policy.Application, error) { - appIssuingTemplate := make(map[string]string) - appIssuingTemplate[cit.Name] = cit.ID +func (c *Connector) SearchCertificates(_ *certificate.SearchRequest) (*certificate.CertSearchResponse, error) { + panic("operation is not supported yet") +} - var owners []policy.OwnerIdType - var err error - var statusCode int - var status string - - //if users are passed to the PS, resolve the related Owners to set them - if len(ps.Users) > 0 { - owners, err = c.resolveOwners(ps.Users) - } else { //if users are not specified in PS, then the current User should be used as owner - var owner *policy.OwnerIdType - owner, err = c.getOwnerFromUserDetails() - if owner != nil { - owners = []policy.OwnerIdType{*owner} - } +func (c *Connector) SearchCertificate(zone string, cn string, sans *certificate.Sans, certMinTimeLeft time.Duration) (certificateInfo *certificate.CertificateInfo, err error) { + if !c.isAuthenticated() { + return nil, fmt.Errorf("must be autheticated to request a certificate") } + // retrieve application name from zone + appName := getAppNameFromZone(zone) + // get application id from name + app, _, err := c.getAppDetailsByName(appName) if err != nil { - return nil, fmt.Errorf("an error happened trying to resolve the owners: %w", err) - } - - //create application - appReq := policy.Application{ - OwnerIdsAndTypes: owners, - Name: appName, - CertificateIssuingTemplateAliasIdMap: appIssuingTemplate, + return nil, err } - url := c.getURL(urlAppRoot) + // format arguments for request + req := formatSearchCertificateArguments(cn, sans, certMinTimeLeft) - statusCode, status, _, err = c.request("POST", url, appReq) + // perform request + searchResult, err := c.searchCertificates(req) if err != nil { return nil, err } - if statusCode != 201 { - return nil, fmt.Errorf("unexpected result %s attempting to create application %s", status, appName) - } - - return &appReq, nil -} -func (c *Connector) updateApplication(name string, ps *policy.PolicySpecification, cit *certificateTemplate, appDetails *ApplicationDetails) error { - - //creating the app to use as request - appReq := createAppUpdateRequest(appDetails) - - //determining if the relationship between application and cit exist - citAddedToApp := false - exist, err := PolicyExist(name, c) - if err != nil { - return err - } - if !exist { - c.addCitToApp(&appReq, cit) - citAddedToApp = true + // fail if no certificate is returned from api + if searchResult.Count == 0 { + return nil, verror.NoCertificateFoundError } - //determining if the owners where provided and should be updated - ownersUpdated := false - //given that the application exists, the only way to update the owners at the application - //is that users in the policy specification were provided - if len(ps.Users) > 0 { - //resolving and setting owners - owners, err := c.resolveOwners(ps.Users) - if err != nil { - return fmt.Errorf("an error happened trying to resolve the owners: %w", err) + // map (convert) response to an array of CertificateInfo + certificates := make([]*certificate.CertificateInfo, 0) + n := 0 + for _, cert := range searchResult.Certificates { + if util.ArrayContainsString(cert.ApplicationIds, app.ApplicationId) { + match := cert.ToCertificateInfo() + certificates = append(certificates, &match) + n = n + 1 } - appReq.OwnerIdsAndTypes = owners - ownersUpdated = true } - //if the cit was added to the app or the owners were updated, then is required - //to update the application - if citAddedToApp || ownersUpdated { - url := c.getURL(urlAppRoot) - url = fmt.Sprint(url, "/", appDetails.ApplicationId) - _, _, _, err = c.request("PUT", url, appReq) - if err != nil { - return err - } + // fail if no certificates found with matching zone + if n == 0 { + return nil, verror.NoCertificateWithMatchingZoneFoundError } - return nil + // at this point all certificates belong to our zone, the next step is + // finding the newest valid certificate matching the provided sans + return certificate.FindNewestCertificateWithSans(certificates, sans) } -func (c *Connector) addCitToApp(app *policy.Application, cit *certificateTemplate) { - //add cit to the map. - value, ok := app.CertificateIssuingTemplateAliasIdMap[cit.Name] - if !ok || value != cit.ID { - app.CertificateIssuingTemplateAliasIdMap[cit.Name] = cit.ID +func (c *Connector) IsCSRServiceGenerated(req *certificate.Request) (bool, error) { + if !c.isAuthenticated() { + return false, fmt.Errorf("must be autheticated to request a certificate") } -} -func (c *Connector) resolveOwners(usersList []string) ([]policy.OwnerIdType, error) { - - var owners []policy.OwnerIdType - var teams *teams - var err error - - for _, userName := range usersList { - //The error should be ignored in order to confirm if the userName is not a TeamName - users, _ := c.retrieveUsers(userName) + if req.PickupID == "" && req.CertID == "" && req.Thumbprint != "" { + // search cert by Thumbprint and fill pickupID + var certificateRequestId string + searchResult, err := c.searchCertificatesByFingerprint(req.Thumbprint) + if err != nil { + return false, fmt.Errorf("failed to retrieve certificate: %s", err) + } + if len(searchResult.Certificates) == 0 { + return false, fmt.Errorf("no certificate found using fingerprint %s", req.Thumbprint) + } - if users != nil { - owners = appendOwner(owners, users.Users[0].ID, UserType) - } else { - if teams == nil { - teams, err = c.retrieveTeams() + var reqIds []string + for _, c := range searchResult.Certificates { + reqIds = append(reqIds, c.CertificateRequestId) + if certificateRequestId != "" && certificateRequestId != c.CertificateRequestId { + return false, fmt.Errorf("more than one CertificateRequestId was found with the same Fingerprint: %s", reqIds) } - if err != nil { - return nil, err + if c.CertificateRequestId != "" { + certificateRequestId = c.CertificateRequestId } - if teams != nil { - var found = false - for _, team := range teams.Teams { - if team.Name == userName { - owners = appendOwner(owners, team.ID, TeamType) - found = true - break - } - } - if !found { - return nil, fmt.Errorf("it was not possible to find the user %s", userName) - } + if c.Id != "" { + req.CertID = c.Id } } + req.PickupID = certificateRequestId } - return owners, err -} - -func appendOwner(owners []policy.OwnerIdType, ownerId string, ownerType OwnerType) []policy.OwnerIdType { - owner := createOwner(ownerId, ownerType) - return append(owners, *owner) -} - -func (c *Connector) getOwnerFromUserDetails() (*policy.OwnerIdType, error) { - userDetails, err := getUserDetails(c) - if err != nil { - return nil, err - } - owner := createOwner(userDetails.User.ID, UserType) - return owner, nil -} - -func createOwner(ownerId string, ownerType OwnerType) *policy.OwnerIdType { - ownerIdType := policy.OwnerIdType{ - OwnerId: ownerId, - OwnerType: ownerType.String(), - } - - return &ownerIdType -} - -// NewConnector creates a new Venafi Cloud Connector object used to communicate with Venafi Cloud -func NewConnector(url string, zone string, verbose bool, trust *x509.CertPool) (*Connector, error) { - cZone := cloudZone{zone: zone} - c := Connector{verbose: verbose, trust: trust, zone: cZone} - + var dekInfo *EdgeEncryptionKey + var currentId string var err error - c.baseURL, err = normalizeURL(url) - if err != nil { - return nil, err + if req.CertID != "" { + dekInfo, err = getDekInfo(c, req.CertID) + } else { + var certificateId string + certificateId, err = getCertificateId(c, req) + if err == nil && certificateId != "" { + dekInfo, err = getDekInfo(c, certificateId) + } } - return &c, nil -} -// normalizeURL allows overriding the default URL used to communicate with Venafi Cloud -func normalizeURL(url string) (normalizedURL string, err error) { - if url == "" { - url = apiURL + if err == nil && dekInfo.Key != "" { + req.CertID = currentId + return true, err } - normalizedURL = util.NormalizeUrl(url) - return normalizedURL, nil + return false, nil } -func (c *Connector) SetZone(z string) { - cZone := cloudZone{zone: z} - c.zone = cZone +func (c *Connector) RetrieveCertificateMetaData(_ string) (*certificate.CertificateMetaData, error) { + panic("operation is not supported yet") } -func (c *Connector) GetType() endpoint.ConnectorType { - return endpoint.ConnectorTypeCloud +// SynchronousRequestCertificate It's not supported yet in VaaS +func (c *Connector) SynchronousRequestCertificate(_ *certificate.Request) (certificates *certificate.PEMCollection, err error) { + panic("operation is not supported yet") } -// Ping attempts to connect to the Venafi Cloud API and returns an error if it cannot -func (c *Connector) Ping() (err error) { - - return nil +// SupportSynchronousRequestCertificate returns if the connector support synchronous calls to request a certificate. +func (c *Connector) SupportSynchronousRequestCertificate() bool { + return false } -// Authenticate authenticates the user with Venafi Cloud using the provided API Key -func (c *Connector) Authenticate(auth *endpoint.Authentication) (err error) { - if auth == nil { - return fmt.Errorf("failed to authenticate: missing credentials") - } - - //1. Access token. Assign it to connector and return - if auth.AccessToken != "" { - c.accessToken = auth.AccessToken - return - } +func (c *Connector) RetrieveSystemVersion() (response string, err error) { + panic("operation is not supported yet") +} - //2. JWT and tenantID. use it to request new access token - if auth.TenantID != "" && auth.ExternalIdPJWT != "" { - c.tenantID = auth.TenantID - c.jwt = auth.ExternalIdPJWT - err = c.getServiceAccountToken() +func getCertificateId(c *Connector, req *certificate.Request) (string, error) { + startTime := time.Now() + //Wait for certificate to be issued by checking its PickupID + //If certID is filled then certificate should be already issued. + for { + if req.PickupID == "" { + break + } + certStatus, err := c.getCertificateStatus(req.PickupID) if err != nil { - return + return "", fmt.Errorf("unable to retrieve: %s", err) + } + if certStatus.Status == "ISSUED" { + return certStatus.CertificateIdsList[0], nil + } else if certStatus.Status == "FAILED" { + return "", fmt.Errorf("failed to retrieve certificate. Status: %v", certStatus) } - auth.AccessToken = c.accessToken - return + if req.Timeout == 0 { + return "", endpoint.ErrCertificatePending{CertificateID: req.PickupID, Status: certStatus.Status} + } else { + log.Println("Issuance of certificate is pending...") + } + if time.Now().After(startTime.Add(req.Timeout)) { + return "", endpoint.ErrRetrieveCertificateTimeout{CertificateID: req.PickupID} + } + time.Sleep(2 * time.Second) } - // 3. API key. Get user to test authentication - c.apiKey = auth.APIKey - url := c.getURL(urlResourceUserAccounts) - statusCode, status, body, err := c.request("GET", url, nil, true) - if err != nil { - return - } - ud, err := parseUserDetailsResult(http.StatusOK, statusCode, status, body) - if err != nil { - return + return "", endpoint.ErrRetrieveCertificateTimeout{CertificateID: req.PickupID} +} + +// normalizeURL allows overriding the default URL used to communicate with Venafi Cloud +func normalizeURL(url string) (normalizedURL string, err error) { + if url == "" { + url = apiURL } - c.user = ud - return + normalizedURL = util.NormalizeUrl(url) + return normalizedURL, nil } func (c *Connector) getServiceAccountToken() error { @@ -777,55 +909,19 @@ func (c *Connector) getServiceAccountToken() error { return nil } -func (c *Connector) ReadPolicyConfiguration() (policy *endpoint.Policy, err error) { - config, err := c.ReadZoneConfiguration() - if err != nil { - return nil, err +func (c *Connector) isAuthenticated() bool { + if c.accessToken != "" { + return true } - policy = &config.Policy - return -} - -// ReadZoneConfiguration reads the Zone information needed for generating and requesting a certificate from Venafi Cloud -func (c *Connector) ReadZoneConfiguration() (config *endpoint.ZoneConfiguration, err error) { - var template *certificateTemplate - var statusCode int - // to fully support the "headless registration" use case... - // if application does not exist and is for the default CIT, create the application - citAlias := c.zone.getTemplateAlias() - if citAlias == "Default" { - appName := c.zone.getApplicationName() - _, statusCode, err = c.getAppDetailsByName(appName) - if err != nil && statusCode == 404 { - log.Printf("creating application %s for issuing template %s", appName, citAlias) - - ps := policy.PolicySpecification{} - template, err = getCit(c, citAlias) - if err != nil { - return - } - _, err = c.createApplication(appName, &ps, template) - if err != nil { - return - } - } - } - if template == nil { - template, err = c.getTemplateByID() - if err != nil { - return - } + if c.user != nil && c.user.Company != nil { + return true } - config = getZoneConfiguration(template) - return config, nil -} -func getCloudRequest(c *Connector, req *certificate.Request) (*certificateRequest, error) { - if (c.accessToken == "" && c.user == nil) || (c.user != nil && c.user.Company == nil) { - return nil, fmt.Errorf("must be autheticated to request a certificate") - } + return false +} +func (c *Connector) getCloudRequest(req *certificate.Request) (*certificateRequest, error) { ipAddr := endpoint.LocalIP origin := endpoint.SDKName for _, f := range req.CustomFields { @@ -850,244 +946,77 @@ func getCloudRequest(c *Connector, req *certificate.Request) (*certificateReques } if req.CsrOrigin != certificate.ServiceGeneratedCSR { - cloudReq.CSR = string(req.GetCSR()) - } else { - - cloudReq.IsVaaSGenerated = true - csrAttr, err := getCsrAttributes(c, req) - if err != nil { - return nil, err - } - cloudReq.CsrAttributes = *(csrAttr) - cloudReq.ApplicationServerTypeId = util.ApplicationServerTypeID - - } - - if req.Location != nil { - workload := req.Location.Workload - if workload == "" { - workload = defaultAppName - } - nodeName := req.Location.Instance - appName := workload - - cloudReq.CertificateUsageMetadata = []certificateUsageMetadata{ - { - AppName: appName, - NodeName: nodeName, - }, - } - } - - validityDuration := req.ValidityDuration - - // DEPRECATED: ValidityHours is deprecated in favor of ValidityDuration, but we - // still support it for backwards compatibility. - if validityDuration == nil && req.ValidityHours > 0 { //nolint:staticcheck - duration := time.Duration(req.ValidityHours) * time.Hour //nolint:staticcheck - validityDuration = &duration - } - - if validityDuration != nil { - cloudReq.ValidityPeriod = "PT" + strings.ToUpper((*validityDuration).Truncate(time.Second).String()) - } - - return &cloudReq, nil -} - -// ResetCertificate resets the state of a certificate. -func (c *Connector) ResetCertificate(_ *certificate.Request, _ bool) (err error) { - return fmt.Errorf("not supported by endpoint") -} - -// RequestCertificate submits the CSR to the Venafi Cloud API for processing -func (c *Connector) RequestCertificate(req *certificate.Request) (requestID string, err error) { - - url := c.getURL(urlResourceCertificateRequests) - cloudReq, err := getCloudRequest(c, req) - if err != nil { - return "", err - } - - statusCode, status, body, err := c.request("POST", url, cloudReq) - - if err != nil { - return "", err - } - cr, err := parseCertificateRequestResult(statusCode, status, body) - if err != nil { - return "", err - } - requestID = cr.CertificateRequests[0].ID - req.PickupID = requestID - return requestID, nil -} - -func (c *Connector) getCertificateStatus(requestID string) (certStatus *certificateStatus, err error) { - url := c.getURL(urlResourceCertificateStatus) - url = fmt.Sprintf(url, requestID) - statusCode, _, body, err := c.request("GET", url, nil) - if err != nil { - return nil, err - } - if statusCode == http.StatusOK { - certStatus = &certificateStatus{} - err = json.Unmarshal(body, certStatus) - if err != nil { - return nil, fmt.Errorf("failed to parse certificate request status response: %s", err) - } - return - } - respErrors, err := parseResponseErrors(body) - if err == nil { - respError := fmt.Sprintf("Unexpected status code on Venafi Cloud certificate search. Status: %d\n", statusCode) - for _, e := range respErrors { - respError += fmt.Sprintf("Error Code: %d Error: %s\n", e.Code, e.Message) - } - return nil, fmt.Errorf(respError) - } - - return nil, fmt.Errorf("unexpected status code on Venafi Cloud certificate search. Status: %d", statusCode) - -} - -// SynchronousRequestCertificate It's not supported yet in VaaS -func (c *Connector) SynchronousRequestCertificate(_ *certificate.Request) (certificates *certificate.PEMCollection, err error) { - panic("operation is not supported yet") -} - -// SupportSynchronousRequestCertificate returns if the connector support synchronous calls to request a certificate. -func (c *Connector) SupportSynchronousRequestCertificate() bool { - return false -} - -// RetrieveCertificate retrieves the certificate for the specified ID -func (c *Connector) RetrieveCertificate(req *certificate.Request) (certificates *certificate.PEMCollection, err error) { - - if req.PickupID == "" && req.CertID == "" && req.Thumbprint != "" { - // search cert by Thumbprint and fill pickupID - var certificateRequestId string - searchResult, err := c.searchCertificatesByFingerprint(req.Thumbprint) - if err != nil { - return nil, fmt.Errorf("failed to retrieve certificate: %s", err) - } - if len(searchResult.Certificates) == 0 { - return nil, fmt.Errorf("no certificate found using fingerprint %s", req.Thumbprint) - } - - var reqIds []string - isOnlyOneCertificateRequestId := true - for _, c := range searchResult.Certificates { - reqIds = append(reqIds, c.CertificateRequestId) - if certificateRequestId != "" && certificateRequestId != c.CertificateRequestId { - isOnlyOneCertificateRequestId = false - } - if c.CertificateRequestId != "" { - certificateRequestId = c.CertificateRequestId - } - if c.Id != "" { - req.CertID = c.Id - } - } - if !isOnlyOneCertificateRequestId { - return nil, fmt.Errorf("more than one CertificateRequestId was found with the same Fingerprint: %s", reqIds) - } - - req.PickupID = certificateRequestId - } - - startTime := time.Now() - //Wait for certificate to be issued by checking its PickupID - //If certID is filled then certificate should be already issued. - var certificateId string - if req.CertID == "" { - for { - if req.PickupID == "" { - break - } - certStatus, err := c.getCertificateStatus(req.PickupID) - if err != nil { - return nil, fmt.Errorf("unable to retrieve: %s", err) - } - if certStatus.Status == "ISSUED" { - certificateId = certStatus.CertificateIdsList[0] - break // to fetch the cert itself - } else if certStatus.Status == "FAILED" { - return nil, fmt.Errorf("failed to retrieve certificate. Status: %v", certStatus) - } - // status.Status == "REQUESTED" || status.Status == "PENDING" - if req.Timeout == 0 { - return nil, endpoint.ErrCertificatePending{CertificateID: req.PickupID, Status: certStatus.Status} - } else { - log.Println("Issuance of certificate is pending...") - } - if time.Now().After(startTime.Add(req.Timeout)) { - return nil, endpoint.ErrRetrieveCertificateTimeout{CertificateID: req.PickupID} - } - // fmt.Printf("pending... %s\n", status.Status) - time.Sleep(2 * time.Second) - } + cloudReq.CSR = string(req.GetCSR()) } else { - certificateId = req.CertID + + cloudReq.IsVaaSGenerated = true + csrAttr, err := getCsrAttributes(c, req) + if err != nil { + return nil, err + } + cloudReq.CsrAttributes = *(csrAttr) + cloudReq.ApplicationServerTypeId = util.ApplicationServerTypeID + } - if c.user == nil || c.user.Company == nil { - return nil, fmt.Errorf("must be autheticated to retieve certificate") + if req.Location != nil { + workload := req.Location.Workload + if workload == "" { + workload = defaultAppName + } + nodeName := req.Location.Instance + appName := workload + + cloudReq.CertificateUsageMetadata = []certificateUsageMetadata{ + { + AppName: appName, + NodeName: nodeName, + }, + } } - url := c.getURL(urlResourceCertificateRetrievePem) - url = fmt.Sprintf(url, certificateId) + validityDuration := req.ValidityDuration - var dekInfo *EdgeEncryptionKey - var currentId string - if req.CertID != "" { - dekInfo, err = getDekInfo(c, req.CertID) - currentId = req.CertID - } else if certificateId != "" { - dekInfo, err = getDekInfo(c, certificateId) - currentId = certificateId + // DEPRECATED: ValidityHours is deprecated in favor of ValidityDuration, but we + // still support it for backwards compatibility. + if validityDuration == nil && req.ValidityHours > 0 { //nolint:staticcheck + duration := time.Duration(req.ValidityHours) * time.Hour //nolint:staticcheck + validityDuration = &duration } - if err == nil && dekInfo.Key != "" { - req.CertID = currentId - return retrieveServiceGeneratedCertData(c, req, dekInfo) + + if validityDuration != nil { + cloudReq.ValidityPeriod = "PT" + strings.ToUpper((*validityDuration).Truncate(time.Second).String()) } - switch { - case req.CertID != "": - statusCode, status, body, err := c.waitForCertificate(url, req) //c.request("GET", url, nil) - if err != nil { - return nil, err - } - if statusCode != http.StatusOK { - return nil, fmt.Errorf("failed to retrieve certificate. StatusCode: %d -- Status: %s -- Server Data: %s", statusCode, status, body) - } - return newPEMCollectionFromResponse(body, certificate.ChainOptionIgnore) - case req.PickupID != "": - url += "?chainOrder=%s&format=PEM" - switch req.ChainOption { - case certificate.ChainOptionRootFirst: - url = fmt.Sprintf(url, condorChainOptionRootFirst) - default: - url = fmt.Sprintf(url, condorChainOptionRootLast) - } - statusCode, status, body, err := c.waitForCertificate(url, req) //c.request("GET", url, nil) + return &cloudReq, nil +} + +func (c *Connector) getCertificateStatus(requestID string) (certStatus *certificateStatus, err error) { + url := c.getURL(urlResourceCertificateStatus) + url = fmt.Sprintf(url, requestID) + statusCode, _, body, err := c.request("GET", url, nil) + if err != nil { + return nil, err + } + if statusCode == http.StatusOK { + certStatus = &certificateStatus{} + err = json.Unmarshal(body, certStatus) if err != nil { - return nil, err + return nil, fmt.Errorf("failed to parse certificate request status response: %s", err) } - if statusCode == http.StatusOK { - certificates, err = newPEMCollectionFromResponse(body, req.ChainOption) - if err != nil { - return nil, err - } - err = req.CheckCertificate(certificates.Certificate) - return certificates, err - } else if statusCode == http.StatusConflict { // Http Status Code 409 means the certificate has not been signed by the ca yet. - return nil, endpoint.ErrCertificatePending{CertificateID: req.PickupID} - } else { - return nil, fmt.Errorf("failed to retrieve certificate. StatusCode: %d -- Status: %s", statusCode, status) + return + } + respErrors, err := parseResponseErrors(body) + if err == nil { + respError := fmt.Sprintf("Unexpected status code on Venafi Cloud certificate search. Status: %d\n", statusCode) + for _, e := range respErrors { + respError += fmt.Sprintf("Error Code: %d Error: %s\n", e.Code, e.Message) } + return nil, fmt.Errorf(respError) } - return nil, fmt.Errorf("couldn't retrieve certificate because both PickupID and CertId are empty") + + return nil, fmt.Errorf("unexpected status code on Venafi Cloud certificate search. Status: %d", statusCode) + } func retrieveServiceGeneratedCertData(c *Connector, req *certificate.Request, dekInfo *EdgeEncryptionKey) (*certificate.PEMCollection, error) { @@ -1205,255 +1134,68 @@ func ConvertZipBytesToPem(dataByte []byte, rootFirst bool) (*certificate.PEMColl f, err := zipFile.Open() if err != nil { - return nil, err - } - - defer f.Close() - fileBytes, err := io.ReadAll(f) - if err != nil { - return nil, err - } - - certs := strings.Split(strings.TrimSpace(string(fileBytes)), "\n\n") - - for i := 0; i < len(certs); i++ { - if i < len(certs)-1 { - if len(chainArr) == 0 { - chainArr = append(chainArr, certs[i]+"\n") - } else { - if rootFirst { - chainArr = append(chainArr, certs[i]+"\n") - } else { - chainArr = append([]string{certs[i] + "\n"}, chainArr...) - } - } - } else { - cert = certs[i] + "\n" - } - } - } - } - - collection.Certificate = cert - collection.PrivateKey = privateKey - collection.Chain = chainArr - - return &collection, nil -} - -// Waits for the Certificate to be available. Fails when the timeout is exceeded -func (c *Connector) waitForCertificate(url string, request *certificate.Request) (statusCode int, status string, body []byte, err error) { - startTime := time.Now() - for { - statusCode, status, body, err = c.request("GET", url, nil) - if err != nil { - return - } - if statusCode == http.StatusOK { - return - } - if request.Timeout == 0 { - err = endpoint.ErrCertificatePending{CertificateID: request.PickupID, Status: status} - return - } - if time.Now().After(startTime.Add(request.Timeout)) { - err = endpoint.ErrRetrieveCertificateTimeout{CertificateID: request.PickupID} - return - } - time.Sleep(2 * time.Second) - } -} - -// RevokeCertificate attempts to revoke the certificate -func (c *Connector) RevokeCertificate(_ *certificate.RevocationRequest) (err error) { - return fmt.Errorf("not supported by endpoint") -} - -// WriteLog Custom Logging not currently supported by VaaS -func (c *Connector) WriteLog(_ *endpoint.LogRequest) (err error) { - return fmt.Errorf("outbound logging not supported by endpoint") -} - -// RenewCertificate attempts to renew the certificate -func (c *Connector) RenewCertificate(renewReq *certificate.RenewalRequest) (requestID string, err error) { - - /* 1st step is to get CertificateRequestId which is required to lookup managedCertificateId and zoneId */ - var certificateRequestId string - - if renewReq.Thumbprint != "" { - // by Thumbprint (aka Fingerprint) - searchResult, err := c.searchCertificatesByFingerprint(renewReq.Thumbprint) - if err != nil { - return "", fmt.Errorf("failed to create renewal request: %s", err) - } - if len(searchResult.Certificates) == 0 { - return "", fmt.Errorf("no certificate found using fingerprint %s", renewReq.Thumbprint) - } - - var reqIds []string - isOnlyOneCertificateRequestId := true - for _, c := range searchResult.Certificates { - reqIds = append(reqIds, c.CertificateRequestId) - if certificateRequestId != "" && certificateRequestId != c.CertificateRequestId { - isOnlyOneCertificateRequestId = false - } - certificateRequestId = c.CertificateRequestId - } - if !isOnlyOneCertificateRequestId { - return "", fmt.Errorf("error: more than one CertificateRequestId was found with the same Fingerprint: %s", reqIds) - } - } else if renewReq.CertificateDN != "" { - // by CertificateDN (which is the same as CertificateRequestId for current implementation) - certificateRequestId = renewReq.CertificateDN - } else { - return "", fmt.Errorf("failed to create renewal request: CertificateDN or Thumbprint required") - } - - /* 2nd step is to get ManagedCertificateId & ZoneId by looking up certificate request record */ - previousRequest, err := c.getCertificateStatus(certificateRequestId) - if err != nil { - return "", fmt.Errorf("certificate renew failed: %s", err) - } - applicationId := previousRequest.ApplicationId - templateId := previousRequest.TemplateId - certificateId := previousRequest.CertificateIdsList[0] - - emptyField := "" - if certificateId == "" { - emptyField = "certificateId" - } else if applicationId == "" { - emptyField = "applicationId" - } else if templateId == "" { - emptyField = "templateId" - } - if emptyField != "" { - return "", fmt.Errorf("failed to submit renewal request for certificate: %s is empty, certificate status is %s", emptyField, previousRequest.Status) - } - - /* 3rd step is to get Certificate Object by id - and check if latestCertificateRequestId there equals to certificateRequestId from 1st step */ - managedCertificate, err := c.getCertificate(certificateId) - if err != nil { - return "", fmt.Errorf("failed to renew certificate: %s", err) - } - if managedCertificate.CertificateRequestId != certificateRequestId { - withThumbprint := "" - if renewReq.Thumbprint != "" { - withThumbprint = fmt.Sprintf("with thumbprint %s ", renewReq.Thumbprint) - } - return "", fmt.Errorf( - "certificate under requestId %s %s is not the latest under CertificateId %s."+ - "The latest request is %s. This error may happen when revoked certificate is requested to be renewed", - certificateRequestId, withThumbprint, certificateId, managedCertificate.CertificateRequestId) - } - - /* 4th step is to send renewal request */ - url := c.getURL(urlResourceCertificateRequests) - if c.user == nil || c.user.Company == nil { - return "", fmt.Errorf("must be autheticated to request a certificate") - } - - req := certificateRequest{ - ExistingCertificateId: certificateId, - ApplicationId: applicationId, - TemplateId: templateId, - } + return nil, err + } - if renewReq.CertificateRequest.Location != nil { - workload := renewReq.CertificateRequest.Location.Workload - if workload == "" { - workload = defaultAppName - } - nodeName := renewReq.CertificateRequest.Location.Instance - appName := workload + defer f.Close() + fileBytes, err := io.ReadAll(f) + if err != nil { + return nil, err + } - req.CertificateUsageMetadata = []certificateUsageMetadata{ - { - AppName: appName, - NodeName: nodeName, - }, + certs := strings.Split(strings.TrimSpace(string(fileBytes)), "\n\n") + + for i := 0; i < len(certs); i++ { + if i < len(certs)-1 { + if len(chainArr) == 0 { + chainArr = append(chainArr, certs[i]+"\n") + } else { + if rootFirst { + chainArr = append(chainArr, certs[i]+"\n") + } else { + chainArr = append([]string{certs[i] + "\n"}, chainArr...) + } + } + } else { + cert = certs[i] + "\n" + } + } } } - if renewReq.CertificateRequest != nil && len(renewReq.CertificateRequest.GetCSR()) != 0 { - req.CSR = string(renewReq.CertificateRequest.GetCSR()) - req.ReuseCSR = false - } else { - req.ReuseCSR = true - return "", fmt.Errorf("reuseCSR option is not currently available for Renew Certificate operation. A new CSR must be provided in the request") - } - statusCode, status, body, err := c.request("POST", url, req) - if err != nil { - return - } + collection.Certificate = cert + collection.PrivateKey = privateKey + collection.Chain = chainArr - cr, err := parseCertificateRequestResult(statusCode, status, body) - if err != nil { - return "", fmt.Errorf("failed to renew certificate: %s", err) - } - return cr.CertificateRequests[0].ID, nil + return &collection, nil } -// RetireCertificate attempts to retire the certificate -func (c *Connector) RetireCertificate(retireReq *certificate.RetireRequest) error { - url := c.getURL(urlResourceCertificatesRetirement) - /* 1st step is to get CertificateRequestId which is required to retire certificate */ - var certificateRequestId string - if retireReq.Thumbprint != "" { - // by Thumbprint (aka Fingerprint) - searchResult, err := c.searchCertificatesByFingerprint(retireReq.Thumbprint) +// Waits for the Certificate to be available. Fails when the timeout is exceeded +func (c *Connector) waitForCertificate(url string, request *certificate.Request) (statusCode int, status string, body []byte, err error) { + startTime := time.Now() + for { + statusCode, status, body, err = c.request("GET", url, nil) if err != nil { - return fmt.Errorf("failed to create retire request: %s", err) - } - if len(searchResult.Certificates) == 0 { - return fmt.Errorf("no certificate found using fingerprint %s", retireReq.Thumbprint) + return } - - var reqIds []string - isOnlyOneCertificateRequestId := true - for _, c := range searchResult.Certificates { - reqIds = append(reqIds, c.CertificateRequestId) - if certificateRequestId != "" && certificateRequestId != c.CertificateRequestId { - isOnlyOneCertificateRequestId = false - } - certificateRequestId = c.CertificateRequestId + if statusCode == http.StatusOK { + return } - if !isOnlyOneCertificateRequestId { - return fmt.Errorf("error: more than one CertificateRequestId was found with the same Fingerprint: %s", reqIds) + if request.Timeout == 0 { + err = endpoint.ErrCertificatePending{CertificateID: request.PickupID, Status: status} + return } - } else if retireReq.CertificateDN != "" { - // by CertificateDN (which is the same as CertificateRequestId for current implementation) - certificateRequestId = retireReq.CertificateDN - } else { - return fmt.Errorf("failed to create retire request: CertificateDN or Thumbprint required") - } - - /* 2nd step is to get ManagedCertificateId & ZoneId by looking up certificate request record */ - previousRequest, err := c.getCertificateStatus(certificateRequestId) - if err != nil { - if strings.Contains(err.Error(), "Unable to find certificateRequest") { - return fmt.Errorf("invalid thumbprint or certificate ID. No certificates were retired") + if time.Now().After(startTime.Add(request.Timeout)) { + err = endpoint.ErrRetrieveCertificateTimeout{CertificateID: request.PickupID} + return } - return fmt.Errorf("certificate retirement failed: error on getting Certificate ID: %s", err) - } - certificateId := previousRequest.CertificateIdsList[0] - - /* Now we do retirement*/ - retRequest := certificateRetireRequest{ - CertificateIds: []string{certificateId}, - } - - statusCode, status, response, err := c.request("POST", url, retRequest) - if err != nil { - return err - } - - err = checkCertificateRetireResults(statusCode, status, response) - if err != nil { - return err + time.Sleep(2 * time.Second) } +} - return nil +// WriteLog Custom Logging not currently supported by VaaS +func (c *Connector) WriteLog(_ *endpoint.LogRequest) (err error) { + return fmt.Errorf("outbound logging not supported by endpoint") } func (c *Connector) searchCertificates(req *SearchRequest) (*CertificateSearchResponse, error) { @@ -1529,117 +1271,6 @@ func (c *Connector) getCertificate(certificateId string) (*managedCertificate, e } } -func (c *Connector) ImportCertificate(req *certificate.ImportRequest) (*certificate.ImportResponse, error) { - pBlock, _ := pem.Decode([]byte(req.CertificateData)) - if pBlock == nil { - return nil, fmt.Errorf("%w can`t parse certificate", verror.UserDataError) - } - zone := req.PolicyDN - if zone == "" { - appDetails, _, err := c.getAppDetailsByName(c.zone.getApplicationName()) - if err != nil { - return nil, err - } - zone = appDetails.ApplicationId - } - ipAddr := endpoint.LocalIP - origin := endpoint.SDKName - for _, f := range req.CustomFields { - if f.Type == certificate.CustomFieldOrigin { - origin = f.Value - } - } - base64.StdEncoding.EncodeToString(pBlock.Bytes) - fingerprint := certThumbprint(pBlock.Bytes) - request := importRequest{ - Certificates: []importRequestCertInfo{ - { - Certificate: base64.StdEncoding.EncodeToString(pBlock.Bytes), - ApplicationIds: []string{zone}, - ApiClientInformation: apiClientInformation{ - Type: origin, - Identifier: ipAddr, - }, - }, - }, - } - - url := c.getURL(urlResourceCertificates) - statusCode, status, body, err := c.request("POST", url, request) - if err != nil { - return nil, fmt.Errorf("%w: %v", verror.ServerTemporaryUnavailableError, err) - } - var r importResponse - switch statusCode { - case http.StatusOK, http.StatusCreated, http.StatusAccepted: - case http.StatusBadRequest, http.StatusForbidden, http.StatusConflict: - return nil, fmt.Errorf("%w: certificate can`t be imported. %d %s %s", verror.ServerBadDataResponce, statusCode, status, string(body)) - case http.StatusInternalServerError, http.StatusBadGateway, http.StatusServiceUnavailable: - return nil, verror.ServerTemporaryUnavailableError - default: - return nil, verror.ServerError - } - err = json.Unmarshal(body, &r) - if err != nil { - return nil, fmt.Errorf("%w: can`t unmarshal json response %s", verror.ServerError, err) - } else if !(len(r.CertificateInformations) == 1) { - return nil, fmt.Errorf("%w: certificate was not imported on unknown reason", verror.ServerBadDataResponce) - } - time.Sleep(time.Second) - foundCert, err := c.searchCertificatesByFingerprint(fingerprint) - if err != nil { - return nil, err - } - if len(foundCert.Certificates) != 1 { - return nil, fmt.Errorf("%w certificate has been imported but could not be found on platform after that", verror.ServerError) - } - cert := foundCert.Certificates[0] - resp := &certificate.ImportResponse{CertificateDN: cert.SubjectCN[0], CertId: cert.Id} - return resp, nil -} - -func (c *Connector) SetHTTPClient(client *http.Client) { - c.client = client -} - -func (c *Connector) ListCertificates(filter endpoint.Filter) ([]certificate.CertificateInfo, error) { - if c.zone.String() == "" { - return nil, fmt.Errorf("empty zone") - } - const batchSize = 50 - limit := 100000000 - if filter.Limit != nil { - limit = *filter.Limit - } - var buf [][]certificate.CertificateInfo - for page := 0; limit > 0; limit, page = limit-batchSize, page+1 { - var b []certificate.CertificateInfo - var err error - b, err = c.getCertsBatch(page, batchSize, filter.WithExpired) - if limit < batchSize && len(b) > limit { - b = b[:limit] - } - if err != nil { - return nil, err - } - buf = append(buf, b) - if len(b) < batchSize { - break - } - } - sumLen := 0 - for _, b := range buf { - sumLen += len(b) - } - infos := make([]certificate.CertificateInfo, sumLen) - offset := 0 - for _, b := range buf { - copy(infos[offset:], b[:]) - offset += len(b) - } - return infos, nil -} - func (c *Connector) getCertsBatch(page, pageSize int, withExpired bool) ([]certificate.CertificateInfo, error) { appDetails, _, err := c.getAppDetailsByName(c.zone.getApplicationName()) @@ -1672,17 +1303,14 @@ func (c *Connector) getCertsBatch(page, pageSize int, withExpired bool) ([]certi return nil, err } infos := make([]certificate.CertificateInfo, len(r.Certificates)) - for i, c := range r.Certificates { - infos[i] = c.ToCertificateInfo() + for i, cert := range r.Certificates { + infos[i] = cert.ToCertificateInfo() } return infos, nil } func (c *Connector) getAppDetailsByName(appName string) (*ApplicationDetails, int, error) { url := c.getURL(urlAppDetailsByName) - if c.accessToken == "" && c.user == nil { - return nil, -1, fmt.Errorf("must be autheticated to read the zone configuration") - } encodedAppName := netUrl.PathEscape(appName) url = fmt.Sprintf(url, encodedAppName) statusCode, status, body, err := c.request("GET", url, nil) @@ -1696,22 +1324,6 @@ func (c *Connector) getAppDetailsByName(appName string) (*ApplicationDetails, in return details, statusCode, nil } -// GetZonesByParent returns a list of valid zones for a VaaS application specified by parent -func (c *Connector) GetZonesByParent(parent string) ([]string, error) { - var zones []string - - appDetails, _, err := c.getAppDetailsByName(parent) - if err != nil { - return nil, err - } - - for citAlias := range appDetails.CitAliasToIdMap { - zone := fmt.Sprintf("%s\\%s", parent, citAlias) - zones = append(zones, zone) - } - return zones, nil -} - func (c *Connector) getTemplateByID() (*certificateTemplate, error) { url := c.getURL(urlResourceTemplate) appNameEncoded := netUrl.PathEscape(c.zone.getApplicationName()) @@ -1789,7 +1401,7 @@ func (c *Connector) CreateUserAccount(userAccount *userAccount) (int, *userDetai return statusCode, ud, nil } -func getUserDetails(c *Connector) (*userDetails, error) { +func (c *Connector) getUserDetails() (*userDetails, error) { url := c.getURL(urlResourceUserAccounts) statusCode, status, body, err := c.request("GET", url, nil) diff --git a/pkg/venafi/cloud/connectorPolicy.go b/pkg/venafi/cloud/connectorPolicy.go new file mode 100644 index 00000000..c4758fa5 --- /dev/null +++ b/pkg/venafi/cloud/connectorPolicy.go @@ -0,0 +1,419 @@ +package cloud + +import ( + "fmt" + "log" + "net/http" + + "github.com/Venafi/vcert/v5/pkg/policy" +) + +func (c *Connector) GetPolicy(name string) (*policy.PolicySpecification, error) { + if !c.isAuthenticated() { + return nil, fmt.Errorf("must be autheticated to request a certificate") + } + + cit, err := retrievePolicySpecification(c, name) + if err != nil { + return nil, err + } + + info, err := getCertificateAuthorityInfoFromCloud(cit.CertificateAuthority, cit.CertificateAuthorityAccountId, cit.CertificateAuthorityProductOptionId, c) + + if err != nil { + return nil, err + } + + log.Println("Building policy") + ps := buildPolicySpecification(cit, info, true) + + // getting the users to set to the PolicySpecification + policyUsers, err := c.getUsers() + if err != nil { + return nil, err + } + ps.Users = policyUsers + + return ps, nil +} + +func (c *Connector) SetPolicy(name string, ps *policy.PolicySpecification) (string, error) { + if !c.isAuthenticated() { + return "", fmt.Errorf("must be autheticated to request a certificate") + } + + err := policy.ValidateCloudPolicySpecification(ps) + if err != nil { + return "", err + } + + log.Printf("policy specification is valid") + + var status string + + //validate if zone name is set and if zone already exist on Venafi cloud if not create it. + citName := policy.GetCitName(name) + + if citName == "" { + return "", fmt.Errorf("cit name is empty, please provide zone in the format: app_name\\cit_name") + } + + //get certificate authority product option io + var caDetails *policy.CADetails + + if ps.Policy != nil && ps.Policy.CertificateAuthority != nil && *(ps.Policy.CertificateAuthority) != "" { + caDetails, err = getCertificateAuthorityDetails(*(ps.Policy.CertificateAuthority), c) + + if err != nil { + return "", err + } + + } else { + if ps.Policy != nil { + + defaultCA := policy.DefaultCA + ps.Policy.CertificateAuthority = &defaultCA + + caDetails, err = getCertificateAuthorityDetails(*(ps.Policy.CertificateAuthority), c) + if err != nil { + return "", err + } + + } else { + //policy is not specified so we get the default CA + caDetails, err = getCertificateAuthorityDetails(policy.DefaultCA, c) + if err != nil { + return "", err + } + } + } + + //at this moment we know that ps.Policy.CertificateAuthority is valid. + + req, err := policy.BuildCloudCitRequest(ps, caDetails) + if err != nil { + return "", err + } + req.Name = citName + + url := c.getURL(urlIssuingTemplate) + + cit, err := getCit(c, citName) + + if err != nil { + return "", err + } + + if cit != nil { + log.Printf("updating issuing template: %s", citName) + //update cit using the new values + url = fmt.Sprint(url, "/", cit.ID) + statusCode, status, body, err := c.request("PUT", url, req) + + if err != nil { + return "", err + } + + cit, err = parseCitResult(http.StatusOK, statusCode, status, body) + + if err != nil { + return status, err + } + + } else { + log.Printf("creating issuing template: %s", citName) + //var body []byte + statusCode, status, body, err := c.request("POST", url, req) + + if err != nil { + return "", err + } + + cit, err = parseCitResult(http.StatusCreated, statusCode, status, body) + + if err != nil { + return status, err + } + + } + + //validate if appName is set and if app already exist on Venafi cloud if not create it + //and as final steps link the app with the cit. + appName := policy.GetApplicationName(name) + + if appName == "" { + return "", fmt.Errorf("application name is empty, please provide zone in the format: app_name\\cit_name") + } + + appDetails, statusCode, err := c.getAppDetailsByName(appName) + + if err != nil && statusCode == 404 { //means application was not found. + log.Printf("creating application: %s", appName) + + _, err = c.createApplication(appName, ps, cit) + if err != nil { + return "", err + } + + } else { //determine if the application needs to be updated + log.Printf("updating application: %s", appName) + err = c.updateApplication(name, ps, cit, appDetails) + if err != nil { + return "", err + } + } + + log.Printf("policy successfully applied to %s", name) + + return status, nil +} + +func (c *Connector) GetPolicyWithRegex(name string) (*policy.PolicySpecification, error) { + + cit, err := retrievePolicySpecification(c, name) + + if err != nil { + return nil, err + } + + info, err := getCertificateAuthorityInfoFromCloud(cit.CertificateAuthority, cit.CertificateAuthorityAccountId, cit.CertificateAuthorityProductOptionId, c) + + if err != nil { + return nil, err + } + + log.Println("Building policy") + ps := buildPolicySpecification(cit, info, false) + + return ps, nil +} + +func retrievePolicySpecification(c *Connector, name string) (*certificateTemplate, error) { + appName := policy.GetApplicationName(name) + if appName != "" { + c.zone.appName = appName + } else { + return nil, fmt.Errorf("application name is not valid, please provide a valid zone name in the format: appName\\CitName") + } + citName := policy.GetCitName(name) + if citName != "" { + c.zone.templateAlias = citName + } else { + return nil, fmt.Errorf("cit name is not valid, please provide a valid zone name in the format: appName\\CitName") + } + + log.Println("Getting CIT") + cit, err := c.getTemplateByID() + + if err != nil { + return nil, err + } + + return cit, nil + +} + +func (c *Connector) getUsers() ([]string, error) { + var usersList []string + appDetails, _, err := c.getAppDetailsByName(c.zone.getApplicationName()) + if err != nil { + return nil, err + } + var teamsList *teams + for _, owner := range appDetails.OwnerIdType { + if owner.OwnerType == UserType.String() { + retrievedUser, userErr := c.retrieveUser(owner.OwnerId) + if userErr != nil { + return nil, userErr + } + usersList = append(usersList, retrievedUser.Username) + } else if owner.OwnerType == TeamType.String() { + if teamsList == nil { + teamsList, err = c.retrieveTeams() + if err != nil { + return nil, err + } + } + if teamsList != nil { + for _, t := range teamsList.Teams { + if t.ID == owner.OwnerId { + usersList = append(usersList, t.Name) + break + } + } + } + } + + } + return usersList, nil +} + +func PolicyExist(policyName string, c *Connector) (bool, error) { + c.zone.appName = policy.GetApplicationName(policyName) + citName := policy.GetCitName(policyName) + if citName != "" { + c.zone.templateAlias = citName + } else { + return false, fmt.Errorf("cit name is not valid, please provide a valid zone name in the format: appName\\CitName") + } + + _, err := c.getTemplateByID() + return err == nil, nil +} + +func (c *Connector) createApplication(appName string, ps *policy.PolicySpecification, cit *certificateTemplate) (*policy.Application, error) { + appIssuingTemplate := make(map[string]string) + appIssuingTemplate[cit.Name] = cit.ID + + var owners []policy.OwnerIdType + var err error + var statusCode int + var status string + + //if users are passed to the PS, resolve the related Owners to set them + if len(ps.Users) > 0 { + owners, err = c.resolveOwners(ps.Users) + } else { //if users are not specified in PS, then the current User should be used as owner + var owner *policy.OwnerIdType + owner, err = c.getOwnerFromUserDetails() + if owner != nil { + owners = []policy.OwnerIdType{*owner} + } + } + + if err != nil { + return nil, fmt.Errorf("an error happened trying to resolve the owners: %w", err) + } + + //create application + appReq := policy.Application{ + OwnerIdsAndTypes: owners, + Name: appName, + CertificateIssuingTemplateAliasIdMap: appIssuingTemplate, + } + + url := c.getURL(urlAppRoot) + + statusCode, status, _, err = c.request("POST", url, appReq) + if err != nil { + return nil, err + } + if statusCode != 201 { + return nil, fmt.Errorf("unexpected result %s attempting to create application %s", status, appName) + } + + return &appReq, nil +} + +func (c *Connector) updateApplication(name string, ps *policy.PolicySpecification, cit *certificateTemplate, appDetails *ApplicationDetails) error { + + //creating the app to use as request + appReq := createAppUpdateRequest(appDetails) + + //determining if the relationship between application and cit exist + citAddedToApp := false + exist, err := PolicyExist(name, c) + if err != nil { + return err + } + if !exist { + c.addCitToApp(&appReq, cit) + citAddedToApp = true + } + + //determining if the owners where provided and should be updated + ownersUpdated := false + //given that the application exists, the only way to update the owners at the application + //is that users in the policy specification were provided + if len(ps.Users) > 0 { + //resolving and setting owners + owners, err := c.resolveOwners(ps.Users) + if err != nil { + return fmt.Errorf("an error happened trying to resolve the owners: %w", err) + } + appReq.OwnerIdsAndTypes = owners + ownersUpdated = true + } + + //if the cit was added to the app or the owners were updated, then is required + //to update the application + if citAddedToApp || ownersUpdated { + url := c.getURL(urlAppRoot) + url = fmt.Sprint(url, "/", appDetails.ApplicationId) + _, _, _, err = c.request("PUT", url, appReq) + if err != nil { + return err + } + } + + return nil +} + +func (c *Connector) addCitToApp(app *policy.Application, cit *certificateTemplate) { + //add cit to the map. + value, ok := app.CertificateIssuingTemplateAliasIdMap[cit.Name] + if !ok || value != cit.ID { + app.CertificateIssuingTemplateAliasIdMap[cit.Name] = cit.ID + } +} + +func (c *Connector) resolveOwners(usersList []string) ([]policy.OwnerIdType, error) { + + var owners []policy.OwnerIdType + var teams *teams + var err error + + for _, userName := range usersList { + //The error should be ignored in order to confirm if the userName is not a TeamName + users, _ := c.retrieveUsers(userName) + + if users != nil { + owners = appendOwner(owners, users.Users[0].ID, UserType) + } else { + if teams == nil { + teams, err = c.retrieveTeams() + } + if err != nil { + return nil, err + } + if teams != nil { + var found = false + for _, team := range teams.Teams { + if team.Name == userName { + owners = appendOwner(owners, team.ID, TeamType) + found = true + break + } + } + if !found { + return nil, fmt.Errorf("it was not possible to find the user %s", userName) + } + } + } + } + + return owners, err +} + +func appendOwner(owners []policy.OwnerIdType, ownerId string, ownerType OwnerType) []policy.OwnerIdType { + owner := createOwner(ownerId, ownerType) + return append(owners, *owner) +} + +func (c *Connector) getOwnerFromUserDetails() (*policy.OwnerIdType, error) { + userDetails, err := c.getUserDetails() + if err != nil { + return nil, err + } + owner := createOwner(userDetails.User.ID, UserType) + return owner, nil +} + +func createOwner(ownerId string, ownerType OwnerType) *policy.OwnerIdType { + ownerIdType := policy.OwnerIdType{ + OwnerId: ownerId, + OwnerType: ownerType.String(), + } + + return &ownerIdType +} diff --git a/pkg/venafi/cloud/connectorSSH.go b/pkg/venafi/cloud/connectorSSH.go new file mode 100644 index 00000000..77b56dcf --- /dev/null +++ b/pkg/venafi/cloud/connectorSSH.go @@ -0,0 +1,19 @@ +package cloud + +import "github.com/Venafi/vcert/v5/pkg/certificate" + +func (c *Connector) RetrieveSshConfig(_ *certificate.SshCaTemplateRequest) (*certificate.SshConfig, error) { + panic("operation is not supported yet") +} + +func (c *Connector) RetrieveSSHCertificate(_ *certificate.SshCertRequest) (response *certificate.SshCertificateObject, err error) { + panic("operation is not supported yet") +} + +func (c *Connector) RequestSSHCertificate(_ *certificate.SshCertRequest) (response *certificate.SshCertificateObject, err error) { + panic("operation is not supported yet") +} + +func (c *Connector) RetrieveAvailableSSHTemplates() (response []certificate.SshAvaliableTemplate, err error) { + panic("operation is not supported yet") +} diff --git a/pkg/venafi/cloud/connector_test.go b/pkg/venafi/cloud/connector_test.go index d8e1a062..59f00c60 100644 --- a/pkg/venafi/cloud/connector_test.go +++ b/pkg/venafi/cloud/connector_test.go @@ -1176,7 +1176,7 @@ func TestSetPolicy(t *testing.T) { } //validate each attribute - userDetails, err := getUserDetails(conn) + userDetails, err := conn.getUserDetails() //validating the default users attribute was created users := []string{ //"jenkins@opensource.qa.venafi.io", diff --git a/pkg/venafi/cloud/search.go b/pkg/venafi/cloud/search.go index 17d71d44..44754bcd 100644 --- a/pkg/venafi/cloud/search.go +++ b/pkg/venafi/cloud/search.go @@ -181,7 +181,7 @@ func formatSearchCertificateArguments(cn string, sans *certificate.Sans, certMin { Field: "validityPeriodDays", Operator: GTE, - Value: certMinTimeDays, + Value: &certMinTimeDays, }, }, }, @@ -200,7 +200,7 @@ func formatSearchCertificateArguments(cn string, sans *certificate.Sans, certMin addOperand(req, Operand{ Field: "subjectCN", Operator: EQ, - Value: cn, + Value: &cn, }) } From 5d079872311376ad08764ac076f58366e9f7f79f Mon Sep 17 00:00:00 2001 From: Russel Vela Date: Thu, 21 Mar 2024 14:30:27 -0600 Subject: [PATCH 2/2] feat(svc-account-auth): Updates plugin to go 1.21 Closes VC-31642 --- go.mod | 6 +++--- go.sum | 4 ++++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 3d1b2a64..dc64e96a 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,9 @@ module github.com/Venafi/vcert/v5 +go 1.21 + require ( + github.com/go-http-utils/headers v0.0.0-20181008091004-fed159eddc2a github.com/google/uuid v1.3.0 github.com/howeyc/gopass v0.0.0-20170109162249-bf9dde6d0d2c github.com/pavel-v-chernykh/keystore-go/v4 v4.1.0 @@ -24,7 +27,6 @@ require ( github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/fsnotify/fsnotify v1.4.9 // indirect - github.com/go-http-utils/headers v0.0.0-20181008091004-fed159eddc2a // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/magiconair/properties v1.8.1 // indirect @@ -47,5 +49,3 @@ require ( google.golang.org/appengine v1.6.7 // indirect google.golang.org/protobuf v1.31.0 // indirect ) - -go 1.20 diff --git a/go.sum b/go.sum index 2941b69a..2f32de13 100644 --- a/go.sum +++ b/go.sum @@ -13,6 +13,7 @@ cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiy dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= +github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= @@ -21,6 +22,7 @@ github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hC github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= +github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= @@ -70,6 +72,7 @@ github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5a github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= @@ -213,6 +216,7 @@ go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI= +go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=