-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy pathauth.go
More file actions
144 lines (132 loc) · 5.63 KB
/
auth.go
File metadata and controls
144 lines (132 loc) · 5.63 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
// SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company
// SPDX-License-Identifier: Apache-2.0
// Package gophercloudext contains convenience functions for use with [Gophercloud].
// Its func NewProviderClient is specifically intended as a lightweight replacement for [gophercloud/utils] with fewer dependencies,
// but there are also other generalized utility functions.
//
// [Gophercloud]: https://github.com/gophercloud/gophercloud
// [gophercloud/utils]: https://github.com/gophercloud/utils
package gophercloudext
import (
"context"
"fmt"
"net/http"
"os"
"strings"
"github.com/gophercloud/gophercloud/v2"
"github.com/gophercloud/gophercloud/v2/openstack"
"github.com/sapcc/go-bits/osext"
)
// ClientOpts contains configuration for NewProviderClient().
type ClientOpts struct {
// EnvPrefix allows a custom environment variable prefix to be used.
// If not set, "OS_" is used.
EnvPrefix string
// HTTPClient is the ProviderClient's internal HTTP client.
// If not set, a fresh http.Client using http.DefaultTransport will be used.
//
// This is a weird behavior, but we cannot do better because
// gophercloud.ProviderClient insists on taking ownership of whatever is
// given here, so we cannot just give http.DefaultClient here.
HTTPClient *http.Client
// CustomizeAuthOptions is a callback that can be used to modify the
// constructed AuthOptions before they are passed to the ProviderClient.
//
// This is used in rare special cases, e.g. when an application needs to
// spawn clients with different token scopes for specific operations.
CustomizeAuthOptions func(*gophercloud.AuthOptions)
}
// NewProviderClient authenticates with OpenStack using the credentials found
// in the usual OS_* environment variables.
//
// Ref: https://docs.openstack.org/python-openstackclient/latest/cli/man/openstack.html
//
// This function has the same purpose as AuthenticatedClient from package
// github.com/gophercloud/utils/openstack/clientconfig, except for some
// differences that make it specifically suited for long-running server
// applications and remove functionality only needed for interactive use:
//
// - It always sets AllowReauth on the ProviderClient.
// - It does not support authenticating with a pre-existing Keystone token.
// - It does not support reading clouds.yaml files.
// - It does not support the old Keystone v2 authentication (only v3).
//
// Also, to simplify things, some legacy or fallback environment variables are
// not supported:
//
// - OS_TENANT_ID (give OS_PROJECT_ID instead)
// - OS_TENANT_NAME (give OS_PROJECT_NAME instead)
// - OS_DEFAULT_DOMAIN_ID (give OS_PROJECT_DOMAIN_ID and OS_USER_DOMAIN_ID instead)
// - OS_DEFAULT_DOMAIN_NAME (give OS_PROJECT_DOMAIN_NAME and OS_USER_DOMAIN_NAME instead)
// - OS_APPLICATION_CREDENTIAL_NAME (give OS_APPLICATION_CREDENTIAL_ID instead)
func NewProviderClient(ctx context.Context, optsPtr *ClientOpts) (*gophercloud.ProviderClient, gophercloud.EndpointOpts, error) {
// apply defaults to `opts`
var opts ClientOpts
if optsPtr != nil {
opts = *optsPtr
}
if opts.EnvPrefix == "" {
opts.EnvPrefix = "OS_"
}
if opts.HTTPClient == nil {
opts.HTTPClient = &http.Client{}
}
// expect an auth URL for v3
authURL, err := osext.NeedGetenv(opts.EnvPrefix + "AUTH_URL")
if err != nil {
return nil, gophercloud.EndpointOpts{}, err
}
if !strings.Contains(authURL, "/v3") {
return nil, gophercloud.EndpointOpts{}, fmt.Errorf(
"expected %sAUTH_URL to refer to Keystone v3, but got %s", opts.EnvPrefix, authURL,
)
}
// most other consistency checks are delegated to gophercloud.AuthOptions,
// so we just build an AuthOptions without checking a lot of stuff
scope := gophercloud.AuthScope{
ProjectID: os.Getenv(opts.EnvPrefix + "PROJECT_ID"),
ProjectName: os.Getenv(opts.EnvPrefix + "PROJECT_NAME"),
}
if scope.ProjectID == "" && scope.ProjectName == "" {
// not project scope, so might be domain scope
scope.DomainID = os.Getenv(opts.EnvPrefix + "DOMAIN_ID")
scope.DomainName = os.Getenv(opts.EnvPrefix + "DOMAIN_NAME")
if scope.DomainID == "" && scope.DomainName == "" {
// not domain scope either, so might be system scope
scope.System = os.Getenv(opts.EnvPrefix+"SYSTEM_SCOPE") != ""
}
} else {
// definitely project scope
scope.DomainID = os.Getenv(opts.EnvPrefix + "PROJECT_DOMAIN_ID")
scope.DomainName = os.Getenv(opts.EnvPrefix + "PROJECT_DOMAIN_NAME")
}
ao := gophercloud.AuthOptions{
IdentityEndpoint: authURL,
Username: os.Getenv(opts.EnvPrefix + "USERNAME"),
UserID: os.Getenv(opts.EnvPrefix + "USER_ID"),
DomainName: os.Getenv(opts.EnvPrefix + "USER_DOMAIN_NAME"),
DomainID: os.Getenv(opts.EnvPrefix + "USER_DOMAIN_ID"),
Password: os.Getenv(opts.EnvPrefix + "PASSWORD"),
AllowReauth: true,
Scope: &scope,
ApplicationCredentialID: os.Getenv(opts.EnvPrefix + "APPLICATION_CREDENTIAL_ID"),
ApplicationCredentialSecret: os.Getenv(opts.EnvPrefix + "APPLICATION_CREDENTIAL_SECRET"),
}
if opts.CustomizeAuthOptions != nil {
opts.CustomizeAuthOptions(&ao)
}
provider, err := openstack.NewClient(ao.IdentityEndpoint)
if err == nil {
provider.HTTPClient = *opts.HTTPClient
err = openstack.Authenticate(ctx, provider, ao)
}
if err != nil {
return nil, gophercloud.EndpointOpts{}, fmt.Errorf(
"cannot initialize OpenStack client from %s* variables: %w", opts.EnvPrefix, err)
}
eo := gophercloud.EndpointOpts{
Availability: gophercloud.Availability(os.Getenv(opts.EnvPrefix + "INTERFACE")),
Region: os.Getenv(opts.EnvPrefix + "REGION_NAME"),
}
return provider, eo, nil
}