github.com/Venafi/vcert/v5@v5.10.2/pkg/venafi/cloud/cloudproviders.go (about)

     1  package cloud
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"fmt"
     7  	"log"
     8  	"net/http"
     9  	"time"
    10  
    11  	"github.com/Khan/genqlient/graphql"
    12  	"github.com/google/uuid"
    13  
    14  	"github.com/Venafi/vcert/v5/pkg/domain"
    15  	"github.com/Venafi/vcert/v5/pkg/httputils"
    16  	"github.com/Venafi/vcert/v5/pkg/util"
    17  	"github.com/Venafi/vcert/v5/pkg/webclient/cloudproviders"
    18  )
    19  
    20  type CloudKeystoreProvisioningResult struct {
    21  	CloudProviderCertificateID string `json:"cloudProviderCertificateId"`
    22  	CloudCertificateName       string `json:"cloudProviderCertificateName"`
    23  	CloudCertificateVersion    string `json:"cloudProviderCertificateVersion"`
    24  	MachineIdentityActionType  string `json:"machineIdentityActionType"`
    25  	MachineIdentityId          string `json:"machineIdentityId"`
    26  	Error                      error  `json:"error"`
    27  }
    28  
    29  func (c *Connector) ProvisionCertificate(req *domain.ProvisioningRequest, options *domain.ProvisioningOptions) (*domain.ProvisioningMetadata, error) {
    30  	log.Printf("Starting Provisioning Flow")
    31  
    32  	if req == nil {
    33  		return nil, fmt.Errorf("missing Provisioning Request")
    34  	}
    35  
    36  	reqData := *req
    37  
    38  	if reqData.Timeout <= 0 {
    39  		reqData.Timeout = util.DefaultTimeout * time.Second
    40  	}
    41  
    42  	if reqData.CertificateID == nil {
    43  		if reqData.PickupID == nil {
    44  			return nil, fmt.Errorf("no Certificate ID or Pickup ID were provided for provisioning")
    45  		}
    46  		log.Printf("Certificate ID was not provided in request. Fetching it by using Pickup ID %s", *(reqData.PickupID))
    47  		certID, err := c.getCertIDFromPickupID(*(reqData.PickupID), reqData.Timeout)
    48  		if err != nil {
    49  			return nil, err
    50  		}
    51  		reqData.CertificateID = certID
    52  	}
    53  	certificateIDString := *(reqData.CertificateID)
    54  	log.Printf("Certificate ID for provisioning: %s", certificateIDString)
    55  
    56  	// Is certificate valid for provisioning?
    57  	log.Printf("Validating if certificate is valid")
    58  	err := c.validateCertificate(*(reqData.CertificateID))
    59  	if err != nil {
    60  		return nil, err
    61  	}
    62  	log.Printf("Good certificate for provisioning!")
    63  
    64  	cloudKeystore := reqData.Keystore
    65  
    66  	if cloudKeystore == nil {
    67  		if reqData.KeystoreID == nil {
    68  			if reqData.ProviderName == nil || reqData.KeystoreName == nil {
    69  				return nil, fmt.Errorf("any of keystore object, keystore ID or both Provider Name and Keystore Name must be provided for provisioning")
    70  			}
    71  		}
    72  
    73  		// Getting Keystore to find type
    74  		keystoreIDInput := util.StringPointerToString(reqData.KeystoreID)
    75  		keystoreNameInput := util.StringPointerToString(reqData.KeystoreName)
    76  		providerNameInput := util.StringPointerToString(reqData.ProviderName)
    77  
    78  		log.Printf("fetching keystore with KeystoreID: %s, KeystoreName: %s, ProviderName: %s", keystoreIDInput, keystoreNameInput, providerNameInput)
    79  		cloudKeystore, err = c.GetCloudKeystore(domain.GetCloudKeystoreRequest{
    80  			CloudProviderName: req.ProviderName,
    81  			CloudKeystoreID:   req.KeystoreID,
    82  			CloudKeystoreName: req.KeystoreName,
    83  		})
    84  		if err != nil {
    85  			return nil, err
    86  		}
    87  
    88  		log.Printf("successfully fetched keystore information")
    89  	}
    90  	log.Printf("Keystore ID for provisioning: %s", cloudKeystore.ID)
    91  
    92  	// setting options for provisioning
    93  	var provisioningOptions *cloudproviders.CertificateProvisioningOptionsInput
    94  	if options != nil {
    95  		log.Println("setting provisioning options")
    96  		provisioningOptions, err = setProvisioningOptions(*options, cloudKeystore.Type)
    97  		if err != nil {
    98  			return nil, err
    99  		}
   100  		log.Println("provisioning options successfully set")
   101  	}
   102  
   103  	wsClientID := uuid.New().String()
   104  	wsConn, err := c.notificationSvcClient.Subscribe(wsClientID)
   105  	if err != nil {
   106  		return nil, err
   107  	}
   108  
   109  	log.Printf("Provisioning Certificate ID %s for Keystore %s", certificateIDString, cloudKeystore.ID)
   110  	_, err = c.cloudProvidersClient.ProvisionCertificate(context.Background(), certificateIDString, cloudKeystore.ID, wsClientID, provisioningOptions)
   111  	if err != nil {
   112  		return nil, err
   113  	}
   114  
   115  	workflowResponse, err := c.notificationSvcClient.ReadResponse(wsConn)
   116  	if err != nil {
   117  		return nil, err
   118  	}
   119  
   120  	// parsing metadata from websocket response
   121  	log.Printf("Getting Cloud Metadata of Certificate ID %s and Keystore ID: %s", certificateIDString, cloudKeystore.ID)
   122  	cloudMetadata, err := getCloudMetadataFromWebsocketResponse(workflowResponse.Data.Result, cloudKeystore.Type)
   123  	if err != nil {
   124  		return nil, err
   125  	}
   126  	log.Printf("Successfully got Cloud Metadata for Certificate ID %s and Keystore ID: %s", certificateIDString, cloudKeystore.ID)
   127  
   128  	log.Printf("Successfully finished Provisioning Flow for Certificate ID %s and Keystore ID %s", certificateIDString, cloudKeystore.ID)
   129  	return cloudMetadata, nil
   130  }
   131  
   132  func (c *Connector) ProvisionCertificateToMachineIdentity(req domain.ProvisioningRequest) (*domain.ProvisioningMetadata, error) {
   133  	log.Printf("Starting Provisioning to Machine Identity Flow")
   134  
   135  	if req.MachineIdentityID == nil {
   136  		return nil, fmt.Errorf("error trying to provision certificate to machine identity: machineIdentityID is nil")
   137  	}
   138  
   139  	machineIdentityID := *req.MachineIdentityID
   140  	certificateID := ""
   141  	timeout := util.DefaultTimeout * time.Second
   142  	if req.Timeout != 0 {
   143  		timeout = req.Timeout
   144  	}
   145  
   146  	if req.CertificateID == nil {
   147  		if req.PickupID == nil {
   148  			return nil, fmt.Errorf("no Certificate ID or Pickup ID were provided for provisioning")
   149  		}
   150  
   151  		log.Printf("Certificate ID was not provided in request. Using Pickup ID %s to fetch it", *req.PickupID)
   152  		certID, err := c.getCertIDFromPickupID(*req.PickupID, timeout)
   153  		if err != nil {
   154  			return nil, err
   155  		}
   156  		certificateID = *certID
   157  	} else {
   158  		certificateID = *req.CertificateID
   159  	}
   160  
   161  	log.Printf("certificate ID for provisioning: %s", certificateID)
   162  
   163  	// Is certificate generated by VCP?
   164  	log.Printf("validating if certificate is generated by VCP")
   165  	err := c.validateCertificate(certificateID)
   166  	if err != nil {
   167  		return nil, err
   168  	}
   169  	log.Println("Certificate is VCP generated")
   170  
   171  	ctx := context.Background()
   172  	wsClientID := uuid.New().String()
   173  
   174  	wsConn, err := c.notificationSvcClient.Subscribe(wsClientID)
   175  	if err != nil {
   176  		return nil, err
   177  	}
   178  
   179  	log.Printf("Provisioning Certificate with ID %s to Machine Identity with ID %s", certificateID, machineIdentityID)
   180  	_, err = c.cloudProvidersClient.ProvisionCertificateToMachineIdentity(ctx, &certificateID, machineIdentityID, wsClientID)
   181  	if err != nil {
   182  		return nil, err
   183  	}
   184  
   185  	ar, err := c.notificationSvcClient.ReadResponse(wsConn)
   186  	if err != nil {
   187  		return nil, err
   188  	}
   189  
   190  	var keystoreType domain.CloudKeystoreType
   191  	if req.Keystore == nil {
   192  		log.Printf("fetching machine identity to get type")
   193  		machineIdentity, err := c.cloudProvidersClient.GetMachineIdentity(ctx, domain.GetCloudMachineIdentityRequest{
   194  			MachineIdentityID: req.MachineIdentityID,
   195  		})
   196  		if err != nil {
   197  			return nil, fmt.Errorf("failed to get machine identity: %w", err)
   198  		}
   199  		log.Printf("successfully fetched machine identity")
   200  		keystoreType = machineIdentity.Metadata.GetKeystoreType()
   201  	} else {
   202  		keystoreType = req.Keystore.Type
   203  	}
   204  
   205  	// parsing metadata from websocket response
   206  	log.Printf("Getting Cloud Metadata of Machine Identity with ID: %s", machineIdentityID)
   207  	cloudMetadata, err := getCloudMetadataFromWebsocketResponse(ar.Data.Result, keystoreType)
   208  	if err != nil {
   209  		return nil, err
   210  	}
   211  	log.Printf("Successfully retrieved Cloud Metadata for Machine Identity with ID: %s", machineIdentityID)
   212  
   213  	log.Printf("Successfully completed Provisioning Flow for Certificate ID %s and Machine Identity ID %s", certificateID, machineIdentityID)
   214  	return cloudMetadata, nil
   215  }
   216  
   217  func (c *Connector) GetCloudProvider(request domain.GetCloudProviderRequest) (*domain.CloudProvider, error) {
   218  	cloudProvider, err := c.cloudProvidersClient.GetCloudProvider(context.Background(), request)
   219  	if err != nil {
   220  		return nil, fmt.Errorf("failed to retrieve Cloud Provider with name %s: %w", request.Name, err)
   221  	}
   222  	if cloudProvider == nil {
   223  		return nil, fmt.Errorf("could not find Cloud Provider with name %s", request.Name)
   224  	}
   225  	return cloudProvider, nil
   226  }
   227  
   228  func (c *Connector) GetCloudKeystore(request domain.GetCloudKeystoreRequest) (*domain.CloudKeystore, error) {
   229  	cloudKeystore, err := c.cloudProvidersClient.GetCloudKeystore(context.Background(), request)
   230  	if err != nil {
   231  		return nil, fmt.Errorf("failed to retrieve Cloud Keystore: %w", err)
   232  	}
   233  	if cloudKeystore == nil {
   234  		msg := util.GetKeystoreOptionsString(request.CloudProviderID, request.CloudKeystoreID, request.CloudProviderName, request.CloudKeystoreName)
   235  		return nil, fmt.Errorf("could not find Cloud Keystore with %s: %w", msg, err)
   236  	}
   237  	return cloudKeystore, nil
   238  }
   239  
   240  func (c *Connector) GetMachineIdentity(request domain.GetCloudMachineIdentityRequest) (*domain.CloudMachineIdentity, error) {
   241  	if request.MachineIdentityID == nil {
   242  		return nil, fmt.Errorf("machine identity ID cannot be empty")
   243  	}
   244  
   245  	machineIdentity, err := c.cloudProvidersClient.GetMachineIdentity(context.Background(), request)
   246  	if err != nil {
   247  		return nil, fmt.Errorf("failed to retrieve Cloud Machine Identity with ID %s: %w", *request.MachineIdentityID, err)
   248  	}
   249  	if machineIdentity == nil {
   250  		return nil, fmt.Errorf("could not find Cloud Machine Identity with ID %s", *request.MachineIdentityID)
   251  	}
   252  	return machineIdentity, nil
   253  }
   254  
   255  func (c *Connector) DeleteMachineIdentity(machineIdentityID string) (bool, error) {
   256  	if machineIdentityID == "" {
   257  		return false, fmt.Errorf("machine identity ID cannot be nil")
   258  	}
   259  	deleted, err := c.cloudProvidersClient.DeleteMachineIdentity(context.Background(), machineIdentityID)
   260  	if err != nil {
   261  		return false, fmt.Errorf("failed to delete machine identity with ID %s: %w", machineIdentityID, err)
   262  	}
   263  	return deleted, nil
   264  }
   265  
   266  func setProvisioningOptions(options domain.ProvisioningOptions, keystoreType domain.CloudKeystoreType) (*cloudproviders.CertificateProvisioningOptionsInput, error) {
   267  	awsOptions := &cloudproviders.CertificateProvisioningAWSOptionsInput{}
   268  	azureOptions := &cloudproviders.CertificateProvisioningAzureOptionsInput{}
   269  	gcpOptions := &cloudproviders.CertificateProvisioningGCPOptionsInput{}
   270  
   271  	switch keystoreType {
   272  	case domain.CloudKeystoreTypeACM:
   273  		awsOptions.Arn = &options.ARN
   274  	case domain.CloudKeystoreTypeAKV:
   275  		azureOptions.Name = &options.CloudCertificateName
   276  	case domain.CloudKeystoreTypeGCM:
   277  		if options.CloudCertificateName != "" {
   278  			gcpOptions.Id = &options.CloudCertificateName
   279  		}
   280  
   281  		//determining if it was provided a valid scope
   282  		if options.GCMCertificateScope == domain.GCMCertificateScopeInvalid {
   283  			return nil, fmt.Errorf("invalid GCM certificate scope")
   284  		}
   285  		//at this point, it's validated that the scope is not invalid, but it's possible
   286  		//that the scope was not provided so in that case should be UNKNOWN.
   287  		// The following method GetGCMCertificateScope returns nil if that was not a one
   288  		//of the accepted valid by GCM Cert Scope, so in that case the scope is not set.
   289  		gcmCertScope := GetGCMCertificateScope(options.GCMCertificateScope)
   290  		if gcmCertScope != nil {
   291  			gcpOptions.Scope = gcmCertScope
   292  		}
   293  	default:
   294  		return nil, fmt.Errorf("unknown cloud keystore type: %s", keystoreType)
   295  	}
   296  
   297  	provisioningOptions := &cloudproviders.CertificateProvisioningOptionsInput{
   298  		AwsOptions:   awsOptions,
   299  		AzureOptions: azureOptions,
   300  		GcpOptions:   gcpOptions,
   301  	}
   302  	return provisioningOptions, nil
   303  }
   304  
   305  func (c *Connector) validateCertificate(certificateId string) error {
   306  	cert, err := c.getCertificates(certificateId)
   307  	if err != nil {
   308  		return fmt.Errorf("error trying to get certificate details for cert with ID: %s, error: %s", certificateId, err.Error())
   309  	}
   310  
   311  	// Is certificate not expired?
   312  	log.Printf("Validating if certificate is not expired")
   313  	now := time.Now()
   314  	if now.Unix() > cert.ValidityEnd.Unix() {
   315  		return fmt.Errorf("error trying to provisioning certificate with ID: %s. Provided certificate is expired", certificateId)
   316  	}
   317  	log.Printf("Certificate is still valid")
   318  
   319  	// Is certificate generated by VCP?
   320  	log.Printf("Validating if certificate is generated by VCP")
   321  	if cert.DekHash == "" {
   322  		return fmt.Errorf("error trying to provisioning certificate with ID: %s. Provided certificate is not VCP generated", certificateId)
   323  	}
   324  	log.Println("Certificate is valid for provisioning (VCP generated)")
   325  	return nil
   326  }
   327  
   328  func (c *Connector) getGraphqlClient() graphql.Client {
   329  	graphqlURL := c.getURL(urlGraphql)
   330  
   331  	// We provide every type of auth here.
   332  	// The logic to decide which auth is inside struct's function: RoundTrip
   333  	httpclient := &http.Client{
   334  		Transport: &httputils.AuthedTransportApi{
   335  			ApiKey:      c.apiKey,
   336  			AccessToken: c.accessToken,
   337  			Wrapped:     http.DefaultTransport,
   338  		},
   339  		Timeout: 30 * time.Second,
   340  	}
   341  
   342  	client := graphql.NewClient(graphqlURL, httpclient)
   343  	return client
   344  }
   345  
   346  func (c *Connector) getGraphqlHTTPClient() *http.Client {
   347  	// We provide every type of auth here.
   348  	// The logic to decide which auth to use is inside struct's function: RoundTrip
   349  	httpclient := &http.Client{
   350  		Transport: &httputils.AuthedTransportApi{
   351  			ApiKey:      c.apiKey,
   352  			AccessToken: c.accessToken,
   353  			Wrapped:     http.DefaultTransport,
   354  			UserAgent:   util.DefaultUserAgent,
   355  		},
   356  		Timeout: 30 * time.Second,
   357  	}
   358  	return httpclient
   359  }
   360  
   361  func getCloudMetadataFromWebsocketResponse(resultMap interface{}, keystoreType domain.CloudKeystoreType) (*domain.ProvisioningMetadata, error) {
   362  
   363  	result := CloudKeystoreProvisioningResult{}
   364  	resultBytes, err := json.Marshal(resultMap)
   365  	if err != nil {
   366  		return nil, fmt.Errorf("unable to encode response data: %w", err)
   367  	}
   368  	err = json.Unmarshal(resultBytes, &result)
   369  	if err != nil {
   370  		return nil, fmt.Errorf("unable to parse response data: %w", err)
   371  	}
   372  	if result.Error != nil {
   373  		return nil, fmt.Errorf("unable to provision certificate: %w", result.Error)
   374  	}
   375  	if result.CloudProviderCertificateID == "" {
   376  		return nil, fmt.Errorf("provisioning failed, certificate ID from response is empty")
   377  	}
   378  
   379  	cloudMetadata := &domain.ProvisioningMetadata{
   380  		CloudKeystoreType:         keystoreType,
   381  		CertificateID:             result.CloudProviderCertificateID,
   382  		CertificateName:           result.CloudCertificateName,
   383  		CertificateVersion:        result.CloudCertificateVersion,
   384  		MachineIdentityID:         result.MachineIdentityId,
   385  		MachineIdentityActionType: result.MachineIdentityActionType,
   386  	}
   387  
   388  	return cloudMetadata, err
   389  }
   390  
   391  func GetGCMCertificateScope(scope domain.GCMCertificateScope) *cloudproviders.GCMCertificateScope {
   392  
   393  	switch scope {
   394  	case domain.GCMCertificateScopeDefault:
   395  		gcmCertificateScope := cloudproviders.GCMCertificateScopeDefault
   396  		return &gcmCertificateScope
   397  	case domain.GCMCertificateScopeEdgeCache:
   398  		gcmCertificateScope := cloudproviders.GCMCertificateScopeEdgeCache
   399  		return &gcmCertificateScope
   400  	case domain.GCMCertificateScopeAllRegions:
   401  		gcmCertificateScope := cloudproviders.GCMCertificateScopeAllRegions
   402  		return &gcmCertificateScope
   403  	}
   404  	return nil
   405  }