fix(audit): populate email/name in audit events

pull/6692/head
Louis Ruch 7 days ago
parent 2c043d82fa
commit fe35f74ee3

@ -289,7 +289,7 @@ func (b *Server) CreateDevLdapAuthMethod(ctx context.Context) error {
createUserFn := func(userName, passwd string, withMembersOf []string) *gldap.Entry {
entryAttrs := map[string][]string{
"name": {userName},
"fullName": {userName},
"email": {fmt.Sprintf("%s@localhost", userName)},
"password": {passwd},
}

@ -287,7 +287,6 @@ func Verify(ctx context.Context, resourceType resource.Type, opt ...Option) (ret
resourcesToFetchGrants := append([]resource.Type{resourceType}, opts.withFetchAdditionalResourceGrants...)
var authResults perms.ACLResults
var userData template.Data
var err error
authResults, ret.UserData, ret.Scope, v.acl, ret.grants, err = v.performAuthCheck(ctx, resourcesToFetchGrants, opts.withRecursive)
if err != nil {
@ -345,17 +344,17 @@ func Verify(ctx context.Context, resourceType resource.Type, opt ...Option) (ret
ea.UserInfo = &event.UserInfo{
UserId: ret.UserId,
}
if userData.Account.Id != nil {
ea.UserInfo.AuthAccountId = *userData.Account.Id
if ret.UserData.Account.Id != nil {
ea.UserInfo.AuthAccountId = *ret.UserData.Account.Id
}
ea.GrantsInfo = &event.GrantsInfo{
Grants: grants,
}
if userData.User.FullName != nil {
ea.UserName = *userData.User.FullName
if ret.UserData.User.FullName != nil {
ea.UserName = *ret.UserData.User.FullName
}
if userData.User.Email != nil {
ea.UserEmail = *userData.User.Email
if ret.UserData.User.Email != nil {
ea.UserEmail = *ret.UserData.User.Email
}
if reqInfo != nil {

@ -14,6 +14,8 @@ import (
"testing"
"github.com/hashicorp/boundary/globals"
"github.com/hashicorp/boundary/internal/auth/ldap"
"github.com/hashicorp/boundary/internal/auth/oidc"
"github.com/hashicorp/boundary/internal/authtoken"
"github.com/hashicorp/boundary/internal/daemon/controller/handlers"
"github.com/hashicorp/boundary/internal/db"
@ -146,7 +148,7 @@ func TestAuthTokenAuthenticator(t *testing.T) {
}
}
func TestVerify_AuditEvent(t *testing.T) {
func TestVerify_RedactedAuth_AuditEvent(t *testing.T) {
ctx := context.Background()
eventConfig := event.TestEventerConfig(t, "Test_Verify", event.TestWithAuditSink(t))
testLock := &sync.Mutex{}
@ -266,6 +268,335 @@ func TestVerify_AuditEvent(t *testing.T) {
}
}
func TestVerify_UnredactedLdapAuth_AuditEvent(t *testing.T) {
ctx := context.Background()
eventConfig := event.TestEventerConfig(t, "Test_Verify", event.TestWithAuditSink(t))
// Disable redaction so we can assert on actual values
for _, sink := range eventConfig.EventerConfig.Sinks {
if sink.AuditConfig != nil {
sink.AuditConfig.FilterOverrides = event.AuditFilterOperations{
event.SensitiveClassification: event.NoOperation,
}
}
}
testLock := &sync.Mutex{}
testLogger := hclog.New(&hclog.LoggerOptions{
Mutex: testLock,
Name: "test",
})
require.NoError(t, event.InitSysEventer(testLogger, testLock, "Test_Verify", event.WithEventerConfig(&eventConfig.EventerConfig)))
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
testKms := kms.TestKms(t, conn, wrapper)
tokenRepo, err := authtoken.NewRepository(ctx, rw, rw, testKms)
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, testKms)
}
// ok lets create a LDAP auth token
o, _ := iam.TestScopes(t, iamRepo)
databaseWrapper, err := testKms.GetWrapper(ctx, o.GetPublicId(), kms.KeyPurposeDatabase)
require.NoError(t, err)
testAuthMethod := ldap.TestAuthMethod(t, conn, databaseWrapper, o.GetPublicId(), []string{"ldaps://ldap1"})
ldapAcct := ldap.TestAccount(t, conn, testAuthMethod, "freyja",
ldap.WithFullName(ctx, "Freyja Happy Doggo"),
ldap.WithEmail(ctx, "you@hellothere.com"),
)
foundLdapAcct := ldap.AllocAccount()
foundLdapAcct.PublicId = ldapAcct.GetPublicId()
require.NoError(t, rw.LookupById(ctx, foundLdapAcct))
require.Equal(t, "Freyja Happy Doggo", foundLdapAcct.FullName)
require.Equal(t, "you@hellothere.com", foundLdapAcct.Email)
user := iam.TestUser(t, iamRepo, o.GetPublicId())
_, err = iamRepo.AddUserAccounts(ctx, user.PublicId, user.Version, []string{ldapAcct.GetPublicId()})
require.NoError(t, err)
// set ldap auth method as primary
s, err := iamRepo.LookupScope(ctx, o.PublicId)
require.NoError(t, err)
iam.TestSetPrimaryAuthMethod(t, iamRepo, s, testAuthMethod.GetPublicId())
// ok lets verify we have a fullName and email to populated in the user
lookedUpUser, _, err := iamRepo.LookupUser(ctx, user.PublicId)
require.NoError(t, err)
require.Equal(t, "Freyja Happy Doggo", lookedUpUser.FullName)
require.Equal(t, "you@hellothere.com", lookedUpUser.Email)
at, err := tokenRepo.CreateAuthToken(ctx, user, ldapAcct.GetPublicId())
require.NoError(t, err)
encToken, err := authtoken.EncryptToken(context.Background(), testKms, o.GetPublicId(), at.GetPublicId(), at.GetToken())
require.NoError(t, err)
tokValue := at.GetPublicId() + "_" + encToken
jsCookieVal, httpCookieVal := tokValue[:len(tokValue)/2], tokValue[len(tokValue)/2:]
tests := []struct {
name string
headers map[string]string
cookies []http.Cookie
opt []Option
disableAuth bool
disableAuthzFails bool
wantResults VerifyResults
wantUserId string
wantNameEmail bool
}{
{
name: "bearer-token",
headers: map[string]string{"Authorization": fmt.Sprintf("Bearer %s", tokValue)},
opt: []Option{WithScopeId(o.PublicId)},
wantUserId: at.IamUserId,
wantNameEmail: true,
},
{
name: "split-cookie-token",
cookies: []http.Cookie{
{Name: handlers.HttpOnlyCookieName, Value: httpCookieVal},
{Name: handlers.JsVisibleCookieName, Value: jsCookieVal},
},
opt: []Option{WithScopeId(o.PublicId)},
wantUserId: at.IamUserId,
wantNameEmail: true,
},
{
name: "no-auth-data",
opt: []Option{WithScopeId(o.PublicId)},
wantUserId: globals.AnonymousUserId,
},
{
name: "disable-auth",
opt: []Option{WithScopeId(o.PublicId)},
disableAuth: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
req := httptest.NewRequest("GET", "http://127.0.0.1/v1/scopes/o_1", nil)
for k, v := range tt.headers {
req.Header.Set(k, v)
}
for _, c := range tt.cookies {
req.AddCookie(&c)
}
// Add values for authn/authz checking
requestInfo := authpb.RequestInfo{
Path: req.URL.Path,
Method: req.Method,
DisableAuthEntirely: tt.disableAuth,
DisableAuthzFailures: true, // we skipped giving grants so disable authz
}
requestInfo.PublicId, requestInfo.EncryptedToken, requestInfo.TokenFormat = GetTokenFromRequest(context.TODO(), testKms, req)
ctx := NewVerifierContext(ctx, iamRepoFn, tokenRepoFn, serversRepoFn, testKms, &requestInfo)
_ = os.WriteFile(eventConfig.AuditEvents.Name(), nil, 0o666) // clean out audit events from previous calls
_ = Verify(ctx, resource.Scope, tt.opt...)
got := api.CloudEventFromFile(t, eventConfig.AuditEvents.Name())
auth, ok := got.Data.(map[string]any)["auth"].(map[string]any)
require.True(ok)
if tt.wantNameEmail {
assert.Equal("Freyja Happy Doggo", auth["name"])
assert.Equal("you@hellothere.com", auth["email"])
} else {
assert.Nil(auth["name"])
assert.Nil(auth["email"])
}
if tt.disableAuth {
assert.Equal(true, auth["disabled_auth_entirely"])
}
if tt.wantUserId != "" {
userInfo, ok := auth["user_info"].(map[string]any)
require.True(ok)
assert.Equal(tt.wantUserId, userInfo["id"])
}
})
}
}
func TestVerify_UnredactedOidcAuth_AuditEvent(t *testing.T) {
ctx := context.Background()
eventConfig := event.TestEventerConfig(t, "Test_Verify", event.TestWithAuditSink(t))
// Disable redaction so we can assert on actual values
for _, sink := range eventConfig.EventerConfig.Sinks {
if sink.AuditConfig != nil {
sink.AuditConfig.FilterOverrides = event.AuditFilterOperations{
event.SensitiveClassification: event.NoOperation,
}
}
}
testLock := &sync.Mutex{}
testLogger := hclog.New(&hclog.LoggerOptions{
Mutex: testLock,
Name: "test",
})
require.NoError(t, event.InitSysEventer(testLogger, testLock, "Test_Verify", event.WithEventerConfig(&eventConfig.EventerConfig)))
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
testKms := kms.TestKms(t, conn, wrapper)
tokenRepo, err := authtoken.NewRepository(ctx, rw, rw, testKms)
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, testKms)
}
// ok lets create a OIDC auth token
o, _ := iam.TestScopes(t, iamRepo)
databaseWrapper, err := testKms.GetWrapper(ctx, o.GetPublicId(), kms.KeyPurposeDatabase)
require.NoError(t, err)
testAuthMethod := oidc.TestAuthMethod(t, conn, databaseWrapper, o.GetPublicId(), oidc.ActivePrivateState,
"alice-rp", "fido",
oidc.WithIssuer(oidc.TestConvertToUrls(t, "https://hellothere.com")[0]),
oidc.WithSigningAlgs(oidc.RS256),
oidc.WithApiUrl(oidc.TestConvertToUrls(t, "http://localhost")[0]),
)
oidcAcct := oidc.TestAccount(t, conn, testAuthMethod, "Freyja",
oidc.WithFullName("Freyja Good Doggo"),
oidc.WithEmail("me@hellothere.com"),
)
user := iam.TestUser(t, iamRepo, o.GetPublicId())
_, err = iamRepo.AddUserAccounts(ctx, user.PublicId, user.Version, []string{oidcAcct.GetPublicId()})
require.NoError(t, err)
s, err := iamRepo.LookupScope(ctx, o.PublicId)
require.NoError(t, err)
// set oidcg auth method as primary
iam.TestSetPrimaryAuthMethod(t, iamRepo, s, testAuthMethod.GetPublicId())
// ok lets verify we have a fullName and email to populated in the user
lookedUpUser, _, err := iamRepo.LookupUser(ctx, user.PublicId)
require.NoError(t, err)
require.Equal(t, "Freyja Good Doggo", lookedUpUser.FullName)
require.Equal(t, "me@hellothere.com", lookedUpUser.Email)
at, err := tokenRepo.CreateAuthToken(ctx, user, oidcAcct.GetPublicId())
require.NoError(t, err)
encToken, err := authtoken.EncryptToken(context.Background(), testKms, o.GetPublicId(), at.GetPublicId(), at.GetToken())
require.NoError(t, err)
tokValue := at.GetPublicId() + "_" + encToken
jsCookieVal, httpCookieVal := tokValue[:len(tokValue)/2], tokValue[len(tokValue)/2:]
tests := []struct {
name string
headers map[string]string
cookies []http.Cookie
opt []Option
disableAuth bool
disableAuthzFails bool
wantResults VerifyResults
wantUserId string
wantNameEmail bool
}{
{
name: "bearer-token",
headers: map[string]string{"Authorization": fmt.Sprintf("Bearer %s", tokValue)},
opt: []Option{WithScopeId(o.PublicId)},
wantUserId: at.IamUserId,
wantNameEmail: true,
},
{
name: "split-cookie-token",
cookies: []http.Cookie{
{Name: handlers.HttpOnlyCookieName, Value: httpCookieVal},
{Name: handlers.JsVisibleCookieName, Value: jsCookieVal},
},
opt: []Option{WithScopeId(o.PublicId)},
wantUserId: at.IamUserId,
wantNameEmail: true,
},
{
name: "no-auth-data",
opt: []Option{WithScopeId(o.PublicId)},
wantUserId: globals.AnonymousUserId,
},
{
name: "disable-auth",
opt: []Option{WithScopeId(o.PublicId)},
disableAuth: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
req := httptest.NewRequest("GET", "http://127.0.0.1/v1/scopes/o_1", nil)
for k, v := range tt.headers {
req.Header.Set(k, v)
}
for _, c := range tt.cookies {
req.AddCookie(&c)
}
// Add values for authn/authz checking
requestInfo := authpb.RequestInfo{
Path: req.URL.Path,
Method: req.Method,
DisableAuthEntirely: tt.disableAuth,
DisableAuthzFailures: true, // we skipped giving grants so disable authz
}
requestInfo.PublicId, requestInfo.EncryptedToken, requestInfo.TokenFormat = GetTokenFromRequest(context.TODO(), testKms, req)
ctx := NewVerifierContext(ctx, iamRepoFn, tokenRepoFn, serversRepoFn, testKms, &requestInfo)
_ = os.WriteFile(eventConfig.AuditEvents.Name(), nil, 0o666) // clean out audit events from previous calls
_ = Verify(ctx, resource.Scope, tt.opt...)
got := api.CloudEventFromFile(t, eventConfig.AuditEvents.Name())
auth, ok := got.Data.(map[string]any)["auth"].(map[string]any)
require.True(ok)
if tt.wantNameEmail {
assert.Equal("Freyja Good Doggo", auth["name"])
assert.Equal("me@hellothere.com", auth["email"])
} else {
assert.Nil(auth["name"])
assert.Nil(auth["email"])
}
if tt.disableAuth {
assert.Equal(true, auth["disabled_auth_entirely"])
}
if tt.wantUserId != "" {
userInfo, ok := auth["user_info"].(map[string]any)
require.True(ok)
assert.Equal(tt.wantUserId, userInfo["id"])
}
})
}
}
func TestGrantsHash(t *testing.T) {
ctx := context.Background()
conn, _ := db.TestSetup(t, "postgres")

@ -0,0 +1,64 @@
-- Copyright IBM Corp. 2026
-- SPDX-License-Identifier: BUSL-1.1
-- Replaces iam_acct_info and iam_user_acct_info from 4/01_iam.up.sql
-- Add auth_ldap_account to iam_acct_info so that LDAP users with a primary
-- LDAP auth method have their full_name and email surfaced through
-- iam_user_acct_info.
begin;
drop view iam_user_acct_info;
drop view iam_acct_info;
create view iam_acct_info as
select aa.iam_user_id,
oa.subject as login_name,
oa.public_id as primary_account_id,
oa.full_name as full_name,
oa.email as email
from iam_scope s,
auth_account aa,
auth_oidc_account oa
where aa.public_id = oa.public_id
and aa.auth_method_id = s.primary_auth_method_id
union
select aa.iam_user_id,
pa.login_name as login_name,
pa.public_id as primary_account_id,
'' as full_name,
'' as email
from iam_scope s,
auth_account aa,
auth_password_account pa
where aa.public_id = pa.public_id
and aa.auth_method_id = s.primary_auth_method_id
union
select aa.iam_user_id,
la.login_name as login_name,
la.public_id as primary_account_id,
la.full_name as full_name,
la.email as email
from iam_scope s,
auth_account aa,
auth_ldap_account la
where aa.public_id = la.public_id
and aa.auth_method_id = s.primary_auth_method_id;
create view iam_user_acct_info as
select u.public_id,
u.scope_id,
u.name,
u.description,
u.create_time,
u.update_time,
u.version,
i.primary_account_id,
i.login_name,
i.full_name,
i.email
from iam_user u
left outer join iam_acct_info i
on u.public_id = i.iam_user_id;
commit;

@ -5,6 +5,7 @@ begin;
-- fix ordering of fields in iam_acct_info for auth_password_account select
-- portion of union. requires recreating both views because of deps.
-- replaced by 104/01_iam_acct_info_ldap.up.sql
drop view iam_user_acct_info;
drop view iam_acct_info;

Loading…
Cancel
Save