Skip to content

Commit

Permalink
Merge pull request #486 from Venafi/provision-to-machine-identity
Browse files Browse the repository at this point in the history
feat(cloud-provisioning): Adds function to provision certificate to existing machine identity
  • Loading branch information
rvelaVenafi authored May 24, 2024
2 parents 16f745f + 60cf687 commit 78ecbb6
Show file tree
Hide file tree
Showing 10 changed files with 469 additions and 382 deletions.
18 changes: 7 additions & 11 deletions cmd/vcert/cmdCloudKeystores.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,17 +75,13 @@ func doCommandProvisionCloudKeystore(c *cli.Context) error {
return err
}

result := ProvisioningResult{}
switch cloudKeystore.Type {
case cloud.KeystoreTypeACM:
result.ARN = metadata.GetAWSCertificateMetadata().GetARN()
case cloud.KeystoreTypeAKV:
result.AzureID = metadata.GetAzureCertificateMetadata().GetID()
result.AzureName = metadata.GetAzureCertificateMetadata().GetName()
result.AzureVersion = metadata.GetAzureCertificateMetadata().GetVersion()
case cloud.KeystoreTypeGCM:
result.GcpID = metadata.GetGCPCertificateMetadata().GetID()
result.GcpName = metadata.GetGCPCertificateMetadata().GetName()
result := ProvisioningResult{
ARN: metadata.GetAWSCertificateMetadata().GetARN(),
AzureID: metadata.GetAzureCertificateMetadata().GetID(),
AzureName: metadata.GetAzureCertificateMetadata().GetName(),
AzureVersion: metadata.GetAzureCertificateMetadata().GetVersion(),
GcpID: metadata.GetGCPCertificateMetadata().GetID(),
GcpName: metadata.GetGCPCertificateMetadata().GetName(),
}

result.MachineIdentityId = metadata.GetMachineIdentityMetadata().GetID()
Expand Down
24 changes: 24 additions & 0 deletions pkg/domain/workflow.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package domain

type WorkFlowResponseData struct {
Result interface{} `json:"result"`
WorkflowID string `json:"workflowId"`
WorkflowName string `json:"workflowName"`
WsClientID string `json:"wsClientId"`
}

type WorkflowResponse struct {
SpecVersion string `json:"specversion"`
Id string `json:"id"`
Source string `json:"source"`
Type string `json:"type"`
Subject string `json:"subject"`
DataContentType string `json:"datacontenttype"`
Time string `json:"time"`
Data WorkFlowResponseData `json:"data"`
EventKind string `json:"eventkind"`
EventResource string `json:"eventresource"`
Recipient string `json:"recipient"`
CorrelationID string `json:"correlationid"`
Stream string `json:"stream"`
}
15 changes: 8 additions & 7 deletions pkg/endpoint/provisioning.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,14 @@ import (
)

type ProvisioningRequest struct {
CertificateID *string
PickupID *string
KeystoreID *string
KeystoreName *string
ProviderName *string
Timeout time.Duration
Keystore *domain.CloudKeystore
MachineIdentityID *string
CertificateID *string
PickupID *string
KeystoreID *string
KeystoreName *string
ProviderName *string
Timeout time.Duration
Keystore *domain.CloudKeystore
}

type ProvisioningMetadata interface {
Expand Down
231 changes: 207 additions & 24 deletions pkg/venafi/cloud/cloudproviders.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"encoding/json"
"fmt"
"log"
"net/http"
"time"

Expand Down Expand Up @@ -216,7 +217,7 @@ func (c *Connector) getGraphqlClient() graphql.Client {

func (c *Connector) getGraphqlHTTPClient() *http.Client {
// We provide every type of auth here.
// The logic to decide which auth is inside struct's function: RoundTrip
// The logic to decide which auth to use is inside struct's function: RoundTrip
httpclient := &http.Client{
Transport: &httputils.AuthedTransportApi{
ApiKey: c.apiKey,
Expand All @@ -229,6 +230,187 @@ func (c *Connector) getGraphqlHTTPClient() *http.Client {
return httpclient
}

func (c *Connector) ProvisionCertificate(req *endpoint.ProvisioningRequest, options *endpoint.ProvisioningOptions) (provisioningMetadata endpoint.ProvisioningMetadata, err error) {
log.Printf("Starting Provisioning Flow")

if req == nil {
return nil, fmt.Errorf("missing Provisioning Request")
}

reqData := *req

if reqData.Timeout <= 0 {
reqData.Timeout = util.DefaultTimeout * time.Second
}

if reqData.CertificateID == nil {
if reqData.PickupID == nil {
return nil, fmt.Errorf("no Certificate ID or Pickup ID were provided for provisioning")
}
log.Printf("Certificate ID was not provided in request. Fetching it by using Pickup ID %s", *(reqData.PickupID))
certID, err := c.getCertIDFromPickupID(*(reqData.PickupID), reqData.Timeout)
if err != nil {
return nil, err
}
reqData.CertificateID = certID
}
certificateIDString := *(reqData.CertificateID)
log.Printf("Certificate ID for provisioning: %s", certificateIDString)

// Is certificate generated by VCP?
log.Printf("Validating if certificate is generated by VCP")
err = c.validateIfCertIsVCPGeneratedByID(*(reqData.CertificateID))
if err != nil {
return nil, err
}
log.Println("Certificate is valid for provisioning (VCP generated)")

// setting options for provisioning
var provisioningOptions *cloudproviders.CertificateProvisioningOptionsInput
if options != nil {
log.Println("setting provisioning options")
provisioningOptions, err = setProvisioningOptions(options)
if err != nil {
return nil, err
}
log.Println("provisioning options successfully set")
}

ctx := context.Background()

var keystoreIDString string

if reqData.Keystore == nil {
if reqData.KeystoreID == nil {
if reqData.ProviderName == nil || reqData.KeystoreName == nil {
return nil, fmt.Errorf("any of keystore object, keystore ID or both Provider Name and Keystore Name must be provided for provisioning")
}
}

// Getting Keystore to find type
keystoreIDInput := util.StringPointerToString(reqData.KeystoreID)
keystoreNameInput := util.StringPointerToString(reqData.KeystoreName)
providerNameInput := util.StringPointerToString(reqData.ProviderName)

log.Printf("fetching keystore information for provided keystore information. KeystoreID: %s, KeystoreName: %s, ProviderName: %s", keystoreIDInput, keystoreNameInput, providerNameInput)
cloudKeystore, err := c.GetCloudKeystore(domain.GetCloudKeystoreRequest{
CloudProviderID: nil,
CloudProviderName: req.ProviderName,
CloudKeystoreID: req.KeystoreID,
CloudKeystoreName: req.KeystoreName,
})
if err != nil {
return nil, err
}

keystoreIDString = cloudKeystore.ID

log.Printf("successfully fetched keystore information for KeystoreID: %s", keystoreIDString)
} else {
log.Printf("Keystore was provided")
keystoreIDString = reqData.Keystore.ID
}
log.Printf("Keystore ID for provisioning: %s", keystoreIDString)
wsClientID := uuid.New().String()

wsConn, err := c.notificationSvcClient.Subscribe(wsClientID)
if err != nil {
return nil, err
}

log.Printf("Provisioning Certificate ID %s for Keystore %s", certificateIDString, keystoreIDString)
_, err = c.cloudProvidersClient.ProvisionCertificate(ctx, certificateIDString, keystoreIDString, wsClientID, provisioningOptions)
if err != nil {
return nil, err
}

ar, err := c.notificationSvcClient.ReadResponse(wsConn)
if err != nil {
return nil, err
}

// parsing metadata from websocket response
log.Printf("Getting Cloud Metadata of Certificate ID %s and Keystore ID: %s", certificateIDString, keystoreIDString)
cloudMetadata, err := getCloudMetadataFromWebsocketResponse(ar.Data.Result)
if err != nil {
return nil, err
}
log.Printf("Successfully got Cloud Metadata for Certificate ID %s and Keystore ID: %s", certificateIDString, keystoreIDString)

log.Printf("Successfully finished Provisioning Flow for Certificate ID %s and Keystore ID %s", certificateIDString, keystoreIDString)
return cloudMetadata, nil
}

func (c *Connector) ProvisionCertificateToMachineIdentity(req endpoint.ProvisioningRequest) (endpoint.ProvisioningMetadata, error) {
log.Printf("Starting Provisioning to Machine Identity Flow")

if req.MachineIdentityID == nil {
return nil, fmt.Errorf("error trying to provision certificate to machine identity: machineIdentityID is nil")
}

machineIdentityID := *req.MachineIdentityID
certificateID := ""
timeout := util.DefaultTimeout * time.Second
if req.Timeout != 0 {
timeout = req.Timeout
}

if req.CertificateID == nil {
if req.PickupID == nil {
return nil, fmt.Errorf("no Certificate ID or Pickup ID were provided for provisioning")
}

log.Printf("Certificate ID was not provided in request. Using Pickup ID %s to fetch it", *req.PickupID)
certID, err := c.getCertIDFromPickupID(*req.PickupID, timeout)
if err != nil {
return nil, err
}
certificateID = *certID
} else {
certificateID = *req.CertificateID
}

log.Printf("certificate ID for provisioning: %s", certificateID)

// Is certificate generated by VCP?
log.Printf("validating if certificate is generated by VCP")
err := c.validateIfCertIsVCPGeneratedByID(certificateID)
if err != nil {
return nil, err
}
log.Println("Certificate is VCP generated")

ctx := context.Background()
wsClientID := uuid.New().String()

wsConn, err := c.notificationSvcClient.Subscribe(wsClientID)
if err != nil {
return nil, err
}

log.Printf("Provisioning Certificate with ID %s to Machine Identity with ID %s", certificateID, machineIdentityID)
_, err = c.cloudProvidersClient.ProvisionCertificateToMachineIdentity(ctx, &certificateID, machineIdentityID, wsClientID)
if err != nil {
return nil, err
}

ar, err := c.notificationSvcClient.ReadResponse(wsConn)
if err != nil {
return nil, err
}

// parsing metadata from websocket response
log.Printf("Getting Cloud Metadata of Machine Identity with ID: %s", machineIdentityID)
cloudMetadata, err := getCloudMetadataFromWebsocketResponse(ar.Data.Result)
if err != nil {
return nil, err
}
log.Printf("Successfully retrieved Cloud Metadata for Machine Identity with ID: %s", machineIdentityID)

log.Printf("Successfully completed Provisioning Flow for Certificate ID %s and Machine Identity ID %s", certificateID, machineIdentityID)
return cloudMetadata, nil
}

func (c *Connector) GetCloudProvider(request domain.GetCloudProviderRequest) (*domain.CloudProvider, error) {
cloudProvider, err := c.cloudProvidersClient.GetCloudProvider(context.Background(), request)
if err != nil {
Expand Down Expand Up @@ -278,39 +460,40 @@ func (c *Connector) DeleteMachineIdentity(id uuid.UUID) (bool, error) {
return deleted, nil
}

func getCloudMetadataFromWebsocketResponse(respMap interface{}, keystoreType string, keystoreId string) (*CloudProvisioningMetadata, error) {
func getCloudMetadataFromWebsocketResponse(resultMap interface{}) (*CloudProvisioningMetadata, error) {

val := CloudKeystoreProvisioningResult{}
valJs, err := json.Marshal(respMap)
result := CloudKeystoreProvisioningResult{}
resultBytes, err := json.Marshal(resultMap)
if err != nil {
return nil, fmt.Errorf("unable to encode response data! Error: %s", err.Error())
return nil, fmt.Errorf("unable to encode response data: %w", err)
}
err = json.Unmarshal(valJs, &val)
err = json.Unmarshal(resultBytes, &result)
if err != nil {
return nil, fmt.Errorf("unable to parse response data! Error: %s", err.Error())
return nil, fmt.Errorf("unable to parse response data: %w", err)
}
if val.Error != nil {
return nil, fmt.Errorf("unable to provision certificate! Error: %s", val.Error)
if result.Error != nil {
return nil, fmt.Errorf("unable to provision certificate: %w", result.Error)
}

if val.CloudProviderCertificateID == "" {
return nil, fmt.Errorf("provisioning is not successful, certificate ID from response is empty")
if result.CloudProviderCertificateID == "" {
return nil, fmt.Errorf("provisioning failed, certificate ID from response is empty")
}

cloudMetadata := &CloudProvisioningMetadata{}
switch keystoreType {
case string(cloudproviders.CloudKeystoreTypeAcm):
cloudMetadata.awsMetadata.result = val
case string(cloudproviders.CloudKeystoreTypeAkv):
cloudMetadata.azureMetadata.result = val
case string(cloudproviders.CloudKeystoreTypeGcm):
cloudMetadata.gcpMetadata.result = val
default:
err = fmt.Errorf("unknown type %v for keystore with ID: %s", keystoreType, keystoreId)
return nil, err
cloudMetadata := &CloudProvisioningMetadata{
machineMetadata: MachineIdentityMetadata{
result: result,
},
}

cloudMetadata.machineMetadata.result = val
// Only ACM returns an ARN value
if result.Arn != "" {
cloudMetadata.awsMetadata.result = result
} else if result.CloudCertificateVersion != "" {
// Only Azure returns a certificate version value
cloudMetadata.azureMetadata.result = result
} else {
// No ARN and no certificate version, default to GCM
cloudMetadata.gcpMetadata.result = result
}

return cloudMetadata, err
}
Loading

0 comments on commit 78ecbb6

Please sign in to comment.