-
Notifications
You must be signed in to change notification settings - Fork 16
/
auth.go
445 lines (388 loc) · 14.7 KB
/
auth.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
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
package auth
import (
"github.com/contiv/auth_proxy/auth/ldap"
"github.com/contiv/auth_proxy/auth/local"
"github.com/contiv/auth_proxy/common"
auth_errors "github.com/contiv/auth_proxy/common/errors"
"github.com/contiv/auth_proxy/common/types"
"github.com/contiv/auth_proxy/db"
"github.com/contiv/auth_proxy/state"
log "github.com/Sirupsen/logrus"
uuid "github.com/satori/go.uuid"
)
// Authenticate authenticates the user against local DB or AD using the given credentials
// it returns a token which carries the role, capabilities, etc.
// params:
// username: local or AD username of the user
// password: password of the user
// return values:
// `Token` string on successful authentication otherwise ErrADConfigNotFound or any relevant error.
func Authenticate(username, password string) (string, error) {
userPrincipals, err := local.Authenticate(username, password)
if err == nil {
return generateToken(userPrincipals, username) // local authentication succeeded!
}
// Same username can be there in both local setup and LDAP.
// So, we try LDAP if `access is denied` from local authentication; coz, the same user(name) could also be part of LDAP.
if err == auth_errors.ErrUserNotFound || err == auth_errors.ErrAccessDenied {
fqdn, userPrincipals, err := ldap.Authenticate(username, password)
// fqdn represents fully qualified domain name of the given `username`
if err == nil {
return generateToken(userPrincipals, fqdn) // ldap authentication succeeded!
}
}
return "", err // error from authentication
}
// generateToken generates JWT(JSON Web Token) with the given user principals
// params:
// principals: user principals; []string containing LDAP groups or username based on the authentication type(LDAP/Local)
// username: local or AD username of the user
// return values:
// `Token` string on successful creation of JWT otherwise any relevant error from the subsequent function
func generateToken(principals []string, username string) (string, error) {
log.Debugf("generating token for user %q", username)
authZ, err := NewTokenWithClaims(principals) // create a new token with default `expiry` claim
if err != nil {
return "", err
}
// finally, add username to the token
authZ.AddClaim(UsernameClaimKey, username)
return authZ.Stringify()
}
//
// checkAccessClaim checks whether the granted role has desired level of access
// which is specified as a role itself.
//
// Parameters:
// granted: role that is present in claim
// desired: access that is required, specified as a role
//
// Return values:
// error: nil if 'granted' role has 'desired' access, otherwise:
// auth_errors.ErrUnauthorized: if 'granted' role doesn't have the 'desired' level of access
// auth_errors.ErrUnsupportedType: if 'desired' is not a role type
//
func checkAccessClaim(granted types.RoleType, desired interface{}) error {
switch desired.(type) {
// A role check
case types.RoleType:
desiredRole := desired.(types.RoleType)
// Role hierarchy implicies that any role that is less or equal
// in value has the desired level of access.
if granted <= desiredRole {
return nil
}
log.Error("access denied for granted role:", granted.String(),
" desired role:", desiredRole.String())
// TODO: Add a case to explicitly check capability
default:
log.Errorf("unsupported type for authorization check, got: %#v, expecting: types.RoleType", desired)
return auth_errors.ErrUnsupportedType
}
return auth_errors.ErrUnauthorized
}
//
// AddAuthorization stores authorization claim(s) for a specific named
// principal in the KV store. Success of various tenant related operations will
// depend on the named principal's capabilities, determined by the role that is
// associated with the claim.
// TODO: principal and tenant should exist
//
// Parameters:
// tenantName: tenant name, if specified
// role: type of role that specifies permissions associated with tenant or global permissions
// principalName: Name of user for whom the authorization is to be added,
// Can either be a local user or an LDAP group.
// isLocal: true if the named principal is a local user, false if ldap group.
//
// Return values:
// types.Authorization: new authorization that was added
// error: nil if successful, else
// auth_errors.ErrIllegalOperation if trying to add authorization to built-in
// local admin user.
//
func AddAuthorization(tenantName string, role types.RoleType, principalName string,
isLocal bool) (types.Authorization, error) {
defer common.Untrace(common.Trace())
var authz types.Authorization
var err error
if isLocal && types.Admin.String() == principalName {
return authz, auth_errors.ErrIllegalOperation
}
// Adding authorization is generally a two part operation
// - Adding tenant claim
// - Adding/updating role claim. This caches "highest" access role available for principal.
switch role {
// Short circuit to just adding/updating role claim since we don't care
// about tenant specific info for admins
case types.Admin:
authz, err = addUpdateRoleAuthorization(role, principalName, isLocal)
default:
authz, err = addTenantAuthorization(tenantName, role, principalName, isLocal)
if err == nil {
// Ignore role authorization claim
_, err = addUpdateRoleAuthorization(role, principalName, isLocal)
}
}
// Upstream callers should ignore value of authz if err != nil
return authz, err
}
// addTenantAuthorization stores authorization claim(s) for a
// specific named principal and a teant. Success of various tenant related
// operations will depend on the named principal's capabilities, determined by
// the role that is associated with the claim.
//
// Parameters:
// tenantName: tenant name
// role: type of role that specifies permissions associated with tenant
// principalName: Name of user for whom the authorization is to be added,
// Can either be a local user or an LDAP group.
// isLocal: true if the named principal is a local user, false if ldap group.
//
// Return values:
// TODO: errors.NonExistentLocalUserError: if a local user doesn't exist
// TODO: errors.NonExistentLdapGroupError: if ldap group doesn't exist
// : error from db.InsertAuthorization if adding a tenant authorization
// fails.
func addTenantAuthorization(tenantName string, role types.RoleType, principalName string,
isLocal bool) (types.Authorization, error) {
claimStr, err := GenerateClaimKey(types.Tenant(tenantName))
if err != nil {
log.Error("failed in generating claim:", err)
return types.Authorization{}, err
}
// create an authorization
sd, err := state.GetStateDriver()
if err != nil {
return types.Authorization{}, err
}
tenantAuthz := types.Authorization{
CommonState: types.CommonState{
StateDriver: sd,
ID: uuid.NewV4().String(),
},
UUID: uuid.NewV4().String(),
PrincipalName: principalName,
Local: isLocal,
ClaimKey: claimStr,
ClaimValue: role.String(),
}
// insert tenant authorization
if err := db.InsertAuthorization(&tenantAuthz); err != nil {
log.Error("failed in adding tenant claim:", err)
return types.Authorization{}, err
}
log.Debugf("successfully added tenant authorization %#v", tenantAuthz)
return tenantAuthz, nil
}
// addUpdaterRoleAuthorization adds/updates authorization claim for a specific
// named principal's role. This claim "caches" the highest privilege role claim
// for the principal. This authorization is used by APIs that only need to
// check for role claim (e.g., admin role claim for global object). Update is
// only perfomed if role specified is higher privilege than existing role claim
// for the principal.
//
// Parameters:
// role: type of role that specifies permissions associated with tenant
// principalName: Name of user for whom the authorization is to be added,
// Can either be a local user or an LDAP group.
// isLocal: true if the named principal is a local user, false if ldap group.
//
// Return values:
// TODO: errors.NonExistentLocalUserError: if a local user doesn't exist
// TODO: errors.NonExistentLdapGroupError: if ldap group doesn't exist
// : error from db.InsertAuthorization if adding/updating a role authorization
// fails.
// : error from db.ListAuthorizationsByClaimAndPrincipal if listing authorizations
// fails.
func addUpdateRoleAuthorization(role types.RoleType, principalName string,
isLocal bool) (types.Authorization, error) {
authz, err := db.ListAuthorizationsByClaimAndPrincipal(types.RoleClaimKey, principalName)
if err != nil {
log.Error("failed in listing role claim for principal ",
principalName, ", error:", err)
return types.Authorization{}, err
}
l := len(authz)
switch {
case l == 0:
// A role authz doesn't exist, add one
return addRoleAuthorization(principalName, isLocal, role)
case l == 1:
roleAuthz := authz[0]
grantedRole, err := types.Role(roleAuthz.ClaimValue)
// Invalid claim in authorizations db
if err != nil {
log.Errorf("illegal role in authorization %#v", roleAuthz)
return types.Authorization{}, err
}
// Need to update if role < grantedRole
if role < grantedRole {
roleAuthz.ClaimValue = role.String()
// Inserting an existing authz updates it
if err := db.InsertAuthorization(&roleAuthz); err != nil {
log.Error("failed in updating role claim:", err)
return types.Authorization{}, err
}
log.Info("updated role claim for principal ", principalName,
", previous:", grantedRole.String(), ", updated:", role.String())
return roleAuthz, nil
}
log.Info("not updating role claim for principal ", principalName,
", previous:", grantedRole.String(), ", requested:", role.String())
return roleAuthz, nil
default:
// There should only be one role authz claim, so return error
log.Error("multiple role authorizations found, expected 1, found ", l)
return types.Authorization{}, auth_errors.ErrInternal
}
}
//
// DeleteAuthorization deletes an authorization for a tenant.
// TODO: Also update role claim for principal if needed
//
// Parameters:
// authUUID: UUID of the tenant authorization object
//
// Return values:
// error: nil if successful, else
// types.UnauthorizedError: if caller isn't authorized to make this API call.
// auth_errors.ErrIllegalOperation: if attempting to delete authorization for
// built-in admin user.
// : error from db.DeleteAuthorization if deleting an authorization
// fails
//
func DeleteAuthorization(authUUID string) error {
defer common.Untrace(common.Trace())
// Return error if authorization doesn't exist
authorization, err := db.GetAuthorization(authUUID)
if err != nil {
log.Warn("failed to get authorization, err: ", err)
return err
}
// don't allow deletion of claims on the built-in "admin" account
if authorization.BelongsToBuiltInAdmin() {
log.Warn("can't delete authorizations on built-in admin user")
return auth_errors.ErrIllegalOperation
}
// delete authz from the KV store
if err := db.DeleteAuthorization(authUUID); err != nil {
log.Warn("failed to delete tenant authZ")
return err
}
log.Debug("successfully deleted authorization ", authUUID)
return nil
}
//
// GetAuthorization returns a specific authorization
// identified by the authzUUID
//
// Parameters:
// authzUUID : UUID of the authorization that needs to be returned
//
// Return values:
// error: nil if successful, else
// : error from db.GetAuthorization if auth lookup fails
func GetAuthorization(authzUUID string) (
types.Authorization, error) {
defer common.Untrace(common.Trace())
// Return error if authorization doesn't exist
authz, err := db.GetAuthorization(authzUUID)
if err != nil {
log.Warn("failed to get authorization; err:", err)
return types.Authorization{}, err
}
log.Debugf("Get authorization successful: %#v", authz)
return authz, nil
}
//
// ListAuthorizations returns all authorizations.
//
// Return values:
// error: nil if successful, else
// errors.ErrUnauthorized: if caller isn't authorized to make this API
// call.
// : error from db.ListAuthorizations if auth lookup fails
//
func ListAuthorizations() ([]types.Authorization, error) {
defer common.Untrace(common.Trace())
// read all authorizations
auths, err := db.ListAuthorizations()
if err != nil {
log.Error("failed to list all authorizations, err:", err)
return nil, err
}
return auths, nil
}
//
// addRoleAuthorization stores a role claim for a specific named principal in
// the KV store. This claim represents the highest privilege role available to
// a principal.
//
// Parameters:
// principalName: Name of user for whom the authorization is to be added,
// Can either be a local user or an LDAP group.
// isLocal: true if the named principal is a local user, false if ldap group.
// role: role that needs to be added as claim value
//
// Return values:
// types.Authorization: new authorization that was added
// error: nil if successful, else
// errors.NonExistentLocalUserError: if a local user doesn't exist
// errors.NonExistentLdapGroupError: if ldap group doesn't exist
// : error from db.InsertAuthorization if adding authorization
// fails.
//
func addRoleAuthorization(principalName string,
isLocal bool, role types.RoleType) (types.Authorization, error) {
defer common.Untrace(common.Trace())
// create an authorization
sd, err := state.GetStateDriver()
if err != nil {
return types.Authorization{}, err
}
roleAuthz := types.Authorization{
CommonState: types.CommonState{
StateDriver: sd,
ID: uuid.NewV4().String(),
},
UUID: uuid.NewV4().String(),
PrincipalName: principalName,
Local: isLocal,
ClaimKey: types.RoleClaimKey,
ClaimValue: role.String(),
}
// insert authorization
if err := db.InsertAuthorization(&roleAuthz); err != nil {
log.Errorf("failed in adding role authorization %#v, error:%#v", roleAuthz, err)
return types.Authorization{}, err
}
log.Debugf("successfully added role authorization %#v", roleAuthz)
return roleAuthz, nil
}
// AddDefaultUsers adds pre-defined users(admin,ops) to the system. Names of
// these users is same as that of role type (admin or ops). Also adds admin role
// authorization for admin user.
func AddDefaultUsers() error {
for _, user := range []types.RoleType{types.Admin, types.Ops} {
log.Infof("Adding local user %q to the system", user.String())
localUser := types.LocalUser{
Username: user.String(),
Disable: false,
Password: user.String(),
// FirstName, LastName = "" for built-in users
}
err := db.AddLocalUser(&localUser)
if err == auth_errors.ErrKeyExists {
continue
} else if err == nil {
if user.String() == types.Admin.String() {
// Add admin role claim for admin user.
addRoleAuthorization(types.Admin.String(), true, types.Admin)
}
continue
}
return err
}
return nil
}