1package approle
2
3import (
4	"context"
5	"crypto/hmac"
6	"crypto/sha256"
7	"encoding/hex"
8	"fmt"
9	"time"
10
11	"github.com/hashicorp/errwrap"
12	uuid "github.com/hashicorp/go-uuid"
13	"github.com/hashicorp/vault/sdk/helper/cidrutil"
14	"github.com/hashicorp/vault/sdk/helper/locksutil"
15	"github.com/hashicorp/vault/sdk/logical"
16)
17
18// secretIDStorageEntry represents the information stored in storage
19// when a SecretID is created. The structure of the SecretID storage
20// entry is the same for all the types of SecretIDs generated.
21type secretIDStorageEntry struct {
22	// Accessor for the SecretID. It is a random UUID serving as
23	// a secondary index for the SecretID. This uniquely identifies
24	// the SecretID it belongs to, and hence can be used for listing
25	// and deleting SecretIDs. Accessors cannot be used as valid
26	// SecretIDs during login.
27	SecretIDAccessor string `json:"secret_id_accessor" mapstructure:"secret_id_accessor"`
28
29	// Number of times this SecretID can be used to perform the login
30	// operation
31	SecretIDNumUses int `json:"secret_id_num_uses" mapstructure:"secret_id_num_uses"`
32
33	// Duration after which this SecretID should expire. This is capped by
34	// the backend mount's max TTL value.
35	SecretIDTTL time.Duration `json:"secret_id_ttl" mapstructure:"secret_id_ttl"`
36
37	// The time when the SecretID was created
38	CreationTime time.Time `json:"creation_time" mapstructure:"creation_time"`
39
40	// The time when the SecretID becomes eligible for tidy operation.
41	// Tidying is performed by the PeriodicFunc of the backend which is 1
42	// minute apart.
43	ExpirationTime time.Time `json:"expiration_time" mapstructure:"expiration_time"`
44
45	// The time representing the last time this storage entry was modified
46	LastUpdatedTime time.Time `json:"last_updated_time" mapstructure:"last_updated_time"`
47
48	// Metadata that belongs to the SecretID
49	Metadata map[string]string `json:"metadata" mapstructure:"metadata"`
50
51	// CIDRList is a set of CIDR blocks that impose source address
52	// restrictions on the usage of SecretID
53	CIDRList []string `json:"cidr_list" mapstructure:"cidr_list"`
54
55	// TokenBoundCIDRs is a set of CIDR blocks that impose source address
56	// restrictions on the usage of the token generated by this SecretID
57	TokenBoundCIDRs []string `json:"token_cidr_list" mapstructure:"token_bound_cidrs"`
58
59	// This is a deprecated field
60	SecretIDNumUsesDeprecated int `json:"SecretIDNumUses" mapstructure:"SecretIDNumUses"`
61}
62
63// Represents the payload of the storage entry of the accessor that maps to a
64// unique SecretID. Note that SecretIDs should never be stored in plaintext
65// anywhere in the backend. SecretIDHMAC will be used as an index to fetch the
66// properties of the SecretID and to delete the SecretID.
67type secretIDAccessorStorageEntry struct {
68	// Hash of the SecretID which can be used to find the storage index at which
69	// properties of SecretID is stored.
70	SecretIDHMAC string `json:"secret_id_hmac" mapstructure:"secret_id_hmac"`
71}
72
73// verifyCIDRRoleSecretIDSubset checks if the CIDR blocks set on the secret ID
74// are a subset of CIDR blocks set on the role
75func verifyCIDRRoleSecretIDSubset(secretIDCIDRs []string, roleBoundCIDRList []string) error {
76	if len(secretIDCIDRs) != 0 {
77		// If there are no CIDR blocks on the role, then the subset
78		// requirement would be satisfied
79		if len(roleBoundCIDRList) != 0 {
80			subset, err := cidrutil.SubsetBlocks(roleBoundCIDRList, secretIDCIDRs)
81			if !subset || err != nil {
82				return errwrap.Wrapf(fmt.Sprintf("failed to verify subset relationship between CIDR blocks on the role %q and CIDR blocks on the secret ID %q: {{err}}", roleBoundCIDRList, secretIDCIDRs), err)
83			}
84		}
85	}
86
87	return nil
88}
89
90// Creates a SHA256 HMAC of the given 'value' using the given 'key' and returns
91// a hex encoded string.
92func createHMAC(key, value string) (string, error) {
93	if key == "" {
94		return "", fmt.Errorf("invalid HMAC key")
95	}
96	hm := hmac.New(sha256.New, []byte(key))
97	hm.Write([]byte(value))
98	return hex.EncodeToString(hm.Sum(nil)), nil
99}
100
101func (b *backend) secretIDLock(secretIDHMAC string) *locksutil.LockEntry {
102	return locksutil.LockForKey(b.secretIDLocks, secretIDHMAC)
103}
104
105func (b *backend) secretIDAccessorLock(secretIDAccessor string) *locksutil.LockEntry {
106	return locksutil.LockForKey(b.secretIDAccessorLocks, secretIDAccessor)
107}
108
109// nonLockedSecretIDStorageEntry fetches the secret ID properties from physical
110// storage. The entry will be indexed based on the given HMACs of both role
111// name and the secret ID. This method will not acquire secret ID lock to fetch
112// the storage entry. Locks need to be acquired before calling this method.
113func (b *backend) nonLockedSecretIDStorageEntry(ctx context.Context, s logical.Storage, roleSecretIDPrefix, roleNameHMAC, secretIDHMAC string) (*secretIDStorageEntry, error) {
114	if secretIDHMAC == "" {
115		return nil, fmt.Errorf("missing secret ID HMAC")
116	}
117
118	if roleNameHMAC == "" {
119		return nil, fmt.Errorf("missing role name HMAC")
120	}
121
122	// Prepare the storage index at which the secret ID will be stored
123	entryIndex := fmt.Sprintf("%s%s/%s", roleSecretIDPrefix, roleNameHMAC, secretIDHMAC)
124
125	entry, err := s.Get(ctx, entryIndex)
126	if err != nil {
127		return nil, err
128	}
129	if entry == nil {
130		return nil, nil
131	}
132
133	result := secretIDStorageEntry{}
134	if err := entry.DecodeJSON(&result); err != nil {
135		return nil, err
136	}
137
138	// TODO: Remove this upgrade bit in future releases
139	persistNeeded := false
140	if result.SecretIDNumUsesDeprecated != 0 {
141		if result.SecretIDNumUses == 0 ||
142			result.SecretIDNumUsesDeprecated < result.SecretIDNumUses {
143			result.SecretIDNumUses = result.SecretIDNumUsesDeprecated
144			persistNeeded = true
145		}
146		if result.SecretIDNumUses < result.SecretIDNumUsesDeprecated {
147			result.SecretIDNumUsesDeprecated = result.SecretIDNumUses
148			persistNeeded = true
149		}
150	}
151
152	if persistNeeded {
153		if err := b.nonLockedSetSecretIDStorageEntry(ctx, s, roleSecretIDPrefix, roleNameHMAC, secretIDHMAC, &result); err != nil {
154			return nil, errwrap.Wrapf("failed to upgrade role storage entry {{err}}", err)
155		}
156	}
157
158	return &result, nil
159}
160
161// nonLockedSetSecretIDStorageEntry creates or updates a secret ID entry at the
162// physical storage. The entry will be indexed based on the given HMACs of both
163// role name and the secret ID. This method will not acquire secret ID lock to
164// create/update the storage entry. Locks need to be acquired before calling
165// this method.
166func (b *backend) nonLockedSetSecretIDStorageEntry(ctx context.Context, s logical.Storage, roleSecretIDPrefix, roleNameHMAC, secretIDHMAC string, secretEntry *secretIDStorageEntry) error {
167	if roleSecretIDPrefix == "" {
168		return fmt.Errorf("missing secret ID prefix")
169	}
170	if secretIDHMAC == "" {
171		return fmt.Errorf("missing secret ID HMAC")
172	}
173
174	if roleNameHMAC == "" {
175		return fmt.Errorf("missing role name HMAC")
176	}
177
178	if secretEntry == nil {
179		return fmt.Errorf("nil secret entry")
180	}
181
182	entryIndex := fmt.Sprintf("%s%s/%s", roleSecretIDPrefix, roleNameHMAC, secretIDHMAC)
183
184	if entry, err := logical.StorageEntryJSON(entryIndex, secretEntry); err != nil {
185		return err
186	} else if err = s.Put(ctx, entry); err != nil {
187		return err
188	}
189
190	return nil
191}
192
193// registerSecretIDEntry creates a new storage entry for the given SecretID.
194func (b *backend) registerSecretIDEntry(ctx context.Context, s logical.Storage, roleName, secretID, hmacKey, roleSecretIDPrefix string, secretEntry *secretIDStorageEntry) (*secretIDStorageEntry, error) {
195	secretIDHMAC, err := createHMAC(hmacKey, secretID)
196	if err != nil {
197		return nil, errwrap.Wrapf("failed to create HMAC of secret ID: {{err}}", err)
198	}
199	roleNameHMAC, err := createHMAC(hmacKey, roleName)
200	if err != nil {
201		return nil, errwrap.Wrapf("failed to create HMAC of role_name: {{err}}", err)
202	}
203
204	lock := b.secretIDLock(secretIDHMAC)
205	lock.RLock()
206
207	entry, err := b.nonLockedSecretIDStorageEntry(ctx, s, roleSecretIDPrefix, roleNameHMAC, secretIDHMAC)
208	if err != nil {
209		lock.RUnlock()
210		return nil, err
211	}
212	if entry != nil {
213		lock.RUnlock()
214		return nil, fmt.Errorf("SecretID is already registered")
215	}
216
217	// If there isn't an entry for the secretID already, switch the read lock
218	// with a write lock and create an entry.
219	lock.RUnlock()
220	lock.Lock()
221	defer lock.Unlock()
222
223	// But before saving a new entry, check if the secretID entry was created during the lock switch.
224	entry, err = b.nonLockedSecretIDStorageEntry(ctx, s, roleSecretIDPrefix, roleNameHMAC, secretIDHMAC)
225	if err != nil {
226		return nil, err
227	}
228	if entry != nil {
229		return nil, fmt.Errorf("SecretID is already registered")
230	}
231
232	//
233	// Create a new entry for the SecretID
234	//
235
236	// Set the creation time for the SecretID
237	currentTime := time.Now()
238	secretEntry.CreationTime = currentTime
239	secretEntry.LastUpdatedTime = currentTime
240
241	// If SecretIDTTL is not specified or if it crosses the backend mount's limit,
242	// cap the expiration to backend's max. Otherwise, use it to determine the
243	// expiration time.
244	if secretEntry.SecretIDTTL < time.Duration(0) || secretEntry.SecretIDTTL > b.System().MaxLeaseTTL() {
245		secretEntry.ExpirationTime = currentTime.Add(b.System().MaxLeaseTTL())
246	} else if secretEntry.SecretIDTTL != time.Duration(0) {
247		// Set the ExpirationTime only if SecretIDTTL was set. SecretIDs should not
248		// expire by default.
249		secretEntry.ExpirationTime = currentTime.Add(secretEntry.SecretIDTTL)
250	}
251
252	// Before storing the SecretID, store its accessor.
253	if err := b.createSecretIDAccessorEntry(ctx, s, secretEntry, secretIDHMAC, roleSecretIDPrefix); err != nil {
254		return nil, err
255	}
256
257	if err := b.nonLockedSetSecretIDStorageEntry(ctx, s, roleSecretIDPrefix, roleNameHMAC, secretIDHMAC, secretEntry); err != nil {
258		return nil, err
259	}
260
261	return secretEntry, nil
262}
263
264// secretIDAccessorEntry is used to read the storage entry that maps an
265// accessor to a secret_id.
266func (b *backend) secretIDAccessorEntry(ctx context.Context, s logical.Storage, secretIDAccessor, roleSecretIDPrefix string) (*secretIDAccessorStorageEntry, error) {
267	if secretIDAccessor == "" {
268		return nil, fmt.Errorf("missing secretIDAccessor")
269	}
270
271	var result secretIDAccessorStorageEntry
272
273	// Create index entry, mapping the accessor to the token ID
274	salt, err := b.Salt(ctx)
275	if err != nil {
276		return nil, err
277	}
278	accessorPrefix := secretIDAccessorPrefix
279	if roleSecretIDPrefix == secretIDLocalPrefix {
280		accessorPrefix = secretIDAccessorLocalPrefix
281	}
282	entryIndex := accessorPrefix + salt.SaltID(secretIDAccessor)
283
284	accessorLock := b.secretIDAccessorLock(secretIDAccessor)
285	accessorLock.RLock()
286	defer accessorLock.RUnlock()
287
288	if entry, err := s.Get(ctx, entryIndex); err != nil {
289		return nil, err
290	} else if entry == nil {
291		return nil, nil
292	} else if err := entry.DecodeJSON(&result); err != nil {
293		return nil, err
294	}
295
296	return &result, nil
297}
298
299// createSecretIDAccessorEntry creates an identifier for the SecretID. A storage index,
300// mapping the accessor to the SecretID is also created. This method should
301// be called when the lock for the corresponding SecretID is held.
302func (b *backend) createSecretIDAccessorEntry(ctx context.Context, s logical.Storage, entry *secretIDStorageEntry, secretIDHMAC, roleSecretIDPrefix string) error {
303	// Create a random accessor
304	accessorUUID, err := uuid.GenerateUUID()
305	if err != nil {
306		return err
307	}
308	entry.SecretIDAccessor = accessorUUID
309
310	// Create index entry, mapping the accessor to the token ID
311	salt, err := b.Salt(ctx)
312	if err != nil {
313		return err
314	}
315
316	accessorPrefix := secretIDAccessorPrefix
317	if roleSecretIDPrefix == secretIDLocalPrefix {
318		accessorPrefix = secretIDAccessorLocalPrefix
319	}
320	entryIndex := accessorPrefix + salt.SaltID(entry.SecretIDAccessor)
321
322	accessorLock := b.secretIDAccessorLock(accessorUUID)
323	accessorLock.Lock()
324	defer accessorLock.Unlock()
325
326	if entry, err := logical.StorageEntryJSON(entryIndex, &secretIDAccessorStorageEntry{
327		SecretIDHMAC: secretIDHMAC,
328	}); err != nil {
329		return err
330	} else if err = s.Put(ctx, entry); err != nil {
331		return errwrap.Wrapf("failed to persist accessor index entry: {{err}}", err)
332	}
333
334	return nil
335}
336
337// deleteSecretIDAccessorEntry deletes the storage index mapping the accessor to a SecretID.
338func (b *backend) deleteSecretIDAccessorEntry(ctx context.Context, s logical.Storage, secretIDAccessor, roleSecretIDPrefix string) error {
339	salt, err := b.Salt(ctx)
340	if err != nil {
341		return err
342	}
343
344	accessorPrefix := secretIDAccessorPrefix
345	if roleSecretIDPrefix == secretIDLocalPrefix {
346		accessorPrefix = secretIDAccessorLocalPrefix
347	}
348	entryIndex := accessorPrefix + salt.SaltID(secretIDAccessor)
349
350	accessorLock := b.secretIDAccessorLock(secretIDAccessor)
351	accessorLock.Lock()
352	defer accessorLock.Unlock()
353
354	// Delete the accessor of the SecretID first
355	if err := s.Delete(ctx, entryIndex); err != nil {
356		return errwrap.Wrapf("failed to delete accessor storage entry: {{err}}", err)
357	}
358
359	return nil
360}
361
362// flushRoleSecrets deletes all the SecretIDs that belong to the given
363// RoleID.
364func (b *backend) flushRoleSecrets(ctx context.Context, s logical.Storage, roleName, hmacKey, roleSecretIDPrefix string) error {
365	roleNameHMAC, err := createHMAC(hmacKey, roleName)
366	if err != nil {
367		return errwrap.Wrapf("failed to create HMAC of role_name: {{err}}", err)
368	}
369
370	// Acquire the custom lock to perform listing of SecretIDs
371	b.secretIDListingLock.RLock()
372	defer b.secretIDListingLock.RUnlock()
373
374	secretIDHMACs, err := s.List(ctx, fmt.Sprintf("%s%s/", roleSecretIDPrefix, roleNameHMAC))
375	if err != nil {
376		return err
377	}
378	for _, secretIDHMAC := range secretIDHMACs {
379		// Acquire the lock belonging to the SecretID
380		lock := b.secretIDLock(secretIDHMAC)
381		lock.Lock()
382		entryIndex := fmt.Sprintf("%s%s/%s", roleSecretIDPrefix, roleNameHMAC, secretIDHMAC)
383		if err := s.Delete(ctx, entryIndex); err != nil {
384			lock.Unlock()
385			return errwrap.Wrapf(fmt.Sprintf("error deleting SecretID %q from storage: {{err}}", secretIDHMAC), err)
386		}
387		lock.Unlock()
388	}
389	return nil
390}
391