@@ -17,21 +17,24 @@ import (
1717 "github.com/go-acme/lego/v4/platform/config/env"
1818 "github.com/go-acme/lego/v4/platform/wait"
1919 "golang.org/x/net/context"
20+ "golang.org/x/oauth2"
2021 "golang.org/x/oauth2/google"
2122 "google.golang.org/api/dns/v1"
2223 "google.golang.org/api/googleapi"
24+ "google.golang.org/api/impersonate"
2325 "google.golang.org/api/option"
2426)
2527
2628// Environment variables names.
2729const (
2830 envNamespace = "GCE_"
2931
30- EnvServiceAccount = envNamespace + "SERVICE_ACCOUNT"
31- EnvProject = envNamespace + "PROJECT"
32- EnvZoneID = envNamespace + "ZONE_ID"
33- EnvAllowPrivateZone = envNamespace + "ALLOW_PRIVATE_ZONE"
34- EnvDebug = envNamespace + "DEBUG"
32+ EnvServiceAccount = envNamespace + "SERVICE_ACCOUNT"
33+ EnvProject = envNamespace + "PROJECT"
34+ EnvZoneID = envNamespace + "ZONE_ID"
35+ EnvAllowPrivateZone = envNamespace + "ALLOW_PRIVATE_ZONE"
36+ EnvDebug = envNamespace + "DEBUG"
37+ EnvImpersonateServiceAccount = envNamespace + "IMPERSONATE_SERVICE_ACCOUNT"
3538
3639 EnvTTL = envNamespace + "TTL"
3740 EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT"
@@ -44,25 +47,27 @@ var _ challenge.ProviderTimeout = (*DNSProvider)(nil)
4447
4548// Config is used to configure the creation of the DNSProvider.
4649type Config struct {
47- Debug bool
48- Project string
49- ZoneID string
50- AllowPrivateZone bool
51- PropagationTimeout time.Duration
52- PollingInterval time.Duration
53- TTL int
54- HTTPClient * http.Client
50+ Debug bool
51+ Project string
52+ ZoneID string
53+ AllowPrivateZone bool
54+ ImpersonateServiceAccount string
55+ PropagationTimeout time.Duration
56+ PollingInterval time.Duration
57+ TTL int
58+ HTTPClient * http.Client
5559}
5660
5761// NewDefaultConfig returns a default configuration for the DNSProvider.
5862func NewDefaultConfig () * Config {
5963 return & Config {
60- Debug : env .GetOrDefaultBool (EnvDebug , false ),
61- ZoneID : env .GetOrDefaultString (EnvZoneID , "" ),
62- AllowPrivateZone : env .GetOrDefaultBool (EnvAllowPrivateZone , false ),
63- TTL : env .GetOrDefaultInt (EnvTTL , dns01 .DefaultTTL ),
64- PropagationTimeout : env .GetOrDefaultSecond (EnvPropagationTimeout , 180 * time .Second ),
65- PollingInterval : env .GetOrDefaultSecond (EnvPollingInterval , 5 * time .Second ),
64+ Debug : env .GetOrDefaultBool (EnvDebug , false ),
65+ ZoneID : env .GetOrDefaultString (EnvZoneID , "" ),
66+ AllowPrivateZone : env .GetOrDefaultBool (EnvAllowPrivateZone , false ),
67+ ImpersonateServiceAccount : env .GetOrDefaultString (EnvImpersonateServiceAccount , "" ),
68+ TTL : env .GetOrDefaultInt (EnvTTL , dns01 .DefaultTTL ),
69+ PropagationTimeout : env .GetOrDefaultSecond (EnvPropagationTimeout , 180 * time .Second ),
70+ PollingInterval : env .GetOrDefaultSecond (EnvPollingInterval , 5 * time .Second ),
6671 }
6772}
6873
@@ -95,14 +100,14 @@ func NewDNSProviderCredentials(project string) (*DNSProvider, error) {
95100 return nil , errors .New ("googlecloud: project name missing" )
96101 }
97102
98- client , err := google .DefaultClient (context .Background (), dns .NdevClouddnsReadwriteScope )
99- if err != nil {
100- return nil , fmt .Errorf ("googlecloud: unable to get Google Cloud client: %w" , err )
101- }
102-
103103 config := NewDefaultConfig ()
104104 config .Project = project
105- config .HTTPClient = client
105+
106+ var err error
107+ config .HTTPClient , err = newClientFromCredentials (context .Background (), config )
108+ if err != nil {
109+ return nil , fmt .Errorf ("googlecloud: %w" , err )
110+ }
106111
107112 return NewDNSProviderConfig (config )
108113}
@@ -129,15 +134,14 @@ func NewDNSProviderServiceAccountKey(saKey []byte) (*DNSProvider, error) {
129134 project = datJSON .ProjectID
130135 }
131136
132- conf , err := google .JWTConfigFromJSON (saKey , dns .NdevClouddnsReadwriteScope )
133- if err != nil {
134- return nil , fmt .Errorf ("googlecloud: unable to acquire config: %w" , err )
135- }
136- client := conf .Client (context .Background ())
137-
138137 config := NewDefaultConfig ()
139138 config .Project = project
140- config .HTTPClient = client
139+
140+ var err error
141+ config .HTTPClient , err = newClientFromServiceAccountKey (context .Background (), config , saKey )
142+ if err != nil {
143+ return nil , fmt .Errorf ("googlecloud: %w" , err )
144+ }
141145
142146 return NewDNSProviderConfig (config )
143147}
@@ -384,6 +388,54 @@ func (d *DNSProvider) findTxtRecords(zone, fqdn string) ([]*dns.ResourceRecordSe
384388 return recs .Rrsets , nil
385389}
386390
391+ func newClientFromCredentials (ctx context.Context , config * Config ) (* http.Client , error ) {
392+ if config .ImpersonateServiceAccount != "" {
393+ ts , err := google .DefaultTokenSource (ctx , "https://www.googleapis.com/auth/cloud-platform" )
394+ if err != nil {
395+ return nil , fmt .Errorf ("unable to get default token source: %w" , err )
396+ }
397+
398+ return newImpersonateClient (ctx , config .ImpersonateServiceAccount , ts )
399+ }
400+
401+ client , err := google .DefaultClient (ctx , dns .NdevClouddnsReadwriteScope )
402+ if err != nil {
403+ return nil , fmt .Errorf ("unable to get Google Cloud client: %w" , err )
404+ }
405+
406+ return client , nil
407+ }
408+
409+ func newClientFromServiceAccountKey (ctx context.Context , config * Config , saKey []byte ) (* http.Client , error ) {
410+ if config .ImpersonateServiceAccount != "" {
411+ conf , err := google .JWTConfigFromJSON (saKey , "https://www.googleapis.com/auth/cloud-platform" )
412+ if err != nil {
413+ return nil , fmt .Errorf ("unable to acquire config: %w" , err )
414+ }
415+
416+ return newImpersonateClient (ctx , config .ImpersonateServiceAccount , conf .TokenSource (ctx ))
417+ }
418+
419+ conf , err := google .JWTConfigFromJSON (saKey , dns .NdevClouddnsReadwriteScope )
420+ if err != nil {
421+ return nil , fmt .Errorf ("unable to acquire config: %w" , err )
422+ }
423+
424+ return conf .Client (ctx ), nil
425+ }
426+
427+ func newImpersonateClient (ctx context.Context , impersonateServiceAccount string , ts oauth2.TokenSource ) (* http.Client , error ) {
428+ impersonatedTS , err := impersonate .CredentialsTokenSource (ctx , impersonate.CredentialsConfig {
429+ TargetPrincipal : impersonateServiceAccount ,
430+ Scopes : []string {dns .NdevClouddnsReadwriteScope },
431+ }, option .WithTokenSource (ts ))
432+ if err != nil {
433+ return nil , fmt .Errorf ("unable to create impersonated credentials: %w" , err )
434+ }
435+
436+ return oauth2 .NewClient (ctx , impersonatedTS ), nil
437+ }
438+
387439func mustUnquote (raw string ) string {
388440 clean , err := strconv .Unquote (raw )
389441 if err != nil {
0 commit comments