@@ -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
4140const (
@@ -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