Skip to content

Commit 85cfb25

Browse files
authored
use gcp certificate manager v1 api for scope support (#3181)
* Refactor GCP Certificate Management to Use New API Client - Updated the certificate management functions to utilize the new certificatemanagerapi client. - Replaced the previous client creation and usage with the new service client. - Implemented error handling for certificate creation and updates using the new API structure. - Added a waitForOperation function to handle long-running operations with exponential backoff. This change enhances the integration with GCP's Certificate Manager API and improves error handling for certificate operations. * use gcp certificate manager v1 api for scope support Signed-off-by: Henry Avetisyan <[email protected]> * address review comments Signed-off-by: Henry Avetisyan <[email protected]> --------- Signed-off-by: Henry Avetisyan <[email protected]>
1 parent 840c3bd commit 85cfb25

File tree

2 files changed

+312
-208
lines changed

2 files changed

+312
-208
lines changed

libs/go/sia/gcp/functions/identity.go

Lines changed: 97 additions & 117 deletions
Original file line numberDiff line numberDiff line change
@@ -22,20 +22,19 @@ import (
2222
"crypto/rsa"
2323
"fmt"
2424
"log"
25+
"net/http"
2526
"os"
2627
"strings"
28+
"time"
2729

28-
certificatemanager "cloud.google.com/go/certificatemanager/apiv1"
29-
"cloud.google.com/go/certificatemanager/apiv1/certificatemanagerpb"
3030
secretmanager "cloud.google.com/go/secretmanager/apiv1"
3131
"cloud.google.com/go/secretmanager/apiv1/secretmanagerpb"
3232
gcpa "github.com/AthenZ/athenz/libs/go/sia/gcp/attestation"
3333
gcpm "github.com/AthenZ/athenz/libs/go/sia/gcp/meta"
3434
"github.com/AthenZ/athenz/libs/go/sia/util"
35-
"github.com/googleapis/gax-go/v2"
36-
"google.golang.org/grpc/codes"
37-
"google.golang.org/grpc/status"
38-
"google.golang.org/protobuf/types/known/fieldmaskpb"
35+
certificatemanagerapi "google.golang.org/api/certificatemanager/v1"
36+
"google.golang.org/api/googleapi"
37+
"google.golang.org/api/option"
3938
)
4039

4140
const (
@@ -232,76 +231,42 @@ func StoreAthenzIdentityInSecretManagerCustomFormat(athenzDomain, athenzService,
232231
return err
233232
}
234233

235-
// CertificateOperationInterface defines the interface for certificate operations
236-
type CertificateOperationInterface interface {
237-
Name() string
238-
Wait(context.Context, ...gax.CallOption) (*certificatemanagerpb.Certificate, error)
239-
}
240-
241-
// CertificateManagerClientInterface defines the interface for certificate manager client operations
242-
type CertificateManagerClientInterface interface {
243-
CreateCertificate(context.Context, *certificatemanagerpb.CreateCertificateRequest, ...gax.CallOption) (CertificateOperationInterface, error)
244-
UpdateCertificate(context.Context, *certificatemanagerpb.UpdateCertificateRequest, ...gax.CallOption) (CertificateOperationInterface, error)
245-
Close() error
246-
}
247-
248-
// certificateOperationAdapter adapts a concrete operation to the interface
249-
type certificateOperationAdapter struct {
250-
createOp *certificatemanager.CreateCertificateOperation
251-
updateOp *certificatemanager.UpdateCertificateOperation
252-
isCreate bool
234+
// MetadataProvider defines the interface for GCP metadata operations
235+
type MetadataProvider interface {
236+
GetProject(metaEndpoint string) (string, error)
253237
}
254238

255-
func (a *certificateOperationAdapter) Name() string {
256-
if a.isCreate {
257-
return a.createOp.Name()
258-
}
259-
return a.updateOp.Name()
260-
}
239+
// DefaultMetadataProvider is the default implementation that uses the gcpm package
240+
type DefaultMetadataProvider struct{}
261241

262-
func (a *certificateOperationAdapter) Wait(ctx context.Context, opts ...gax.CallOption) (*certificatemanagerpb.Certificate, error) {
263-
if a.isCreate {
264-
return a.createOp.Wait(ctx, opts...)
265-
}
266-
return a.updateOp.Wait(ctx, opts...)
242+
// GetProject retrieves the GCP project ID from the metadata server
243+
func (p *DefaultMetadataProvider) GetProject(metaEndpoint string) (string, error) {
244+
return gcpm.GetProject(metaEndpoint)
267245
}
268246

269-
// certificateManagerClientAdapter adapts the real client to the interface
270-
type certificateManagerClientAdapter struct {
271-
client *certificatemanager.Client
247+
// CertificateManagerOperations defines operations needed for certificate management
248+
type CertificateManagerOperations interface {
249+
CreateCertificate(ctx context.Context, parent string, certificate *certificatemanagerapi.Certificate, certificateId string) (*certificatemanagerapi.Operation, error)
250+
PatchCertificate(ctx context.Context, name string, certificate *certificatemanagerapi.Certificate, updateMask string) (*certificatemanagerapi.Operation, error)
251+
GetOperation(ctx context.Context, operationName string) (*certificatemanagerapi.Operation, error)
272252
}
273253

274-
func (a *certificateManagerClientAdapter) CreateCertificate(ctx context.Context, req *certificatemanagerpb.CreateCertificateRequest, opts ...gax.CallOption) (CertificateOperationInterface, error) {
275-
op, err := a.client.CreateCertificate(ctx, req, opts...)
276-
if err != nil {
277-
return nil, err
278-
}
279-
return &certificateOperationAdapter{createOp: op, isCreate: true}, nil
254+
// certificateManagerOperationsImpl implements CertificateManagerOperations using the REST API
255+
type certificateManagerOperationsImpl struct {
256+
service *certificatemanagerapi.Service
280257
}
281258

282-
func (a *certificateManagerClientAdapter) UpdateCertificate(ctx context.Context, req *certificatemanagerpb.UpdateCertificateRequest, opts ...gax.CallOption) (CertificateOperationInterface, error) {
283-
op, err := a.client.UpdateCertificate(ctx, req, opts...)
284-
if err != nil {
285-
return nil, err
286-
}
287-
return &certificateOperationAdapter{updateOp: op, isCreate: false}, nil
259+
func (c *certificateManagerOperationsImpl) CreateCertificate(ctx context.Context, parent string, certificate *certificatemanagerapi.Certificate, certificateId string) (*certificatemanagerapi.Operation, error) {
260+
return c.service.Projects.Locations.Certificates.Create(parent, certificate).CertificateId(certificateId).Context(ctx).Do()
288261
}
289262

290-
func (a *certificateManagerClientAdapter) Close() error {
291-
return a.client.Close()
263+
func (c *certificateManagerOperationsImpl) PatchCertificate(ctx context.Context, name string, certificate *certificatemanagerapi.Certificate, updateMask string) (*certificatemanagerapi.Operation, error) {
264+
return c.service.Projects.Locations.Certificates.Patch(name, certificate).UpdateMask(updateMask).Context(ctx).Do()
292265
}
293266

294-
// MetadataProvider defines the interface for GCP metadata operations
295-
type MetadataProvider interface {
296-
GetProject(metaEndpoint string) (string, error)
297-
}
298-
299-
// DefaultMetadataProvider is the default implementation that uses the gcpm package
300-
type DefaultMetadataProvider struct{}
301-
302-
// GetProject retrieves the GCP project ID from the metadata server
303-
func (p *DefaultMetadataProvider) GetProject(metaEndpoint string) (string, error) {
304-
return gcpm.GetProject(metaEndpoint)
267+
func (c *certificateManagerOperationsImpl) GetOperation(ctx context.Context, operationName string) (*certificatemanagerapi.Operation, error) {
268+
opsService := certificatemanagerapi.NewProjectsLocationsOperationsService(c.service)
269+
return opsService.Get(operationName).Context(ctx).Do()
305270
}
306271

307272
// StoreAthenzIdentityInCertificateManager store the retrieved athenz identity certificate
@@ -314,31 +279,30 @@ func (p *DefaultMetadataProvider) GetProject(metaEndpoint string) (string, error
314279
//
315280
// The location parameter specifies where the certificate should be created (e.g., "global").
316281
// For regional certificates, specify the region (e.g., "us-central1").
317-
func StoreAthenzIdentityInCertificateManager(certificateName, location string, siaCertData *util.SiaCertData, resourceLabels map[string]string) error {
282+
//
283+
// The scope parameter specifies the scope of the certificate. The valid values are:
284+
// "DEFAULT", "EDGE_CACHE", "ALL_REGIONS", and "CLIENT_AUTH".
285+
func StoreAthenzIdentityInCertificateManager(certificateName, location string, siaCertData *util.SiaCertData, scope string, resourceLabels map[string]string) error {
318286

319287
// Validate input
320288
if siaCertData == nil || siaCertData.X509CertificatePem == "" || siaCertData.PrivateKeyPem == "" {
321289
return fmt.Errorf("invalid certificate data: certificate and private key must be provided")
322290
}
323291

324-
// Create the GCP certificate manager client.
292+
// Create the GCP certificate manager service client.
325293
ctx := context.Background()
326294

327-
certificateManagerClient, err := certificatemanager.NewClient(ctx)
295+
certificateManagerService, err := certificatemanagerapi.NewService(ctx, option.WithScopes("https://www.googleapis.com/auth/cloud-platform"))
328296
if err != nil {
329-
return fmt.Errorf("unable to create certificate manager client: %v", err)
297+
return fmt.Errorf("unable to create certificate manager service: %v", err)
330298
}
331-
defer (func() {
332-
_ = certificateManagerClient.Close()
333-
})()
334299

335-
// Wrap the real client in an adapter to match the interface
336-
clientAdapter := &certificateManagerClientAdapter{client: certificateManagerClient}
300+
operations := &certificateManagerOperationsImpl{service: certificateManagerService}
337301
metadataProvider := &DefaultMetadataProvider{}
338-
return storeIdentityInCertificateManager(ctx, clientAdapter, metadataProvider, certificateName, location, siaCertData, resourceLabels)
302+
return storeIdentityInCertificateManager(ctx, operations, metadataProvider, certificateName, location, siaCertData, scope, resourceLabels)
339303
}
340304

341-
func storeIdentityInCertificateManager(ctx context.Context, certificateManagerClient CertificateManagerClientInterface, metadataProvider MetadataProvider, certificateName, location string, siaCertData *util.SiaCertData, resourceLabels map[string]string) error {
305+
func storeIdentityInCertificateManager(ctx context.Context, operations CertificateManagerOperations, metadataProvider MetadataProvider, certificateName, location string, siaCertData *util.SiaCertData, scope string, resourceLabels map[string]string) error {
342306

343307
// Get the project id from metadata
344308
gcpProjectId, err := metadataProvider.GetProject(gcpMetaDataServer)
@@ -352,27 +316,22 @@ func storeIdentityInCertificateManager(ctx context.Context, certificateManagerCl
352316
// Build the certificate resource name
353317
certificateFullName := fmt.Sprintf("%s/certificates/%s", parent, certificateName)
354318

355-
// Create the self-managed certificate request
356-
createCertificateReq := &certificatemanagerpb.CreateCertificateRequest{
357-
Parent: parent,
358-
CertificateId: certificateName,
359-
Certificate: &certificatemanagerpb.Certificate{
360-
Name: certificateFullName,
361-
Type: &certificatemanagerpb.Certificate_SelfManaged{
362-
SelfManaged: &certificatemanagerpb.Certificate_SelfManagedCertificate{
363-
PemCertificate: siaCertData.X509CertificatePem,
364-
PemPrivateKey: siaCertData.PrivateKeyPem,
365-
},
366-
},
367-
Labels: resourceLabels,
319+
// Create the self-managed certificate object
320+
certificate := &certificatemanagerapi.Certificate{
321+
Name: certificateFullName,
322+
SelfManaged: &certificatemanagerapi.SelfManagedCertificate{
323+
PemCertificate: siaCertData.X509CertificatePem,
324+
PemPrivateKey: siaCertData.PrivateKeyPem,
368325
},
326+
Scope: scope,
327+
Labels: resourceLabels,
369328
}
370329

371330
// Try to create the certificate
372-
createOp, err := certificateManagerClient.CreateCertificate(ctx, createCertificateReq)
331+
createOp, err := operations.CreateCertificate(ctx, parent, certificate, certificateName)
373332
if err == nil {
374-
log.Printf("Waiting for CreateCertificate operation %s to complete...\n", createOp.Name())
375-
_, err = createOp.Wait(ctx)
333+
log.Printf("Waiting for CreateCertificate operation %s to complete...\n", createOp.Name)
334+
err = waitForOperation(ctx, operations, createOp.Name)
376335
if err != nil {
377336
log.Printf("CreateCertificate (wait) operation failed: %v\n", err)
378337
} else {
@@ -381,45 +340,66 @@ func storeIdentityInCertificateManager(ctx context.Context, certificateManagerCl
381340
return err
382341
}
383342

384-
// check if the error because the certificate already exists and as such
385-
// we just need to update the certificate instead
386-
387-
st, ok := status.FromError(err)
388-
if !ok || st.Code() != codes.AlreadyExists {
343+
// Check if the error is because the certificate already exists
344+
googleErr, ok := err.(*googleapi.Error)
345+
if !ok || googleErr.Code != http.StatusConflict {
389346
log.Printf("CreateCertificate operation failed: %v\n", err)
390347
return err
391348
}
392349

393350
log.Println("Certificate already exists, we'll be updating it...")
394351

395-
// Update the existing certificate
396-
updateCertificateReq := &certificatemanagerpb.UpdateCertificateRequest{
397-
Certificate: &certificatemanagerpb.Certificate{
398-
Name: certificateFullName,
399-
Type: &certificatemanagerpb.Certificate_SelfManaged{
400-
SelfManaged: &certificatemanagerpb.Certificate_SelfManagedCertificate{
401-
PemCertificate: siaCertData.X509CertificatePem,
402-
PemPrivateKey: siaCertData.PrivateKeyPem,
403-
},
404-
},
405-
Labels: resourceLabels,
406-
},
407-
UpdateMask: &fieldmaskpb.FieldMask{
408-
Paths: []string{"self_managed", "labels"},
409-
},
352+
// Update the existing certificate using Patch
353+
updateMaskParts := []string{"selfManaged"}
354+
if certificate.Labels != nil {
355+
updateMaskParts = append(updateMaskParts, "labels")
410356
}
411-
412-
updateOp, err := certificateManagerClient.UpdateCertificate(ctx, updateCertificateReq)
357+
updateMask := strings.Join(updateMaskParts, ",")
358+
updateOp, err := operations.PatchCertificate(ctx, certificateFullName, certificate, updateMask)
413359
if err == nil {
414-
log.Printf("Waiting for UpdateCertificate operation %s to complete...\n", updateOp.Name())
415-
_, err = updateOp.Wait(ctx)
360+
log.Printf("Waiting for PatchCertificate operation %s to complete...\n", updateOp.Name)
361+
err = waitForOperation(ctx, operations, updateOp.Name)
416362
if err != nil {
417-
log.Printf("UpdateCertificate (wait) operation failed: %v\n", err)
363+
log.Printf("PatchCertificate (wait) operation failed: %v\n", err)
418364
} else {
419-
log.Println("UpdateCertificate operation succeeded")
365+
log.Println("PatchCertificate operation succeeded")
420366
}
421367
} else {
422-
log.Printf("UpdateCertificate operation failed: %v\n", err)
368+
log.Printf("PatchCertificate operation failed: %v\n", err)
423369
}
424370
return err
425371
}
372+
373+
// waitForOperation polls a long-running operation until it completes
374+
// operationName should be the full operation path: projects/{project}/locations/{location}/operations/{operation_id}
375+
func waitForOperation(ctx context.Context, operations CertificateManagerOperations, operationName string) error {
376+
// Poll the operation with exponential backoff
377+
maxAttempts := 60
378+
backoff := 2 * time.Second
379+
for i := 0; i < maxAttempts; i++ {
380+
op, err := operations.GetOperation(ctx, operationName)
381+
if err != nil {
382+
return fmt.Errorf("failed to get operation status: %v", err)
383+
}
384+
385+
if op.Done {
386+
if op.Error != nil {
387+
return fmt.Errorf("operation failed: %s", op.Error.Message)
388+
}
389+
return nil
390+
}
391+
392+
// Wait before next poll
393+
select {
394+
case <-ctx.Done():
395+
return ctx.Err()
396+
case <-time.After(backoff):
397+
// Exponential backoff with max 30 seconds
398+
if backoff < 30*time.Second {
399+
backoff *= 2
400+
}
401+
}
402+
}
403+
404+
return fmt.Errorf("operation did not complete within expected time: %s", operationName)
405+
}

0 commit comments

Comments
 (0)