internal/auth: add GrantsHash method to results
The new GrantsHash method can be used to track changes to a users grants across requests.
This commit is contained in:
1 parent
7c996d0e02
commit
7b5a4be0ab
2 files changed
+139
-4
No files matched your search
@@ -5,9 +5,13 @@ package auth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/binary"
|
||||
stderrors "errors"
|
||||
"fmt"
|
||||
"hash"
|
||||
"hash/fnv"
|
||||
"net/http"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -96,6 +100,9 @@ type VerifyResults struct {
|
||||
|
||||
// Used for additional verification
|
||||
v *verifier
|
||||
|
||||
// Used to generate a hash of all grants
|
||||
grants []perms.GrantTuple
|
||||
}
|
||||
|
||||
type verifier struct {
|
||||
@@ -271,10 +278,9 @@ func Verify(ctx context.Context, opt ...Option) (ret VerifyResults) {
|
||||
}
|
||||
|
||||
var authResults perms.ACLResults
|
||||
var grantTuples []perms.GrantTuple
|
||||
var userData template.Data
|
||||
var err error
|
||||
authResults, ret.UserData, ret.Scope, v.acl, grantTuples, err = v.performAuthCheck(ctx)
|
||||
authResults, ret.UserData, ret.Scope, v.acl, ret.grants, err = v.performAuthCheck(ctx)
|
||||
if err != nil {
|
||||
event.WriteError(ctx, op, err, event.WithInfoMsg("error performing authn/authz check"))
|
||||
return
|
||||
@@ -319,8 +325,8 @@ func Verify(ctx context.Context, opt ...Option) (ret VerifyResults) {
|
||||
}
|
||||
}
|
||||
|
||||
grants := make([]event.Grant, 0, len(grantTuples))
|
||||
for _, g := range grantTuples {
|
||||
grants := make([]event.Grant, 0, len(ret.grants))
|
||||
for _, g := range ret.grants {
|
||||
grants = append(grants, event.Grant{
|
||||
Grant: g.Grant,
|
||||
RoleId: g.RoleId,
|
||||
@@ -934,3 +940,52 @@ func (r *VerifyResults) ScopesAuthorizedForList(ctx context.Context, rootScopeId
|
||||
|
||||
return scopeResourceMap, nil
|
||||
}
|
||||
|
||||
// GrantsHash returns a stable hash of all the grants in the verify results.
|
||||
func (r *VerifyResults) GrantsHash(ctx context.Context) ([]byte, error) {
|
||||
const op = "auth.GrantsHash"
|
||||
var values []string
|
||||
for _, grant := range r.grants {
|
||||
values = append(values, grant.Grant, grant.RoleId, grant.ScopeId)
|
||||
}
|
||||
// Sort for deterministic output
|
||||
slices.Sort(values)
|
||||
hashVal, err := hashStrings(values...)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(ctx, err, op)
|
||||
}
|
||||
return binary.LittleEndian.AppendUint64(make([]byte, 0, 4), hashVal), nil
|
||||
}
|
||||
|
||||
func hashStrings(s ...string) (uint64, error) {
|
||||
hasher := fnv.New64()
|
||||
var h uint64
|
||||
var err error
|
||||
for _, current := range s {
|
||||
hasher.Reset()
|
||||
if _, err = hasher.Write([]byte(current)); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if h, err = hashUpdateOrdered(hasher, h, hasher.Sum64()); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
return h, nil
|
||||
}
|
||||
|
||||
// hashUpdateOrdered is taken directly from
|
||||
// https://github.com/mitchellh/hashstructure
|
||||
func hashUpdateOrdered(h hash.Hash64, a, b uint64) (uint64, error) {
|
||||
// For ordered updates, use a real hash function
|
||||
h.Reset()
|
||||
|
||||
e1 := binary.Write(h, binary.LittleEndian, a)
|
||||
e2 := binary.Write(h, binary.LittleEndian, b)
|
||||
if e1 != nil {
|
||||
return 0, e1
|
||||
}
|
||||
if e2 != nil {
|
||||
return 0, e2
|
||||
}
|
||||
return h.Sum64(), nil
|
||||
}
|
||||
@@ -4,6 +4,7 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
@@ -263,3 +264,82 @@ func TestVerify_AuditEvent(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGrantsHash(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
rw := db.New(conn)
|
||||
wrapper := db.TestWrapper(t)
|
||||
kms := kms.TestKms(t, conn, wrapper)
|
||||
tokenRepo, err := authtoken.NewRepository(ctx, rw, rw, kms)
|
||||
require.NoError(t, err)
|
||||
iamRepo := iam.TestRepo(t, conn, wrapper)
|
||||
tokenRepoFn := func() (*authtoken.Repository, error) {
|
||||
return tokenRepo, nil
|
||||
}
|
||||
iamRepoFn := func() (*iam.Repository, error) {
|
||||
return iamRepo, nil
|
||||
}
|
||||
serversRepoFn := func() (*server.Repository, error) {
|
||||
return server.NewRepository(ctx, rw, rw, kms)
|
||||
}
|
||||
|
||||
o, _ := iam.TestScopes(t, iamRepo)
|
||||
req := httptest.NewRequest("GET", "http://127.0.0.1/v1/scopes/"+o.GetPublicId(), nil)
|
||||
|
||||
at := authtoken.TestAuthToken(t, conn, kms, o.GetPublicId())
|
||||
encToken, err := authtoken.EncryptToken(context.Background(), kms, o.GetPublicId(), at.GetPublicId(), at.GetToken())
|
||||
require.NoError(t, err)
|
||||
tokValue := at.GetPublicId() + "_" + encToken
|
||||
|
||||
// Add values for authn/authz checking
|
||||
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", tokValue))
|
||||
requestInfo := authpb.RequestInfo{
|
||||
Path: req.URL.Path,
|
||||
Method: req.Method,
|
||||
}
|
||||
requestInfo.PublicId, requestInfo.EncryptedToken, requestInfo.TokenFormat = GetTokenFromRequest(context.TODO(), kms, req)
|
||||
verifierCtx := NewVerifierContext(ctx, iamRepoFn, tokenRepoFn, serversRepoFn, kms, &requestInfo)
|
||||
|
||||
// Create auth result from token
|
||||
res := Verify(verifierCtx, WithScopeId(o.GetPublicId()))
|
||||
hash1, err := res.GrantsHash(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Look up the user and create a second token
|
||||
user, _, err := iamRepo.LookupUser(ctx, at.IamUserId)
|
||||
require.NoError(t, err)
|
||||
at, err = tokenRepo.CreateAuthToken(ctx, user, at.AuthAccountId)
|
||||
require.NoError(t, err)
|
||||
encToken, err = authtoken.EncryptToken(context.Background(), kms, o.GetPublicId(), at.GetPublicId(), at.GetToken())
|
||||
require.NoError(t, err)
|
||||
tokValue = at.GetPublicId() + "_" + encToken
|
||||
|
||||
// Add values for authn/authz checking
|
||||
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", tokValue))
|
||||
requestInfo = authpb.RequestInfo{
|
||||
Path: req.URL.Path,
|
||||
Method: req.Method,
|
||||
}
|
||||
requestInfo.PublicId, requestInfo.EncryptedToken, requestInfo.TokenFormat = GetTokenFromRequest(context.TODO(), kms, req)
|
||||
verifierCtx = NewVerifierContext(ctx, iamRepoFn, tokenRepoFn, serversRepoFn, kms, &requestInfo)
|
||||
|
||||
// Create auth result from token
|
||||
res = Verify(verifierCtx, WithScopeId(o.GetPublicId()))
|
||||
hash2, err := res.GrantsHash(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.True(t, bytes.Equal(hash1, hash2))
|
||||
|
||||
// Change grants of the user
|
||||
newRole := iam.TestRole(t, conn, o.GetPublicId())
|
||||
_ = iam.TestRoleGrant(t, conn, newRole.GetPublicId(), "id=*;type=*;actions=list-keys")
|
||||
_ = iam.TestUserRole(t, conn, newRole.GetPublicId(), user.GetPublicId())
|
||||
|
||||
// Recreate auth result with new grants, should have a new hash
|
||||
res = Verify(verifierCtx, WithScopeId(o.GetPublicId()))
|
||||
hash3, err := res.GrantsHash(ctx)
|
||||
require.NoError(t, err)
|
||||
assert.False(t, bytes.Equal(hash1, hash3))
|
||||
assert.False(t, bytes.Equal(hash1, hash3))
|
||||
}
|
||||
Reference in new issue
Block a user