mirror of
https://github.com/hashicorp/vault.git
synced 2025-09-19 04:41:09 +02:00
Co-authored-by: Kuba Wieczorek <kuba.wieczorek@hashicorp.com>
This commit is contained in:
parent
eaf949cb1f
commit
5d632efcf3
@ -153,6 +153,19 @@ func (pq *PriorityQueue) PopByKey(key string) (*Item, error) {
|
|||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PeekByKey returns the item with the given key without removing it from the queue.
|
||||||
|
func (pq *PriorityQueue) PeekByKey(id string) *Item {
|
||||||
|
pq.lock.RLock()
|
||||||
|
defer pq.lock.RUnlock()
|
||||||
|
|
||||||
|
item, ok := pq.dataMap[id]
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return item
|
||||||
|
}
|
||||||
|
|
||||||
// Len returns the number of items in the queue data structure. Do not use this
|
// Len returns the number of items in the queue data structure. Do not use this
|
||||||
// method directly on the queue, use PriorityQueue.Len() instead.
|
// method directly on the queue, use PriorityQueue.Len() instead.
|
||||||
func (q queue) Len() int { return len(q) }
|
func (q queue) Len() int { return len(q) }
|
||||||
|
@ -141,6 +141,67 @@ func TestPriorityQueue_Pop(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestPriorityQueue_PeekByKey tests the PeekByKey method of PriorityQueue.
|
||||||
|
// It verifies that PeekByKey returns the correct item without removing it from the queue,
|
||||||
|
// handles non-existing keys appropriately, works correctly on empty queues, and
|
||||||
|
// properly handles empty key strings.
|
||||||
|
func TestPriorityQueue_PeekByKey(t *testing.T) {
|
||||||
|
pq := New()
|
||||||
|
tc := testCases()
|
||||||
|
expectedLength := len(tc)
|
||||||
|
|
||||||
|
// Peek from empty queue
|
||||||
|
peekedItem := pq.PeekByKey("item-2")
|
||||||
|
if peekedItem != nil {
|
||||||
|
t.Fatal("expected nil when peeking from empty queue, got item")
|
||||||
|
}
|
||||||
|
if pq.Len() != 0 {
|
||||||
|
t.Fatalf("expected empty queue to remain size 0, got %d", pq.Len())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Push test items
|
||||||
|
for _, item := range tc {
|
||||||
|
if err := pq.Push(item); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Peek with empty key
|
||||||
|
peekedItem = pq.PeekByKey("")
|
||||||
|
if peekedItem != nil {
|
||||||
|
t.Fatal("expected nil for empty key, got item")
|
||||||
|
}
|
||||||
|
// Verify queue size unchanged
|
||||||
|
if pq.Len() != expectedLength {
|
||||||
|
t.Fatalf("expected queue size to remain %d, got %d", expectedLength, pq.Len())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Peek at non-existing item
|
||||||
|
peekedItem = pq.PeekByKey("non-existing-key")
|
||||||
|
if peekedItem != nil {
|
||||||
|
t.Fatal("expected nil for non-existing key, got item")
|
||||||
|
}
|
||||||
|
// Verify queue size unchanged
|
||||||
|
if pq.Len() != expectedLength {
|
||||||
|
t.Fatalf("expected queue size to remain %d, got %d", expectedLength, pq.Len())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Peek at a specific item
|
||||||
|
peekedItem = pq.PeekByKey("item-2")
|
||||||
|
if peekedItem == nil {
|
||||||
|
t.Fatal("expected to peek item-2, got nil")
|
||||||
|
}
|
||||||
|
// Verify queue size unchanged
|
||||||
|
if pq.Len() != expectedLength {
|
||||||
|
t.Fatalf("expected queue size to remain %d, got %d", expectedLength, pq.Len())
|
||||||
|
}
|
||||||
|
// Verify item still exists in queue
|
||||||
|
stillExists := pq.PeekByKey("item-2")
|
||||||
|
if stillExists == nil {
|
||||||
|
t.Fatal("item should still exist after peek")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestPriorityQueue_PopByKey(t *testing.T) {
|
func TestPriorityQueue_PopByKey(t *testing.T) {
|
||||||
pq := New()
|
pq := New()
|
||||||
|
|
||||||
|
@ -3993,14 +3993,34 @@ func (c *Core) loadLoginMFAConfigs(ctx context.Context) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MFACachedAuthResponse represents an authentication response that has been
|
||||||
|
// temporarily cached during a two-phase MFA (Multi-Factor Authentication) login flow.
|
||||||
|
//
|
||||||
|
// This struct is used when an MFA enforcement is configured and a login request
|
||||||
|
// lacks MFA credentials. Instead of completing the authentication immediately,
|
||||||
|
// Vault caches the auth response and returns an MFARequirement to the client.
|
||||||
|
// The client must then complete MFA validation using the mfa/validate endpoint
|
||||||
|
// to retrieve the cached authentication and receive their token.
|
||||||
|
//
|
||||||
|
// The cached response includes the original authentication details along with
|
||||||
|
// request metadata needed for MFA validation, such as the client's IP address
|
||||||
|
// for methods like Duo that require connection information.
|
||||||
|
//
|
||||||
|
// This struct is also used to cache self-enrollment TOTP MFA secrets generated
|
||||||
|
// during login when self-enrollment is enabled. This allows Vault to avoid
|
||||||
|
// persisting the newly generated MFA secret until it has been successfully used
|
||||||
|
// for validating an MFA-enforced login request.
|
||||||
type MFACachedAuthResponse struct {
|
type MFACachedAuthResponse struct {
|
||||||
CachedAuth *logical.Auth
|
CachedAuth *logical.Auth
|
||||||
RequestPath string
|
RequestPath string
|
||||||
RequestNSID string
|
RequestNSID string
|
||||||
RequestNSPath string
|
RequestNSPath string
|
||||||
RequestConnRemoteAddr string
|
RequestConnRemoteAddr string
|
||||||
TimeOfStorage time.Time
|
TimeOfStorage time.Time
|
||||||
RequestID string
|
RequestID string
|
||||||
|
SelfEnrollmentMFASecret *mfa.Secret
|
||||||
|
// Store the secret key string separately to avoid anyone accidentally persisting it on an Entity.
|
||||||
|
SelfEnrollmentMFASecretKey string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Core) setupCachedMFAResponseAuth() {
|
func (c *Core) setupCachedMFAResponseAuth() {
|
||||||
|
@ -165,6 +165,7 @@ func (i *IdentityStore) paths() []*framework.Path {
|
|||||||
mfaDuoPaths(i),
|
mfaDuoPaths(i),
|
||||||
mfaPingIDPaths(i),
|
mfaPingIDPaths(i),
|
||||||
mfaLoginEnforcementPaths(i),
|
mfaLoginEnforcementPaths(i),
|
||||||
|
mfaLoginEnterprisePaths(i),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
package vault
|
package vault
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -71,6 +72,20 @@ func (pq *LoginMFAPriorityQueue) PopByKey(reqID string) (*MFACachedAuthResponse,
|
|||||||
return item.Value.(*MFACachedAuthResponse), nil
|
return item.Value.(*MFACachedAuthResponse), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PeekByKey returns the item with the given key without removing it from the queue.
|
||||||
|
func (pq *LoginMFAPriorityQueue) PeekByKey(reqID string) (*MFACachedAuthResponse, error) {
|
||||||
|
pq.l.RLock()
|
||||||
|
defer pq.l.RUnlock()
|
||||||
|
|
||||||
|
item := pq.wrapped.PeekByKey(reqID)
|
||||||
|
if item == nil {
|
||||||
|
return nil, errors.New("no item found with the given request ID")
|
||||||
|
}
|
||||||
|
|
||||||
|
mfaResp := item.Value.(*MFACachedAuthResponse)
|
||||||
|
return mfaResp, nil
|
||||||
|
}
|
||||||
|
|
||||||
// RemoveExpiredMfaAuthResponse pops elements of the queue and check
|
// RemoveExpiredMfaAuthResponse pops elements of the queue and check
|
||||||
// if the entry has expired or not. If the entry has not expired, it pushes
|
// if the entry has expired or not. If the entry has not expired, it pushes
|
||||||
// back the entry to the queue. It returns false if there is no expired element
|
// back the entry to the queue. It returns false if there is no expired element
|
||||||
|
@ -87,6 +87,89 @@ func TestLoginMFAPriorityQueue_PushPopByKey(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestLoginMFAPriorityQueue_PeekByKey tests the PeekByKey method of
|
||||||
|
// LoginMFAPriorityQueue. It verifies that PeekByKey returns the correct item
|
||||||
|
// without removing it from the queue, returns errors on unhappy paths, handles
|
||||||
|
// non-existing keys appropriately, works correctly on empty queues, and
|
||||||
|
// properly handles empty key strings.
|
||||||
|
func TestLoginMFAPriorityQueue_PeekByKey(t *testing.T) {
|
||||||
|
pq := NewLoginMFAPriorityQueue()
|
||||||
|
tc := testCases()
|
||||||
|
expectedLength := len(tc)
|
||||||
|
|
||||||
|
// Peek from empty queue
|
||||||
|
peekedItem, err := pq.PeekByKey("item-2")
|
||||||
|
if peekedItem != nil {
|
||||||
|
t.Fatal("expected nil when peeking from empty queue, got item")
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expected an error when peeking from empty queue, got nil")
|
||||||
|
}
|
||||||
|
if pq.Len() != 0 {
|
||||||
|
t.Fatalf("expected empty queue to remain size 0, got %d", pq.Len())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Push test items
|
||||||
|
for _, item := range tc {
|
||||||
|
if err := pq.Push(item); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Peek with empty key
|
||||||
|
peekedItem, err = pq.PeekByKey("")
|
||||||
|
if peekedItem != nil {
|
||||||
|
t.Fatal("expected nil for empty key, got item")
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expected error when peeking with empty key , got nil")
|
||||||
|
}
|
||||||
|
// Verify queue size unchanged
|
||||||
|
if pq.Len() != expectedLength {
|
||||||
|
t.Fatalf("expected queue size to remain %d, got %d", expectedLength, pq.Len())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Peek at non-existing item
|
||||||
|
peekedItem, err = pq.PeekByKey("non-existing-key")
|
||||||
|
if peekedItem != nil {
|
||||||
|
t.Fatal("expected nil for non-existing key, got item")
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expected error when peeking with non-existing key, got nil")
|
||||||
|
}
|
||||||
|
// Verify queue size unchanged
|
||||||
|
if pq.Len() != expectedLength {
|
||||||
|
t.Fatalf("expected queue size to remain %d, got %d", expectedLength, pq.Len())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Peek at a specific item
|
||||||
|
peekedItem, err = pq.PeekByKey(tc[2].RequestID)
|
||||||
|
if peekedItem == nil {
|
||||||
|
t.Fatal("expected to peek item-2, got nil")
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("expected no error when peeking existing key, got", err)
|
||||||
|
}
|
||||||
|
if peekedItem.RequestID != tc[2].RequestID {
|
||||||
|
t.Fatal("expected the same item on subsequent peeks, got different items")
|
||||||
|
}
|
||||||
|
// Verify queue size unchanged
|
||||||
|
if pq.Len() != expectedLength {
|
||||||
|
t.Fatalf("expected queue size to remain %d, got %d", expectedLength, pq.Len())
|
||||||
|
}
|
||||||
|
// Verify item still exists in queue
|
||||||
|
stillExists, err := pq.PeekByKey(tc[2].RequestID)
|
||||||
|
if stillExists == nil {
|
||||||
|
t.Fatal("item should still exist after peek")
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("expected no error when peeking existing key for the second time, got", err)
|
||||||
|
}
|
||||||
|
if stillExists.RequestID != tc[2].RequestID {
|
||||||
|
t.Fatal("expected the same item on subsequent peeks, got different items")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestLoginMFARemoveStaleEntries(t *testing.T) {
|
func TestLoginMFARemoveStaleEntries(t *testing.T) {
|
||||||
pq := NewLoginMFAPriorityQueue()
|
pq := NewLoginMFAPriorityQueue()
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user