Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(cloud-provisioning): Adds function to provision certificate to existing machine identity #486

Merged
merged 4 commits into from
May 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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"`
rvelaVenafi marked this conversation as resolved.
Show resolved Hide resolved
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)
rvelaVenafi marked this conversation as resolved.
Show resolved Hide resolved

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
rvelaVenafi marked this conversation as resolved.
Show resolved Hide resolved
}

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