4 AuthOptions stores information needed to authenticate to an OpenStack Cloud.
5 You can populate one manually, or use a provider's AuthOptionsFromEnv() function
6 to read relevant information from the standard environment variables. Pass one
7 to a provider's AuthenticatedClient function to authenticate and obtain a
8 ProviderClient representing an active session on that provider.
10 Its fields are the union of those recognized by each identity implementation and
13 An example of manually providing authentication information:
15 opts := gophercloud.AuthOptions{
16 IdentityEndpoint: "https://openstack.example.com:5000/v2.0",
17 Username: "{username}",
18 Password: "{password}",
19 TenantID: "{tenant_id}",
22 provider, err := openstack.AuthenticatedClient(opts)
24 An example of using AuthOptionsFromEnv(), where the environment variables can
25 be read from a file, such as a standard openrc file:
27 opts, err := openstack.AuthOptionsFromEnv()
28 provider, err := openstack.AuthenticatedClient(opts)
30 type AuthOptions struct {
31 // IdentityEndpoint specifies the HTTP endpoint that is required to work with
32 // the Identity API of the appropriate version. While it's ultimately needed by
33 // all of the identity services, it will often be populated by a provider-level
36 // The IdentityEndpoint is typically referred to as the "auth_url" or
37 // "OS_AUTH_URL" in the information provided by the cloud operator.
38 IdentityEndpoint string `json:"-"`
40 // Username is required if using Identity V2 API. Consult with your provider's
41 // control panel to discover your account's username. In Identity V3, either
42 // UserID or a combination of Username and DomainID or DomainName are needed.
43 Username string `json:"username,omitempty"`
44 UserID string `json:"-"`
46 Password string `json:"password,omitempty"`
48 // At most one of DomainID and DomainName must be provided if using Username
49 // with Identity V3. Otherwise, either are optional.
50 DomainID string `json:"-"`
51 DomainName string `json:"name,omitempty"`
53 // The TenantID and TenantName fields are optional for the Identity V2 API.
54 // The same fields are known as project_id and project_name in the Identity
55 // V3 API, but are collected as TenantID and TenantName here in both cases.
56 // Some providers allow you to specify a TenantName instead of the TenantId.
57 // Some require both. Your provider's authentication policies will determine
58 // how these fields influence authentication.
59 // If DomainID or DomainName are provided, they will also apply to TenantName.
60 // It is not currently possible to authenticate with Username and a Domain
61 // and scope to a Project in a different Domain by using TenantName. To
62 // accomplish that, the ProjectID will need to be provided as the TenantID
64 TenantID string `json:"tenantId,omitempty"`
65 TenantName string `json:"tenantName,omitempty"`
67 // AllowReauth should be set to true if you grant permission for Gophercloud to
68 // cache your credentials in memory, and to allow Gophercloud to attempt to
69 // re-authenticate automatically if/when your token expires. If you set it to
70 // false, it will not cache these settings, but re-authentication will not be
71 // possible. This setting defaults to false.
73 // NOTE: The reauth function will try to re-authenticate endlessly if left
74 // unchecked. The way to limit the number of attempts is to provide a custom
75 // HTTP client to the provider client and provide a transport that implements
76 // the RoundTripper interface and stores the number of failed retries. For an
77 // example of this, see here:
78 // https://github.com/rackspace/rack/blob/1.0.0/auth/clients.go#L311
79 AllowReauth bool `json:"-"`
81 // TokenID allows users to authenticate (possibly as another user) with an
82 // authentication token ID.
83 TokenID string `json:"-"`
85 // Scope determines the scoping of the authentication request.
86 Scope *AuthScope `json:"-"`
88 // Authentication through Application Credentials requires supplying name, project and secret
89 // For project we can use TenantID
90 ApplicationCredentialID string `json:"-"`
91 ApplicationCredentialName string `json:"-"`
92 ApplicationCredentialSecret string `json:"-"`
95 // AuthScope allows a created token to be limited to a specific domain or project.
96 type AuthScope struct {
103 // ToTokenV2CreateMap allows AuthOptions to satisfy the AuthOptionsBuilder
104 // interface in the v2 tokens package
105 func (opts AuthOptions) ToTokenV2CreateMap() (map[string]interface{}, error) {
106 // Populate the request map.
107 authMap := make(map[string]interface{})
109 if opts.Username != "" {
110 if opts.Password != "" {
111 authMap["passwordCredentials"] = map[string]interface{}{
112 "username": opts.Username,
113 "password": opts.Password,
116 return nil, ErrMissingInput{Argument: "Password"}
118 } else if opts.TokenID != "" {
119 authMap["token"] = map[string]interface{}{
123 return nil, ErrMissingInput{Argument: "Username"}
126 if opts.TenantID != "" {
127 authMap["tenantId"] = opts.TenantID
129 if opts.TenantName != "" {
130 authMap["tenantName"] = opts.TenantName
133 return map[string]interface{}{"auth": authMap}, nil
136 func (opts *AuthOptions) ToTokenV3CreateMap(scope map[string]interface{}) (map[string]interface{}, error) {
137 type domainReq struct {
138 ID *string `json:"id,omitempty"`
139 Name *string `json:"name,omitempty"`
142 type projectReq struct {
143 Domain *domainReq `json:"domain,omitempty"`
144 Name *string `json:"name,omitempty"`
145 ID *string `json:"id,omitempty"`
148 type userReq struct {
149 ID *string `json:"id,omitempty"`
150 Name *string `json:"name,omitempty"`
151 Password string `json:"password,omitempty"`
152 Domain *domainReq `json:"domain,omitempty"`
155 type passwordReq struct {
156 User userReq `json:"user"`
159 type tokenReq struct {
160 ID string `json:"id"`
163 type applicationCredentialReq struct {
164 ID *string `json:"id,omitempty"`
165 Name *string `json:"name,omitempty"`
166 User *userReq `json:"user,omitempty"`
167 Secret *string `json:"secret,omitempty"`
170 type identityReq struct {
171 Methods []string `json:"methods"`
172 Password *passwordReq `json:"password,omitempty"`
173 Token *tokenReq `json:"token,omitempty"`
174 ApplicationCredential *applicationCredentialReq `json:"application_credential,omitempty"`
177 type authReq struct {
178 Identity identityReq `json:"identity"`
181 type request struct {
182 Auth authReq `json:"auth"`
185 // Populate the request structure based on the provided arguments. Create and return an error
186 // if insufficient or incompatible information is present.
189 if opts.Password == "" {
190 if opts.TokenID != "" {
191 // Because we aren't using password authentication, it's an error to also provide any of the user-based authentication
193 if opts.Username != "" {
194 return nil, ErrUsernameWithToken{}
196 if opts.UserID != "" {
197 return nil, ErrUserIDWithToken{}
199 if opts.DomainID != "" {
200 return nil, ErrDomainIDWithToken{}
202 if opts.DomainName != "" {
203 return nil, ErrDomainNameWithToken{}
206 // Configure the request for Token authentication.
207 req.Auth.Identity.Methods = []string{"token"}
208 req.Auth.Identity.Token = &tokenReq{
212 } else if opts.ApplicationCredentialID != "" {
213 // Configure the request for ApplicationCredentialID authentication.
214 // https://github.com/openstack/keystoneauth/blob/stable/rocky/keystoneauth1/identity/v3/application_credential.py#L48-L67
215 // There are three kinds of possible application_credential requests
216 // 1. application_credential id + secret
217 // 2. application_credential name + secret + user_id
218 // 3. application_credential name + secret + username + domain_id / domain_name
219 if opts.ApplicationCredentialSecret == "" {
220 return nil, ErrAppCredMissingSecret{}
222 req.Auth.Identity.Methods = []string{"application_credential"}
223 req.Auth.Identity.ApplicationCredential = &applicationCredentialReq{
224 ID: &opts.ApplicationCredentialID,
225 Secret: &opts.ApplicationCredentialSecret,
227 } else if opts.ApplicationCredentialName != "" {
228 if opts.ApplicationCredentialSecret == "" {
229 return nil, ErrAppCredMissingSecret{}
232 var userRequest *userReq
234 if opts.UserID != "" {
235 // UserID could be used without the domain information
236 userRequest = &userReq{
241 if userRequest == nil && opts.Username == "" {
242 // Make sure that Username or UserID are provided
243 return nil, ErrUsernameOrUserID{}
246 if userRequest == nil && opts.DomainID != "" {
247 userRequest = &userReq{
248 Name: &opts.Username,
249 Domain: &domainReq{ID: &opts.DomainID},
253 if userRequest == nil && opts.DomainName != "" {
254 userRequest = &userReq{
255 Name: &opts.Username,
256 Domain: &domainReq{Name: &opts.DomainName},
260 // Make sure that DomainID or DomainName are provided among Username
261 if userRequest == nil {
262 return nil, ErrDomainIDOrDomainName{}
265 req.Auth.Identity.Methods = []string{"application_credential"}
266 req.Auth.Identity.ApplicationCredential = &applicationCredentialReq{
267 Name: &opts.ApplicationCredentialName,
269 Secret: &opts.ApplicationCredentialSecret,
272 // If no password or token ID or ApplicationCredential are available, authentication can't continue.
273 return nil, ErrMissingPassword{}
276 // Password authentication.
277 req.Auth.Identity.Methods = []string{"password"}
279 // At least one of Username and UserID must be specified.
280 if opts.Username == "" && opts.UserID == "" {
281 return nil, ErrUsernameOrUserID{}
284 if opts.Username != "" {
285 // If Username is provided, UserID may not be provided.
286 if opts.UserID != "" {
287 return nil, ErrUsernameOrUserID{}
290 // Either DomainID or DomainName must also be specified.
291 if opts.DomainID == "" && opts.DomainName == "" {
292 return nil, ErrDomainIDOrDomainName{}
295 if opts.DomainID != "" {
296 if opts.DomainName != "" {
297 return nil, ErrDomainIDOrDomainName{}
300 // Configure the request for Username and Password authentication with a DomainID.
301 req.Auth.Identity.Password = &passwordReq{
303 Name: &opts.Username,
304 Password: opts.Password,
305 Domain: &domainReq{ID: &opts.DomainID},
310 if opts.DomainName != "" {
311 // Configure the request for Username and Password authentication with a DomainName.
312 req.Auth.Identity.Password = &passwordReq{
314 Name: &opts.Username,
315 Password: opts.Password,
316 Domain: &domainReq{Name: &opts.DomainName},
322 if opts.UserID != "" {
323 // If UserID is specified, neither DomainID nor DomainName may be.
324 if opts.DomainID != "" {
325 return nil, ErrDomainIDWithUserID{}
327 if opts.DomainName != "" {
328 return nil, ErrDomainNameWithUserID{}
331 // Configure the request for UserID and Password authentication.
332 req.Auth.Identity.Password = &passwordReq{
333 User: userReq{ID: &opts.UserID, Password: opts.Password},
338 b, err := BuildRequestBody(req, "")
344 b["auth"].(map[string]interface{})["scope"] = scope
350 func (opts *AuthOptions) ToTokenV3ScopeMap() (map[string]interface{}, error) {
351 // For backwards compatibility.
352 // If AuthOptions.Scope was not set, try to determine it.
353 // This works well for common scenarios.
354 if opts.Scope == nil {
355 opts.Scope = new(AuthScope)
356 if opts.TenantID != "" {
357 opts.Scope.ProjectID = opts.TenantID
359 if opts.TenantName != "" {
360 opts.Scope.ProjectName = opts.TenantName
361 opts.Scope.DomainID = opts.DomainID
362 opts.Scope.DomainName = opts.DomainName
367 if opts.Scope.ProjectName != "" {
368 // ProjectName provided: either DomainID or DomainName must also be supplied.
369 // ProjectID may not be supplied.
370 if opts.Scope.DomainID == "" && opts.Scope.DomainName == "" {
371 return nil, ErrScopeDomainIDOrDomainName{}
373 if opts.Scope.ProjectID != "" {
374 return nil, ErrScopeProjectIDOrProjectName{}
377 if opts.Scope.DomainID != "" {
378 // ProjectName + DomainID
379 return map[string]interface{}{
380 "project": map[string]interface{}{
381 "name": &opts.Scope.ProjectName,
382 "domain": map[string]interface{}{"id": &opts.Scope.DomainID},
387 if opts.Scope.DomainName != "" {
388 // ProjectName + DomainName
389 return map[string]interface{}{
390 "project": map[string]interface{}{
391 "name": &opts.Scope.ProjectName,
392 "domain": map[string]interface{}{"name": &opts.Scope.DomainName},
396 } else if opts.Scope.ProjectID != "" {
397 // ProjectID provided. ProjectName, DomainID, and DomainName may not be provided.
398 if opts.Scope.DomainID != "" {
399 return nil, ErrScopeProjectIDAlone{}
401 if opts.Scope.DomainName != "" {
402 return nil, ErrScopeProjectIDAlone{}
406 return map[string]interface{}{
407 "project": map[string]interface{}{
408 "id": &opts.Scope.ProjectID,
411 } else if opts.Scope.DomainID != "" {
412 // DomainID provided. ProjectID, ProjectName, and DomainName may not be provided.
413 if opts.Scope.DomainName != "" {
414 return nil, ErrScopeDomainIDOrDomainName{}
418 return map[string]interface{}{
419 "domain": map[string]interface{}{
420 "id": &opts.Scope.DomainID,
423 } else if opts.Scope.DomainName != "" {
425 return map[string]interface{}{
426 "domain": map[string]interface{}{
427 "name": &opts.Scope.DomainName,
435 func (opts AuthOptions) CanReauth() bool {
436 return opts.AllowReauth