1package userpass 2 3import ( 4 "context" 5 "crypto/subtle" 6 "fmt" 7 "strings" 8 9 "github.com/hashicorp/vault/sdk/framework" 10 "github.com/hashicorp/vault/sdk/helper/cidrutil" 11 "github.com/hashicorp/vault/sdk/helper/policyutil" 12 "github.com/hashicorp/vault/sdk/logical" 13 "golang.org/x/crypto/bcrypt" 14) 15 16func pathLogin(b *backend) *framework.Path { 17 return &framework.Path{ 18 Pattern: "login/" + framework.GenericNameRegex("username"), 19 Fields: map[string]*framework.FieldSchema{ 20 "username": &framework.FieldSchema{ 21 Type: framework.TypeString, 22 Description: "Username of the user.", 23 }, 24 25 "password": &framework.FieldSchema{ 26 Type: framework.TypeString, 27 Description: "Password for this user.", 28 }, 29 }, 30 31 Callbacks: map[logical.Operation]framework.OperationFunc{ 32 logical.UpdateOperation: b.pathLogin, 33 logical.AliasLookaheadOperation: b.pathLoginAliasLookahead, 34 }, 35 36 HelpSynopsis: pathLoginSyn, 37 HelpDescription: pathLoginDesc, 38 } 39} 40 41func (b *backend) pathLoginAliasLookahead(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) { 42 username := strings.ToLower(d.Get("username").(string)) 43 if username == "" { 44 return nil, fmt.Errorf("missing username") 45 } 46 47 return &logical.Response{ 48 Auth: &logical.Auth{ 49 Alias: &logical.Alias{ 50 Name: username, 51 }, 52 }, 53 }, nil 54} 55 56func (b *backend) pathLogin(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) { 57 username := strings.ToLower(d.Get("username").(string)) 58 59 password := d.Get("password").(string) 60 if password == "" { 61 return nil, fmt.Errorf("missing password") 62 } 63 64 // Get the user and validate auth 65 user, userError := b.user(ctx, req.Storage, username) 66 67 var userPassword []byte 68 var legacyPassword bool 69 // If there was an error or it's nil, we fake a password for the bcrypt 70 // check so as not to have a timing leak. Specifics of the underlying 71 // storage still leaks a bit but generally much more in the noise compared 72 // to bcrypt. 73 if user != nil && userError == nil { 74 if user.PasswordHash == nil { 75 userPassword = []byte(user.Password) 76 legacyPassword = true 77 } else { 78 userPassword = user.PasswordHash 79 } 80 } else { 81 // This is still acceptable as bcrypt will still make sure it takes 82 // a long time, it's just nicer to be random if possible 83 userPassword = []byte("dummy") 84 } 85 86 // Check for a password match. Check for a hash collision for Vault 0.2+, 87 // but handle the older legacy passwords with a constant time comparison. 88 passwordBytes := []byte(password) 89 if !legacyPassword { 90 if err := bcrypt.CompareHashAndPassword(userPassword, passwordBytes); err != nil { 91 return logical.ErrorResponse("invalid username or password"), nil 92 } 93 } else { 94 if subtle.ConstantTimeCompare(userPassword, passwordBytes) != 1 { 95 return logical.ErrorResponse("invalid username or password"), nil 96 } 97 } 98 99 if userError != nil { 100 return nil, userError 101 } 102 if user == nil { 103 return logical.ErrorResponse("invalid username or password"), nil 104 } 105 106 // Check for a CIDR match. 107 if len(user.TokenBoundCIDRs) > 0 { 108 if req.Connection == nil { 109 b.Logger().Warn("token bound CIDRs found but no connection information available for validation") 110 return nil, logical.ErrPermissionDenied 111 } 112 if !cidrutil.RemoteAddrIsOk(req.Connection.RemoteAddr, user.TokenBoundCIDRs) { 113 return nil, logical.ErrPermissionDenied 114 } 115 } 116 117 auth := &logical.Auth{ 118 Metadata: map[string]string{ 119 "username": username, 120 }, 121 DisplayName: username, 122 Alias: &logical.Alias{ 123 Name: username, 124 }, 125 } 126 user.PopulateTokenAuth(auth) 127 128 return &logical.Response{ 129 Auth: auth, 130 }, nil 131} 132 133func (b *backend) pathLoginRenew(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) { 134 // Get the user 135 user, err := b.user(ctx, req.Storage, req.Auth.Metadata["username"]) 136 if err != nil { 137 return nil, err 138 } 139 if user == nil { 140 // User no longer exists, do not renew 141 return nil, nil 142 } 143 144 if !policyutil.EquivalentPolicies(user.TokenPolicies, req.Auth.TokenPolicies) { 145 return nil, fmt.Errorf("policies have changed, not renewing") 146 } 147 148 resp := &logical.Response{Auth: req.Auth} 149 resp.Auth.Period = user.TokenPeriod 150 resp.Auth.TTL = user.TokenTTL 151 resp.Auth.MaxTTL = user.TokenMaxTTL 152 return resp, nil 153} 154 155const pathLoginSyn = ` 156Log in with a username and password. 157` 158 159const pathLoginDesc = ` 160This endpoint authenticates using a username and password. 161` 162