-
Notifications
You must be signed in to change notification settings - Fork 4.2k
/
path_login.go
158 lines (137 loc) · 4.27 KB
/
path_login.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
package userpass
import (
"context"
"crypto/subtle"
"fmt"
"strings"
"github.com/hashicorp/vault/helper/cidrutil"
"github.com/hashicorp/vault/helper/policyutil"
"github.com/hashicorp/vault/logical"
"github.com/hashicorp/vault/logical/framework"
"golang.org/x/crypto/bcrypt"
)
func pathLogin(b *backend) *framework.Path {
return &framework.Path{
Pattern: "login/" + framework.GenericNameRegex("username"),
Fields: map[string]*framework.FieldSchema{
"username": &framework.FieldSchema{
Type: framework.TypeString,
Description: "Username of the user.",
},
"password": &framework.FieldSchema{
Type: framework.TypeString,
Description: "Password for this user.",
},
},
Callbacks: map[logical.Operation]framework.OperationFunc{
logical.UpdateOperation: b.pathLogin,
logical.AliasLookaheadOperation: b.pathLoginAliasLookahead,
},
HelpSynopsis: pathLoginSyn,
HelpDescription: pathLoginDesc,
}
}
func (b *backend) pathLoginAliasLookahead(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
username := strings.ToLower(d.Get("username").(string))
if username == "" {
return nil, fmt.Errorf("missing username")
}
return &logical.Response{
Auth: &logical.Auth{
Alias: &logical.Alias{
Name: username,
},
},
}, nil
}
func (b *backend) pathLogin(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
username := strings.ToLower(d.Get("username").(string))
password := d.Get("password").(string)
if password == "" {
return nil, fmt.Errorf("missing password")
}
// Get the user and validate auth
user, userError := b.user(ctx, req.Storage, username)
var userPassword []byte
var legacyPassword bool
// If there was an error or it's nil, we fake a password for the bcrypt
// check so as not to have a timing leak. Specifics of the underlying
// storage still leaks a bit but generally much more in the noise compared
// to bcrypt.
if user != nil && userError == nil {
if user.PasswordHash == nil {
userPassword = []byte(user.Password)
legacyPassword = true
} else {
userPassword = user.PasswordHash
}
} else {
// This is still acceptable as bcrypt will still make sure it takes
// a long time, it's just nicer to be random if possible
userPassword = []byte("dummy")
}
// Check for a password match. Check for a hash collision for Vault 0.2+,
// but handle the older legacy passwords with a constant time comparison.
passwordBytes := []byte(password)
if !legacyPassword {
if err := bcrypt.CompareHashAndPassword(userPassword, passwordBytes); err != nil {
return logical.ErrorResponse("invalid username or password"), nil
}
} else {
if subtle.ConstantTimeCompare(userPassword, passwordBytes) != 1 {
return logical.ErrorResponse("invalid username or password"), nil
}
}
if userError != nil {
return nil, userError
}
if user == nil {
return logical.ErrorResponse("invalid username or password"), nil
}
// Check for a CIDR match.
if !cidrutil.RemoteAddrIsOk(req.Connection.RemoteAddr, user.BoundCIDRs) {
return logical.ErrorResponse("login request originated from invalid CIDR"), nil
}
return &logical.Response{
Auth: &logical.Auth{
Policies: user.Policies,
Metadata: map[string]string{
"username": username,
},
DisplayName: username,
LeaseOptions: logical.LeaseOptions{
TTL: user.TTL,
MaxTTL: user.MaxTTL,
Renewable: true,
},
Alias: &logical.Alias{
Name: username,
},
BoundCIDRs: user.BoundCIDRs,
},
}, nil
}
func (b *backend) pathLoginRenew(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
// Get the user
user, err := b.user(ctx, req.Storage, req.Auth.Metadata["username"])
if err != nil {
return nil, err
}
if user == nil {
// User no longer exists, do not renew
return nil, nil
}
if !policyutil.EquivalentPolicies(user.Policies, req.Auth.TokenPolicies) {
return nil, fmt.Errorf("policies have changed, not renewing")
}
resp := &logical.Response{Auth: req.Auth}
resp.Auth.TTL = user.TTL
resp.Auth.MaxTTL = user.MaxTTL
return resp, nil
}
const pathLoginSyn = `
Log in with a username and password.
`
const pathLoginDesc = `
This endpoint authenticates using a username and password.
`