Skip to content

Commit

Permalink
Telemetry(observations): add oidc and ldap observation events (#3950)
Browse files Browse the repository at this point in the history
* telemetry(observations): Add observations for oidc and ldap authentications

* telemetry(observations): Add tests ldap and oidc observation events
  • Loading branch information
sepehrfrgh authored Oct 30, 2023
1 parent e1b7593 commit b8f3e3b
Show file tree
Hide file tree
Showing 5 changed files with 138 additions and 12 deletions.
6 changes: 5 additions & 1 deletion internal/auth/ldap/service_authenticate.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (

"github.com/hashicorp/boundary/internal/authtoken"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/event"
"github.com/hashicorp/boundary/internal/iam"
)

Expand Down Expand Up @@ -82,7 +83,10 @@ func Authenticate(
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}

if err := event.WriteObservation(ctx, op, event.WithDetails("user_id", user.GetPublicId(), "auth_token_start",
token.GetCreateTime(), "auth_token_end", token.GetExpirationTime())); err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("Unable to write observation event for authenticate method"))
}
return token, nil
}

Expand Down
29 changes: 28 additions & 1 deletion internal/auth/ldap/service_authenticate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,20 @@ package ldap

import (
"context"
"encoding/json"
"fmt"
"io/ioutil"
"os"
"sync"
"testing"

"github.com/hashicorp/boundary/internal/authtoken"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/event"
"github.com/hashicorp/boundary/internal/iam"
"github.com/hashicorp/boundary/internal/kms"
"github.com/hashicorp/eventlogger/formatter_filters/cloudevents"
"github.com/hashicorp/go-hclog"
"github.com/jimlambrt/gldap"
"github.com/jimlambrt/gldap/testdirectory"
Expand All @@ -27,7 +33,14 @@ func TestAuthenticate(t *testing.T) {
testRw := db.New(testConn)
rootWrapper := db.TestWrapper(t)
testKms := kms.TestKms(t, testConn, rootWrapper)

opt := event.TestWithObservationSink(t)
c := event.TestEventerConfig(t, "Test_StartAuth_to_Callback", opt)
testLock := &sync.Mutex{}
testLogger := hclog.New(&hclog.LoggerOptions{
Mutex: testLock,
Name: "test",
})
require.NoError(t, event.InitSysEventer(testLogger, testLock, "use-Test_Authenticate", event.WithEventerConfig(&c.EventerConfig)))
// some standard factories for unit tests
authenticatorFn := func() (Authenticator, error) {
return NewRepository(testCtx, testRw, testRw, testKms)
Expand Down Expand Up @@ -313,6 +326,20 @@ func TestAuthenticate(t *testing.T) {
}
require.NoError(err)
assert.NotEmpty(got)
sinkFileName := c.ObservationEvents.Name()
defer func() { _ = os.WriteFile(sinkFileName, nil, 0o666) }()
b, err := ioutil.ReadFile(sinkFileName)
require.NoError(err)
gotRes := &cloudevents.Event{}
err = json.Unmarshal(b, gotRes)
require.NoErrorf(err, "json: %s", string(b))
details, ok := gotRes.Data.(map[string]any)["details"]
require.True(ok)
for _, key := range details.([]any) {
assert.Contains(key.(map[string]any)["payload"], "user_id")
assert.Contains(key.(map[string]any)["payload"], "auth_token_start")
assert.Contains(key.(map[string]any)["payload"], "auth_token_end")
}
})
}
}
Expand Down
8 changes: 7 additions & 1 deletion internal/auth/oidc/service_callback.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"github.com/hashicorp/boundary/internal/auth/oidc/request"
"github.com/hashicorp/boundary/internal/authtoken"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/event"
"github.com/hashicorp/cap/oidc"
"github.com/hashicorp/go-bexpr"
"github.com/mitchellh/pointerstructure"
Expand Down Expand Up @@ -252,12 +253,17 @@ func Callback(
if err != nil {
return "", errors.Wrap(ctx, err, op)
}
if _, err := tokenRepo.CreateAuthToken(ctx, user, acct.PublicId, authtoken.WithPublicId(reqState.TokenRequestId), authtoken.WithStatus(authtoken.PendingStatus)); err != nil {
authToken, err := tokenRepo.CreateAuthToken(ctx, user, acct.PublicId, authtoken.WithPublicId(reqState.TokenRequestId), authtoken.WithStatus(authtoken.PendingStatus))
if err != nil {
if errors.Match(errors.T(errors.NotUnique), err) {
return "", errors.New(ctx, errors.Forbidden, op, "not a unique request", errors.WithWrap(err))
}
return "", errors.Wrap(ctx, err, op)
}
if err := event.WriteObservation(ctx, op, event.WithDetails("user_id", user.GetPublicId(), "auth_token_start",
authToken.GetCreateTime(), "auth_token_end", authToken.GetExpirationTime())); err != nil {
return "", errors.Wrap(ctx, err, op, errors.WithMsg("Unable to write observation event for authenticate method"))
}
// tada! we can return a final redirect URL for the successful authentication.
return reqState.FinalRedirectUrl, nil
}
77 changes: 70 additions & 7 deletions internal/auth/oidc/service_callback_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,14 @@ package oidc

import (
"context"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"net/http/httptest"
"net/url"
"os"
"sync"
"testing"
"time"

Expand All @@ -19,11 +22,14 @@ import (
"github.com/hashicorp/boundary/internal/authtoken"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/event"
"github.com/hashicorp/boundary/internal/iam"
iamStore "github.com/hashicorp/boundary/internal/iam/store"
"github.com/hashicorp/boundary/internal/kms"
"github.com/hashicorp/boundary/internal/oplog"
"github.com/hashicorp/cap/oidc"
"github.com/hashicorp/eventlogger/formatter_filters/cloudevents"
"github.com/hashicorp/go-hclog"
"github.com/hashicorp/go-secure-stdlib/strutil"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
Expand All @@ -39,7 +45,14 @@ func Test_Callback(t *testing.T) {
kmsCache := kms.TestKms(t, conn, rootWrapper)

testCtx := context.Background()

opt := event.TestWithObservationSink(t)
c := event.TestEventerConfig(t, "Test_StartAuth_to_Callback", opt)
testLock := &sync.Mutex{}
testLogger := hclog.New(&hclog.LoggerOptions{
Mutex: testLock,
Name: "test",
})
require.NoError(t, event.InitSysEventer(testLogger, testLock, "use-Test_Callback", event.WithEventerConfig(&c.EventerConfig)))
// some standard factories for unit tests which
// are used in the Callback(...) call
iamRepoFn := func() (*iam.Repository, error) {
Expand Down Expand Up @@ -332,7 +345,6 @@ func Test_Callback(t *testing.T) {
if len(info) > 0 {
tp.SetUserInfoReply(info)
}

gotRedirect, err := Callback(ctx,
tt.oidcRepoFn,
tt.iamRepoFn,
Expand Down Expand Up @@ -367,6 +379,21 @@ func Test_Callback(t *testing.T) {
require.NoError(err)
assert.Equal(tt.wantFinalRedirect, gotRedirect)

sinkFileName := c.ObservationEvents.Name()
defer func() { _ = os.WriteFile(sinkFileName, nil, 0o666) }()
b, err := ioutil.ReadFile(sinkFileName)
require.NoError(err)
got := &cloudevents.Event{}
err = json.Unmarshal(b, got)
require.NoErrorf(err, "json: %s", string(b))
details, ok := got.Data.(map[string]any)["details"]
require.True(ok)
for _, key := range details.([]any) {
assert.Contains(key.(map[string]any)["payload"], "user_id")
assert.Contains(key.(map[string]any)["payload"], "auth_token_start")
assert.Contains(key.(map[string]any)["payload"], "auth_token_end")
}

// make sure a pending token was created.
var tokens []*authtoken.AuthToken
err = rw.SearchWhere(ctx, &tokens, "1=?", []any{1})
Expand Down Expand Up @@ -461,7 +488,18 @@ func Test_Callback(t *testing.T) {

tp.SetUserInfoReply(map[string]any{"sub": wantSubject})
tp.SetExpectedAuthNonce(testNonce)

config := event.EventerConfig{
ObservationsEnabled: true,
}
testLock := &sync.Mutex{}
testLogger := hclog.New(&hclog.LoggerOptions{
Mutex: testLock,
Name: "test",
})
e, err := event.NewEventer(testLogger, testLock, "replay-attack-with-dup-state", config)
require.NoError(err)
ctx, err := event.NewEventerContext(ctx, e)
require.NoError(err)
// the first request should succeed.
gotRedirect, err := Callback(ctx,
repoFn,
Expand Down Expand Up @@ -496,7 +534,13 @@ func Test_StartAuth_to_Callback(t *testing.T) {
t.Run("startAuth-to-Callback", func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
ctx := context.Background()

c := event.TestEventerConfig(t, "Test_StartAuth_to_Callback")
testLock := &sync.Mutex{}
testLogger := hclog.New(&hclog.LoggerOptions{
Mutex: testLock,
Name: "test",
})
require.NoError(event.InitSysEventer(testLogger, testLock, "use-Test_StartAuth_to_Callback", event.WithEventerConfig(&c.EventerConfig)))
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
// start with no tokens in the db
Expand Down Expand Up @@ -615,7 +659,14 @@ func Test_ManagedGroupFiltering(t *testing.T) {
rw := db.New(conn)
rootWrapper := db.TestWrapper(t)
kmsCache := kms.TestKms(t, conn, rootWrapper)

opt := event.TestWithObservationSink(t)
c := event.TestEventerConfig(t, "Test_StartAuth_to_Callback", opt)
testLock := &sync.Mutex{}
testLogger := hclog.New(&hclog.LoggerOptions{
Mutex: testLock,
Name: "test",
})
require.NoError(t, event.InitSysEventer(testLogger, testLock, "use-Test_ManagedGroupFiltering", event.WithEventerConfig(&c.EventerConfig)))
// some standard factories for unit tests which
// are used in the Callback(...) call
iamRepoFn := func() (*iam.Repository, error) {
Expand Down Expand Up @@ -779,7 +830,6 @@ func Test_ManagedGroupFiltering(t *testing.T) {
require.Equal(numUpdated, 1)
require.NoError(err)
}

// Run the callback
_, err = Callback(ctx,
repoFn,
Expand All @@ -790,7 +840,20 @@ func Test_ManagedGroupFiltering(t *testing.T) {
code,
)
require.NoError(err)

sinkFileName := c.ObservationEvents.Name()
defer func() { _ = os.WriteFile(sinkFileName, nil, 0o666) }()
b, err := ioutil.ReadFile(sinkFileName)
require.NoError(err)
got := &cloudevents.Event{}
err = json.Unmarshal(b, got)
require.NoErrorf(err, "json: %s", string(b))
details, ok := got.Data.(map[string]any)["details"]
require.True(ok)
for _, key := range details.([]any) {
assert.Contains(key.(map[string]any)["payload"], "user_id")
assert.Contains(key.(map[string]any)["payload"], "auth_token_start")
assert.Contains(key.(map[string]any)["payload"], "auth_token_end")
}
// Ensure that we get the expected groups
memberships, err := repo.ListManagedGroupMembershipsByMember(ctx, account.PublicId)
require.NoError(err)
Expand Down
30 changes: 28 additions & 2 deletions internal/daemon/controller/handlers/authmethods/ldap_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,12 @@ package authmethods_test

import (
"context"
"encoding/json"
"fmt"
"io/ioutil"
"os"
"strings"
"sync"
"testing"

"github.com/google/go-cmp/cmp"
Expand All @@ -20,12 +24,14 @@ import (
"github.com/hashicorp/boundary/internal/daemon/controller/handlers/authmethods"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/event"
pbs "github.com/hashicorp/boundary/internal/gen/controller/api/services"
"github.com/hashicorp/boundary/internal/iam"
"github.com/hashicorp/boundary/internal/kms"
"github.com/hashicorp/boundary/internal/types/scope"
pb "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/authmethods"
scopepb "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/scopes"
"github.com/hashicorp/eventlogger/formatter_filters/cloudevents"
"github.com/hashicorp/go-hclog"
"github.com/jimlambrt/gldap"
"github.com/jimlambrt/gldap/testdirectory"
Expand Down Expand Up @@ -876,7 +882,14 @@ func TestAuthenticate_Ldap(t *testing.T) {
testRootWrapper := db.TestWrapper(t)
testKms := kms.TestKms(t, testConn, testRootWrapper)
o, _ := iam.TestScopes(t, iam.TestRepo(t, testConn, testRootWrapper))

opt := event.TestWithObservationSink(t)
c := event.TestEventerConfig(t, "Test_StartAuth_to_Callback", opt)
testLock := &sync.Mutex{}
testLogger := hclog.New(&hclog.LoggerOptions{
Mutex: testLock,
Name: "test",
})
require.NoError(t, event.InitSysEventer(testLogger, testLock, "use-Test_Authenticate", event.WithEventerConfig(&c.EventerConfig)))
iamRepoFn := func() (*iam.Repository, error) {
return iam.TestRepo(t, testConn, testRootWrapper), nil
}
Expand Down Expand Up @@ -1094,7 +1107,20 @@ func TestAuthenticate_Ldap(t *testing.T) {
assert.Equal(aToken.GetCreatedTime(), aToken.GetApproximateLastUsedTime())
assert.Equal(testAm.GetPublicId(), aToken.GetAuthMethodId())
assert.Equal(tc.wantType, resp.GetType())

sinkFileName := c.ObservationEvents.Name()
defer func() { _ = os.WriteFile(sinkFileName, nil, 0o666) }()
b, err := ioutil.ReadFile(sinkFileName)
require.NoError(err)
gotRes := &cloudevents.Event{}
err = json.Unmarshal(b, gotRes)
require.NoErrorf(err, "json: %s", string(b))
details, ok := gotRes.Data.(map[string]any)["details"]
require.True(ok)
for _, key := range details.([]any) {
assert.Contains(key.(map[string]any)["payload"], "user_id")
assert.Contains(key.(map[string]any)["payload"], "auth_token_start")
assert.Contains(key.(map[string]any)["payload"], "auth_token_end")
}
// support testing for pre-provisioned accounts
if tc.acctId != "" {
assert.Equal(tc.acctId, aToken.GetAccountId())
Expand Down

0 comments on commit b8f3e3b

Please sign in to comment.