1package approle
2
3import (
4	"context"
5	"fmt"
6	"strings"
7	"time"
8
9	"github.com/hashicorp/errwrap"
10	"github.com/hashicorp/vault/sdk/framework"
11	"github.com/hashicorp/vault/sdk/helper/cidrutil"
12	"github.com/hashicorp/vault/sdk/helper/parseutil"
13	"github.com/hashicorp/vault/sdk/logical"
14)
15
16func pathLogin(b *backend) *framework.Path {
17	return &framework.Path{
18		Pattern: "login$",
19		Fields: map[string]*framework.FieldSchema{
20			"role_id": &framework.FieldSchema{
21				Type:        framework.TypeString,
22				Description: "Unique identifier of the Role. Required to be supplied when the 'bind_secret_id' constraint is set.",
23			},
24			"secret_id": &framework.FieldSchema{
25				Type:        framework.TypeString,
26				Default:     "",
27				Description: "SecretID belong to the App role",
28			},
29		},
30		Callbacks: map[logical.Operation]framework.OperationFunc{
31			logical.UpdateOperation:         b.pathLoginUpdate,
32			logical.AliasLookaheadOperation: b.pathLoginUpdateAliasLookahead,
33		},
34		HelpSynopsis:    pathLoginHelpSys,
35		HelpDescription: pathLoginHelpDesc,
36	}
37}
38
39func (b *backend) pathLoginUpdateAliasLookahead(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
40	roleID := strings.TrimSpace(data.Get("role_id").(string))
41	if roleID == "" {
42		return nil, fmt.Errorf("missing role_id")
43	}
44
45	return &logical.Response{
46		Auth: &logical.Auth{
47			Alias: &logical.Alias{
48				Name: roleID,
49			},
50		},
51	}, nil
52}
53
54// Returns the Auth object indicating the authentication and authorization information
55// if the credentials provided are validated by the backend.
56func (b *backend) pathLoginUpdate(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
57
58	// RoleID must be supplied during every login
59	roleID := strings.TrimSpace(data.Get("role_id").(string))
60	if roleID == "" {
61		return logical.ErrorResponse("missing role_id"), nil
62	}
63
64	// Look for the storage entry that maps the roleID to role
65	roleIDIndex, err := b.roleIDEntry(ctx, req.Storage, roleID)
66	if err != nil {
67		return nil, err
68	}
69	if roleIDIndex == nil {
70		return logical.ErrorResponse("invalid role ID"), nil
71	}
72
73	roleName := roleIDIndex.Name
74
75	roleLock := b.roleLock(roleName)
76	roleLock.RLock()
77
78	role, err := b.roleEntry(ctx, req.Storage, roleName)
79	roleLock.RUnlock()
80	if err != nil {
81		return nil, err
82	}
83	if role == nil {
84		return logical.ErrorResponse("invalid role ID"), nil
85	}
86
87	metadata := make(map[string]string)
88	var entry *secretIDStorageEntry
89	if role.BindSecretID {
90		secretID := strings.TrimSpace(data.Get("secret_id").(string))
91		if secretID == "" {
92			return logical.ErrorResponse("missing secret_id"), nil
93		}
94
95		secretIDHMAC, err := createHMAC(role.HMACKey, secretID)
96		if err != nil {
97			return nil, errwrap.Wrapf("failed to create HMAC of secret_id: {{err}}", err)
98		}
99
100		roleNameHMAC, err := createHMAC(role.HMACKey, role.name)
101		if err != nil {
102			return nil, errwrap.Wrapf("failed to create HMAC of role_name: {{err}}", err)
103		}
104
105		entryIndex := fmt.Sprintf("%s%s/%s", role.SecretIDPrefix, roleNameHMAC, secretIDHMAC)
106
107		secretIDLock := b.secretIDLock(secretIDHMAC)
108		secretIDLock.RLock()
109
110		unlockFunc := secretIDLock.RUnlock
111		defer func() {
112			unlockFunc()
113		}()
114
115		entry, err = b.nonLockedSecretIDStorageEntry(ctx, req.Storage, role.SecretIDPrefix, roleNameHMAC, secretIDHMAC)
116		if err != nil {
117			return nil, err
118		}
119		if entry == nil {
120			return logical.ErrorResponse("invalid secret id"), nil
121		}
122
123		// If a secret ID entry does not have a corresponding accessor
124		// entry, revoke the secret ID immediately
125		accessorEntry, err := b.secretIDAccessorEntry(ctx, req.Storage, entry.SecretIDAccessor, role.SecretIDPrefix)
126		if err != nil {
127			return nil, errwrap.Wrapf("failed to read secret ID accessor entry: {{err}}", err)
128		}
129		if accessorEntry == nil {
130			// Switch the locks and recheck the conditions
131			secretIDLock.RUnlock()
132			secretIDLock.Lock()
133			unlockFunc = secretIDLock.Unlock
134
135			entry, err = b.nonLockedSecretIDStorageEntry(ctx, req.Storage, role.SecretIDPrefix, roleNameHMAC, secretIDHMAC)
136			if err != nil {
137				return nil, err
138			}
139			if entry == nil {
140				return logical.ErrorResponse("invalid secret id"), nil
141			}
142
143			accessorEntry, err := b.secretIDAccessorEntry(ctx, req.Storage, entry.SecretIDAccessor, role.SecretIDPrefix)
144			if err != nil {
145				return nil, errwrap.Wrapf("failed to read secret ID accessor entry: {{err}}", err)
146			}
147
148			if accessorEntry == nil {
149				if err := req.Storage.Delete(ctx, entryIndex); err != nil {
150					return nil, errwrap.Wrapf(fmt.Sprintf("error deleting secret ID %q from storage: {{err}}", secretIDHMAC), err)
151				}
152			}
153			return logical.ErrorResponse("invalid secret id"), nil
154		}
155
156		switch {
157		case entry.SecretIDNumUses == 0:
158			//
159			// SecretIDNumUses will be zero only if the usage limit was not set at all,
160			// in which case, the SecretID will remain to be valid as long as it is not
161			// expired.
162			//
163
164			// Ensure that the CIDRs on the secret ID are still a subset of that of
165			// role's
166			err = verifyCIDRRoleSecretIDSubset(entry.CIDRList, role.SecretIDBoundCIDRs)
167			if err != nil {
168				return nil, err
169			}
170
171			// If CIDR restrictions are present on the secret ID, check if the
172			// source IP complies to it
173			if len(entry.CIDRList) != 0 {
174				if req.Connection == nil || req.Connection.RemoteAddr == "" {
175					return nil, fmt.Errorf("failed to get connection information")
176				}
177
178				belongs, err := cidrutil.IPBelongsToCIDRBlocksSlice(req.Connection.RemoteAddr, entry.CIDRList)
179				if !belongs || err != nil {
180					return logical.ErrorResponse(errwrap.Wrapf(fmt.Sprintf("source address %q unauthorized through CIDR restrictions on the secret ID: {{err}}", req.Connection.RemoteAddr), err).Error()), nil
181				}
182			}
183		default:
184			//
185			// If the SecretIDNumUses is non-zero, it means that its use-count should be updated
186			// in the storage. Switch the lock from a `read` to a `write` and update
187			// the storage entry.
188			//
189
190			secretIDLock.RUnlock()
191			secretIDLock.Lock()
192			unlockFunc = secretIDLock.Unlock
193
194			// Lock switching may change the data. Refresh the contents.
195			entry, err = b.nonLockedSecretIDStorageEntry(ctx, req.Storage, role.SecretIDPrefix, roleNameHMAC, secretIDHMAC)
196			if err != nil {
197				return nil, err
198			}
199			if entry == nil {
200				return logical.ErrorResponse(fmt.Sprintf("invalid secret_id %q", secretID)), nil
201			}
202
203			// If there exists a single use left, delete the SecretID entry from
204			// the storage but do not fail the validation request. Subsequent
205			// requests to use the same SecretID will fail.
206			if entry.SecretIDNumUses == 1 {
207				// Delete the secret IDs accessor first
208				err = b.deleteSecretIDAccessorEntry(ctx, req.Storage, entry.SecretIDAccessor, role.SecretIDPrefix)
209				if err != nil {
210					return nil, err
211				}
212				err = req.Storage.Delete(ctx, entryIndex)
213				if err != nil {
214					return nil, errwrap.Wrapf("failed to delete secret ID: {{err}}", err)
215				}
216			} else {
217				// If the use count is greater than one, decrement it and update the last updated time.
218				entry.SecretIDNumUses -= 1
219				entry.LastUpdatedTime = time.Now()
220
221				sEntry, err := logical.StorageEntryJSON(entryIndex, &entry)
222				if err != nil {
223					return nil, err
224				}
225
226				err = req.Storage.Put(ctx, sEntry)
227				if err != nil {
228					return nil, err
229				}
230			}
231
232			// Ensure that the CIDRs on the secret ID are still a subset of that of
233			// role's
234			err = verifyCIDRRoleSecretIDSubset(entry.CIDRList, role.SecretIDBoundCIDRs)
235			if err != nil {
236				return nil, err
237			}
238
239			// If CIDR restrictions are present on the secret ID, check if the
240			// source IP complies to it
241			if len(entry.CIDRList) != 0 {
242				if req.Connection == nil || req.Connection.RemoteAddr == "" {
243					return nil, fmt.Errorf("failed to get connection information")
244				}
245
246				belongs, err := cidrutil.IPBelongsToCIDRBlocksSlice(req.Connection.RemoteAddr, entry.CIDRList)
247				if err != nil || !belongs {
248					return logical.ErrorResponse(errwrap.Wrapf(fmt.Sprintf("source address %q unauthorized by CIDR restrictions on the secret ID: {{err}}", req.Connection.RemoteAddr), err).Error()), nil
249				}
250			}
251		}
252
253		metadata = entry.Metadata
254	}
255
256	if len(role.SecretIDBoundCIDRs) != 0 {
257		if req.Connection == nil || req.Connection.RemoteAddr == "" {
258			return nil, fmt.Errorf("failed to get connection information")
259		}
260		belongs, err := cidrutil.IPBelongsToCIDRBlocksSlice(req.Connection.RemoteAddr, role.SecretIDBoundCIDRs)
261		if err != nil || !belongs {
262			return logical.ErrorResponse(errwrap.Wrapf(fmt.Sprintf("source address %q unauthorized by CIDR restrictions on the role: {{err}}", req.Connection.RemoteAddr), err).Error()), nil
263		}
264	}
265
266	// Parse the CIDRs we should be binding the token to.
267	tokenBoundCIDRs := role.TokenBoundCIDRs
268	if entry != nil && len(entry.TokenBoundCIDRs) > 0 {
269		tokenBoundCIDRs, err = parseutil.ParseAddrs(entry.TokenBoundCIDRs)
270		if err != nil {
271			return logical.ErrorResponse(err.Error()), nil
272		}
273	}
274
275	// For some reason, if metadata was set to nil while processing secret ID
276	// binding, ensure that it is initialized again to avoid a panic.
277	if metadata == nil {
278		metadata = make(map[string]string)
279	}
280
281	// Always include the role name, for later filtering
282	metadata["role_name"] = role.name
283
284	auth := &logical.Auth{
285		InternalData: map[string]interface{}{
286			"role_name": role.name,
287		},
288		Metadata: metadata,
289		Alias: &logical.Alias{
290			Name: role.RoleID,
291		},
292	}
293	role.PopulateTokenAuth(auth)
294
295	// Allow for overridden token bound CIDRs
296	auth.BoundCIDRs = tokenBoundCIDRs
297
298	return &logical.Response{
299		Auth: auth,
300	}, nil
301}
302
303// Invoked when the token issued by this backend is attempting a renewal.
304func (b *backend) pathLoginRenew(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
305	roleName := req.Auth.InternalData["role_name"].(string)
306	if roleName == "" {
307		return nil, fmt.Errorf("failed to fetch role_name during renewal")
308	}
309
310	lock := b.roleLock(roleName)
311	lock.RLock()
312	defer lock.RUnlock()
313
314	// Ensure that the Role still exists.
315	role, err := b.roleEntry(ctx, req.Storage, roleName)
316	if err != nil {
317		return nil, errwrap.Wrapf(fmt.Sprintf("failed to validate role %q during renewal: {{err}}", roleName), err)
318	}
319	if role == nil {
320		return nil, fmt.Errorf("role %q does not exist during renewal", roleName)
321	}
322
323	resp := &logical.Response{Auth: req.Auth}
324	resp.Auth.TTL = role.TokenTTL
325	resp.Auth.MaxTTL = role.TokenMaxTTL
326	resp.Auth.Period = role.TokenPeriod
327	return resp, nil
328}
329
330const pathLoginHelpSys = "Issue a token based on the credentials supplied"
331
332const pathLoginHelpDesc = `
333While the credential 'role_id' is required at all times,
334other credentials required depends on the properties App role
335to which the 'role_id' belongs to. The 'bind_secret_id'
336constraint (enabled by default) on the App role requires the
337'secret_id' credential to be presented.
338
339'role_id' is fetched using the 'role/<role_name>/role_id'
340endpoint and 'secret_id' is fetched using the 'role/<role_name>/secret_id'
341endpoint.`
342