From 3dbac75f3d55ec439b8f90cf1676b7bb9e2e79e4 Mon Sep 17 00:00:00 2001 From: TobiWo Date: Mon, 29 Jun 2026 12:38:17 +0200 Subject: [PATCH 1/3] [Fix] IAM: Use temporary AK/SK for cross-tenant agency auth * Replace agency token flow with temporary AK/SK (assume_role) auth * Add tests for delegated-project and domain-scope paths --- openstack/client.go | 53 +++---- openstack/testing/client_test.go | 256 +++++++++++++++++++++++++++++++ 2 files changed, 276 insertions(+), 33 deletions(-) diff --git a/openstack/client.go b/openstack/client.go index 9fda5f077..72e3d0a6b 100644 --- a/openstack/client.go +++ b/openstack/client.go @@ -9,6 +9,7 @@ import ( golangsdk "github.com/opentelekomcloud/gophertelekomcloud" "github.com/opentelekomcloud/gophertelekomcloud/openstack/identity/v3/catalog" + "github.com/opentelekomcloud/gophertelekomcloud/openstack/identity/v3/credentials" "github.com/opentelekomcloud/gophertelekomcloud/openstack/identity/v3/domains" "github.com/opentelekomcloud/gophertelekomcloud/openstack/identity/v3/projects" tokens3 "github.com/opentelekomcloud/gophertelekomcloud/openstack/identity/v3/tokens" @@ -358,50 +359,36 @@ func authWithAgencyByAKSK(client *golangsdk.ProviderClient, endpoint string, opt return fmt.Errorf("must config domain name") } - opts2 := golangsdk.AgencyAuthOptions{ - AgencyName: opts.AgencyName, - AgencyDomainName: opts.AgencyDomainName, - DelegatedProject: opts.DelegatedProject, - } - result := tokens3.Create(v3Client, &opts2) - token, err := result.ExtractToken() + credential, err := credentials.CreateTemporary(v3Client, credentials.CreateTemporaryOpts{ + Methods: []string{"assume_role"}, + DomainName: opts.AgencyDomainName, + AgencyName: opts.AgencyName, + }).Extract() if err != nil { - return err + return fmt.Errorf("error obtaining temporary AK/SK for agency: %w", err) } - project, err := result.ExtractProject() - if err != nil { - return fmt.Errorf("error extracting project info: %s", err) + agencyOpts := golangsdk.AKSKAuthOptions{ + IdentityEndpoint: opts.IdentityEndpoint, + ProjectName: opts.DelegatedProject, + Region: opts.Region, + AccessKey: credential.AccessKey, + SecretKey: credential.SecretKey, + SecurityToken: credential.SecurityToken, } - - user, err := result.ExtractUser() - if err != nil { - return fmt.Errorf("error extracting user info: %s", err) + if opts.DelegatedProject == "" { + agencyOpts.Domain = opts.AgencyDomainName } - serviceCatalog, err := result.ExtractServiceCatalog() - if err != nil { - return err + if err := v3AKSKAuth(client, endpoint, agencyOpts, eo); err != nil { + return fmt.Errorf("error authenticating with temporary agency credentials: %w", err) } - - client.TokenID = token.ID - if project != nil { - client.ProjectID = project.ID - } - if user != nil { - client.UserID = user.ID + if opts.DelegatedProject == "" { + client.DomainID = client.AKSKAuthOptions.DomainID } - client.ReauthFunc = func() error { - client.TokenID = "" return authWithAgencyByAKSK(client, endpoint, opts, eo) } - - client.EndpointLocator = func(opts golangsdk.EndpointOpts) (string, error) { - return V3EndpointURL(serviceCatalog, opts) - } - - client.AKSKAuthOptions.AccessKey = "" return nil } diff --git a/openstack/testing/client_test.go b/openstack/testing/client_test.go index 527c5864f..255f137f4 100644 --- a/openstack/testing/client_test.go +++ b/openstack/testing/client_test.go @@ -3,6 +3,7 @@ package testing import ( "fmt" "net/http" + "strings" "testing" "github.com/opentelekomcloud/gophertelekomcloud" @@ -220,3 +221,258 @@ func TestAuthenticatedClientV3Fails(t *testing.T) { func TestAuthenticatedClientV2Fails(t *testing.T) { testAuthenticatedClientFails(t, "http://bad-address.example.com/v2.0") } + +func TestAuthenticatedClientV3WithAgencyAKSKUsesTemporaryCredentials(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + var temporaryCredentialRequests int + + th.Mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + _, _ = fmt.Fprintf(w, ` + { + "versions": { + "values": [ + { + "status": "stable", + "id": "v3.0", + "links": [ + { "href": "%s", "rel": "self" } + ] + } + ] + } + } + `, th.Endpoint()+"v3/") + }) + + th.Mux.HandleFunc("/v3/auth/catalog", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + if r.Header.Get("Authorization") == "" { + t.Errorf("expected request to be signed") + } + + _, _ = fmt.Fprintf(w, ` + { + "catalog": [ + { + "type": "identity", + "name": "iam", + "endpoints": [ + { + "interface": "public", + "region": "eu-de", + "url": "%s" + } + ] + } + ], + "links": { + "next": null, + "previous": null + } + } + `, th.Endpoint()+"v3/") + }) + + th.Mux.HandleFunc("/v3.0/OS-CREDENTIAL/securitytokens", func(w http.ResponseWriter, r *http.Request) { + temporaryCredentialRequests++ + th.TestMethod(t, r, "POST") + th.TestHeader(t, r, "X-Domain-Id", "source-domain-id") + if r.Header.Get("Authorization") == "" { + t.Errorf("expected request to be signed") + } + th.TestJSONRequest(t, r, ` + { + "auth": { + "identity": { + "methods": ["assume_role"], + "assume_role": { + "agency_name": "target-agency", + "domain_name": "target-domain" + } + } + } + } + `) + + w.WriteHeader(http.StatusCreated) + _, _ = fmt.Fprintf(w, ` + { + "credential": { + "access": "temporary-ak-%[1]d", + "secret": "temporary-sk-%[1]d", + "securitytoken": "temporary-security-token-%[1]d", + "expires_at": "2030-01-01T00:00:00.000000Z" + } + } + `, temporaryCredentialRequests) + }) + + th.Mux.HandleFunc("/v3/projects", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestFormValues(t, r, map[string]string{"name": "target-project"}) + if r.Header.Get("Authorization") == "" { + t.Errorf("expected request to be signed") + } + th.TestHeader(t, r, "X-Security-Token", fmt.Sprintf("temporary-security-token-%d", temporaryCredentialRequests)) + if !strings.Contains(r.Header.Get("Authorization"), fmt.Sprintf("Credential=temporary-ak-%d/", temporaryCredentialRequests)) { + t.Errorf("expected temporary AK in Authorization header, got %q", r.Header.Get("Authorization")) + } + + _, _ = fmt.Fprint(w, ` + { + "projects": [ + { + "id": "target-project-id", + "name": "target-project" + } + ], + "links": { + "next": null, + "previous": null + } + } + `) + }) + + options := golangsdk.AKSKAuthOptions{ + IdentityEndpoint: th.Endpoint(), + DomainID: "source-domain-id", + AccessKey: "source-ak", + SecretKey: "source-sk", + AgencyName: "target-agency", + AgencyDomainName: "target-domain", + DelegatedProject: "target-project", + } + + client, err := openstack.AuthenticatedClient(options) + th.AssertNoErr(t, err) + th.CheckEquals(t, 1, temporaryCredentialRequests) + th.CheckEquals(t, "temporary-ak-1", client.AKSKAuthOptions.AccessKey) + th.CheckEquals(t, "temporary-sk-1", client.AKSKAuthOptions.SecretKey) + th.CheckEquals(t, "temporary-security-token-1", client.AKSKAuthOptions.SecurityToken) + th.CheckEquals(t, "target-project-id", client.ProjectID) + + err = client.ReauthFunc() + th.AssertNoErr(t, err) + th.CheckEquals(t, 2, temporaryCredentialRequests) + th.CheckEquals(t, "temporary-ak-2", client.AKSKAuthOptions.AccessKey) + th.CheckEquals(t, "temporary-sk-2", client.AKSKAuthOptions.SecretKey) + th.CheckEquals(t, "temporary-security-token-2", client.AKSKAuthOptions.SecurityToken) +} + +func TestAuthenticatedClientV3WithAgencyAKSKWithoutDelegatedProjectKeepsDomainScope(t *testing.T) { + th.SetupHTTP() + defer th.TeardownHTTP() + + th.Mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + _, _ = fmt.Fprintf(w, ` + { + "versions": { + "values": [ + { + "status": "stable", + "id": "v3.0", + "links": [ + { "href": "%s", "rel": "self" } + ] + } + ] + } + } + `, th.Endpoint()+"v3/") + }) + + th.Mux.HandleFunc("/v3/auth/catalog", func(w http.ResponseWriter, r *http.Request) { + _, _ = fmt.Fprintf(w, ` + { + "catalog": [ + { + "type": "identity", + "name": "iam", + "endpoints": [ + { + "interface": "public", + "region": "eu-de", + "url": "%s" + } + ] + } + ], + "links": { + "next": null, + "previous": null + } + } + `, th.Endpoint()+"v3/") + }) + + th.Mux.HandleFunc("/v3.0/OS-CREDENTIAL/securitytokens", func(w http.ResponseWriter, r *http.Request) { + th.TestJSONRequest(t, r, ` + { + "auth": { + "identity": { + "methods": ["assume_role"], + "assume_role": { + "agency_name": "target-agency", + "domain_name": "target-domain" + } + } + } + } + `) + + w.WriteHeader(http.StatusCreated) + _, _ = fmt.Fprint(w, ` + { + "credential": { + "access": "temporary-ak", + "secret": "temporary-sk", + "securitytoken": "temporary-security-token", + "expires_at": "2030-01-01T00:00:00.000000Z" + } + } + `) + }) + + th.Mux.HandleFunc("/v3/auth/domains", func(w http.ResponseWriter, r *http.Request) { + th.TestMethod(t, r, "GET") + th.TestFormValues(t, r, map[string]string{"name": "target-domain"}) + th.TestHeader(t, r, "X-Security-Token", "temporary-security-token") + if !strings.Contains(r.Header.Get("Authorization"), "Credential=temporary-ak/") { + t.Errorf("expected temporary AK in Authorization header, got %q", r.Header.Get("Authorization")) + } + + _, _ = fmt.Fprint(w, ` + { + "domains": [ + { + "id": "target-domain-id", + "name": "target-domain" + } + ], + "links": { + "next": null, + "previous": null + } + } + `) + }) + + options := golangsdk.AKSKAuthOptions{ + IdentityEndpoint: th.Endpoint(), + DomainID: "source-domain-id", + AccessKey: "source-ak", + SecretKey: "source-sk", + AgencyName: "target-agency", + AgencyDomainName: "target-domain", + } + + client, err := openstack.AuthenticatedClient(options) + th.AssertNoErr(t, err) + th.CheckEquals(t, "target-domain-id", client.DomainID) + th.CheckEquals(t, "target-domain-id", client.AKSKAuthOptions.DomainID) + th.CheckEquals(t, "temporary-ak", client.AKSKAuthOptions.AccessKey) + th.CheckEquals(t, "temporary-security-token", client.AKSKAuthOptions.SecurityToken) +} From 9bf7f0078969c2c15a5b9d1b1c34c0db4169e7ba Mon Sep 17 00:00:00 2001 From: TobiWo Date: Tue, 30 Jun 2026 13:07:18 +0200 Subject: [PATCH 2/3] [Fix] IAM: Skip forbidden catalog re-fetch on agency AK/SK reauth * Add reuseEndpointLocator to v3AKSKAuth to reuse the first pass's locator * Skip the assumed-role GET /v3/auth/catalog that returns 403 * Assert the catalog is never fetched with the temporary AK/SK in tests --- openstack/client.go | 21 +++++++++++++++------ openstack/testing/client_test.go | 14 ++++++++++++++ 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/openstack/client.go b/openstack/client.go index 72e3d0a6b..7ef855a01 100644 --- a/openstack/client.go +++ b/openstack/client.go @@ -128,7 +128,7 @@ func Authenticate(client *golangsdk.ProviderClient, options golangsdk.AuthOption if akskAuthOptions.AgencyDomainName != "" && akskAuthOptions.AgencyName != "" { return authWithAgencyByAKSK(client, endpoint, akskAuthOptions, golangsdk.EndpointOpts{}) } - return v3AKSKAuth(client, endpoint, akskAuthOptions, golangsdk.EndpointOpts{}) + return v3AKSKAuth(client, endpoint, akskAuthOptions, golangsdk.EndpointOpts{}, false) } return fmt.Errorf("unrecognized auth options provider: %s", reflect.TypeOf(options)) @@ -267,7 +267,7 @@ func getProjectID(client *golangsdk.ServiceClient, name string) (string, error) return extractProjects[0].ID, nil } -func v3AKSKAuth(client *golangsdk.ProviderClient, endpoint string, options golangsdk.AKSKAuthOptions, eo golangsdk.EndpointOpts) error { +func v3AKSKAuth(client *golangsdk.ProviderClient, endpoint string, options golangsdk.AKSKAuthOptions, eo golangsdk.EndpointOpts, reuseEndpointLocator bool) error { v3Client, err := NewIdentityV3(client, eo) if err != nil { return err @@ -318,6 +318,17 @@ func v3AKSKAuth(client *golangsdk.ProviderClient, endpoint string, options golan client.ProjectID = options.ProjectId client.DomainID = options.BssDomainID + clientRegion := utils.GetRegionFromAKSK(options) + client.RegionID = clientRegion + + // When re-authenticating with assumed-role (agency) credentials, the global + // catalog (GET /v3/auth/catalog) is identical to the one already fetched with + // the build-user identity and is forbidden (403) for the assumed identity. + // Reuse the EndpointLocator built by the first authentication pass. + if reuseEndpointLocator && client.EndpointLocator != nil { + return nil + } + var entries = make([]tokens3.CatalogEntry, 0, 1) err = catalog.List(v3Client).EachPage(func(page pagination.Page) (bool, error) { catalogList, err := catalog.ExtractServiceCatalog(page) @@ -333,8 +344,6 @@ func v3AKSKAuth(client *golangsdk.ProviderClient, endpoint string, options golan if err != nil { return err } - clientRegion := utils.GetRegionFromAKSK(options) - client.RegionID = clientRegion client.EndpointLocator = func(opts golangsdk.EndpointOpts) (string, error) { return V3EndpointURL(&tokens3.ServiceCatalog{ @@ -345,7 +354,7 @@ func v3AKSKAuth(client *golangsdk.ProviderClient, endpoint string, options golan } func authWithAgencyByAKSK(client *golangsdk.ProviderClient, endpoint string, opts golangsdk.AKSKAuthOptions, eo golangsdk.EndpointOpts) error { - err := v3AKSKAuth(client, endpoint, opts, eo) + err := v3AKSKAuth(client, endpoint, opts, eo, false) if err != nil { return err } @@ -380,7 +389,7 @@ func authWithAgencyByAKSK(client *golangsdk.ProviderClient, endpoint string, opt agencyOpts.Domain = opts.AgencyDomainName } - if err := v3AKSKAuth(client, endpoint, agencyOpts, eo); err != nil { + if err := v3AKSKAuth(client, endpoint, agencyOpts, eo, true); err != nil { return fmt.Errorf("error authenticating with temporary agency credentials: %w", err) } if opts.DelegatedProject == "" { diff --git a/openstack/testing/client_test.go b/openstack/testing/client_test.go index 255f137f4..8eb974640 100644 --- a/openstack/testing/client_test.go +++ b/openstack/testing/client_test.go @@ -227,6 +227,7 @@ func TestAuthenticatedClientV3WithAgencyAKSKUsesTemporaryCredentials(t *testing. defer th.TeardownHTTP() var temporaryCredentialRequests int + var catalogRequests int th.Mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { _, _ = fmt.Fprintf(w, ` @@ -251,6 +252,10 @@ func TestAuthenticatedClientV3WithAgencyAKSKUsesTemporaryCredentials(t *testing. if r.Header.Get("Authorization") == "" { t.Errorf("expected request to be signed") } + if strings.Contains(r.Header.Get("Authorization"), "Credential=temporary-ak") { + t.Errorf("catalog must not be fetched with assumed-role credentials, got %q", r.Header.Get("Authorization")) + } + catalogRequests++ _, _ = fmt.Fprintf(w, ` { @@ -349,6 +354,7 @@ func TestAuthenticatedClientV3WithAgencyAKSKUsesTemporaryCredentials(t *testing. client, err := openstack.AuthenticatedClient(options) th.AssertNoErr(t, err) th.CheckEquals(t, 1, temporaryCredentialRequests) + th.CheckEquals(t, 1, catalogRequests) th.CheckEquals(t, "temporary-ak-1", client.AKSKAuthOptions.AccessKey) th.CheckEquals(t, "temporary-sk-1", client.AKSKAuthOptions.SecretKey) th.CheckEquals(t, "temporary-security-token-1", client.AKSKAuthOptions.SecurityToken) @@ -357,6 +363,7 @@ func TestAuthenticatedClientV3WithAgencyAKSKUsesTemporaryCredentials(t *testing. err = client.ReauthFunc() th.AssertNoErr(t, err) th.CheckEquals(t, 2, temporaryCredentialRequests) + th.CheckEquals(t, 2, catalogRequests) th.CheckEquals(t, "temporary-ak-2", client.AKSKAuthOptions.AccessKey) th.CheckEquals(t, "temporary-sk-2", client.AKSKAuthOptions.SecretKey) th.CheckEquals(t, "temporary-security-token-2", client.AKSKAuthOptions.SecurityToken) @@ -366,6 +373,8 @@ func TestAuthenticatedClientV3WithAgencyAKSKWithoutDelegatedProjectKeepsDomainSc th.SetupHTTP() defer th.TeardownHTTP() + var catalogRequests int + th.Mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { _, _ = fmt.Fprintf(w, ` { @@ -385,6 +394,10 @@ func TestAuthenticatedClientV3WithAgencyAKSKWithoutDelegatedProjectKeepsDomainSc }) th.Mux.HandleFunc("/v3/auth/catalog", func(w http.ResponseWriter, r *http.Request) { + if strings.Contains(r.Header.Get("Authorization"), "Credential=temporary-ak") { + t.Errorf("catalog must not be fetched with assumed-role credentials, got %q", r.Header.Get("Authorization")) + } + catalogRequests++ _, _ = fmt.Fprintf(w, ` { "catalog": [ @@ -471,6 +484,7 @@ func TestAuthenticatedClientV3WithAgencyAKSKWithoutDelegatedProjectKeepsDomainSc client, err := openstack.AuthenticatedClient(options) th.AssertNoErr(t, err) + th.CheckEquals(t, 1, catalogRequests) th.CheckEquals(t, "target-domain-id", client.DomainID) th.CheckEquals(t, "target-domain-id", client.AKSKAuthOptions.DomainID) th.CheckEquals(t, "temporary-ak", client.AKSKAuthOptions.AccessKey) From 2c5a12f820bd9f378a06c87d9a196144bc42a3a6 Mon Sep 17 00:00:00 2001 From: TobiWo Date: Fri, 3 Jul 2026 13:08:15 +0200 Subject: [PATCH 3/3] [Fix] IAM: Rework agency second pass per review * Drop reuseEndpointLocator; restore v3AKSKAuth to its original signature * Apply temporary AK/SK to the client directly instead of re-running v3AKSKAuth * Resolve delegated project or agency domain ID as the assumed identity * Return the agency-domain lookup error instead of swallowing it --- openstack/client.go | 56 +++++++++++++++++++++------------------------ 1 file changed, 26 insertions(+), 30 deletions(-) diff --git a/openstack/client.go b/openstack/client.go index 7ef855a01..df0e4071f 100644 --- a/openstack/client.go +++ b/openstack/client.go @@ -128,7 +128,7 @@ func Authenticate(client *golangsdk.ProviderClient, options golangsdk.AuthOption if akskAuthOptions.AgencyDomainName != "" && akskAuthOptions.AgencyName != "" { return authWithAgencyByAKSK(client, endpoint, akskAuthOptions, golangsdk.EndpointOpts{}) } - return v3AKSKAuth(client, endpoint, akskAuthOptions, golangsdk.EndpointOpts{}, false) + return v3AKSKAuth(client, endpoint, akskAuthOptions, golangsdk.EndpointOpts{}) } return fmt.Errorf("unrecognized auth options provider: %s", reflect.TypeOf(options)) @@ -267,7 +267,7 @@ func getProjectID(client *golangsdk.ServiceClient, name string) (string, error) return extractProjects[0].ID, nil } -func v3AKSKAuth(client *golangsdk.ProviderClient, endpoint string, options golangsdk.AKSKAuthOptions, eo golangsdk.EndpointOpts, reuseEndpointLocator bool) error { +func v3AKSKAuth(client *golangsdk.ProviderClient, endpoint string, options golangsdk.AKSKAuthOptions, eo golangsdk.EndpointOpts) error { v3Client, err := NewIdentityV3(client, eo) if err != nil { return err @@ -318,17 +318,6 @@ func v3AKSKAuth(client *golangsdk.ProviderClient, endpoint string, options golan client.ProjectID = options.ProjectId client.DomainID = options.BssDomainID - clientRegion := utils.GetRegionFromAKSK(options) - client.RegionID = clientRegion - - // When re-authenticating with assumed-role (agency) credentials, the global - // catalog (GET /v3/auth/catalog) is identical to the one already fetched with - // the build-user identity and is forbidden (403) for the assumed identity. - // Reuse the EndpointLocator built by the first authentication pass. - if reuseEndpointLocator && client.EndpointLocator != nil { - return nil - } - var entries = make([]tokens3.CatalogEntry, 0, 1) err = catalog.List(v3Client).EachPage(func(page pagination.Page) (bool, error) { catalogList, err := catalog.ExtractServiceCatalog(page) @@ -344,6 +333,8 @@ func v3AKSKAuth(client *golangsdk.ProviderClient, endpoint string, options golan if err != nil { return err } + clientRegion := utils.GetRegionFromAKSK(options) + client.RegionID = clientRegion client.EndpointLocator = func(opts golangsdk.EndpointOpts) (string, error) { return V3EndpointURL(&tokens3.ServiceCatalog{ @@ -354,7 +345,7 @@ func v3AKSKAuth(client *golangsdk.ProviderClient, endpoint string, options golan } func authWithAgencyByAKSK(client *golangsdk.ProviderClient, endpoint string, opts golangsdk.AKSKAuthOptions, eo golangsdk.EndpointOpts) error { - err := v3AKSKAuth(client, endpoint, opts, eo, false) + err := v3AKSKAuth(client, endpoint, opts, eo) if err != nil { return err } @@ -377,24 +368,29 @@ func authWithAgencyByAKSK(client *golangsdk.ProviderClient, endpoint string, opt return fmt.Errorf("error obtaining temporary AK/SK for agency: %w", err) } - agencyOpts := golangsdk.AKSKAuthOptions{ - IdentityEndpoint: opts.IdentityEndpoint, - ProjectName: opts.DelegatedProject, - Region: opts.Region, - AccessKey: credential.AccessKey, - SecretKey: credential.SecretKey, - SecurityToken: credential.SecurityToken, - } - if opts.DelegatedProject == "" { - agencyOpts.Domain = opts.AgencyDomainName - } + client.AKSKAuthOptions.AccessKey = credential.AccessKey + client.AKSKAuthOptions.SecretKey = credential.SecretKey + client.AKSKAuthOptions.SecurityToken = credential.SecurityToken + client.AKSKAuthOptions.ProjectId = "" + client.AKSKAuthOptions.DomainID = "" - if err := v3AKSKAuth(client, endpoint, agencyOpts, eo, true); err != nil { - return fmt.Errorf("error authenticating with temporary agency credentials: %w", err) - } - if opts.DelegatedProject == "" { - client.DomainID = client.AKSKAuthOptions.DomainID + if opts.DelegatedProject != "" { + projectID, err := getProjectID(v3Client, opts.DelegatedProject) + if err != nil { + return fmt.Errorf("error resolving delegated project %q: %w", opts.DelegatedProject, err) + } + client.ProjectID = projectID + client.AKSKAuthOptions.ProjectId = projectID + } else { + domainID, err := getDomainID(opts.AgencyDomainName, v3Client) + if err != nil { + return fmt.Errorf("error resolving agency domain %q: %w", opts.AgencyDomainName, err) + } + client.ProjectID = "" + client.DomainID = domainID + client.AKSKAuthOptions.DomainID = domainID } + client.ReauthFunc = func() error { return authWithAgencyByAKSK(client, endpoint, opts, eo) }