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 }