forked from go-goose/goose
-
Notifications
You must be signed in to change notification settings - Fork 0
/
identity.go
270 lines (247 loc) · 7.68 KB
/
identity.go
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
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
// goose/identity - Go package to interact with OpenStack Identity (Keystone) API.
package identity
import (
"fmt"
"net/http"
"os"
"reflect"
"strings"
goosehttp "gopkg.in/niedbalski/goose.v3/http"
"gopkg.in/niedbalski/goose.v3/logging"
)
// AuthMode defines the authentication method to use (see Auth*
// constants below).
type AuthMode int
const (
AuthLegacy = AuthMode(iota) // Legacy authentication
AuthUserPass // Username + password authentication
AuthKeyPair // Access/secret key pair authentication
AuthUserPassV3 // Username + password authentication (v3 API)
)
func (a AuthMode) String() string {
switch a {
case AuthKeyPair:
return "Access/Secret Key Authentication"
case AuthLegacy:
return "Legacy Authentication"
case AuthUserPass:
return "Username/password Authentication"
case AuthUserPassV3:
return "Username/password Authentication (Version 3)"
}
panic(fmt.Errorf("Unknown athentication type: %d", a))
}
type AuthOption struct {
Mode AuthMode
Endpoint string
}
type AuthOptions []AuthOption
type ServiceURLs map[string]string
// AuthDetails defines all the necessary information, needed for an
// authenticated session with OpenStack.
type AuthDetails struct {
Token string
TenantId string
UserId string
Domain string
RegionServiceURLs map[string]ServiceURLs // Service type to endpoint URLs for each region
}
// Credentials defines necessary parameters for authentication.
type Credentials struct {
URL string // The URL to authenticate against
User string // The username to authenticate as
Secrets string // The secrets to pass
Region string // Region to send requests to
TenantName string // The project name for this connection
Domain string `credentials:"optional"` // The domain for authorization (new in keystone v3)
UserDomain string `credentials:"optional"` // The owning domain for this user (new in keystone v3)
ProjectDomain string `credentials:"optional"` // The project domain for authorization (new in keystone v3)
}
// Authenticator is implemented by each authentication method.
type Authenticator interface {
Auth(creds *Credentials) (*AuthDetails, error)
}
// getConfig returns the value of the first available environment
// variable, among the given ones.
func getConfig(envVars []string) (value string) {
value = ""
for _, v := range envVars {
value = os.Getenv(v)
if value != "" {
break
}
}
return
}
// The following variables hold the names of environment variables
// that are used by CredentialsFromEnv to populate a Credentials
// value. The most preferred names are at the start of the slices.
var (
// CredEnvAuthURL is used for Credentials.URL.
CredEnvAuthURL = []string{
"OS_AUTH_URL",
}
// CredEnvUser is used for Credentials.User.
CredEnvUser = []string{
"OS_USERNAME",
"NOVA_USERNAME",
"OS_ACCESS_KEY",
"NOVA_API_KEY",
}
// CredEnvSecrets is used for Credentials.Secrets.
CredEnvSecrets = []string{
"OS_PASSWORD",
"NOVA_PASSWORD",
"OS_SECRET_KEY",
// Apparently some clients did use this.
"AWS_SECRET_ACCESS_KEY",
// This is manifestly a misspelling but we leave
// it here just in case anyone did actually use it.
"EC2_SECRET_KEYS",
}
// CredEnvRegion is used for Credentials.Region.
CredEnvRegion = []string{
"OS_REGION_NAME",
"NOVA_REGION",
}
// CredEnvTenantName is used for Credentials.TenantName.
CredEnvTenantName = []string{
"OS_PROJECT_NAME",
"OS_TENANT_NAME",
"NOVA_PROJECT_ID",
"OS_PROJECT_ID",
"OS_TENANT_ID",
}
// The following env vars are set according to what type
// of keystone v3 domain authorization is required.
CredEnvDefaultDomainName = []string{
"OS_DEFAULT_DOMAIN_NAME",
}
CredEnvProjectDomainName = []string{
"OS_PROJECT_DOMAIN_NAME",
}
CredEnvUserDomainName = []string{
"OS_USER_DOMAIN_NAME",
}
CredEnvDomainName = []string{
"OS_DOMAIN_NAME",
}
)
// CredentialsFromEnv creates and initializes the credentials from the
// environment variables.
func CredentialsFromEnv() *Credentials {
cred := &Credentials{
URL: getConfig(CredEnvAuthURL),
User: getConfig(CredEnvUser),
Secrets: getConfig(CredEnvSecrets),
Region: getConfig(CredEnvRegion),
TenantName: getConfig(CredEnvTenantName),
Domain: getConfig(CredEnvDomainName),
UserDomain: getConfig(CredEnvUserDomainName),
ProjectDomain: getConfig(CredEnvProjectDomainName),
}
defaultDomain := getConfig(CredEnvDefaultDomainName)
if defaultDomain != "" {
if cred.ProjectDomain == "" {
cred.ProjectDomain = defaultDomain
}
if cred.UserDomain == "" {
cred.UserDomain = defaultDomain
}
}
return cred
}
// CompleteCredentialsFromEnv gets and verifies all the required
// authentication parameters have values in the environment.
func CompleteCredentialsFromEnv() (cred *Credentials, err error) {
cred = CredentialsFromEnv()
v := reflect.ValueOf(cred).Elem()
t := v.Type()
for i := 0; i < v.NumField(); i++ {
f := v.Field(i)
tag := t.Field(i).Tag.Get("credentials")
if f.String() == "" && tag != "optional" {
err = fmt.Errorf("required environment variable not set for credentials attribute: %s", t.Field(i).Name)
}
}
return
}
// NewAuthenticator creates an authenticator matching the supplied AuthMode.
// The httpclient is allowed to be nil, the Authenticator will just use the
// default http.Client
func NewAuthenticator(authMode AuthMode, httpClient *goosehttp.Client) Authenticator {
if httpClient == nil {
httpClient = goosehttp.New()
}
switch authMode {
default:
panic(fmt.Errorf("Invalid identity authorisation mode: %d", authMode))
case AuthLegacy:
return &Legacy{client: httpClient}
case AuthUserPass:
return &UserPass{client: httpClient}
case AuthKeyPair:
return &KeyPair{client: httpClient}
case AuthUserPassV3:
return &V3UserPass{client: httpClient}
}
}
type authInformationLink struct {
Href string `json:"href"`
Rel string `json:"base"`
}
type authInformationMediaType struct {
Base string `json:"base"`
MediaType string `json:"type"`
}
type authInformationValue struct {
ID string `json:"id"`
Links []authInformationLink `json:"links"`
MediaTypes []authInformationMediaType `json:"media-types"`
Status string `json:"status"`
Updated string `json:"updates"`
}
type authInformationVersions struct {
Values []authInformationValue `json:"values"`
}
type authInformation struct {
Versions authInformationVersions `json:"versions"`
}
// FetchAuthOptions returns the authentication options advertised by this
// openstack.
func FetchAuthOptions(url string, client *goosehttp.Client, compatLogger logging.CompatLogger) (AuthOptions, error) {
var resp authInformation
req := goosehttp.RequestData{
RespValue: &resp,
ExpectedStatus: []int{
http.StatusMultipleChoices,
},
}
if err := client.JsonRequest("GET", url, "", &req, nil); err != nil {
return nil, fmt.Errorf("request available auth options: %v", err)
}
logger := logging.FromCompat(compatLogger)
var auths AuthOptions
if len(resp.Versions.Values) > 0 {
for _, version := range resp.Versions.Values {
// TODO(perrito666) figure more cases.
link := ""
if len(version.Links) > 0 {
link = version.Links[0].Href
}
var opt AuthOption
switch {
case strings.HasPrefix(version.ID, "v3"):
opt = AuthOption{Mode: AuthUserPassV3, Endpoint: link}
case strings.HasPrefix(version.ID, "v2"):
opt = AuthOption{Mode: AuthUserPass, Endpoint: link}
default:
if logger != nil {
logger.Debugf("Unknown authentication version %q\n", version.ID)
}
}
auths = append(auths, opt)
}
}
return auths, nil
}