Merge remote-tracking branch 'origin/main' into plugin-hostcatalogs
This commit is contained in:
commit
b1a72bd445
69 files changed
+1213
-277
No files matched your search
@@ -4,6 +4,25 @@ Canonical reference for changes, improvements, and bugfixes for Boundary.
|
||||
|
||||
## Next
|
||||
|
||||
### Deprecations/Changes
|
||||
|
||||
* tls: Boundary's support for TLS 1.0/1.1 on the API listener was broken. Rather
|
||||
than fix this, we are simply not supporting TLS 1.0/1.1 as they are insecure.
|
||||
|
||||
### New and Improved
|
||||
|
||||
* workers: The existing worker connection replay prevention logic has been
|
||||
enhanced to be more robust against attackers that have decryption access to
|
||||
the shared `worker-auth` KMS key
|
||||
([PR](https://github.com/hashicorp/boundary/pull/1641))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* tls: Support TLS 1.2 for more clients. This was broken for some clients due to
|
||||
a missing mandated cipher suite of the HTTP/2 (`h2`) specification that could
|
||||
result in no shared cipher suites between the Boundary API listener and those
|
||||
clients. ([PR](https://github.com/hashicorp/boundary/pull/1637))
|
||||
|
||||
## 0.6.2 (2021/09/27)
|
||||
|
||||
### Deprecations/Changes
|
||||
|
||||
@@ -183,3 +183,4 @@ Note that if `max_connections` is set too low, it may result in sporadic test
|
||||
failures if a connection cannot be established. In this case, reduce the number
|
||||
of concurrent tests via `GOMAXPROCS` or selectively run tests.
|
||||
|
||||
## [Adding additional field to an existing API (or new API)](internal/adding-a-new-field-readme.md)
|
||||
@@ -30,4 +30,8 @@ var (
|
||||
// RecoveryTokenValidityPeriod is exported so we can modify it in tests if
|
||||
// we want
|
||||
RecoveryTokenValidityPeriod = 5 * time.Minute
|
||||
|
||||
// WorkerAuthNonceValidityPeriod is exported so we can modify it in tests if
|
||||
// we want
|
||||
WorkerAuthNonceValidityPeriod = 2 * time.Minute
|
||||
)
|
||||
@@ -25,7 +25,7 @@ require (
|
||||
github.com/hashicorp/dawdle v0.4.0
|
||||
github.com/hashicorp/dbassert v0.0.0-20210708202608-ecf920cf1ed8
|
||||
github.com/hashicorp/eventlogger v0.1.0
|
||||
github.com/hashicorp/eventlogger/filters/encrypt v0.1.4-0.20210928205053-80364fba97eb
|
||||
github.com/hashicorp/eventlogger/filters/encrypt v0.1.5
|
||||
github.com/hashicorp/go-bexpr v0.1.10
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2
|
||||
github.com/hashicorp/go-hclog v0.16.2
|
||||
@@ -63,7 +63,6 @@ require (
|
||||
github.com/mr-tron/base58 v1.2.0
|
||||
github.com/oligot/go-mod-upgrade v0.6.1
|
||||
github.com/ory/dockertest/v3 v3.7.0
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible
|
||||
github.com/pires/go-proxyproto v0.6.1
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/posener/complete v1.2.3
|
||||
|
||||
@@ -477,9 +477,9 @@ github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY
|
||||
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/eventlogger v0.1.0 h1:S6xc4gZVzewuDUP4R4Ngko419h/CGDuV/b4ADL3XLik=
|
||||
github.com/hashicorp/eventlogger v0.1.0/go.mod h1:a3IXf1aEJfpCPzseTOrwKj4fVW/Qn3oEmpQeaIznzH0=
|
||||
github.com/hashicorp/eventlogger/filters/encrypt v0.1.3/go.mod h1:8rcez7Kw1zanB0/074qnOuGu7zxmNh9Xr2ZI+K4xVIA=
|
||||
github.com/hashicorp/eventlogger/filters/encrypt v0.1.4-0.20210928205053-80364fba97eb h1:AhprXfDoXGI8hP5fUCyCB29jpfd01Ck996ErqG0nVGk=
|
||||
github.com/hashicorp/eventlogger/filters/encrypt v0.1.4-0.20210928205053-80364fba97eb/go.mod h1:8rcez7Kw1zanB0/074qnOuGu7zxmNh9Xr2ZI+K4xVIA=
|
||||
github.com/hashicorp/eventlogger/filters/encrypt v0.1.5-0.20211025115820-78e1ded4aea1/go.mod h1:8rcez7Kw1zanB0/074qnOuGu7zxmNh9Xr2ZI+K4xVIA=
|
||||
github.com/hashicorp/eventlogger/filters/encrypt v0.1.5 h1:kNkH4G6zzWlZSoI1I+B/ud4chVKTPL516C6jB7dRdlE=
|
||||
github.com/hashicorp/eventlogger/filters/encrypt v0.1.5/go.mod h1:8rcez7Kw1zanB0/074qnOuGu7zxmNh9Xr2ZI+K4xVIA=
|
||||
github.com/hashicorp/go-bexpr v0.1.10 h1:9kuI5PFotCboP3dkDYFr/wi0gg0QVbSNz5oFRpxn4uE=
|
||||
github.com/hashicorp/go-bexpr v0.1.10/go.mod h1:oxlubA2vC/gFVfX1A6JGp7ls7uCDlfJn732ehYYg+g0=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
||||
@@ -875,8 +875,6 @@ github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIw
|
||||
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY=
|
||||
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
|
||||
github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
|
||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||
github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac=
|
||||
|
||||
@@ -7,13 +7,16 @@ Once you've figured out that you need an additional field in Boundary's domain m
|
||||
|
||||
* Make schema changes:
|
||||
* Define the new column and provide for the migration of existing data
|
||||
* Create a new migration under `internal/db/schema/migrations/postgres`
|
||||
* Run `make migrations` after modifying DDL in SQL files.
|
||||
|
||||
* Create a new migration under `internal/db/schema/migrations/oss/postgres`
|
||||
|
||||
* Add the new field to the storage protobuf
|
||||
* storage protobufs are under: `internal/proto/local/controller/storage`
|
||||
* Define a gorm tag for the new field via `@inject_tag`
|
||||
|
||||
* Define a gorm tag for the new field via `@gotags` (`@inject_tag` has been deprecated)
|
||||
|
||||
* Define a `custom_options.v1.mask_mapping` for the field which maps the storage `this` field to the API `that` field (yes, it's the opposite of how it's defined for the API protobuf)
|
||||
|
||||
* Run `make proto` after modifying storage protobuf
|
||||
|
||||
* Extend the existing repository function for Updating the resource to incorporate the new field. This could/may entail defining new options for the Update function.
|
||||
@@ -29,6 +32,12 @@ Now that the repository supports the new field, you can move on to adding this n
|
||||
|
||||
* Define a `custom_options.v1.generate_sdk_option` tag for the SDK
|
||||
|
||||
* Define a data classification/filter tag for the field via `@gotags` or the
|
||||
`encrypt.Taggable` interface which specifies how sensitive/secret/public data
|
||||
will be handled for the API's audit events. Please write unit tests to verify
|
||||
the audit event is properly "redacted" (see the unit tests of
|
||||
`TestAuthenticate_Tags` and `TestAuthenticate` for examples).
|
||||
|
||||
* Run `make proto` and `make api` after modifying the API/SDK protobufs
|
||||
|
||||
## Update the API handler service
|
||||
|
||||
@@ -383,7 +383,7 @@ func (r *Repository) IssueAuthToken(ctx context.Context, tokenRequestId string)
|
||||
// trigger to set ApproximateLastAccessTime to the commit timestamp.
|
||||
rowsUpdated, err := w.Update(ctx, at, []string{"Status"}, []string{"ApproximateLastAccessTime"}, db.WithWhere("status = ?", PendingStatus))
|
||||
if err != nil {
|
||||
return errors.Wrap(ctx, err, op)
|
||||
return errors.Wrap(ctx, err, op, errors.WithoutEvent())
|
||||
}
|
||||
if rowsUpdated == 0 {
|
||||
return errors.New(ctx, errors.RecordNotFound, op, "pending auth token not found")
|
||||
|
||||
@@ -510,7 +510,6 @@ func (b *Server) CreateInitialTarget(ctx context.Context) (target.Target, error)
|
||||
target.WithName("Generated target"),
|
||||
target.WithDescription("Provides an initial target in Boundary"),
|
||||
target.WithDefaultPort(uint32(b.DevTargetDefaultPort)),
|
||||
target.WithHostSources([]string{b.DevHostSetId}),
|
||||
target.WithSessionMaxSeconds(uint32(b.DevTargetSessionMaxSeconds)),
|
||||
target.WithSessionConnectionLimit(int32(b.DevTargetSessionConnectionLimit)),
|
||||
target.WithPublicId(b.DevTargetId),
|
||||
@@ -523,6 +522,10 @@ func (b *Server) CreateInitialTarget(ctx context.Context) (target.Target, error)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error saving target to the db: %w", err)
|
||||
}
|
||||
tt, _, _, err = targetRepo.AddTargetHostSources(ctx, tt.GetPublicId(), tt.GetVersion(), []string{b.DevHostSetId})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error saving target to the db: %w", err)
|
||||
}
|
||||
b.InfoKeys = append(b.InfoKeys, "generated target id")
|
||||
b.Info["generated target id"] = b.DevTargetId
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ func NewListener(l *listenerutil.ListenerConfig, ui cli.Ui) (*alpnmux.ALPNMux, m
|
||||
}
|
||||
|
||||
if len(l.Purpose) != 1 {
|
||||
return nil, nil, nil, fmt.Errorf("Expected single listener purpose, found %d", len(l.Purpose))
|
||||
return nil, nil, nil, fmt.Errorf("expected single listener purpose, found %d", len(l.Purpose))
|
||||
}
|
||||
purpose := l.Purpose[0]
|
||||
|
||||
@@ -65,6 +65,17 @@ func NewListener(l *listenerutil.ListenerConfig, ui cli.Ui) (*alpnmux.ALPNMux, m
|
||||
// TODO: Eventually we'll support bringing your own cert, and we'd only
|
||||
// want to disable if you aren't actually bringing your own
|
||||
l.TLSDisable = true
|
||||
default:
|
||||
switch l.TLSMinVersion {
|
||||
case "", "tls12", "tls13":
|
||||
default:
|
||||
return nil, nil, nil, fmt.Errorf("unsupported minimum tls version %q", l.TLSMinVersion)
|
||||
}
|
||||
switch l.TLSMaxVersion {
|
||||
case "", "tls12", "tls13":
|
||||
default:
|
||||
return nil, nil, nil, fmt.Errorf("unsupported maximum tls version %q", l.TLSMaxVersion)
|
||||
}
|
||||
}
|
||||
|
||||
finalAddr, ln, err := f(purpose, l, ui)
|
||||
|
||||
@@ -394,7 +394,9 @@ func (b *Server) SetupListeners(ui cli.Ui, config *configutil.SharedConfig, allo
|
||||
// 1.2
|
||||
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
|
||||
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
|
||||
tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
|
||||
tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,
|
||||
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
|
||||
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -238,6 +238,7 @@ func TestTokenRenewalJob_RunLimits(t *testing.T) {
|
||||
kmsCache := kms.TestKms(t, conn, wrapper)
|
||||
_, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
|
||||
v := NewTestVaultServer(t)
|
||||
sche := scheduler.TestScheduler(t, conn, wrapper)
|
||||
|
||||
count := 5
|
||||
tests := []struct {
|
||||
@@ -273,9 +274,10 @@ func TestTokenRenewalJob_RunLimits(t *testing.T) {
|
||||
_, token := v.CreateToken(t)
|
||||
in, err := NewCredentialStore(prj.GetPublicId(), v.Addr, []byte(token))
|
||||
require.NoError(err)
|
||||
sche := scheduler.TestScheduler(t, conn, wrapper)
|
||||
repo, err := NewRepository(rw, rw, kmsCache, sche)
|
||||
require.NoError(err)
|
||||
err = RegisterJobs(context.Background(), sche, rw, rw, kmsCache)
|
||||
require.NoError(err)
|
||||
cs, err := repo.CreateCredentialStore(context.Background(), in)
|
||||
require.NoError(err)
|
||||
|
||||
@@ -330,17 +332,17 @@ func TestTokenRenewalJob_Run(t *testing.T) {
|
||||
in, err := NewCredentialStore(prj.GetPublicId(), v.Addr, []byte(token))
|
||||
require.NoError(err)
|
||||
sche := scheduler.TestScheduler(t, conn, wrapper)
|
||||
repo, err := NewRepository(rw, rw, kmsCache, sche)
|
||||
require.NoError(err)
|
||||
cs, err := repo.CreateCredentialStore(context.Background(), in)
|
||||
require.NoError(err)
|
||||
|
||||
r, err := newTokenRenewalJob(rw, rw, kmsCache)
|
||||
require.NoError(err)
|
||||
|
||||
err = sche.RegisterJob(context.Background(), r)
|
||||
require.NoError(err)
|
||||
|
||||
repo, err := NewRepository(rw, rw, kmsCache, sche)
|
||||
require.NoError(err)
|
||||
cs, err := repo.CreateCredentialStore(context.Background(), in)
|
||||
require.NoError(err)
|
||||
|
||||
err = r.Run(context.Background())
|
||||
require.NoError(err)
|
||||
// No tokens should have been renewed since token expiration is 24 hours by default
|
||||
@@ -474,6 +476,7 @@ func TestTokenRenewalJob_NextRunIn(t *testing.T) {
|
||||
wrapper := db.TestWrapper(t)
|
||||
kmsCache := kms.TestKms(t, conn, wrapper)
|
||||
_, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
|
||||
sche := scheduler.TestScheduler(t, conn, wrapper)
|
||||
|
||||
v := NewTestVaultServer(t)
|
||||
|
||||
@@ -562,9 +565,10 @@ func TestTokenRenewalJob_NextRunIn(t *testing.T) {
|
||||
_, token := v.CreateToken(t)
|
||||
in, err := NewCredentialStore(prj.GetPublicId(), v.Addr, []byte(token))
|
||||
require.NoError(err)
|
||||
sche := scheduler.TestScheduler(t, conn, wrapper)
|
||||
repo, err := NewRepository(rw, rw, kmsCache, sche)
|
||||
require.NoError(err)
|
||||
err = RegisterJobs(context.Background(), sche, rw, rw, kmsCache)
|
||||
require.NoError(err)
|
||||
cs, err := repo.CreateCredentialStore(context.Background(), in)
|
||||
require.NoError(err)
|
||||
|
||||
@@ -682,6 +686,7 @@ func TestTokenRevocationJob_RunLimits(t *testing.T) {
|
||||
wrapper := db.TestWrapper(t)
|
||||
kmsCache := kms.TestKms(t, conn, wrapper)
|
||||
_, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
|
||||
sche := scheduler.TestScheduler(t, conn, wrapper)
|
||||
|
||||
v := NewTestVaultServer(t)
|
||||
|
||||
@@ -719,9 +724,11 @@ func TestTokenRevocationJob_RunLimits(t *testing.T) {
|
||||
_, token := v.CreateToken(t)
|
||||
in, err := NewCredentialStore(prj.GetPublicId(), v.Addr, []byte(token))
|
||||
require.NoError(err)
|
||||
sche := scheduler.TestScheduler(t, conn, wrapper)
|
||||
repo, err := NewRepository(rw, rw, kmsCache, sche)
|
||||
require.NoError(err)
|
||||
err = RegisterJobs(context.Background(), sche, rw, rw, kmsCache)
|
||||
require.NoError(err)
|
||||
|
||||
cs, err := repo.CreateCredentialStore(context.Background(), in)
|
||||
require.NoError(err)
|
||||
|
||||
@@ -777,6 +784,12 @@ func TestTokenRevocationJob_Run(t *testing.T) {
|
||||
sche := scheduler.TestScheduler(t, conn, wrapper)
|
||||
repo, err := NewRepository(rw, rw, kmsCache, sche)
|
||||
require.NoError(err)
|
||||
|
||||
j, err := newTokenRenewalJob(rw, rw, kmsCache)
|
||||
require.NoError(err)
|
||||
err = sche.RegisterJob(context.Background(), j)
|
||||
require.NoError(err)
|
||||
|
||||
cs, err := repo.CreateCredentialStore(context.Background(), in)
|
||||
require.NoError(err)
|
||||
|
||||
@@ -992,6 +1005,12 @@ func TestCredentialRenewalJob_RunLimits(t *testing.T) {
|
||||
sche := scheduler.TestScheduler(t, conn, wrapper)
|
||||
repo, err := NewRepository(rw, rw, kmsCache, sche)
|
||||
require.NoError(t, err)
|
||||
|
||||
j, err := newTokenRenewalJob(rw, rw, kmsCache)
|
||||
require.NoError(t, err)
|
||||
err = sche.RegisterJob(context.Background(), j)
|
||||
require.NoError(t, err)
|
||||
|
||||
cs, err := repo.CreateCredentialStore(context.Background(), in)
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -1103,6 +1122,10 @@ func TestCredentialRenewalJob_Run(t *testing.T) {
|
||||
_, token := v.CreateToken(t, WithPolicies([]string{"default", "boundary-controller", "database"}))
|
||||
credStoreIn, err := NewCredentialStore(prj.GetPublicId(), v.Addr, []byte(token))
|
||||
require.NoError(err)
|
||||
j, err := newTokenRenewalJob(rw, rw, kmsCache)
|
||||
require.NoError(err)
|
||||
err = sche.RegisterJob(context.Background(), j)
|
||||
require.NoError(err)
|
||||
cs, err := repo.CreateCredentialStore(context.Background(), credStoreIn)
|
||||
require.NoError(err)
|
||||
|
||||
@@ -1209,6 +1232,10 @@ func TestCredentialRenewalJob_RunExpired(t *testing.T) {
|
||||
_, token := v.CreateToken(t, WithPolicies([]string{"default", "boundary-controller", "database"}))
|
||||
credStoreIn, err := NewCredentialStore(prj.GetPublicId(), v.Addr, []byte(token))
|
||||
require.NoError(err)
|
||||
j, err := newTokenRenewalJob(rw, rw, kmsCache)
|
||||
require.NoError(err)
|
||||
err = sche.RegisterJob(context.Background(), j)
|
||||
require.NoError(err)
|
||||
cs, err := repo.CreateCredentialStore(context.Background(), credStoreIn)
|
||||
require.NoError(err)
|
||||
|
||||
@@ -1282,6 +1309,10 @@ func TestCredentialRenewalJob_NextRunIn(t *testing.T) {
|
||||
sche := scheduler.TestScheduler(t, conn, wrapper)
|
||||
repo, err := NewRepository(rw, rw, kmsCache, sche)
|
||||
require.NoError(t, err)
|
||||
j, err := newTokenRenewalJob(rw, rw, kmsCache)
|
||||
require.NoError(t, err)
|
||||
err = sche.RegisterJob(context.Background(), j)
|
||||
require.NoError(t, err)
|
||||
cs, err := repo.CreateCredentialStore(context.Background(), in)
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -1519,6 +1550,10 @@ func TestCredentialRevocationJob_RunLimits(t *testing.T) {
|
||||
sche := scheduler.TestScheduler(t, conn, wrapper)
|
||||
repo, err := NewRepository(rw, rw, kmsCache, sche)
|
||||
require.NoError(t, err)
|
||||
j, err := newTokenRenewalJob(rw, rw, kmsCache)
|
||||
require.NoError(t, err)
|
||||
err = sche.RegisterJob(context.Background(), j)
|
||||
require.NoError(t, err)
|
||||
cs, err := repo.CreateCredentialStore(context.Background(), in)
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -1632,6 +1667,10 @@ func TestCredentialRevocationJob_Run(t *testing.T) {
|
||||
_, token := v.CreateToken(t, WithPolicies([]string{"default", "boundary-controller", "database"}))
|
||||
credStoreIn, err := NewCredentialStore(prj.GetPublicId(), v.Addr, []byte(token))
|
||||
require.NoError(err)
|
||||
j, err := newTokenRenewalJob(rw, rw, kmsCache)
|
||||
require.NoError(err)
|
||||
err = sche.RegisterJob(context.Background(), j)
|
||||
require.NoError(err)
|
||||
cs, err := repo.CreateCredentialStore(context.Background(), credStoreIn)
|
||||
require.NoError(err)
|
||||
|
||||
@@ -1722,6 +1761,10 @@ func TestCredentialRevocationJob_RunDeleted(t *testing.T) {
|
||||
_, token := v.CreateToken(t, WithPolicies([]string{"default", "boundary-controller", "database"}))
|
||||
credStoreIn, err := NewCredentialStore(prj.GetPublicId(), v.Addr, []byte(token))
|
||||
require.NoError(err)
|
||||
j, err := newTokenRenewalJob(rw, rw, kmsCache)
|
||||
require.NoError(err)
|
||||
err = sche.RegisterJob(context.Background(), j)
|
||||
require.NoError(err)
|
||||
cs, err := repo.CreateCredentialStore(context.Background(), credStoreIn)
|
||||
require.NoError(err)
|
||||
|
||||
@@ -1907,6 +1950,10 @@ func TestCredentialStoreCleanupJob_Run(t *testing.T) {
|
||||
in, err := NewCredentialStore(prj.GetPublicId(), v.Addr, []byte(ct))
|
||||
require.NoError(err)
|
||||
sche := scheduler.TestScheduler(t, conn, wrapper)
|
||||
j, err := newTokenRenewalJob(rw, rw, kmsCache)
|
||||
require.NoError(err)
|
||||
err = sche.RegisterJob(context.Background(), j)
|
||||
require.NoError(err)
|
||||
repo, err := NewRepository(rw, rw, kmsCache, sche)
|
||||
require.NoError(err)
|
||||
cs1, err := repo.CreateCredentialStore(context.Background(), in)
|
||||
@@ -1941,6 +1988,12 @@ func TestCredentialStoreCleanupJob_Run(t *testing.T) {
|
||||
require.NoError(err)
|
||||
assert.Equal(0, r.numStores)
|
||||
|
||||
// Register token revocation job needed for delete
|
||||
j1, err := newTokenRevocationJob(rw, rw, kmsCache)
|
||||
require.NoError(err)
|
||||
err = sche.RegisterJob(context.Background(), j1)
|
||||
require.NoError(err)
|
||||
|
||||
// Soft delete both credential stores
|
||||
count, err := repo.DeleteCredentialStore(context.Background(), cs1.PublicId)
|
||||
require.NoError(err)
|
||||
@@ -2100,6 +2153,10 @@ func TestCredentialCleanupJob_Run(t *testing.T) {
|
||||
_, token := v.CreateToken(t, WithPolicies([]string{"default", "boundary-controller", "database"}))
|
||||
credStoreIn, err := NewCredentialStore(prj.GetPublicId(), v.Addr, []byte(token))
|
||||
require.NoError(err)
|
||||
j, err := newTokenRenewalJob(rw, rw, kmsCache)
|
||||
require.NoError(err)
|
||||
err = sche.RegisterJob(context.Background(), j)
|
||||
require.NoError(err)
|
||||
cs, err := repo.CreateCredentialStore(context.Background(), credStoreIn)
|
||||
require.NoError(err)
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ func TestRepository_getPrivateLibraries(t *testing.T) {
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
rw := db.New(conn)
|
||||
wrapper := db.TestWrapper(t)
|
||||
sche := scheduler.TestScheduler(t, conn, wrapper)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
@@ -44,10 +45,11 @@ func TestRepository_getPrivateLibraries(t *testing.T) {
|
||||
_, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
|
||||
kms := kms.TestKms(t, conn, wrapper)
|
||||
|
||||
sche := scheduler.TestScheduler(t, conn, wrapper)
|
||||
repo, err := NewRepository(rw, rw, kms, sche)
|
||||
require.NoError(err)
|
||||
require.NotNil(repo)
|
||||
err = RegisterJobs(ctx, sche, rw, rw, kms)
|
||||
require.NoError(err)
|
||||
|
||||
var opts []Option
|
||||
if tt.tls == TestServerTLS {
|
||||
|
||||
@@ -17,6 +17,7 @@ func TestRepository_lookupPrivateStore(t *testing.T) {
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
rw := db.New(conn)
|
||||
wrapper := db.TestWrapper(t)
|
||||
sche := scheduler.TestScheduler(t, conn, wrapper)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
@@ -40,10 +41,12 @@ func TestRepository_lookupPrivateStore(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
ctx := context.Background()
|
||||
kms := kms.TestKms(t, conn, wrapper)
|
||||
sche := scheduler.TestScheduler(t, conn, wrapper)
|
||||
repo, err := NewRepository(rw, rw, kms, sche)
|
||||
require.NoError(err)
|
||||
require.NotNil(repo)
|
||||
err = RegisterJobs(ctx, sche, rw, rw, kms)
|
||||
require.NoError(err)
|
||||
|
||||
_, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
|
||||
|
||||
v := NewTestVaultServer(t, WithTestVaultTLS(tt.tls))
|
||||
|
||||
@@ -30,16 +30,18 @@ func TestRepository_CreateCredentialStoreResource(t *testing.T) {
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
rw := db.New(conn)
|
||||
wrapper := db.TestWrapper(t)
|
||||
sche := scheduler.TestScheduler(t, conn, wrapper)
|
||||
|
||||
t.Run("invalid-duplicate-names", func(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
ctx := context.Background()
|
||||
kms := kms.TestKms(t, conn, wrapper)
|
||||
sche := scheduler.TestScheduler(t, conn, wrapper)
|
||||
repo, err := NewRepository(rw, rw, kms, sche)
|
||||
require.NoError(err)
|
||||
require.NotNil(repo)
|
||||
_, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
|
||||
err = RegisterJobs(ctx, sche, rw, rw, kms)
|
||||
require.NoError(err)
|
||||
|
||||
v := NewTestVaultServer(t)
|
||||
_, token := v.CreateToken(t)
|
||||
@@ -68,11 +70,12 @@ func TestRepository_CreateCredentialStoreResource(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
ctx := context.Background()
|
||||
kms := kms.TestKms(t, conn, wrapper)
|
||||
sche := scheduler.TestScheduler(t, conn, wrapper)
|
||||
repo, err := NewRepository(rw, rw, kms, sche)
|
||||
require.NoError(err)
|
||||
require.NotNil(repo)
|
||||
org, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
|
||||
err = RegisterJobs(ctx, sche, rw, rw, kms)
|
||||
require.NoError(err)
|
||||
|
||||
v := NewTestVaultServer(t)
|
||||
|
||||
@@ -123,6 +126,7 @@ func TestRepository_CreateCredentialStoreNonResource(t *testing.T) {
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
rw := db.New(conn)
|
||||
wrapper := db.TestWrapper(t)
|
||||
sche := scheduler.TestScheduler(t, conn, wrapper)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
@@ -168,11 +172,12 @@ func TestRepository_CreateCredentialStoreNonResource(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
ctx := context.Background()
|
||||
kms := kms.TestKms(t, conn, wrapper)
|
||||
sche := scheduler.TestScheduler(t, conn, wrapper)
|
||||
repo, err := NewRepository(rw, rw, kms, sche)
|
||||
require.NoError(err)
|
||||
require.NotNil(repo)
|
||||
_, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
|
||||
err = RegisterJobs(ctx, sche, rw, rw, kms)
|
||||
require.NoError(err)
|
||||
|
||||
v := NewTestVaultServer(t, WithTestVaultTLS(tt.tls))
|
||||
_, token := v.CreateToken(t, tt.tokenOpts...)
|
||||
@@ -226,6 +231,7 @@ func TestRepository_LookupCredentialStore(t *testing.T) {
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
rw := db.New(conn)
|
||||
wrapper := db.TestWrapper(t)
|
||||
sche := scheduler.TestScheduler(t, conn, wrapper)
|
||||
|
||||
_, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
|
||||
stores := TestCredentialStores(t, conn, wrapper, prj.PublicId, 2)
|
||||
@@ -278,10 +284,11 @@ func TestRepository_LookupCredentialStore(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
ctx := context.Background()
|
||||
kms := kms.TestKms(t, conn, wrapper)
|
||||
sche := scheduler.TestScheduler(t, conn, wrapper)
|
||||
repo, err := NewRepository(rw, rw, kms, sche)
|
||||
assert.NoError(err)
|
||||
require.NotNil(repo)
|
||||
err = RegisterJobs(ctx, sche, rw, rw, kms)
|
||||
require.NoError(err)
|
||||
|
||||
got, err := repo.LookupCredentialStore(ctx, tt.id)
|
||||
if tt.wantErr != 0 {
|
||||
@@ -314,6 +321,7 @@ func TestRepository_UpdateCredentialStore_Attributes(t *testing.T) {
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
rw := db.New(conn)
|
||||
wrapper := db.TestWrapper(t)
|
||||
sche := scheduler.TestScheduler(t, conn, wrapper)
|
||||
|
||||
changeNamespace := func(n string) func(*CredentialStore) *CredentialStore {
|
||||
return func(cs *CredentialStore) *CredentialStore {
|
||||
@@ -716,10 +724,11 @@ func TestRepository_UpdateCredentialStore_Attributes(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
ctx := context.Background()
|
||||
kms := kms.TestKms(t, conn, wrapper)
|
||||
sche := scheduler.TestScheduler(t, conn, wrapper)
|
||||
repo, err := NewRepository(rw, rw, kms, sche)
|
||||
assert.NoError(err)
|
||||
require.NotNil(repo)
|
||||
err = RegisterJobs(ctx, sche, rw, rw, kms)
|
||||
require.NoError(err)
|
||||
|
||||
_, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
|
||||
tt.orig.ScopeId = prj.GetPublicId()
|
||||
@@ -796,10 +805,11 @@ func TestRepository_UpdateCredentialStore_Attributes(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
ctx := context.Background()
|
||||
kms := kms.TestKms(t, conn, wrapper)
|
||||
sche := scheduler.TestScheduler(t, conn, wrapper)
|
||||
repo, err := NewRepository(rw, rw, kms, sche)
|
||||
assert.NoError(err)
|
||||
require.NotNil(repo)
|
||||
err = RegisterJobs(ctx, sche, rw, rw, kms)
|
||||
require.NoError(err)
|
||||
|
||||
vs := NewTestVaultServer(t)
|
||||
_, token := vs.CreateToken(t)
|
||||
@@ -847,10 +857,11 @@ func TestRepository_UpdateCredentialStore_Attributes(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
ctx := context.Background()
|
||||
kms := kms.TestKms(t, conn, wrapper)
|
||||
sche := scheduler.TestScheduler(t, conn, wrapper)
|
||||
repo, err := NewRepository(rw, rw, kms, sche)
|
||||
assert.NoError(err)
|
||||
require.NotNil(repo)
|
||||
err = RegisterJobs(ctx, sche, rw, rw, kms)
|
||||
require.NoError(err)
|
||||
|
||||
vs := NewTestVaultServer(t, WithTestVaultTLS(TestServerTLS))
|
||||
|
||||
@@ -896,10 +907,11 @@ func TestRepository_UpdateCredentialStore_Attributes(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
ctx := context.Background()
|
||||
kms := kms.TestKms(t, conn, wrapper)
|
||||
sche := scheduler.TestScheduler(t, conn, wrapper)
|
||||
repo, err := NewRepository(rw, rw, kms, sche)
|
||||
assert.NoError(err)
|
||||
require.NotNil(repo)
|
||||
err = RegisterJobs(ctx, sche, rw, rw, kms)
|
||||
require.NoError(err)
|
||||
|
||||
name := "test-dup-name"
|
||||
_, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
|
||||
@@ -926,10 +938,11 @@ func TestRepository_UpdateCredentialStore_Attributes(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
ctx := context.Background()
|
||||
kms := kms.TestKms(t, conn, wrapper)
|
||||
sche := scheduler.TestScheduler(t, conn, wrapper)
|
||||
repo, err := NewRepository(rw, rw, kms, sche)
|
||||
assert.NoError(err)
|
||||
require.NotNil(repo)
|
||||
err = RegisterJobs(ctx, sche, rw, rw, kms)
|
||||
require.NoError(err)
|
||||
|
||||
vs := NewTestVaultServer(t)
|
||||
|
||||
@@ -975,10 +988,11 @@ func TestRepository_UpdateCredentialStore_Attributes(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
ctx := context.Background()
|
||||
kms := kms.TestKms(t, conn, wrapper)
|
||||
sche := scheduler.TestScheduler(t, conn, wrapper)
|
||||
repo, err := NewRepository(rw, rw, kms, sche)
|
||||
assert.NoError(err)
|
||||
require.NotNil(repo)
|
||||
err = RegisterJobs(ctx, sche, rw, rw, kms)
|
||||
require.NoError(err)
|
||||
|
||||
iamRepo := iam.TestRepo(t, conn, wrapper)
|
||||
_, prj1 := iam.TestScopes(t, iamRepo)
|
||||
@@ -1004,6 +1018,7 @@ func TestRepository_UpdateCredentialStore_VaultToken(t *testing.T) {
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
rw := db.New(conn)
|
||||
wrapper := db.TestWrapper(t)
|
||||
sche := scheduler.TestScheduler(t, conn, wrapper)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
@@ -1044,11 +1059,12 @@ func TestRepository_UpdateCredentialStore_VaultToken(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
ctx := context.Background()
|
||||
kms := kms.TestKms(t, conn, wrapper)
|
||||
sche := scheduler.TestScheduler(t, conn, wrapper)
|
||||
repo, err := NewRepository(rw, rw, kms, sche)
|
||||
require.NoError(err)
|
||||
require.NotNil(repo)
|
||||
_, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
|
||||
err = RegisterJobs(ctx, sche, rw, rw, kms)
|
||||
require.NoError(err)
|
||||
|
||||
v := NewTestVaultServer(t)
|
||||
_, origToken := v.CreateToken(t)
|
||||
@@ -1100,6 +1116,7 @@ func TestRepository_UpdateCredentialStore_ClientCert(t *testing.T) {
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
rw := db.New(conn)
|
||||
wrapper := db.TestWrapper(t)
|
||||
sche := scheduler.TestScheduler(t, conn, wrapper)
|
||||
|
||||
existingClientCert := func(t *testing.T, v *TestVaultServer) *ClientCertificate {
|
||||
clientCert, err := NewClientCertificate(v.ClientCert, v.ClientKey)
|
||||
@@ -1176,11 +1193,12 @@ func TestRepository_UpdateCredentialStore_ClientCert(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
ctx := context.Background()
|
||||
kms := kms.TestKms(t, conn, wrapper)
|
||||
sche := scheduler.TestScheduler(t, conn, wrapper)
|
||||
repo, err := NewRepository(rw, rw, kms, sche)
|
||||
require.NoError(err)
|
||||
require.NotNil(repo)
|
||||
_, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
|
||||
err = RegisterJobs(ctx, sche, rw, rw, kms)
|
||||
require.NoError(err)
|
||||
|
||||
v := NewTestVaultServer(t, WithTestVaultTLS(tt.tls))
|
||||
|
||||
@@ -1244,6 +1262,8 @@ func TestRepository_ListCredentialStores_Multiple_Scopes(t *testing.T) {
|
||||
repo, err := NewRepository(rw, rw, kms, sche)
|
||||
assert.NoError(err)
|
||||
require.NotNil(repo)
|
||||
err = RegisterJobs(context.Background(), sche, rw, rw, kms)
|
||||
require.NoError(err)
|
||||
|
||||
const numPerScope = 10
|
||||
var prjs []string
|
||||
@@ -1279,6 +1299,8 @@ func TestRepository_DeleteCredentialStore(t *testing.T) {
|
||||
repo, err := NewRepository(rw, rw, kms, sche)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, repo)
|
||||
err = RegisterJobs(context.Background(), sche, rw, rw, kms)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
|
||||
scopeId = prj.GetPublicId()
|
||||
|
||||
@@ -41,6 +41,8 @@ func TestRepository_IssueCredentials(t *testing.T) {
|
||||
repo, err := vault.NewRepository(rw, rw, kms, sche)
|
||||
require.NoError(err)
|
||||
require.NotNil(repo)
|
||||
err = vault.RegisterJobs(ctx, sche, rw, rw, kms)
|
||||
require.NoError(err)
|
||||
|
||||
_, token := v.CreateToken(t, vault.WithPolicies([]string{"default", "boundary-controller", "database", "pki"}))
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@ before
|
||||
insert on server
|
||||
for each row execute procedure default_create_time();
|
||||
|
||||
-- UPDATED in 18/01_nonce
|
||||
create table recovery_nonces (
|
||||
nonce text
|
||||
primary key,
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
begin;
|
||||
|
||||
alter table wh_credential_group_membership
|
||||
add primary key (credential_group_key, credential_key);
|
||||
|
||||
commit;
|
||||
@@ -0,0 +1,57 @@
|
||||
begin;
|
||||
|
||||
-- This migration exists to generalize the nonce behavior since we need it for
|
||||
-- more than recovery nonces.
|
||||
|
||||
create table nonce_type_enm (
|
||||
name text primary key
|
||||
constraint only_predefined_nonce_types_allowed
|
||||
check (
|
||||
name in (
|
||||
'recovery',
|
||||
'worker-auth'
|
||||
)
|
||||
)
|
||||
);
|
||||
comment on table nonce_type_enm is
|
||||
'nonce_type_enm is an enumeration table for nonce types. '
|
||||
'It contains rows for representing nonces as either for recovery or worker authentication purposes.';
|
||||
|
||||
insert into nonce_type_enm (name) values
|
||||
('recovery'),
|
||||
('worker-auth');
|
||||
|
||||
-- Drop existing triggers
|
||||
drop trigger default_create_time_column on recovery_nonces;
|
||||
drop trigger immutable_columns on recovery_nonces;
|
||||
|
||||
-- Update table name
|
||||
alter table recovery_nonces rename to nonce;
|
||||
|
||||
-- Add purpose field
|
||||
alter table nonce
|
||||
add column purpose text not null
|
||||
default 'recovery'
|
||||
constraint nonce_type_enm_fkey
|
||||
references nonce_type_enm(name)
|
||||
on update cascade
|
||||
on delete restrict;
|
||||
|
||||
alter table nonce
|
||||
alter column purpose drop default;
|
||||
|
||||
|
||||
-- Recreate triggers
|
||||
create trigger
|
||||
default_create_time_column
|
||||
before
|
||||
insert on nonce
|
||||
for each row execute procedure default_create_time();
|
||||
|
||||
create trigger
|
||||
immutable_columns
|
||||
before
|
||||
update on nonce
|
||||
for each row execute procedure immutable_columns('nonce', 'create_time', 'purpose');
|
||||
|
||||
commit;
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -47,7 +47,7 @@ func TestMigrations_CredentialDimension(t *testing.T) {
|
||||
))
|
||||
require.NoError(err)
|
||||
|
||||
assert.NoError(m.ApplyMigrations(ctx))
|
||||
require.NoError(m.ApplyMigrations(ctx))
|
||||
state, err := m.CurrentState(ctx)
|
||||
require.NoError(err)
|
||||
want := &schema.State{
|
||||
@@ -127,7 +127,7 @@ func TestMigrations_CredentialDimension(t *testing.T) {
|
||||
require.NoError(err)
|
||||
|
||||
count, err := sessionRepo.TerminateCompletedSessions(ctx)
|
||||
assert.NoError(err)
|
||||
require.NoError(err)
|
||||
assert.Zero(count)
|
||||
|
||||
for _, sess := range sessions {
|
||||
@@ -142,8 +142,9 @@ func TestMigrations_CredentialDimension(t *testing.T) {
|
||||
))
|
||||
require.NoError(err)
|
||||
|
||||
assert.NoError(m.ApplyMigrations(ctx))
|
||||
require.NoError(m.ApplyMigrations(ctx))
|
||||
state, err = m.CurrentState(ctx)
|
||||
require.NoError(err)
|
||||
want = &schema.State{
|
||||
Initialized: true,
|
||||
Editions: []schema.EditionState{
|
||||
|
||||
@@ -0,0 +1,162 @@
|
||||
package oss_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/boundary/internal/db"
|
||||
"github.com/hashicorp/boundary/internal/db/common"
|
||||
"github.com/hashicorp/boundary/internal/db/schema"
|
||||
"github.com/hashicorp/boundary/internal/kms"
|
||||
"github.com/hashicorp/boundary/internal/servers"
|
||||
"github.com/hashicorp/boundary/testing/dbtest"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestMigrations_Nonce(t *testing.T) {
|
||||
const (
|
||||
priorMigration = 16005
|
||||
currentMigration = 18001
|
||||
)
|
||||
|
||||
t.Parallel()
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
ctx := context.Background()
|
||||
dialect := dbtest.Postgres
|
||||
|
||||
c, u, _, err := dbtest.StartUsingTemplate(dialect, dbtest.WithTemplate(dbtest.Template1))
|
||||
require.NoError(err)
|
||||
t.Cleanup(func() {
|
||||
require.NoError(c())
|
||||
})
|
||||
d, err := common.SqlOpen(dialect, u)
|
||||
require.NoError(err)
|
||||
|
||||
// migration to the prior migration (before the one we want to test)
|
||||
m, err := schema.NewManager(ctx, schema.Dialect(dialect), d, schema.WithEditions(
|
||||
schema.TestCreatePartialEditions(schema.Dialect(dialect), schema.PartialEditions{"oss": priorMigration}),
|
||||
))
|
||||
require.NoError(err)
|
||||
|
||||
require.NoError(m.ApplyMigrations(ctx))
|
||||
state, err := m.CurrentState(ctx)
|
||||
require.NoError(err)
|
||||
want := &schema.State{
|
||||
Initialized: true,
|
||||
Editions: []schema.EditionState{
|
||||
{
|
||||
Name: "oss",
|
||||
BinarySchemaVersion: priorMigration,
|
||||
DatabaseSchemaVersion: priorMigration,
|
||||
DatabaseSchemaState: schema.Equal,
|
||||
},
|
||||
},
|
||||
}
|
||||
require.Equal(want, state)
|
||||
|
||||
// get a connection
|
||||
dbType, err := db.StringToDbType(dialect)
|
||||
require.NoError(err)
|
||||
conn, err := db.Open(dbType, u)
|
||||
require.NoError(err)
|
||||
rw := db.New(conn)
|
||||
|
||||
// okay, now we can seed the database with test data and validate existing
|
||||
// functionality. At the end of this we will have two values in the table
|
||||
// with no purpose.
|
||||
{
|
||||
// Create an old version of the struct
|
||||
type RecoveryNonce struct {
|
||||
Nonce string
|
||||
}
|
||||
|
||||
require.NoError(rw.CreateItems(ctx, []interface{}{
|
||||
&RecoveryNonce{Nonce: "abcd"},
|
||||
&RecoveryNonce{Nonce: "dcba"},
|
||||
}))
|
||||
|
||||
var results []*RecoveryNonce
|
||||
require.NoError(rw.SearchWhere(ctx, &results, "", nil))
|
||||
require.Len(results, 2)
|
||||
}
|
||||
|
||||
// now we're ready for the migration we want to test.
|
||||
m, err = schema.NewManager(ctx, schema.Dialect(dialect), d, schema.WithEditions(
|
||||
schema.TestCreatePartialEditions(schema.Dialect(dialect), schema.PartialEditions{"oss": currentMigration}),
|
||||
))
|
||||
require.NoError(err)
|
||||
|
||||
require.NoError(m.ApplyMigrations(ctx))
|
||||
state, err = m.CurrentState(ctx)
|
||||
require.NoError(err)
|
||||
want = &schema.State{
|
||||
Initialized: true,
|
||||
Editions: []schema.EditionState{
|
||||
{
|
||||
Name: "oss",
|
||||
BinarySchemaVersion: currentMigration,
|
||||
DatabaseSchemaVersion: currentMigration,
|
||||
DatabaseSchemaState: schema.Equal,
|
||||
},
|
||||
},
|
||||
}
|
||||
require.Equal(want, state)
|
||||
|
||||
// now we have migrated and we will validate functionality; we'll do this
|
||||
// via the repo
|
||||
{
|
||||
wrapper := db.TestWrapper(t)
|
||||
kmsCache := kms.TestKms(t, conn, wrapper)
|
||||
rw := db.New(conn)
|
||||
repo, err := servers.NewRepository(rw, rw, kmsCache)
|
||||
require.NoError(err)
|
||||
require.NotNil(repo)
|
||||
|
||||
// First list and validate that the upgrade adjusted purpose correctly
|
||||
results, err := repo.ListNonces(ctx, servers.NoncePurposeRecovery)
|
||||
require.NoError(err)
|
||||
assert.Len(results, 2)
|
||||
for _, n := range results {
|
||||
assert.Equal(servers.NoncePurposeRecovery, n.Purpose)
|
||||
}
|
||||
|
||||
// Test the constraint, first via the repo, then directly
|
||||
const badPurpose = "foobar"
|
||||
err = repo.AddNonce(ctx, "xyz", badPurpose)
|
||||
require.Error(err)
|
||||
assert.Contains(err.Error(), "unknown nonce purpose")
|
||||
|
||||
// Check that null errors
|
||||
require.Error(rw.CreateItems(ctx, []interface{}{
|
||||
&servers.Nonce{Nonce: "dcba"},
|
||||
}))
|
||||
// Check that bad purpose errors
|
||||
require.Error(rw.CreateItems(ctx, []interface{}{
|
||||
&servers.Nonce{Nonce: "dcba", Purpose: badPurpose},
|
||||
}))
|
||||
|
||||
// Add some valid values, ensure that we can then list for both purposes
|
||||
// and get different values
|
||||
require.NoError(repo.AddNonce(ctx, "wxyz", servers.NoncePurposeWorkerAuth))
|
||||
require.NoError(repo.AddNonce(ctx, "zyxw", servers.NoncePurposeWorkerAuth))
|
||||
results, err = repo.ListNonces(ctx, servers.NoncePurposeRecovery)
|
||||
require.NoError(err)
|
||||
assert.Len(results, 2)
|
||||
for _, n := range results {
|
||||
assert.Equal(servers.NoncePurposeRecovery, n.Purpose)
|
||||
}
|
||||
results, err = repo.ListNonces(ctx, servers.NoncePurposeWorkerAuth)
|
||||
require.NoError(err)
|
||||
assert.Len(results, 2)
|
||||
for _, n := range results {
|
||||
assert.Equal(servers.NoncePurposeWorkerAuth, n.Purpose)
|
||||
}
|
||||
|
||||
// Last: add the same nonce, ensure we error
|
||||
require.NoError(repo.AddNonce(ctx, "double", servers.NoncePurposeWorkerAuth))
|
||||
err = repo.AddNonce(ctx, "double", servers.NoncePurposeWorkerAuth)
|
||||
require.Error(err)
|
||||
require.Contains(err.Error(), "duplicate key value")
|
||||
}
|
||||
}
|
||||
@@ -30,7 +30,7 @@ type GetAuthMethodRequest struct {
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
|
||||
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty" class:"public"` // @gotags: `class:"public"`
|
||||
}
|
||||
|
||||
func (x *GetAuthMethodRequest) Reset() {
|
||||
@@ -281,7 +281,7 @@ type CreateAuthMethodResponse struct {
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Uri string `protobuf:"bytes,1,opt,name=uri,proto3" json:"uri,omitempty"`
|
||||
Uri string `protobuf:"bytes,1,opt,name=uri,proto3" json:"uri,omitempty" class:"public"` // @gotags: `class:"public"`
|
||||
Item *authmethods.AuthMethod `protobuf:"bytes,2,opt,name=item,proto3" json:"item,omitempty"`
|
||||
}
|
||||
|
||||
@@ -336,7 +336,7 @@ type UpdateAuthMethodRequest struct {
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
|
||||
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty" class:"public"` // @gotags: `class:"public"`
|
||||
Item *authmethods.AuthMethod `protobuf:"bytes,2,opt,name=item,proto3" json:"item,omitempty"`
|
||||
UpdateMask *fieldmaskpb.FieldMask `protobuf:"bytes,3,opt,name=update_mask,proto3" json:"update_mask,omitempty"`
|
||||
}
|
||||
@@ -591,7 +591,7 @@ type ChangeStateRequest struct {
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
|
||||
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty" class:"public"` // @gotags: `class:"public"`
|
||||
// Version is used to ensure this resource has not changed.
|
||||
// The mutation will fail if the version does not match the latest known good version.
|
||||
Version uint32 `protobuf:"varint,2,opt,name=version,proto3" json:"version,omitempty"`
|
||||
@@ -876,7 +876,7 @@ type AuthenticateRequest struct {
|
||||
AuthMethodId string `protobuf:"bytes,1,opt,name=auth_method_id,proto3" json:"auth_method_id,omitempty" class:"public"` // @gotags: `class:"public"`
|
||||
// This can be "cookie" or "token". If not provided, "token" will be used. "cookie" activates a split-cookie method where the token is split partially between http-only and regular cookies in order
|
||||
// to keep it safe from rogue JS in the browser.
|
||||
TokenType string `protobuf:"bytes,2,opt,name=token_type,proto3" json:"token_type,omitempty"`
|
||||
TokenType string `protobuf:"bytes,2,opt,name=token_type,proto3" json:"token_type,omitempty" class:"public"` // @gotags: `class:"public"`
|
||||
// Attributes are passed to the Auth Method; the valid keys and values depend on the type of Auth Method as well as the command.
|
||||
Attributes *structpb.Struct `protobuf:"bytes,4,opt,name=attributes,proto3" json:"attributes,omitempty"`
|
||||
// The command to perform.
|
||||
|
||||
@@ -54,6 +54,22 @@ func (req *AuthenticateResponse) Tags() ([]encrypt.PointerTag, error) {
|
||||
Pointer: "/Attributes/Fields/user_id",
|
||||
Classification: encrypt.PublicClassification,
|
||||
},
|
||||
{
|
||||
Pointer: "/Attributes/Fields/status",
|
||||
Classification: encrypt.PublicClassification,
|
||||
},
|
||||
{
|
||||
Pointer: "/Attributes/Fields/auth_url",
|
||||
Classification: encrypt.PublicClassification,
|
||||
},
|
||||
{
|
||||
Pointer: "/Attributes/Fields/token_id",
|
||||
Classification: encrypt.PublicClassification,
|
||||
},
|
||||
{
|
||||
Pointer: "/Attributes/Fields/final_redirect_url",
|
||||
Classification: encrypt.PublicClassification,
|
||||
},
|
||||
// secret fields
|
||||
{
|
||||
Pointer: "/Attributes/Fields/token",
|
||||
@@ -61,3 +77,54 @@ func (req *AuthenticateResponse) Tags() ([]encrypt.PointerTag, error) {
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Tags implements the encrypt.Taggable interface which allows
|
||||
// AuthenticateRequest Attributes to be classified for the encrypt filter.
|
||||
func (req *AuthenticateRequest) Tags() ([]encrypt.PointerTag, error) {
|
||||
if req.Attributes == nil {
|
||||
return nil, nil
|
||||
}
|
||||
return []encrypt.PointerTag{
|
||||
// public fields
|
||||
{
|
||||
Pointer: "/Attributes/Fields/login_name",
|
||||
Classification: encrypt.PublicClassification,
|
||||
},
|
||||
{
|
||||
Pointer: "/Attributes/Fields/auth_url",
|
||||
Classification: encrypt.PublicClassification,
|
||||
},
|
||||
{
|
||||
Pointer: "/Attributes/Fields/token_id",
|
||||
Classification: encrypt.PublicClassification,
|
||||
},
|
||||
{
|
||||
Pointer: "/Attributes/Fields/state",
|
||||
Classification: encrypt.PublicClassification,
|
||||
},
|
||||
// secret fields
|
||||
{
|
||||
Pointer: "/Attributes/Fields/password",
|
||||
Classification: encrypt.SecretClassification,
|
||||
},
|
||||
{
|
||||
Pointer: "/Attributes/Fields/code",
|
||||
Classification: encrypt.SecretClassification,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Tags implements the encrypt.Taggable interface which allows
|
||||
// ChangeStateRequest Attributes to be classified for the encrypt filter.
|
||||
func (req *ChangeStateRequest) Tags() ([]encrypt.PointerTag, error) {
|
||||
if req.Attributes == nil {
|
||||
return nil, nil
|
||||
}
|
||||
return []encrypt.PointerTag{
|
||||
// public fields
|
||||
{
|
||||
Pointer: "/Attributes/Fields/state",
|
||||
Classification: encrypt.PublicClassification,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package services
|
||||
package services_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -6,23 +6,24 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/boundary/internal/gen/controller/api/services"
|
||||
"github.com/hashicorp/boundary/sdk/pbs/controller/api"
|
||||
"github.com/hashicorp/boundary/sdk/wrapper"
|
||||
"github.com/hashicorp/eventlogger"
|
||||
"github.com/hashicorp/eventlogger/filters/encrypt"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
structpb "google.golang.org/protobuf/types/known/structpb"
|
||||
)
|
||||
|
||||
func TestScope_Tags(t *testing.T) {
|
||||
// TestAuthenticate_Tags will test that the response filtering aligns with the
|
||||
// AuthenticateResponse and AuthenticateResponse tags. See:
|
||||
// internal/tests/api/authmethods/authenticate_test.go TestAuthenticate where
|
||||
// the audit events produced using these tags is unit tested.
|
||||
func TestAuthenticate_Tags(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
now := time.Now()
|
||||
wrapper := wrapper.TestWrapper(t)
|
||||
testEncryptingFilter := &encrypt.Filter{
|
||||
Wrapper: wrapper,
|
||||
HmacSalt: []byte("salt"),
|
||||
HmacInfo: []byte("info"),
|
||||
}
|
||||
testEncryptingFilter := api.NewEncryptFilter(t, wrapper)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
@@ -30,11 +31,11 @@ func TestScope_Tags(t *testing.T) {
|
||||
wantEvent *eventlogger.Event
|
||||
}{
|
||||
{
|
||||
name: "validate-filtering",
|
||||
name: "validate-response-filtering",
|
||||
testEvent: &eventlogger.Event{
|
||||
Type: "test",
|
||||
CreatedAt: now,
|
||||
Payload: &AuthenticateResponse{
|
||||
Payload: &services.AuthenticateResponse{
|
||||
Command: "public-command",
|
||||
Attributes: &structpb.Struct{
|
||||
Fields: map[string]*structpb.Value{
|
||||
@@ -49,6 +50,10 @@ func TestScope_Tags(t *testing.T) {
|
||||
"token_type": structpb.NewStringValue("public-token_type"),
|
||||
"updated_time": structpb.NewStringValue("public-updated_time"),
|
||||
"user_id": structpb.NewStringValue("public-user_id"),
|
||||
"status": structpb.NewStringValue("public-status"),
|
||||
"auth_url": structpb.NewStringValue("public-auth_url"),
|
||||
"token_id": structpb.NewStringValue("public-token_id"),
|
||||
"final_redirect_url": structpb.NewStringValue("public-final_redirect_url"),
|
||||
"token": structpb.NewStringValue("secret-token"),
|
||||
},
|
||||
},
|
||||
@@ -57,7 +62,7 @@ func TestScope_Tags(t *testing.T) {
|
||||
wantEvent: &eventlogger.Event{
|
||||
Type: "test",
|
||||
CreatedAt: now,
|
||||
Payload: &AuthenticateResponse{
|
||||
Payload: &services.AuthenticateResponse{
|
||||
Command: "public-command",
|
||||
Attributes: &structpb.Struct{
|
||||
Fields: map[string]*structpb.Value{
|
||||
@@ -72,12 +77,57 @@ func TestScope_Tags(t *testing.T) {
|
||||
"token_type": structpb.NewStringValue("public-token_type"),
|
||||
"updated_time": structpb.NewStringValue("public-updated_time"),
|
||||
"user_id": structpb.NewStringValue("public-user_id"),
|
||||
"status": structpb.NewStringValue("public-status"),
|
||||
"auth_url": structpb.NewStringValue("public-auth_url"),
|
||||
"token_id": structpb.NewStringValue("public-token_id"),
|
||||
"final_redirect_url": structpb.NewStringValue("public-final_redirect_url"),
|
||||
"token": structpb.NewStringValue("<REDACTED>"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "validate-request-filtering",
|
||||
testEvent: &eventlogger.Event{
|
||||
Type: "test",
|
||||
CreatedAt: now,
|
||||
Payload: &services.AuthenticateRequest{
|
||||
AuthMethodId: "public-auth-method-id",
|
||||
TokenType: "public-token-type",
|
||||
Command: "public-command",
|
||||
Attributes: &structpb.Struct{
|
||||
Fields: map[string]*structpb.Value{
|
||||
"login_name": structpb.NewStringValue("public-login_name"),
|
||||
"auth_url": structpb.NewStringValue("public-auth_url"),
|
||||
"token_id": structpb.NewStringValue("public-token_id"),
|
||||
"state": structpb.NewStringValue("public-state"),
|
||||
"password": structpb.NewStringValue("secret-password"),
|
||||
"code": structpb.NewStringValue("secret-code"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantEvent: &eventlogger.Event{
|
||||
Type: "test",
|
||||
CreatedAt: now,
|
||||
Payload: &services.AuthenticateRequest{
|
||||
AuthMethodId: "public-auth-method-id",
|
||||
TokenType: "public-token-type",
|
||||
Command: "public-command",
|
||||
Attributes: &structpb.Struct{
|
||||
Fields: map[string]*structpb.Value{
|
||||
"login_name": structpb.NewStringValue("public-login_name"),
|
||||
"auth_url": structpb.NewStringValue("public-auth_url"),
|
||||
"token_id": structpb.NewStringValue("public-token_id"),
|
||||
"state": structpb.NewStringValue("public-state"),
|
||||
"password": structpb.NewStringValue("<REDACTED>"),
|
||||
"code": structpb.NewStringValue("<REDACTED>"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
||||
@@ -49,6 +49,10 @@ type RequestInfo struct {
|
||||
// ticket is a unique id that allows the grpc-gateway to verify that the info
|
||||
// came from its companion http proxy
|
||||
Ticket string `protobuf:"bytes,110,opt,name=ticket,proto3" json:"ticket,omitempty"`
|
||||
// trace_id is the request's trace_id
|
||||
TraceId string `protobuf:"bytes,120,opt,name=trace_id,json=traceId,proto3" json:"trace_id,omitempty"`
|
||||
// event_id is the request's event id
|
||||
EventId string `protobuf:"bytes,130,opt,name=event_id,json=eventId,proto3" json:"event_id,omitempty"`
|
||||
}
|
||||
|
||||
func (x *RequestInfo) Reset() {
|
||||
@@ -160,13 +164,27 @@ func (x *RequestInfo) GetTicket() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *RequestInfo) GetTraceId() string {
|
||||
if x != nil {
|
||||
return x.TraceId
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *RequestInfo) GetEventId() string {
|
||||
if x != nil {
|
||||
return x.EventId
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
var File_controller_auth_v1_auth_proto protoreflect.FileDescriptor
|
||||
|
||||
var file_controller_auth_v1_auth_proto_rawDesc = []byte{
|
||||
0x0a, 0x1d, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x61, 0x75, 0x74,
|
||||
0x68, 0x2f, 0x76, 0x31, 0x2f, 0x61, 0x75, 0x74, 0x68, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12,
|
||||
0x12, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x75, 0x74, 0x68,
|
||||
0x2e, 0x76, 0x31, 0x22, 0x90, 0x03, 0x0a, 0x0b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49,
|
||||
0x2e, 0x76, 0x31, 0x22, 0xc7, 0x03, 0x0a, 0x0b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49,
|
||||
0x6e, 0x66, 0x6f, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x0a, 0x20, 0x01, 0x28,
|
||||
0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x12, 0x16, 0x0a, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f,
|
||||
0x64, 0x18, 0x14, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12,
|
||||
@@ -191,12 +209,15 @@ var file_controller_auth_v1_auth_proto_rawDesc = []byte{
|
||||
0x65, 0x6c, 0x79, 0x18, 0x64, 0x20, 0x01, 0x28, 0x08, 0x52, 0x13, 0x64, 0x69, 0x73, 0x61, 0x62,
|
||||
0x6c, 0x65, 0x41, 0x75, 0x74, 0x68, 0x45, 0x6e, 0x74, 0x69, 0x72, 0x65, 0x6c, 0x79, 0x12, 0x16,
|
||||
0x0a, 0x06, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x18, 0x6e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06,
|
||||
0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x42, 0x41, 0x5a, 0x3f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62,
|
||||
0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2f, 0x62,
|
||||
0x6f, 0x75, 0x6e, 0x64, 0x61, 0x72, 0x79, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c,
|
||||
0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f,
|
||||
0x61, 0x75, 0x74, 0x68, 0x3b, 0x61, 0x75, 0x74, 0x68, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f,
|
||||
0x33,
|
||||
0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x74, 0x72, 0x61, 0x63, 0x65, 0x5f,
|
||||
0x69, 0x64, 0x18, 0x78, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x74, 0x72, 0x61, 0x63, 0x65, 0x49,
|
||||
0x64, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x82, 0x01,
|
||||
0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x42, 0x41, 0x5a,
|
||||
0x3f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61, 0x73, 0x68,
|
||||
0x69, 0x63, 0x6f, 0x72, 0x70, 0x2f, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x61, 0x72, 0x79, 0x2f, 0x69,
|
||||
0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x63, 0x6f, 0x6e, 0x74,
|
||||
0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x61, 0x75, 0x74, 0x68, 0x3b, 0x61, 0x75, 0x74, 0x68,
|
||||
0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"log"
|
||||
"net/url"
|
||||
"os"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -19,8 +20,8 @@ import (
|
||||
"github.com/hashicorp/eventlogger/filters/gated"
|
||||
"github.com/hashicorp/eventlogger/formatter_filters/cloudevents"
|
||||
"github.com/hashicorp/eventlogger/sinks/writer"
|
||||
|
||||
"github.com/hashicorp/go-hclog"
|
||||
"google.golang.org/protobuf/types/known/fieldmaskpb"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -137,6 +138,19 @@ func SysEventer() *Eventer {
|
||||
return sysEventer
|
||||
}
|
||||
|
||||
// NewAuditEncryptFilter returns a new encrypt filter which is initialized for
|
||||
// audit events.
|
||||
func NewAuditEncryptFilter(opt ...Option) (*encrypt.Filter, error) {
|
||||
opts := getOpts(opt...)
|
||||
|
||||
return &encrypt.Filter{
|
||||
Wrapper: opts.withAuditWrapper,
|
||||
IgnoreTypes: []reflect.Type{
|
||||
reflect.TypeOf(&fieldmaskpb.FieldMask{}),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// NewEventer creates a new Eventer using the config. Supports options:
|
||||
// WithNow, WithSerializationLock, WithBroker, WithAuditWrapper
|
||||
func NewEventer(log hclog.Logger, serializationLock *sync.Mutex, serverName string, c EventerConfig, opt ...Option) (*Eventer, error) {
|
||||
@@ -269,8 +283,9 @@ func NewEventer(log hclog.Logger, serializationLock *sync.Mutex, serverName stri
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", op, err)
|
||||
}
|
||||
encryptFilter := &encrypt.Filter{
|
||||
Wrapper: opts.withAuditWrapper,
|
||||
encryptFilter, err := NewAuditEncryptFilter(opt...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", op, err)
|
||||
}
|
||||
if len(s.AuditConfig.FilterOverrides) > 0 {
|
||||
overrides := encrypt.DefaultFilterOperations()
|
||||
|
||||
@@ -353,7 +353,7 @@ func Test_NewEventer(t *testing.T) {
|
||||
t.Parallel()
|
||||
testSetup := TestEventerConfig(t, "Test_NewEventer")
|
||||
|
||||
testSetupWithOpts := TestEventerConfig(t, "Test_NewEventer", TestWithAuditSink(t), TestWithObservationSink(t), testWithSysSink(t))
|
||||
testSetupWithOpts := TestEventerConfig(t, "Test_NewEventer", TestWithAuditSink(t), TestWithObservationSink(t), TestWithSysSink(t))
|
||||
|
||||
testHclogSetup := TestEventerConfig(t, "Test_NewEventer", testWithSinkFormat(t, TextHclogSinkFormat))
|
||||
|
||||
|
||||
@@ -222,7 +222,7 @@ func TestWithBroker(t *testing.T, b broker) Option {
|
||||
}
|
||||
}
|
||||
|
||||
// TestWithObservationSink is an unexported and a test option
|
||||
// TestWithObservationSink is a test option
|
||||
func TestWithObservationSink(t *testing.T) Option {
|
||||
t.Helper()
|
||||
return func(o *options) {
|
||||
@@ -230,7 +230,7 @@ func TestWithObservationSink(t *testing.T) Option {
|
||||
}
|
||||
}
|
||||
|
||||
// TestWithAuditSink is an unexported and a test option
|
||||
// TestWithAuditSink is a test option
|
||||
func TestWithAuditSink(t *testing.T) Option {
|
||||
t.Helper()
|
||||
return func(o *options) {
|
||||
@@ -238,8 +238,8 @@ func TestWithAuditSink(t *testing.T) Option {
|
||||
}
|
||||
}
|
||||
|
||||
// testWithSysSink is an unexported and a test option
|
||||
func testWithSysSink(t *testing.T) Option {
|
||||
// TestWithSysSink is a test option
|
||||
func TestWithSysSink(t *testing.T) Option {
|
||||
t.Helper()
|
||||
return func(o *options) {
|
||||
o.withSysSink = true
|
||||
|
||||
@@ -153,36 +153,36 @@ message OidcAuthMethodAttributes {
|
||||
// The structure of the OIDC authenticate start response, in the JSON object
|
||||
message OidcAuthMethodAuthenticateStartResponse {
|
||||
// The returned authentication URL
|
||||
string auth_url = 10 [json_name = "auth_url"];
|
||||
string auth_url = 10 [json_name = "auth_url"]; // @gotags: `class:"public"`
|
||||
|
||||
// The returned token ID
|
||||
string token_id = 30 [json_name = "token_id"];
|
||||
string token_id = 30 [json_name = "token_id"]; // @gotags: `class:"public"`
|
||||
}
|
||||
|
||||
// The structure of OIDC callback request parameters
|
||||
message OidcAuthMethodAuthenticateCallbackRequest {
|
||||
// The returned code
|
||||
string code = 10 [json_name = "code"];
|
||||
string code = 10 [json_name = "code"]; // @gotags: `class:"secret"`
|
||||
|
||||
// The returned state
|
||||
string state = 20 [json_name = "state"];
|
||||
string state = 20 [json_name = "state"]; // @gotags: `class:"public"`
|
||||
|
||||
// Error parameters, if they are returned
|
||||
string error = 30 [json_name = "error"];
|
||||
string error_description = 40 [json_name = "error_description"];
|
||||
string error_uri = 50 [json_name = "error_uri"];
|
||||
string error = 30 [json_name = "error"]; // @gotags: `class:"public"`
|
||||
string error_description = 40 [json_name = "error_description"]; // @gotags: `class:"public"`
|
||||
string error_uri = 50 [json_name = "error_uri"]; // @gotags: `class:"public"`
|
||||
}
|
||||
|
||||
// The structure of OIDC callback response parameters
|
||||
message OidcAuthMethodAuthenticateCallbackResponse {
|
||||
// The final redirection URL
|
||||
string final_redirect_url = 10 [json_name = "final_redirect_url"];
|
||||
string final_redirect_url = 10 [json_name = "final_redirect_url"]; // @gotags: `class:"public"`
|
||||
}
|
||||
|
||||
// The structure of OIDC token request parameters
|
||||
message OidcAuthMethodAuthenticateTokenRequest {
|
||||
// The ID of the pending token
|
||||
string token_id = 10 [json_name = "token_id"];
|
||||
string token_id = 10 [json_name = "token_id"]; // @gotags: `class:"private"`
|
||||
}
|
||||
|
||||
// Internal only: the structure of a token response if it _does not_ contain a
|
||||
@@ -190,5 +190,5 @@ message OidcAuthMethodAuthenticateTokenRequest {
|
||||
message OidcAuthMethodAuthenticateTokenResponse {
|
||||
// The status. This will always be "unknown". It will never be forwarded to
|
||||
// the consumer.
|
||||
string status = 10;
|
||||
string status = 10; // @gotags: `class:"public"`
|
||||
}
|
||||
@@ -107,7 +107,7 @@ service AuthMethodService {
|
||||
}
|
||||
|
||||
message GetAuthMethodRequest {
|
||||
string id = 1;
|
||||
string id = 1; // @gotags: `class:"public"`
|
||||
}
|
||||
|
||||
message GetAuthMethodResponse {
|
||||
@@ -129,12 +129,12 @@ message CreateAuthMethodRequest {
|
||||
}
|
||||
|
||||
message CreateAuthMethodResponse {
|
||||
string uri = 1;
|
||||
string uri = 1; // @gotags: `class:"public"`
|
||||
resources.authmethods.v1.AuthMethod item = 2;
|
||||
}
|
||||
|
||||
message UpdateAuthMethodRequest {
|
||||
string id = 1;
|
||||
string id = 1; // @gotags: `class:"public"`
|
||||
resources.authmethods.v1.AuthMethod item = 2;
|
||||
google.protobuf.FieldMask update_mask = 3 [json_name = "update_mask"];
|
||||
}
|
||||
@@ -160,7 +160,7 @@ message OidcChangeStateAttributes {
|
||||
}
|
||||
|
||||
message ChangeStateRequest {
|
||||
string id = 1;
|
||||
string id = 1; // @gotags: `class:"public"`
|
||||
// Version is used to ensure this resource has not changed.
|
||||
// The mutation will fail if the version does not match the latest known good version.
|
||||
uint32 version = 2;
|
||||
@@ -203,7 +203,7 @@ message AuthenticateRequest {
|
||||
string auth_method_id = 1 [json_name = "auth_method_id"]; // @gotags: `class:"public"`
|
||||
// This can be "cookie" or "token". If not provided, "token" will be used. "cookie" activates a split-cookie method where the token is split partially between http-only and regular cookies in order
|
||||
// to keep it safe from rogue JS in the browser.
|
||||
string token_type = 2 [json_name = "token_type"];
|
||||
string token_type = 2 [json_name = "token_type"]; // @gotags: `class:"public"`
|
||||
// Attributes are passed to the Auth Method; the valid keys and values depend on the type of Auth Method as well as the command.
|
||||
google.protobuf.Struct attributes = 4 [json_name = "attributes"];
|
||||
// The command to perform.
|
||||
|
||||
@@ -39,4 +39,10 @@ message RequestInfo {
|
||||
// ticket is a unique id that allows the grpc-gateway to verify that the info
|
||||
// came from its companion http proxy
|
||||
string ticket = 110;
|
||||
|
||||
// trace_id is the request's trace_id
|
||||
string trace_id = 120;
|
||||
|
||||
// event_id is the request's event id
|
||||
string event_id = 130;
|
||||
}
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
"github.com/hashicorp/boundary/internal/observability/event"
|
||||
"github.com/hashicorp/boundary/internal/perms"
|
||||
"github.com/hashicorp/boundary/internal/requests"
|
||||
"github.com/hashicorp/boundary/internal/servers"
|
||||
"github.com/hashicorp/boundary/internal/servers/controller/common"
|
||||
"github.com/hashicorp/boundary/internal/servers/controller/handlers"
|
||||
"github.com/hashicorp/boundary/internal/types/action"
|
||||
@@ -391,7 +392,7 @@ func (v *verifier) decryptToken(ctx context.Context) {
|
||||
v.requestInfo.TokenFormat = uint32(AuthTokenTypeUnknown)
|
||||
return
|
||||
}
|
||||
if err := repo.AddRecoveryNonce(v.ctx, info.Nonce); err != nil {
|
||||
if err := repo.AddNonce(v.ctx, info.Nonce, servers.NoncePurposeRecovery); err != nil {
|
||||
event.WriteError(ctx, op, err, event.WithInfoMsg("decrypt recovery token: error adding nonce to database (possible replay attack)"))
|
||||
v.requestInfo.TokenFormat = uint32(AuthTokenTypeUnknown)
|
||||
return
|
||||
|
||||
@@ -32,7 +32,6 @@ import (
|
||||
external_host_plugins "github.com/hashicorp/boundary/sdk/plugins/host"
|
||||
"github.com/hashicorp/go-hclog"
|
||||
"github.com/hashicorp/go-secure-stdlib/mlock"
|
||||
"github.com/patrickmn/go-cache"
|
||||
ua "go.uber.org/atomic"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
@@ -45,10 +44,10 @@ type Controller struct {
|
||||
baseCancel context.CancelFunc
|
||||
started *ua.Bool
|
||||
|
||||
tickerWg sync.WaitGroup
|
||||
schedulerWg sync.WaitGroup
|
||||
tickerWg *sync.WaitGroup
|
||||
schedulerWg *sync.WaitGroup
|
||||
|
||||
workerAuthCache *cache.Cache
|
||||
workerAuthCache *sync.Map
|
||||
|
||||
// Used for testing and tracking worker health
|
||||
workerStatusUpdateTimes *sync.Map
|
||||
@@ -82,6 +81,9 @@ func New(ctx context.Context, conf *Config) (*Controller, error) {
|
||||
conf: conf,
|
||||
logger: conf.Logger.Named("controller"),
|
||||
started: ua.NewBool(false),
|
||||
tickerWg: new(sync.WaitGroup),
|
||||
schedulerWg: new(sync.WaitGroup),
|
||||
workerAuthCache: new(sync.Map),
|
||||
workerStatusUpdateTimes: new(sync.Map),
|
||||
}
|
||||
|
||||
@@ -209,7 +211,6 @@ func New(ctx context.Context, conf *Config) (*Controller, error) {
|
||||
c.SessionRepoFn = func() (*session.Repository, error) {
|
||||
return session.NewRepository(dbase, dbase, c.kms)
|
||||
}
|
||||
c.workerAuthCache = cache.New(0, 0)
|
||||
|
||||
return c, nil
|
||||
}
|
||||
@@ -224,7 +225,7 @@ func (c *Controller) Start() error {
|
||||
if err := c.registerJobs(); err != nil {
|
||||
return fmt.Errorf("error registering jobs: %w", err)
|
||||
}
|
||||
if err := c.scheduler.Start(c.baseContext, &c.schedulerWg); err != nil {
|
||||
if err := c.scheduler.Start(c.baseContext, c.schedulerWg); err != nil {
|
||||
return fmt.Errorf("error starting scheduler: %w", err)
|
||||
}
|
||||
if err := c.startListeners(c.baseContext); err != nil {
|
||||
@@ -238,7 +239,7 @@ func (c *Controller) Start() error {
|
||||
}()
|
||||
go func() {
|
||||
defer c.tickerWg.Done()
|
||||
c.startRecoveryNonceCleanupTicking(c.baseContext)
|
||||
c.startNonceCleanupTicking(c.baseContext)
|
||||
}()
|
||||
go func() {
|
||||
defer c.tickerWg.Done()
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"github.com/hashicorp/boundary/internal/db"
|
||||
"github.com/hashicorp/boundary/internal/errors"
|
||||
"github.com/hashicorp/boundary/internal/kms"
|
||||
"github.com/hashicorp/boundary/internal/observability/event"
|
||||
"github.com/hashicorp/boundary/internal/servers/controller/common"
|
||||
"github.com/hashicorp/boundary/internal/servers/controller/handlers"
|
||||
"google.golang.org/grpc"
|
||||
@@ -49,19 +50,34 @@ func newGatewayMux() *runtime.ServeMux {
|
||||
)
|
||||
}
|
||||
|
||||
func newGatewayServer(ctx context.Context, iamRepoFn common.IamRepoFactory, authTokenRepoFn common.AuthTokenRepoFactory, serversRepoFn common.ServersRepoFactory, kms *kms.Kms) (*grpc.Server, string, error) {
|
||||
func newGatewayServer(
|
||||
ctx context.Context,
|
||||
iamRepoFn common.IamRepoFactory,
|
||||
authTokenRepoFn common.AuthTokenRepoFactory,
|
||||
serversRepoFn common.ServersRepoFactory,
|
||||
kms *kms.Kms,
|
||||
eventer *event.Eventer,
|
||||
) (*grpc.Server, string, error) {
|
||||
const op = "controller.newGatewayServer"
|
||||
ticket, err := db.NewPrivateId("gwticket")
|
||||
if err != nil {
|
||||
return nil, "", errors.Wrap(ctx, err, op, errors.WithMsg("unable to generate gateway ticket"))
|
||||
}
|
||||
requestCtxInterceptor, err := requestCtxInterceptor(ctx, iamRepoFn, authTokenRepoFn, serversRepoFn, kms, ticket)
|
||||
requestCtxInterceptor, err := requestCtxInterceptor(ctx, iamRepoFn, authTokenRepoFn, serversRepoFn, kms, ticket, eventer)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
return grpc.NewServer(
|
||||
grpc.MaxRecvMsgSize(math.MaxInt32),
|
||||
grpc.MaxSendMsgSize(math.MaxInt32),
|
||||
grpc.UnaryInterceptor(grpc_middleware.ChainUnaryServer(requestCtxInterceptor, errorInterceptor(ctx), statusCodeInterceptor(ctx))),
|
||||
grpc.UnaryInterceptor(
|
||||
grpc_middleware.ChainUnaryServer(
|
||||
requestCtxInterceptor, // populated requestInfo from headers into the request ctx
|
||||
auditRequestInterceptor(ctx), // before we get started, audit the request
|
||||
errorInterceptor(ctx), // convert domain and api errors into headers for the http proxy
|
||||
statusCodeInterceptor(ctx), // convert grpc codes into http status codes for the http proxy (can modify the resp)
|
||||
auditResponseInterceptor(ctx), // as we finish, audit the response
|
||||
),
|
||||
),
|
||||
), ticket, nil
|
||||
}
|
||||
@@ -288,6 +288,16 @@ func wrapHandlerWithCommonFuncs(h http.Handler, c *Controller, props HandlerProp
|
||||
|
||||
requestInfo.PublicId, requestInfo.EncryptedToken, requestInfo.TokenFormat = auth.GetTokenFromRequest(ctx, c.kms, r)
|
||||
|
||||
if info, ok := event.RequestInfoFromContext(ctx); ok {
|
||||
// piggyback some eventing fields with the auth info proto message
|
||||
requestInfo.EventId = info.EventId
|
||||
requestInfo.TraceId = info.Id
|
||||
} else {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
event.WriteError(ctx, op, errors.New("unable to read event request info from context"))
|
||||
return
|
||||
}
|
||||
|
||||
// Serialize the request info to send it across the wire to the
|
||||
// grpc-gateway via an http header
|
||||
requestInfo.Ticket = c.gatewayTicket // allows the grpc-gateway to verify the request info came from it's in-memory companion http proxy
|
||||
@@ -302,6 +312,8 @@ func wrapHandlerWithCommonFuncs(h http.Handler, c *Controller, props HandlerProp
|
||||
// See: https://pkg.go.dev/github.com/grpc-ecosystem/grpc-gateway/runtime#DefaultHeaderMatcher
|
||||
r.Header.Set("Grpc-Metadata-"+requestInfoMdKey, base58.FastBase58Encoding(marshalledRequestInfo))
|
||||
|
||||
// Set the context back on the request
|
||||
r = r.WithContext(ctx)
|
||||
h.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package credentialstores
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
@@ -52,6 +53,8 @@ func TestList(t *testing.T) {
|
||||
kms := kms.TestKms(t, conn, wrapper)
|
||||
sche := scheduler.TestScheduler(t, conn, wrapper)
|
||||
rw := db.New(conn)
|
||||
err := vault.RegisterJobs(context.Background(), sche, rw, rw, kms)
|
||||
require.NoError(t, err)
|
||||
|
||||
iamRepo := iam.TestRepo(t, conn, wrapper)
|
||||
iamRepoFn := func() (*iam.Repository, error) {
|
||||
@@ -156,6 +159,8 @@ func TestCreate(t *testing.T) {
|
||||
kms := kms.TestKms(t, conn, wrapper)
|
||||
sche := scheduler.TestScheduler(t, conn, wrapper)
|
||||
rw := db.New(conn)
|
||||
err := vault.RegisterJobs(context.Background(), sche, rw, rw, kms)
|
||||
require.NoError(t, err)
|
||||
|
||||
iamRepo := iam.TestRepo(t, conn, wrapper)
|
||||
iamRepoFn := func() (*iam.Repository, error) {
|
||||
@@ -655,6 +660,8 @@ func TestDelete(t *testing.T) {
|
||||
kms := kms.TestKms(t, conn, wrapper)
|
||||
sche := scheduler.TestScheduler(t, conn, wrapper)
|
||||
rw := db.New(conn)
|
||||
err := vault.RegisterJobs(context.Background(), sche, rw, rw, kms)
|
||||
require.NoError(t, err)
|
||||
|
||||
iamRepo := iam.TestRepo(t, conn, wrapper)
|
||||
iamRepoFn := func() (*iam.Repository, error) {
|
||||
@@ -714,6 +721,8 @@ func TestUpdate(t *testing.T) {
|
||||
kms := kms.TestKms(t, conn, wrapper)
|
||||
sche := scheduler.TestScheduler(t, conn, wrapper)
|
||||
rw := db.New(conn)
|
||||
err := vault.RegisterJobs(context.Background(), sche, rw, rw, kms)
|
||||
require.NoError(t, err)
|
||||
|
||||
iamRepo := iam.TestRepo(t, conn, wrapper)
|
||||
iamRepoFn := func() (*iam.Repository, error) {
|
||||
|
||||
@@ -11,7 +11,6 @@ import (
|
||||
|
||||
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
|
||||
pbs "github.com/hashicorp/boundary/internal/gen/controller/api/services"
|
||||
"github.com/hashicorp/boundary/internal/observability/event"
|
||||
authtokenpb "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/authtokens"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
@@ -65,12 +64,6 @@ func OutgoingResponseFilter(ctx context.Context, w http.ResponseWriter, m proto.
|
||||
}
|
||||
}
|
||||
|
||||
// add the outgoing resp proto. See: common.flushGatedEvents is where the
|
||||
// event.Response.StatusCode is added.
|
||||
if err := event.WriteAudit(ctx, op, event.WithResponse(&event.Response{Details: m})); err != nil {
|
||||
return fmt.Errorf("%s: unable to write audit event: %w", op, err)
|
||||
}
|
||||
|
||||
switch m := m.(type) {
|
||||
case *pbs.AuthenticateResponse:
|
||||
if m.GetAttributes() == nil || m.GetAttributes().GetFields() == nil {
|
||||
|
||||
@@ -581,11 +581,13 @@ func TestUpdate(t *testing.T) {
|
||||
tar, err := tcp.New(proj.GetPublicId(), target.WithName("default"), target.WithDescription("default"))
|
||||
tar.DefaultPort = 2
|
||||
require.NoError(t, err)
|
||||
gtar, _, _, err := repo.CreateTarget(context.Background(), tar, target.WithHostSources([]string{hs[0].GetPublicId(), hs[1].GetPublicId()}))
|
||||
gtar, _, _, err := repo.CreateTarget(context.Background(), tar)
|
||||
require.NoError(t, err)
|
||||
gtar, _, _, err = repo.AddTargetHostSources(context.Background(), gtar.GetPublicId(), gtar.GetVersion(), []string{hs[0].GetPublicId(), hs[1].GetPublicId()})
|
||||
require.NoError(t, err)
|
||||
tar = gtar.(*tcp.Target)
|
||||
|
||||
var version uint32 = 1
|
||||
var version uint32 = gtar.GetVersion()
|
||||
|
||||
resetTarget := func() {
|
||||
version++
|
||||
@@ -2567,6 +2569,9 @@ func TestAuthorizeSession(t *testing.T) {
|
||||
kms := kms.TestKms(t, conn, wrapper)
|
||||
|
||||
sche := scheduler.TestScheduler(t, conn, wrapper)
|
||||
err := vault.RegisterJobs(context.Background(), sche, rw, rw, kms)
|
||||
require.NoError(t, err)
|
||||
|
||||
repoFn := func() (*target.Repository, error) {
|
||||
return target.NewRepository(rw, rw, kms)
|
||||
}
|
||||
@@ -2799,6 +2804,9 @@ func TestAuthorizeSession_Errors(t *testing.T) {
|
||||
kms := kms.TestKms(t, conn, wrapper)
|
||||
|
||||
sche := scheduler.TestScheduler(t, conn, wrapper)
|
||||
err := vault.RegisterJobs(context.Background(), sche, rw, rw, kms)
|
||||
require.NoError(t, err)
|
||||
|
||||
repoFn := func() (*target.Repository, error) {
|
||||
return target.NewRepository(rw, rw, kms)
|
||||
}
|
||||
|
||||
@@ -195,9 +195,5 @@ func ValidNameDescription(in string) bool {
|
||||
return !unicode.IsPrint(c)
|
||||
})
|
||||
|
||||
if idx != -1 {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
return idx == -1
|
||||
}
|
||||
@@ -70,7 +70,7 @@ func (m *interceptingListener) Accept() (net.Conn, error) {
|
||||
}
|
||||
return nil, fmt.Errorf("error reading nonce from worker, expected %d bytes, got %d", 20, read)
|
||||
}
|
||||
workerInfoRaw, found := m.c.workerAuthCache.Get(string(nonce))
|
||||
workerInfoRaw, found := m.c.workerAuthCache.Load(string(nonce))
|
||||
if !found {
|
||||
if err := conn.Close(); err != nil {
|
||||
event.WriteError(ctx, op, err, event.WithInfoMsg("error closing worker connection"))
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
authpb "github.com/hashicorp/boundary/internal/gen/controller/auth"
|
||||
pberrors "github.com/hashicorp/boundary/internal/gen/errors"
|
||||
"github.com/hashicorp/boundary/internal/kms"
|
||||
"github.com/hashicorp/boundary/internal/observability/event"
|
||||
"github.com/hashicorp/boundary/internal/requests"
|
||||
"github.com/hashicorp/boundary/internal/servers/controller/auth"
|
||||
"github.com/hashicorp/boundary/internal/servers/controller/common"
|
||||
@@ -48,6 +49,7 @@ func requestCtxInterceptor(
|
||||
serversRepoFn common.ServersRepoFactory,
|
||||
kms *kms.Kms,
|
||||
ticket string,
|
||||
eventer *event.Eventer,
|
||||
) (grpc.UnaryServerInterceptor, error) {
|
||||
const op = "controller.requestCtxInterceptor"
|
||||
if iamRepoFn == nil {
|
||||
@@ -65,6 +67,9 @@ func requestCtxInterceptor(
|
||||
if ticket == "" {
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing ticket")
|
||||
}
|
||||
if eventer == nil {
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing eventer")
|
||||
}
|
||||
// Authorization unary interceptor function to handle authorize per RPC call
|
||||
return func(interceptorCtx context.Context,
|
||||
req interface{},
|
||||
@@ -112,6 +117,23 @@ func requestCtxInterceptor(
|
||||
Method: requestInfo.Method,
|
||||
})
|
||||
|
||||
// This event request info is required by downstream handlers
|
||||
info := &event.RequestInfo{
|
||||
EventId: requestInfo.EventId,
|
||||
Id: requestInfo.TraceId,
|
||||
PublicId: requestInfo.PublicId,
|
||||
Method: requestInfo.Method,
|
||||
Path: requestInfo.Path,
|
||||
}
|
||||
interceptorCtx, err = event.NewRequestInfoContext(interceptorCtx, info)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(interceptorCtx, err, op, errors.WithCode(errors.Internal), errors.WithMsg("unable to create context with request info"))
|
||||
}
|
||||
interceptorCtx, err = event.NewEventerContext(interceptorCtx, eventer)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(interceptorCtx, err, op, errors.WithCode(errors.Internal), errors.WithMsg("unable to create context with eventer"))
|
||||
}
|
||||
|
||||
// Calls the handler
|
||||
h, err := handler(interceptorCtx, req)
|
||||
|
||||
@@ -205,3 +227,44 @@ func isNil(i interface{}) bool {
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func auditRequestInterceptor(
|
||||
_ context.Context,
|
||||
) grpc.UnaryServerInterceptor {
|
||||
const op = "controller.auditRequestInterceptor"
|
||||
return func(interceptorCtx context.Context,
|
||||
req interface{},
|
||||
_ *grpc.UnaryServerInfo,
|
||||
handler grpc.UnaryHandler) (interface{}, error) {
|
||||
|
||||
if msg, ok := req.(proto.Message); ok {
|
||||
if err := event.WriteAudit(interceptorCtx, op, event.WithRequest(&event.Request{Details: msg})); err != nil {
|
||||
return req, status.Errorf(codes.Internal, "unable to write request msg audit: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
return handler(interceptorCtx, req)
|
||||
}
|
||||
}
|
||||
|
||||
func auditResponseInterceptor(
|
||||
_ context.Context,
|
||||
) grpc.UnaryServerInterceptor {
|
||||
const op = "controller.auditRequestInterceptor"
|
||||
return func(interceptorCtx context.Context,
|
||||
req interface{},
|
||||
_ *grpc.UnaryServerInfo,
|
||||
handler grpc.UnaryHandler) (interface{}, error) {
|
||||
|
||||
// call the handler...
|
||||
resp, err := handler(interceptorCtx, req)
|
||||
|
||||
if msg, ok := resp.(proto.Message); ok {
|
||||
if err := event.WriteAudit(interceptorCtx, op, event.WithResponse(&event.Response{Details: msg})); err != nil {
|
||||
return req, status.Errorf(codes.Internal, "unable to write response msg audit: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
return resp, err
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strconv"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/boundary/internal/authtoken"
|
||||
@@ -17,10 +18,12 @@ import (
|
||||
"github.com/hashicorp/boundary/internal/gen/testing/interceptor"
|
||||
"github.com/hashicorp/boundary/internal/iam"
|
||||
"github.com/hashicorp/boundary/internal/kms"
|
||||
"github.com/hashicorp/boundary/internal/observability/event"
|
||||
"github.com/hashicorp/boundary/internal/servers"
|
||||
"github.com/hashicorp/boundary/internal/servers/controller/auth"
|
||||
"github.com/hashicorp/boundary/internal/servers/controller/common"
|
||||
"github.com/hashicorp/boundary/internal/servers/controller/handlers"
|
||||
"github.com/hashicorp/go-hclog"
|
||||
"github.com/mr-tron/base58"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
@@ -59,8 +62,10 @@ func Test_requestCtxInterceptor(t *testing.T) {
|
||||
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", tokValue))
|
||||
// Add values for authn/authz checking
|
||||
requestInfo := authpb.RequestInfo{
|
||||
Path: req.URL.Path,
|
||||
Method: req.Method,
|
||||
Path: req.URL.Path,
|
||||
Method: req.Method,
|
||||
EventId: "test-event-id",
|
||||
TraceId: "test-trace-id",
|
||||
}
|
||||
requestInfo.PublicId, requestInfo.EncryptedToken, requestInfo.TokenFormat = auth.GetTokenFromRequest(context.TODO(), kmsCache, req)
|
||||
requestInfo.Ticket = gwTicket // allows the grpc-gateway to verify the request info came from it's in-memory companion http proxy
|
||||
@@ -82,6 +87,15 @@ func Test_requestCtxInterceptor(t *testing.T) {
|
||||
return ctx, nil
|
||||
}
|
||||
|
||||
c := event.TestEventerConfig(t, "Test_requestCtxInterceptor", event.TestWithAuditSink(t), event.TestWithObservationSink(t))
|
||||
testLock := &sync.Mutex{}
|
||||
testLogger := hclog.New(&hclog.LoggerOptions{
|
||||
Mutex: testLock,
|
||||
Name: "test",
|
||||
})
|
||||
testEventer, err := event.NewEventer(testLogger, testLock, "Test_requestCtxInterceptor", c.EventerConfig)
|
||||
require.NoError(t, err)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
requestCtx context.Context
|
||||
@@ -89,6 +103,7 @@ func Test_requestCtxInterceptor(t *testing.T) {
|
||||
authTokenRepoFn common.AuthTokenRepoFactory
|
||||
serversRepoFn common.ServersRepoFactory
|
||||
kms *kms.Kms
|
||||
eventer *event.Eventer
|
||||
ticket string
|
||||
wantFactoryErr bool
|
||||
wantFactoryErrMatch *errors.Template
|
||||
@@ -103,6 +118,7 @@ func Test_requestCtxInterceptor(t *testing.T) {
|
||||
authTokenRepoFn: atRepoFn,
|
||||
serversRepoFn: serversRepoFn,
|
||||
kms: kmsCache,
|
||||
eventer: testEventer,
|
||||
wantFactoryErr: true,
|
||||
wantFactoryErrMatch: errors.T(errors.InvalidParameter),
|
||||
wantFactoryErrContains: "missing iam repo",
|
||||
@@ -113,6 +129,7 @@ func Test_requestCtxInterceptor(t *testing.T) {
|
||||
iamRepoFn: iamRepoFn,
|
||||
serversRepoFn: serversRepoFn,
|
||||
kms: kmsCache,
|
||||
eventer: testEventer,
|
||||
wantFactoryErr: true,
|
||||
wantFactoryErrMatch: errors.T(errors.InvalidParameter),
|
||||
wantFactoryErrContains: "missing auth token repo",
|
||||
@@ -123,6 +140,7 @@ func Test_requestCtxInterceptor(t *testing.T) {
|
||||
iamRepoFn: iamRepoFn,
|
||||
authTokenRepoFn: atRepoFn,
|
||||
kms: kmsCache,
|
||||
eventer: testEventer,
|
||||
wantFactoryErr: true,
|
||||
wantFactoryErrMatch: errors.T(errors.InvalidParameter),
|
||||
wantFactoryErrContains: "missing servers repo function",
|
||||
@@ -133,6 +151,17 @@ func Test_requestCtxInterceptor(t *testing.T) {
|
||||
iamRepoFn: iamRepoFn,
|
||||
authTokenRepoFn: atRepoFn,
|
||||
serversRepoFn: serversRepoFn,
|
||||
eventer: testEventer,
|
||||
wantFactoryErr: true,
|
||||
wantFactoryErrMatch: errors.T(errors.InvalidParameter),
|
||||
wantFactoryErrContains: "missing kms",
|
||||
},
|
||||
{
|
||||
name: "missing-eventer",
|
||||
requestCtx: newReqCtx(validGatewayTicket),
|
||||
iamRepoFn: iamRepoFn,
|
||||
authTokenRepoFn: atRepoFn,
|
||||
serversRepoFn: serversRepoFn,
|
||||
wantFactoryErr: true,
|
||||
wantFactoryErrMatch: errors.T(errors.InvalidParameter),
|
||||
wantFactoryErrContains: "missing kms",
|
||||
@@ -144,6 +173,7 @@ func Test_requestCtxInterceptor(t *testing.T) {
|
||||
authTokenRepoFn: atRepoFn,
|
||||
serversRepoFn: serversRepoFn,
|
||||
kms: kmsCache,
|
||||
eventer: testEventer,
|
||||
wantFactoryErr: true,
|
||||
wantFactoryErrMatch: errors.T(errors.InvalidParameter),
|
||||
wantFactoryErrContains: "missing ticket",
|
||||
@@ -155,6 +185,7 @@ func Test_requestCtxInterceptor(t *testing.T) {
|
||||
authTokenRepoFn: atRepoFn,
|
||||
serversRepoFn: serversRepoFn,
|
||||
kms: kmsCache,
|
||||
eventer: testEventer,
|
||||
ticket: validGatewayTicket,
|
||||
wantRequestErr: true,
|
||||
wantRequestErrMatch: errors.T(errors.Internal),
|
||||
@@ -170,6 +201,7 @@ func Test_requestCtxInterceptor(t *testing.T) {
|
||||
authTokenRepoFn: atRepoFn,
|
||||
serversRepoFn: serversRepoFn,
|
||||
kms: kmsCache,
|
||||
eventer: testEventer,
|
||||
ticket: validGatewayTicket,
|
||||
wantRequestErr: true,
|
||||
wantRequestErrMatch: errors.T(errors.Internal),
|
||||
@@ -185,6 +217,7 @@ func Test_requestCtxInterceptor(t *testing.T) {
|
||||
authTokenRepoFn: atRepoFn,
|
||||
serversRepoFn: serversRepoFn,
|
||||
kms: kmsCache,
|
||||
eventer: testEventer,
|
||||
ticket: validGatewayTicket,
|
||||
wantRequestErr: true,
|
||||
wantRequestErrMatch: errors.T(errors.Internal),
|
||||
@@ -200,6 +233,7 @@ func Test_requestCtxInterceptor(t *testing.T) {
|
||||
authTokenRepoFn: atRepoFn,
|
||||
serversRepoFn: serversRepoFn,
|
||||
kms: kmsCache,
|
||||
eventer: testEventer,
|
||||
ticket: validGatewayTicket,
|
||||
wantRequestErr: true,
|
||||
wantRequestErrMatch: errors.T(errors.Internal),
|
||||
@@ -215,6 +249,7 @@ func Test_requestCtxInterceptor(t *testing.T) {
|
||||
authTokenRepoFn: atRepoFn,
|
||||
serversRepoFn: serversRepoFn,
|
||||
kms: kmsCache,
|
||||
eventer: testEventer,
|
||||
ticket: validGatewayTicket,
|
||||
wantRequestErr: true,
|
||||
wantRequestErrMatch: errors.T(errors.Internal),
|
||||
@@ -230,6 +265,7 @@ func Test_requestCtxInterceptor(t *testing.T) {
|
||||
authTokenRepoFn: atRepoFn,
|
||||
serversRepoFn: serversRepoFn,
|
||||
kms: kmsCache,
|
||||
eventer: testEventer,
|
||||
ticket: validGatewayTicket,
|
||||
wantRequestErr: true,
|
||||
wantRequestErrMatch: errors.T(errors.Internal),
|
||||
@@ -242,6 +278,7 @@ func Test_requestCtxInterceptor(t *testing.T) {
|
||||
authTokenRepoFn: atRepoFn,
|
||||
serversRepoFn: serversRepoFn,
|
||||
kms: kmsCache,
|
||||
eventer: testEventer,
|
||||
ticket: "validGatewayTicket",
|
||||
wantRequestErr: true,
|
||||
wantRequestErrMatch: errors.T(errors.Internal),
|
||||
@@ -254,6 +291,7 @@ func Test_requestCtxInterceptor(t *testing.T) {
|
||||
authTokenRepoFn: atRepoFn,
|
||||
serversRepoFn: serversRepoFn,
|
||||
kms: kmsCache,
|
||||
eventer: testEventer,
|
||||
ticket: "validGatewayTicket",
|
||||
wantRequestErr: true,
|
||||
wantRequestErrMatch: errors.T(errors.Internal),
|
||||
@@ -266,13 +304,14 @@ func Test_requestCtxInterceptor(t *testing.T) {
|
||||
authTokenRepoFn: atRepoFn,
|
||||
serversRepoFn: serversRepoFn,
|
||||
kms: kmsCache,
|
||||
eventer: testEventer,
|
||||
ticket: validGatewayTicket,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
interceptor, err := requestCtxInterceptor(factoryCtx, tt.iamRepoFn, tt.authTokenRepoFn, tt.serversRepoFn, tt.kms, tt.ticket)
|
||||
interceptor, err := requestCtxInterceptor(factoryCtx, tt.iamRepoFn, tt.authTokenRepoFn, tt.serversRepoFn, tt.kms, tt.ticket, tt.eventer)
|
||||
if tt.wantFactoryErr {
|
||||
require.Error(err)
|
||||
assert.Nil(interceptor)
|
||||
|
||||
@@ -26,7 +26,7 @@ func (c *Controller) startListeners(ctx context.Context) error {
|
||||
|
||||
configureForAPI := func(ln *base.ServerListener) error {
|
||||
var err error
|
||||
if c.gatewayServer, c.gatewayTicket, err = newGatewayServer(ctx, c.IamRepoFn, c.AuthTokenRepoFn, c.ServersRepoFn, c.kms); err != nil {
|
||||
if c.gatewayServer, c.gatewayTicket, err = newGatewayServer(ctx, c.IamRepoFn, c.AuthTokenRepoFn, c.ServersRepoFn, c.kms, c.conf.Eventer); err != nil {
|
||||
return err
|
||||
}
|
||||
c.gatewayMux = newGatewayMux()
|
||||
|
||||
@@ -17,7 +17,7 @@ const (
|
||||
)
|
||||
|
||||
// This is exported so it can be tweaked in tests
|
||||
var RecoveryNonceCleanupInterval = 2 * time.Minute
|
||||
var NonceCleanupInterval = 2 * time.Minute
|
||||
|
||||
func (c *Controller) startStatusTicking(cancelCtx context.Context) {
|
||||
const op = "controller.(Controller).startStatusTicking"
|
||||
@@ -51,8 +51,8 @@ func (c *Controller) startStatusTicking(cancelCtx context.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Controller) startRecoveryNonceCleanupTicking(cancelCtx context.Context) {
|
||||
const op = "controller.(Controller).startRecoveryNonceCleanupTicking"
|
||||
func (c *Controller) startNonceCleanupTicking(cancelCtx context.Context) {
|
||||
const op = "controller.(Controller).startNonceCleanupTicking"
|
||||
timer := time.NewTimer(0)
|
||||
for {
|
||||
select {
|
||||
@@ -72,7 +72,7 @@ func (c *Controller) startRecoveryNonceCleanupTicking(cancelCtx context.Context)
|
||||
event.WriteSysEvent(cancelCtx, op, "recovery nonce cleanup successful", "nonces_cleaned", nonceCount)
|
||||
}
|
||||
}
|
||||
timer.Reset(RecoveryNonceCleanupInterval)
|
||||
timer.Reset(NonceCleanupInterval)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,10 +7,12 @@ import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/boundary/internal/cmd/base"
|
||||
"github.com/hashicorp/boundary/internal/servers"
|
||||
wrapping "github.com/hashicorp/go-kms-wrapping"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
@@ -20,16 +22,30 @@ type workerAuthEntry struct {
|
||||
conn net.Conn
|
||||
}
|
||||
|
||||
// validateWorkerTls is called by the Go TLS stack with client info. It calls
|
||||
// v1WorkerAuthConfig to validate the encryption and ensure the nonce has not
|
||||
// been replayed, then stores connection information into the auth cache, which
|
||||
// is used by the intercepting listener to log information.
|
||||
func (c Controller) validateWorkerTls(hello *tls.ClientHelloInfo) (*tls.Config, error) {
|
||||
for _, p := range hello.SupportedProtos {
|
||||
switch {
|
||||
case strings.HasPrefix(p, "v1workerauth-"):
|
||||
tlsConf, workerInfo, err := c.v1WorkerAuthConfig(hello.SupportedProtos)
|
||||
if err == nil {
|
||||
// Set the info we need to prevent replays
|
||||
c.workerAuthCache.Set(workerInfo.ConnectionNonce, &workerAuthEntry{
|
||||
// Store info that can be retrieved in the intercepting
|
||||
// listener.
|
||||
//
|
||||
// NOTE: This is not a load-or-store because we need to ensure
|
||||
// replays can't happen across controllers, hence storing in the
|
||||
// database in v1WorkerAuthConfig. Although this could serve as
|
||||
// a secondary check, a load-or-store here keeps the test from
|
||||
// ensuring that it is rejected at the database level via the
|
||||
// nonce unique constraint, which is by far the more important
|
||||
// thing to validate (although there are DB-level tests for that
|
||||
// too).
|
||||
c.workerAuthCache.Store(workerInfo.ConnectionNonce, &workerAuthEntry{
|
||||
WorkerAuthInfo: workerInfo,
|
||||
}, 0)
|
||||
})
|
||||
}
|
||||
return tlsConf, err
|
||||
}
|
||||
@@ -37,6 +53,13 @@ func (c Controller) validateWorkerTls(hello *tls.ClientHelloInfo) (*tls.Config,
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// v1WorkerAuthConfig:
|
||||
//
|
||||
// * Reads the information from ALPN, combining across protos
|
||||
// * Combines them to form the encrypted auth information
|
||||
// * Validates it by decrypting against the configured KMS
|
||||
// * Ensures that the nonce is unique to prevent replay attacks
|
||||
// * Returns the shared TLS configuration that is used to establish the connection
|
||||
func (c Controller) v1WorkerAuthConfig(protos []string) (*tls.Config, *base.WorkerAuthInfo, error) {
|
||||
var firstMatchProto string
|
||||
var encString string
|
||||
@@ -69,6 +92,15 @@ func (c Controller) v1WorkerAuthConfig(protos []string) (*tls.Config, *base.Work
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// Check for replays
|
||||
serversRepo, err := c.ServersRepoFn()
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("unable to fetch servers repo: %w", err)
|
||||
}
|
||||
if err := serversRepo.AddNonce(c.baseContext, info.ConnectionNonce, servers.NoncePurposeWorkerAuth); err != nil {
|
||||
return nil, nil, fmt.Errorf("unable to add connection nonce to database: %w", err)
|
||||
}
|
||||
|
||||
rootCAs := x509.NewCertPool()
|
||||
if ok := rootCAs.AppendCertsFromPEM(info.CertPEM); !ok {
|
||||
return nil, info, errors.New("unable to add ca cert to cert pool")
|
||||
|
||||
@@ -207,18 +207,38 @@ func (r *Repository) UpsertServer(ctx context.Context, server *Server, opt ...Op
|
||||
return controllers, int(rowsUpdated), nil
|
||||
}
|
||||
|
||||
type RecoveryNonce struct {
|
||||
Nonce string
|
||||
type Nonce struct {
|
||||
Nonce string
|
||||
Purpose string
|
||||
}
|
||||
|
||||
// AddRecoveryNonce adds a nonce
|
||||
func (r *Repository) AddRecoveryNonce(ctx context.Context, nonce string, opt ...Option) error {
|
||||
const op = "servers.AddRecoveryNonce"
|
||||
// TableName returns the table name.
|
||||
func (n *Nonce) TableName() string {
|
||||
return "nonce"
|
||||
}
|
||||
|
||||
const (
|
||||
NoncePurposeRecovery = "recovery"
|
||||
NoncePurposeWorkerAuth = "worker-auth"
|
||||
)
|
||||
|
||||
// AddNonce adds a nonce
|
||||
func (r *Repository) AddNonce(ctx context.Context, nonce, purpose string, opt ...Option) error {
|
||||
const op = "servers.AddNonce"
|
||||
if nonce == "" {
|
||||
return errors.New(ctx, errors.InvalidParameter, op, "empty nonce")
|
||||
}
|
||||
rn := &RecoveryNonce{Nonce: nonce}
|
||||
if err := r.writer.Create(ctx, rn); err != nil {
|
||||
switch purpose {
|
||||
case NoncePurposeRecovery, NoncePurposeWorkerAuth:
|
||||
case "":
|
||||
return errors.New(ctx, errors.InvalidParameter, op, "empty nonce purpose")
|
||||
default:
|
||||
return errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("unknown nonce purpose %q", purpose))
|
||||
}
|
||||
if err := r.writer.Create(ctx, &Nonce{
|
||||
Nonce: nonce,
|
||||
Purpose: purpose,
|
||||
}); err != nil {
|
||||
return errors.Wrap(ctx, err, op+":Insertion")
|
||||
}
|
||||
return nil
|
||||
@@ -226,10 +246,15 @@ func (r *Repository) AddRecoveryNonce(ctx context.Context, nonce string, opt ...
|
||||
|
||||
// CleanupNonces removes nonces that no longer need to be stored
|
||||
func (r *Repository) CleanupNonces(ctx context.Context, opt ...Option) (int, error) {
|
||||
// Use the largest validity period out of the various nonces we're looking at
|
||||
maxDuration := globals.RecoveryTokenValidityPeriod
|
||||
if globals.WorkerAuthNonceValidityPeriod > maxDuration {
|
||||
maxDuration = globals.WorkerAuthNonceValidityPeriod
|
||||
}
|
||||
// If something was inserted before 3x the actual validity period, clean it out
|
||||
endTime := time.Now().Add(-3 * globals.RecoveryTokenValidityPeriod)
|
||||
endTime := time.Now().Add(-3 * maxDuration)
|
||||
|
||||
rows, err := r.writer.Delete(ctx, &RecoveryNonce{}, db.WithWhere(deleteWhereCreateTimeSql, endTime))
|
||||
rows, err := r.writer.Delete(ctx, &Nonce{}, db.WithWhere(deleteWhereCreateTimeSql, endTime))
|
||||
if err != nil {
|
||||
return db.NoRowsAffected, errors.Wrap(ctx, err, "servers.CleanupNonces")
|
||||
}
|
||||
@@ -237,9 +262,9 @@ func (r *Repository) CleanupNonces(ctx context.Context, opt ...Option) (int, err
|
||||
}
|
||||
|
||||
// ListNonces lists nonces. Used only for tests at the moment.
|
||||
func (r *Repository) ListNonces(ctx context.Context, opt ...Option) ([]*RecoveryNonce, error) {
|
||||
var nonces []*RecoveryNonce
|
||||
if err := r.reader.SearchWhere(ctx, &nonces, "", nil, db.WithLimit(-1)); err != nil {
|
||||
func (r *Repository) ListNonces(ctx context.Context, purpose string, opt ...Option) ([]*Nonce, error) {
|
||||
var nonces []*Nonce
|
||||
if err := r.reader.SearchWhere(ctx, &nonces, "purpose = ?", []interface{}{purpose}, db.WithLimit(-1)); err != nil {
|
||||
return nil, errors.Wrap(ctx, err, "servers.ListNonces")
|
||||
}
|
||||
return nonces, nil
|
||||
|
||||
@@ -22,7 +22,8 @@ func TestRecoveryNonces(t *testing.T) {
|
||||
|
||||
// Set these low so that we can not have the test run forever
|
||||
globals.RecoveryTokenValidityPeriod = 10 * time.Second
|
||||
controller.RecoveryNonceCleanupInterval = 20 * time.Second
|
||||
globals.WorkerAuthNonceValidityPeriod = 10 * time.Second
|
||||
controller.NonceCleanupInterval = 20 * time.Second
|
||||
|
||||
wrapper := db.TestWrapper(t)
|
||||
tc := controller.NewTestController(t, &controller.TestControllerOpts{
|
||||
@@ -46,14 +47,14 @@ func TestRecoveryNonces(t *testing.T) {
|
||||
roleClient := roles.NewClient(client)
|
||||
_, err = roleClient.Create(tc.Context(), scope.Global.String())
|
||||
require.NoError(err)
|
||||
nonces, err := repo.ListNonces(tc.Context())
|
||||
nonces, err := repo.ListNonces(tc.Context(), servers.NoncePurposeRecovery)
|
||||
require.NoError(err)
|
||||
assert.Len(nonces, 1)
|
||||
|
||||
// Token 1, try 2
|
||||
_, err = roleClient.Create(tc.Context(), scope.Global.String())
|
||||
require.Error(err)
|
||||
nonces, err = repo.ListNonces(tc.Context())
|
||||
nonces, err = repo.ListNonces(tc.Context(), servers.NoncePurposeRecovery)
|
||||
require.NoError(err)
|
||||
assert.Len(nonces, 1)
|
||||
|
||||
@@ -61,13 +62,13 @@ func TestRecoveryNonces(t *testing.T) {
|
||||
roleClient.ApiClient().SetToken(token2)
|
||||
_, err = roleClient.Create(tc.Context(), scope.Global.String())
|
||||
require.NoError(err)
|
||||
nonces, err = repo.ListNonces(tc.Context())
|
||||
nonces, err = repo.ListNonces(tc.Context(), servers.NoncePurposeRecovery)
|
||||
require.NoError(err)
|
||||
assert.Len(nonces, 2)
|
||||
|
||||
// Make sure they get cleaned up
|
||||
time.Sleep(2 * controller.RecoveryNonceCleanupInterval)
|
||||
nonces, err = repo.ListNonces(tc.Context())
|
||||
time.Sleep(2 * controller.NonceCleanupInterval)
|
||||
nonces, err = repo.ListNonces(tc.Context(), servers.NoncePurposeRecovery)
|
||||
require.NoError(err)
|
||||
assert.Len(nonces, 0)
|
||||
|
||||
@@ -76,7 +77,7 @@ func TestRecoveryNonces(t *testing.T) {
|
||||
roleClient.ApiClient().SetToken(token)
|
||||
_, err = roleClient.Create(tc.Context(), scope.Global.String())
|
||||
require.Error(err)
|
||||
nonces, err = repo.ListNonces(tc.Context())
|
||||
nonces, err = repo.ListNonces(tc.Context(), servers.NoncePurposeRecovery)
|
||||
require.NoError(err)
|
||||
assert.Len(nonces, 0)
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/boundary/globals"
|
||||
"github.com/hashicorp/boundary/internal/cmd/base"
|
||||
pbs "github.com/hashicorp/boundary/internal/gen/controller/servers/services"
|
||||
"github.com/hashicorp/boundary/internal/observability/event"
|
||||
@@ -137,11 +138,17 @@ func (w *Worker) createClientConn(addr string) error {
|
||||
func (w *Worker) workerAuthTLSConfig() (*tls.Config, *base.WorkerAuthInfo, error) {
|
||||
var err error
|
||||
info := &base.WorkerAuthInfo{
|
||||
Name: w.conf.RawConfig.Worker.Name,
|
||||
Description: w.conf.RawConfig.Worker.Description,
|
||||
Name: w.conf.RawConfig.Worker.Name,
|
||||
Description: w.conf.RawConfig.Worker.Description,
|
||||
ConnectionNonce: w.testReusedAuthNonce,
|
||||
}
|
||||
if info.ConnectionNonce, err = base62.Random(20); err != nil {
|
||||
return nil, nil, err
|
||||
if info.ConnectionNonce == "" {
|
||||
if info.ConnectionNonce, err = base62.Random(20); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if w.testReuseAuthNonces {
|
||||
w.testReusedAuthNonce = info.ConnectionNonce
|
||||
}
|
||||
}
|
||||
|
||||
pubKey, privKey, err := ed25519.GenerateKey(w.conf.SecureRandomReader)
|
||||
@@ -162,7 +169,7 @@ func (w *Worker) workerAuthTLSConfig() (*tls.Config, *base.WorkerAuthInfo, error
|
||||
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment | x509.KeyUsageKeyAgreement | x509.KeyUsageCertSign,
|
||||
SerialNumber: big.NewInt(mathrand.Int63()),
|
||||
NotBefore: time.Now().Add(-30 * time.Second),
|
||||
NotAfter: time.Now().Add(2 * time.Minute),
|
||||
NotAfter: time.Now().Add(globals.WorkerAuthNonceValidityPeriod),
|
||||
BasicConstraintsValid: true,
|
||||
IsCA: true,
|
||||
}
|
||||
|
||||
@@ -182,6 +182,9 @@ type TestWorkerOpts struct {
|
||||
// The amount of time to wait before marking connections as closed when a
|
||||
// connection cannot be made back to the controller
|
||||
StatusGracePeriodDuration time.Duration
|
||||
|
||||
// Whether to set the replay value
|
||||
EnableAuthReplay bool
|
||||
}
|
||||
|
||||
func NewTestWorker(t *testing.T, opts *TestWorkerOpts) *TestWorker {
|
||||
@@ -277,6 +280,10 @@ func NewTestWorker(t *testing.T, opts *TestWorkerOpts) *TestWorker {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if opts.EnableAuthReplay {
|
||||
tw.w.testReuseAuthNonces = true
|
||||
}
|
||||
|
||||
if !opts.DisableAutoStart {
|
||||
if err := tw.w.Start(); err != nil {
|
||||
tw.Shutdown()
|
||||
|
||||
@@ -52,6 +52,10 @@ type Worker struct {
|
||||
// request. It can be set via startup in New below, or (eventually) via
|
||||
// SIGHUP.
|
||||
updateTags ua.Bool
|
||||
|
||||
// Test-related values
|
||||
testReuseAuthNonces bool
|
||||
testReusedAuthNonce string
|
||||
}
|
||||
|
||||
func New(conf *Config) (*Worker, error) {
|
||||
|
||||
@@ -323,7 +323,7 @@ func (r *Repository) update(ctx context.Context, target Target, version uint32,
|
||||
|
||||
// CreateTarget inserts into the repository and returns the new Target with
|
||||
// its list of host sets and credential libraries.
|
||||
// WithHostSources and WithCredentialSources are the only supported option.
|
||||
// WithPublicId is the only supported option.
|
||||
func (r *Repository) CreateTarget(ctx context.Context, target Target, opt ...Option) (Target, []HostSource, []CredentialSource, error) {
|
||||
const op = "target.(Repository).CreateTarget"
|
||||
opts := GetOpts(opt...)
|
||||
@@ -371,24 +371,6 @@ func (r *Repository) CreateTarget(ctx context.Context, target Target, opt ...Opt
|
||||
return nil, nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to get oplog wrapper"))
|
||||
}
|
||||
|
||||
newHostSets := make([]interface{}, 0, len(opts.WithHostSources))
|
||||
for _, hsId := range opts.WithHostSources {
|
||||
hostSet, err := NewTargetHostSet(t.GetPublicId(), hsId)
|
||||
if err != nil {
|
||||
return nil, nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to create in memory target host set"))
|
||||
}
|
||||
newHostSets = append(newHostSets, hostSet)
|
||||
}
|
||||
|
||||
newCredLibs := make([]interface{}, 0, len(opts.WithCredentialSources))
|
||||
for _, clId := range opts.WithCredentialSources {
|
||||
credLib, err := NewCredentialLibrary(t.GetPublicId(), clId)
|
||||
if err != nil {
|
||||
return nil, nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to create in memory target credential library"))
|
||||
}
|
||||
newCredLibs = append(newCredLibs, credLib)
|
||||
}
|
||||
|
||||
metadata := t.Oplog(oplog.OpType_OP_TYPE_CREATE)
|
||||
var returnedTarget interface{}
|
||||
var returnedHostSources []HostSource
|
||||
@@ -409,26 +391,6 @@ func (r *Repository) CreateTarget(ctx context.Context, target Target, opt ...Opt
|
||||
return errors.Wrap(ctx, err, op, errors.WithMsg("unable to create target"))
|
||||
}
|
||||
msgs = append(msgs, &targetOplogMsg)
|
||||
if len(newHostSets) > 0 {
|
||||
hostSetOplogMsgs := make([]*oplog.Message, 0, len(newHostSets))
|
||||
if err := w.CreateItems(ctx, newHostSets, db.NewOplogMsgs(&hostSetOplogMsgs)); err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithMsg("unable to add host sets"))
|
||||
}
|
||||
if returnedHostSources, err = fetchHostSources(ctx, read, t.GetPublicId()); err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithMsg("unable to read host sources"))
|
||||
}
|
||||
msgs = append(msgs, hostSetOplogMsgs...)
|
||||
}
|
||||
if len(newCredLibs) > 0 {
|
||||
credLibOplogMsgs := make([]*oplog.Message, 0, len(newCredLibs))
|
||||
if err := w.CreateItems(ctx, newCredLibs, db.NewOplogMsgs(&credLibOplogMsgs)); err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithMsg("unable to add credential sources"))
|
||||
}
|
||||
if returnedCredSources, err = fetchCredentialSources(ctx, read, t.GetPublicId()); err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithMsg("unable to read credential sources"))
|
||||
}
|
||||
msgs = append(msgs, credLibOplogMsgs...)
|
||||
}
|
||||
if err := w.WriteOplogEntryWith(ctx, oplogWrapper, targetTicket, metadata, msgs); err != nil {
|
||||
return errors.Wrap(ctx, err, op, errors.WithMsg("unable to write oplog"))
|
||||
}
|
||||
|
||||
@@ -72,60 +72,6 @@ func TestRepository_CreateTarget(t *testing.T) {
|
||||
wantCredLibs: []string{},
|
||||
wantHostSources: []string{},
|
||||
},
|
||||
{
|
||||
name: "valid-org-with-host-sets",
|
||||
args: args{
|
||||
target: func() *tcp.Target {
|
||||
target, err := tcp.New(proj.PublicId,
|
||||
target.WithName("valid-org-with-host-sets"),
|
||||
target.WithDescription("valid-org"),
|
||||
target.WithDefaultPort(uint32(22)))
|
||||
require.NoError(t, err)
|
||||
return target
|
||||
}(),
|
||||
opt: []target.Option{target.WithHostSources(sets)},
|
||||
},
|
||||
wantErr: false,
|
||||
wantHostSources: sets,
|
||||
wantCredLibs: []string{},
|
||||
},
|
||||
{
|
||||
name: "valid-org-with-cred-libs",
|
||||
args: args{
|
||||
target: func() *tcp.Target {
|
||||
target, err := tcp.New(proj.PublicId,
|
||||
target.WithName("valid-org-with-cred-libs"),
|
||||
target.WithDescription("valid-org"),
|
||||
target.WithDefaultPort(uint32(22)))
|
||||
require.NoError(t, err)
|
||||
return target
|
||||
}(),
|
||||
opt: []target.Option{target.WithCredentialSources(clIds)},
|
||||
},
|
||||
wantErr: false,
|
||||
wantCredLibs: clIds,
|
||||
wantHostSources: []string{},
|
||||
},
|
||||
{
|
||||
name: "valid-org-with-cred-libs-and-host-sets",
|
||||
args: args{
|
||||
target: func() *tcp.Target {
|
||||
target, err := tcp.New(proj.PublicId,
|
||||
target.WithName("valid-org-with-cred-libs-and-host-sets"),
|
||||
target.WithDescription("valid-org"),
|
||||
target.WithDefaultPort(uint32(22)))
|
||||
require.NoError(t, err)
|
||||
return target
|
||||
}(),
|
||||
opt: []target.Option{
|
||||
target.WithHostSources(sets),
|
||||
target.WithCredentialSources(clIds),
|
||||
},
|
||||
},
|
||||
wantErr: false,
|
||||
wantCredLibs: clIds,
|
||||
wantHostSources: sets,
|
||||
},
|
||||
{
|
||||
name: "nil-target",
|
||||
args: args{
|
||||
|
||||
@@ -4,19 +4,41 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/boundary/api"
|
||||
"github.com/hashicorp/boundary/api/authmethods"
|
||||
"github.com/hashicorp/boundary/api/authtokens"
|
||||
"github.com/hashicorp/boundary/internal/cmd/config"
|
||||
"github.com/hashicorp/boundary/internal/observability/event"
|
||||
"github.com/hashicorp/boundary/internal/servers/controller"
|
||||
tests_api "github.com/hashicorp/boundary/internal/tests/api"
|
||||
"github.com/hashicorp/go-hclog"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// TestAuthenticate tests the api calls and the audit events it should produce
|
||||
func TestAuthenticate(t *testing.T) {
|
||||
// this cannot run in parallel because it relies on envvar
|
||||
// globals.BOUNDARY_DEVELOPER_ENABLE_EVENTS
|
||||
event.TestEnableEventing(t, true)
|
||||
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
tc := controller.NewTestController(t, nil)
|
||||
eventConfig := event.TestEventerConfig(t, "TestAuthenticateAuditEntry", event.TestWithAuditSink(t))
|
||||
testLock := &sync.Mutex{}
|
||||
testLogger := hclog.New(&hclog.LoggerOptions{
|
||||
Mutex: testLock,
|
||||
Name: "test",
|
||||
})
|
||||
require.NoError(event.InitSysEventer(testLogger, testLock, "TestAuthenticateAuditEntry", event.WithEventerConfig(&eventConfig.EventerConfig)))
|
||||
tcConfig, err := config.DevController()
|
||||
require.NoError(err)
|
||||
tcConfig.Eventing = &eventConfig.EventerConfig
|
||||
|
||||
tc := controller.NewTestController(t, &controller.TestControllerOpts{Config: tcConfig})
|
||||
defer tc.Shutdown()
|
||||
|
||||
client := tc.Client()
|
||||
@@ -49,4 +71,30 @@ func TestAuthenticate(t *testing.T) {
|
||||
token := new(authtokens.AuthToken)
|
||||
require.NoError(json.Unmarshal(result.GetRawAttributes(), token))
|
||||
require.NotEmpty(token.Token)
|
||||
|
||||
require.NotNil(eventConfig.AuditEvents)
|
||||
_ = os.WriteFile(eventConfig.AuditEvents.Name(), nil, 0o666) // clean out audit events from previous calls
|
||||
|
||||
tok, err = methods.Authenticate(tc.Context(), tc.Server().DevPasswordAuthMethodId, "login", map[string]interface{}{"login_name": "user", "password": "passpass"})
|
||||
require.NoError(err)
|
||||
assert.NotNil(tok)
|
||||
got := tests_api.CloudEventFromFile(t, eventConfig.AuditEvents.Name())
|
||||
|
||||
reqDetails := tests_api.GetEventDetails(t, got, "request")
|
||||
tests_api.AssertRedactedValues(t, reqDetails)
|
||||
tests_api.AssertRedactedValues(t, reqDetails["attributes"], "password")
|
||||
|
||||
respDetails := tests_api.GetEventDetails(t, got, "response")
|
||||
tests_api.AssertRedactedValues(t, respDetails)
|
||||
tests_api.AssertRedactedValues(t, respDetails["attributes"], "token")
|
||||
|
||||
_ = os.WriteFile(eventConfig.AuditEvents.Name(), nil, 0o666) // clean out audit events from previous calls
|
||||
tok, err = methods.Authenticate(tc.Context(), tc.Server().DevPasswordAuthMethodId, "login", map[string]interface{}{"login_name": "user", "password": "bad-pass"})
|
||||
require.Error(err)
|
||||
assert.Nil(tok)
|
||||
got = tests_api.CloudEventFromFile(t, eventConfig.AuditEvents.Name())
|
||||
|
||||
reqDetails = tests_api.GetEventDetails(t, got, "request")
|
||||
tests_api.AssertRedactedValues(t, reqDetails)
|
||||
tests_api.AssertRedactedValues(t, reqDetails["attributes"], "password")
|
||||
}
|
||||
@@ -2,13 +2,19 @@ package authmethods_test
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"os"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/boundary/api"
|
||||
"github.com/hashicorp/boundary/api/authmethods"
|
||||
"github.com/hashicorp/boundary/internal/auth/password"
|
||||
"github.com/hashicorp/boundary/internal/cmd/config"
|
||||
"github.com/hashicorp/boundary/internal/observability/event"
|
||||
"github.com/hashicorp/boundary/internal/servers/controller"
|
||||
tests_api "github.com/hashicorp/boundary/internal/tests/api"
|
||||
capoidc "github.com/hashicorp/cap/oidc"
|
||||
"github.com/hashicorp/go-hclog"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
@@ -16,8 +22,23 @@ import (
|
||||
const global = "global"
|
||||
|
||||
func TestCrud(t *testing.T) {
|
||||
// this cannot run in parallel because it relies on envvar
|
||||
// globals.BOUNDARY_DEVELOPER_ENABLE_EVENTS
|
||||
event.TestEnableEventing(t, true)
|
||||
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
tc := controller.NewTestController(t, nil)
|
||||
eventConfig := event.TestEventerConfig(t, "TestCrud", event.TestWithAuditSink(t))
|
||||
testLock := &sync.Mutex{}
|
||||
testLogger := hclog.New(&hclog.LoggerOptions{
|
||||
Mutex: testLock,
|
||||
Name: "test",
|
||||
})
|
||||
require.NoError(event.InitSysEventer(testLogger, testLock, "TestCrud", event.WithEventerConfig(&eventConfig.EventerConfig)))
|
||||
tcConfig, err := config.DevController()
|
||||
require.NoError(err)
|
||||
tcConfig.Eventing = &eventConfig.EventerConfig
|
||||
|
||||
tc := controller.NewTestController(t, &controller.TestControllerOpts{Config: tcConfig})
|
||||
defer tc.Shutdown()
|
||||
|
||||
client := tc.Client()
|
||||
@@ -35,18 +56,44 @@ func TestCrud(t *testing.T) {
|
||||
assert.EqualValues(wantedVersion, u.Version)
|
||||
}
|
||||
|
||||
require.NotNil(eventConfig.AuditEvents)
|
||||
_ = os.WriteFile(eventConfig.AuditEvents.Name(), nil, 0o666) // clean out audit events from previous calls
|
||||
u, err := amClient.Create(tc.Context(), "password", global,
|
||||
authmethods.WithName("bar"))
|
||||
require.NoError(err)
|
||||
checkAuthMethod("create", u.Item, "bar", 1)
|
||||
|
||||
got := tests_api.CloudEventFromFile(t, eventConfig.AuditEvents.Name())
|
||||
reqItem := tests_api.GetEventDetails(t, got, "request")["item"].(map[string]interface{})
|
||||
tests_api.AssertRedactedValues(t, reqItem)
|
||||
|
||||
respItem := tests_api.GetEventDetails(t, got, "response")["item"].(map[string]interface{})
|
||||
tests_api.AssertRedactedValues(t, respItem)
|
||||
tests_api.AssertRedactedValues(t, respItem["attributes"])
|
||||
|
||||
_ = os.WriteFile(eventConfig.AuditEvents.Name(), nil, 0o666) // clean out audit events from previous calls
|
||||
u, err = amClient.Read(tc.Context(), u.Item.Id)
|
||||
require.NoError(err)
|
||||
checkAuthMethod("read", u.Item, "bar", 1)
|
||||
|
||||
got = tests_api.CloudEventFromFile(t, eventConfig.AuditEvents.Name())
|
||||
tests_api.AssertRedactedValues(t, tests_api.GetEventDetails(t, got, "request"))
|
||||
|
||||
respItem = tests_api.GetEventDetails(t, got, "response")["item"].(map[string]interface{})
|
||||
tests_api.AssertRedactedValues(t, respItem)
|
||||
tests_api.AssertRedactedValues(t, respItem["attributes"])
|
||||
|
||||
_ = os.WriteFile(eventConfig.AuditEvents.Name(), nil, 0o666) // clean out audit events from previous calls
|
||||
u, err = amClient.Update(tc.Context(), u.Item.Id, u.Item.Version, authmethods.WithName("buz"))
|
||||
require.NoError(err)
|
||||
checkAuthMethod("update", u.Item, "buz", 2)
|
||||
got = tests_api.CloudEventFromFile(t, eventConfig.AuditEvents.Name())
|
||||
|
||||
tests_api.AssertRedactedValues(t, tests_api.GetEventDetails(t, got, "request")["item"].(map[string]interface{}))
|
||||
|
||||
respItem = tests_api.GetEventDetails(t, got, "response")["item"].(map[string]interface{})
|
||||
tests_api.AssertRedactedValues(t, respItem)
|
||||
tests_api.AssertRedactedValues(t, respItem["attributes"])
|
||||
|
||||
u, err = amClient.Update(tc.Context(), u.Item.Id, u.Item.Version, authmethods.DefaultName())
|
||||
require.NoError(err)
|
||||
@@ -55,6 +102,7 @@ func TestCrud(t *testing.T) {
|
||||
_, err = amClient.Delete(tc.Context(), u.Item.Id)
|
||||
require.NoError(err)
|
||||
|
||||
_ = os.WriteFile(eventConfig.AuditEvents.Name(), nil, 0o666) // clean out audit events from previous calls
|
||||
// OIDC auth methods
|
||||
u, err = amClient.Create(tc.Context(), "oidc", global,
|
||||
authmethods.WithName("foo"),
|
||||
@@ -64,6 +112,15 @@ func TestCrud(t *testing.T) {
|
||||
authmethods.WithOidcAuthMethodClientId("client-id"))
|
||||
require.NoError(err)
|
||||
checkAuthMethod("create", u.Item, "foo", 1)
|
||||
got = tests_api.CloudEventFromFile(t, eventConfig.AuditEvents.Name())
|
||||
|
||||
reqItem = tests_api.GetEventDetails(t, got, "request")["item"].(map[string]interface{})
|
||||
tests_api.AssertRedactedValues(t, reqItem)
|
||||
tests_api.AssertRedactedValues(t, reqItem["attributes"], "client_secret")
|
||||
|
||||
respItem = tests_api.GetEventDetails(t, got, "response")["item"].(map[string]interface{})
|
||||
tests_api.AssertRedactedValues(t, respItem)
|
||||
tests_api.AssertRedactedValues(t, respItem["attributes"])
|
||||
|
||||
u, err = amClient.Read(tc.Context(), u.Item.Id)
|
||||
require.NoError(err)
|
||||
@@ -82,7 +139,23 @@ func TestCrud(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCustomMethods(t *testing.T) {
|
||||
tc := controller.NewTestController(t, nil)
|
||||
// this cannot run in parallel because it relies on envvar
|
||||
// globals.BOUNDARY_DEVELOPER_ENABLE_EVENTS
|
||||
event.TestEnableEventing(t, true)
|
||||
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
eventConfig := event.TestEventerConfig(t, "TestCrud", event.TestWithAuditSink(t))
|
||||
testLock := &sync.Mutex{}
|
||||
testLogger := hclog.New(&hclog.LoggerOptions{
|
||||
Mutex: testLock,
|
||||
Name: "test",
|
||||
})
|
||||
require.NoError(event.InitSysEventer(testLogger, testLock, "TestCrud", event.WithEventerConfig(&eventConfig.EventerConfig)))
|
||||
tcConfig, err := config.DevController()
|
||||
require.NoError(err)
|
||||
tcConfig.Eventing = &eventConfig.EventerConfig
|
||||
|
||||
tc := controller.NewTestController(t, &controller.TestControllerOpts{Config: tcConfig})
|
||||
defer tc.Shutdown()
|
||||
|
||||
client := tc.Client()
|
||||
@@ -104,20 +177,30 @@ func TestCustomMethods(t *testing.T) {
|
||||
authmethods.WithOidcAuthMethodClientId("client-id"),
|
||||
authmethods.WithOidcAuthMethodSigningAlgorithms([]string{string("EdDSA")}),
|
||||
authmethods.WithOidcAuthMethodIdpCaCerts([]string{tp.CACert()}))
|
||||
require.NoError(t, err)
|
||||
require.NoError(err)
|
||||
|
||||
const newState = "active-private"
|
||||
nilU, err := amClient.ChangeState(tc.Context(), u.Item.Id, u.Item.Version, newState)
|
||||
require.Error(t, err)
|
||||
assert.Nil(t, nilU)
|
||||
require.Error(err)
|
||||
assert.Nil(nilU)
|
||||
|
||||
_ = os.WriteFile(eventConfig.AuditEvents.Name(), nil, 0o666) // clean out audit events from previous calls
|
||||
u, err = amClient.ChangeState(tc.Context(), u.Item.Id, u.Item.Version, newState, authmethods.WithOidcAuthMethodDisableDiscoveredConfigValidation(true))
|
||||
require.NoError(t, err)
|
||||
assert.NotNil(t, u)
|
||||
assert.Equal(t, newState, u.Item.Attributes["state"])
|
||||
require.NoError(err)
|
||||
assert.NotNil(u)
|
||||
assert.Equal(newState, u.Item.Attributes["state"])
|
||||
got := tests_api.CloudEventFromFile(t, eventConfig.AuditEvents.Name())
|
||||
|
||||
reqDetails := tests_api.GetEventDetails(t, got, "request")
|
||||
tests_api.AssertRedactedValues(t, reqDetails)
|
||||
tests_api.AssertRedactedValues(t, reqDetails["attributes"])
|
||||
|
||||
respItem := tests_api.GetEventDetails(t, got, "response")["item"].(map[string]interface{})
|
||||
tests_api.AssertRedactedValues(t, respItem)
|
||||
tests_api.AssertRedactedValues(t, respItem["attributes"])
|
||||
|
||||
_, err = amClient.ChangeState(tc.Context(), u.Item.Id, u.Item.Version, "", authmethods.WithOidcAuthMethodDisableDiscoveredConfigValidation(true))
|
||||
assert.Error(t, err)
|
||||
assert.Error(err)
|
||||
}
|
||||
|
||||
func TestErrors(t *testing.T) {
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/eventlogger/filters/encrypt"
|
||||
"github.com/hashicorp/eventlogger/formatter_filters/cloudevents"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// CloudEventFromFile will marshal a single cloud event from the provided file
|
||||
// name
|
||||
func CloudEventFromFile(t *testing.T, fileName string) *cloudevents.Event {
|
||||
t.Helper()
|
||||
b, err := ioutil.ReadFile(fileName)
|
||||
assert.NoError(t, err)
|
||||
got := &cloudevents.Event{}
|
||||
err = json.Unmarshal(b, got)
|
||||
require.NoErrorf(t, err, "json: %s", string(b))
|
||||
return got
|
||||
}
|
||||
|
||||
// GetEventDetails is a testing helper will return the details from the event
|
||||
// payload for a given messageType (request or response)
|
||||
func GetEventDetails(t *testing.T, e *cloudevents.Event, messageType string) map[string]interface{} {
|
||||
t.Helper()
|
||||
require := require.New(t)
|
||||
require.NotNil(e)
|
||||
require.NotEmpty(messageType)
|
||||
data, ok := e.Data.(map[string]interface{})
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
msgType, ok := data[messageType].(map[string]interface{})
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
details, ok := msgType["details"].(map[string]interface{})
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return details
|
||||
}
|
||||
|
||||
// AssertRedactedValues will assert that the values for the given keys within
|
||||
// the data have been redacted
|
||||
func AssertRedactedValues(t *testing.T, data interface{}, keys ...string) {
|
||||
t.Helper()
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
require.NotNil(data)
|
||||
dataMap, ok := data.(map[string]interface{})
|
||||
require.Truef(ok, "data must be a map[string]interface{}")
|
||||
|
||||
rMap := make(map[string]bool, len(keys))
|
||||
for _, s := range keys {
|
||||
rMap[s] = true
|
||||
}
|
||||
for k, v := range dataMap {
|
||||
switch typ := v.(type) {
|
||||
case []interface{}:
|
||||
for _, s := range typ {
|
||||
if _, ok := rMap[k]; ok {
|
||||
assert.Equalf(encrypt.RedactedData, s, "expected %s to be redacted and it was set to: %s", k, v)
|
||||
} else {
|
||||
assert.NotEqualf(encrypt.RedactedData, s, "did not expect %s to be redacted", k)
|
||||
}
|
||||
}
|
||||
default:
|
||||
if _, ok := rMap[k]; ok {
|
||||
assert.Equalf(encrypt.RedactedData, v, "expected %s to be redacted and it was set to: %s", k, v)
|
||||
} else {
|
||||
assert.NotEqualf(encrypt.RedactedData, v, "did not expect %s to be redacted", k)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package cluster
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/boundary/internal/cmd/config"
|
||||
"github.com/hashicorp/boundary/internal/observability/event"
|
||||
"github.com/hashicorp/boundary/internal/servers"
|
||||
"github.com/hashicorp/boundary/internal/servers/controller"
|
||||
"github.com/hashicorp/boundary/internal/servers/worker"
|
||||
"github.com/hashicorp/go-hclog"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestWorkerReplay(t *testing.T) {
|
||||
ec := event.TestEventerConfig(t, "TestWorkerReplay", event.TestWithObservationSink(t), event.TestWithSysSink(t))
|
||||
testLock := &sync.Mutex{}
|
||||
testLogger := hclog.New(&hclog.LoggerOptions{
|
||||
Mutex: testLock,
|
||||
Name: "test",
|
||||
})
|
||||
require.NoError(t, event.InitSysEventer(testLogger, testLock, "use-TestWorkerReplay", event.WithEventerConfig(&ec.EventerConfig)))
|
||||
|
||||
conf, err := config.DevController()
|
||||
conf.Eventing = &ec.EventerConfig
|
||||
require.NoError(t, err)
|
||||
c1 := controller.NewTestController(t, &controller.TestControllerOpts{
|
||||
Config: conf,
|
||||
})
|
||||
defer c1.Shutdown()
|
||||
|
||||
conf, err = config.DevWorker()
|
||||
conf.Eventing = &ec.EventerConfig
|
||||
require.NoError(t, err)
|
||||
w1 := worker.NewTestWorker(t, &worker.TestWorkerOpts{
|
||||
Config: conf,
|
||||
WorkerAuthKms: c1.Config().WorkerAuthKms,
|
||||
InitialControllers: c1.ClusterAddrs(),
|
||||
EnableAuthReplay: true,
|
||||
})
|
||||
|
||||
// Give time for it to connect
|
||||
time.Sleep(10 * time.Second)
|
||||
require.NoError(t, w1.Worker().Shutdown(true))
|
||||
|
||||
// Now, start up again
|
||||
require.NoError(t, w1.Worker().Start())
|
||||
time.Sleep(10 * time.Second)
|
||||
|
||||
// We should find only one nonce, and one successful worker authentication,
|
||||
// both in the output and in the database
|
||||
ec.AllEvents.Close()
|
||||
logBuf, err := os.ReadFile(ec.AllEvents.Name())
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 1, strings.Count(string(logBuf), "worker successfully authed"))
|
||||
nonces, err := c1.ServersRepo().ListNonces(c1.Context(), servers.NoncePurposeWorkerAuth)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, nonces, 1)
|
||||
}
|
||||
+1
-1
@@ -7,7 +7,7 @@ require (
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
github.com/google/go-cmp v0.5.6 // indirect
|
||||
github.com/hashicorp/eventlogger v0.1.0
|
||||
github.com/hashicorp/eventlogger/filters/encrypt v0.1.3
|
||||
github.com/hashicorp/eventlogger/filters/encrypt v0.1.5
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
||||
github.com/hashicorp/go-hclog v0.16.2
|
||||
github.com/hashicorp/go-kms-wrapping v0.6.6
|
||||
|
||||
+2
-2
@@ -243,8 +243,8 @@ github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY
|
||||
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/eventlogger v0.1.0 h1:S6xc4gZVzewuDUP4R4Ngko419h/CGDuV/b4ADL3XLik=
|
||||
github.com/hashicorp/eventlogger v0.1.0/go.mod h1:a3IXf1aEJfpCPzseTOrwKj4fVW/Qn3oEmpQeaIznzH0=
|
||||
github.com/hashicorp/eventlogger/filters/encrypt v0.1.3 h1:RKYUGplnpDoE/Cj2vupQqUJS/wWBVvmpxEYeEpGL88s=
|
||||
github.com/hashicorp/eventlogger/filters/encrypt v0.1.3/go.mod h1:8rcez7Kw1zanB0/074qnOuGu7zxmNh9Xr2ZI+K4xVIA=
|
||||
github.com/hashicorp/eventlogger/filters/encrypt v0.1.5 h1:kNkH4G6zzWlZSoI1I+B/ud4chVKTPL516C6jB7dRdlE=
|
||||
github.com/hashicorp/eventlogger/filters/encrypt v0.1.5/go.mod h1:8rcez7Kw1zanB0/074qnOuGu7zxmNh9Xr2ZI+K4xVIA=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
|
||||
|
||||
@@ -452,9 +452,9 @@ type OidcAuthMethodAuthenticateStartResponse struct {
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
// The returned authentication URL
|
||||
AuthUrl string `protobuf:"bytes,10,opt,name=auth_url,proto3" json:"auth_url,omitempty"`
|
||||
AuthUrl string `protobuf:"bytes,10,opt,name=auth_url,proto3" json:"auth_url,omitempty" class:"public"` // @gotags: `class:"public"`
|
||||
// The returned token ID
|
||||
TokenId string `protobuf:"bytes,30,opt,name=token_id,proto3" json:"token_id,omitempty"`
|
||||
TokenId string `protobuf:"bytes,30,opt,name=token_id,proto3" json:"token_id,omitempty" class:"public"` // @gotags: `class:"public"`
|
||||
}
|
||||
|
||||
func (x *OidcAuthMethodAuthenticateStartResponse) Reset() {
|
||||
@@ -510,13 +510,13 @@ type OidcAuthMethodAuthenticateCallbackRequest struct {
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
// The returned code
|
||||
Code string `protobuf:"bytes,10,opt,name=code,proto3" json:"code,omitempty"`
|
||||
Code string `protobuf:"bytes,10,opt,name=code,proto3" json:"code,omitempty" class:"secret"` // @gotags: `class:"secret"`
|
||||
// The returned state
|
||||
State string `protobuf:"bytes,20,opt,name=state,proto3" json:"state,omitempty"`
|
||||
State string `protobuf:"bytes,20,opt,name=state,proto3" json:"state,omitempty" class:"public"` // @gotags: `class:"public"`
|
||||
// Error parameters, if they are returned
|
||||
Error string `protobuf:"bytes,30,opt,name=error,proto3" json:"error,omitempty"`
|
||||
ErrorDescription string `protobuf:"bytes,40,opt,name=error_description,proto3" json:"error_description,omitempty"`
|
||||
ErrorUri string `protobuf:"bytes,50,opt,name=error_uri,proto3" json:"error_uri,omitempty"`
|
||||
Error string `protobuf:"bytes,30,opt,name=error,proto3" json:"error,omitempty" class:"public"` // @gotags: `class:"public"`
|
||||
ErrorDescription string `protobuf:"bytes,40,opt,name=error_description,proto3" json:"error_description,omitempty" class:"public"` // @gotags: `class:"public"`
|
||||
ErrorUri string `protobuf:"bytes,50,opt,name=error_uri,proto3" json:"error_uri,omitempty" class:"public"` // @gotags: `class:"public"`
|
||||
}
|
||||
|
||||
func (x *OidcAuthMethodAuthenticateCallbackRequest) Reset() {
|
||||
@@ -593,7 +593,7 @@ type OidcAuthMethodAuthenticateCallbackResponse struct {
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
// The final redirection URL
|
||||
FinalRedirectUrl string `protobuf:"bytes,10,opt,name=final_redirect_url,proto3" json:"final_redirect_url,omitempty"`
|
||||
FinalRedirectUrl string `protobuf:"bytes,10,opt,name=final_redirect_url,proto3" json:"final_redirect_url,omitempty" class:"public"` // @gotags: `class:"public"`
|
||||
}
|
||||
|
||||
func (x *OidcAuthMethodAuthenticateCallbackResponse) Reset() {
|
||||
@@ -642,7 +642,7 @@ type OidcAuthMethodAuthenticateTokenRequest struct {
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
// The ID of the pending token
|
||||
TokenId string `protobuf:"bytes,10,opt,name=token_id,proto3" json:"token_id,omitempty"`
|
||||
TokenId string `protobuf:"bytes,10,opt,name=token_id,proto3" json:"token_id,omitempty" class:"private"` // @gotags: `class:"private"`
|
||||
}
|
||||
|
||||
func (x *OidcAuthMethodAuthenticateTokenRequest) Reset() {
|
||||
@@ -693,7 +693,7 @@ type OidcAuthMethodAuthenticateTokenResponse struct {
|
||||
|
||||
// The status. This will always be "unknown". It will never be forwarded to
|
||||
// the consumer.
|
||||
Status string `protobuf:"bytes,10,opt,name=status,proto3" json:"status,omitempty"`
|
||||
Status string `protobuf:"bytes,10,opt,name=status,proto3" json:"status,omitempty" class:"public"` // @gotags: `class:"public"`
|
||||
}
|
||||
|
||||
func (x *OidcAuthMethodAuthenticateTokenResponse) Reset() {
|
||||
|
||||
@@ -45,6 +45,10 @@ func (req *AuthMethod) Tags() ([]encrypt.PointerTag, error) {
|
||||
Pointer: "/Attributes/Fields/signing_algorithms",
|
||||
Classification: encrypt.PublicClassification,
|
||||
},
|
||||
{
|
||||
Pointer: "/Attributes/Fields/idp_ca_certs",
|
||||
Classification: encrypt.PublicClassification,
|
||||
},
|
||||
{
|
||||
Pointer: "/Attributes/Fields/api_url_prefix",
|
||||
Classification: encrypt.PublicClassification,
|
||||
|
||||
@@ -6,9 +6,9 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/boundary/sdk/pbs/controller/api"
|
||||
"github.com/hashicorp/boundary/sdk/wrapper"
|
||||
"github.com/hashicorp/eventlogger"
|
||||
"github.com/hashicorp/eventlogger/filters/encrypt"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/protobuf/types/known/structpb"
|
||||
@@ -19,11 +19,7 @@ func TestAuthMethod_Tags(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
now := time.Now()
|
||||
wrapper := wrapper.TestWrapper(t)
|
||||
testEncryptingFilter := &encrypt.Filter{
|
||||
Wrapper: wrapper,
|
||||
HmacSalt: []byte("salt"),
|
||||
HmacInfo: []byte("info"),
|
||||
}
|
||||
testEncryptingFilter := api.NewEncryptFilter(t, wrapper)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
@@ -49,6 +45,7 @@ func TestAuthMethod_Tags(t *testing.T) {
|
||||
"client_secret_hmac": structpb.NewStringValue("public-client_secret_hmac"),
|
||||
"max_age": structpb.NewStringValue("public-max_age"),
|
||||
"signing_algorithms": structpb.NewStringValue("public-signing_algorithms"),
|
||||
"idp_ca_certs": structpb.NewStringValue("public-signing_algorithms"),
|
||||
"api_url_prefix": structpb.NewStringValue("public-api_url_prefix"),
|
||||
"callback_url": structpb.NewStringValue("public-callback_url"),
|
||||
"allowed_audiences": structpb.NewStringValue("public-allowed_audiences"),
|
||||
@@ -87,6 +84,7 @@ func TestAuthMethod_Tags(t *testing.T) {
|
||||
"client_secret_hmac": structpb.NewStringValue("public-client_secret_hmac"),
|
||||
"max_age": structpb.NewStringValue("public-max_age"),
|
||||
"signing_algorithms": structpb.NewStringValue("public-signing_algorithms"),
|
||||
"idp_ca_certs": structpb.NewStringValue("public-signing_algorithms"),
|
||||
"api_url_prefix": structpb.NewStringValue("public-api_url_prefix"),
|
||||
"callback_url": structpb.NewStringValue("public-callback_url"),
|
||||
"allowed_audiences": structpb.NewStringValue("public-allowed_audiences"),
|
||||
@@ -119,7 +117,7 @@ func TestAuthMethod_Tags(t *testing.T) {
|
||||
ScopeId: "scope-id",
|
||||
Name: &wrapperspb.StringValue{Value: "name"},
|
||||
Description: &wrapperspb.StringValue{Value: "description"},
|
||||
Type: "oidc",
|
||||
Type: "password",
|
||||
Attributes: &structpb.Struct{
|
||||
Fields: map[string]*structpb.Value{
|
||||
"min_login_name_length": structpb.NewStringValue("public-min_login_name_length"),
|
||||
@@ -145,7 +143,7 @@ func TestAuthMethod_Tags(t *testing.T) {
|
||||
ScopeId: "scope-id",
|
||||
Name: &wrapperspb.StringValue{Value: "name"},
|
||||
Description: &wrapperspb.StringValue{Value: "description"},
|
||||
Type: "oidc",
|
||||
Type: "password",
|
||||
Attributes: &structpb.Struct{
|
||||
Fields: map[string]*structpb.Value{
|
||||
"min_login_name_length": structpb.NewStringValue("public-min_login_name_length"),
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/eventlogger/filters/encrypt"
|
||||
wrapping "github.com/hashicorp/go-kms-wrapping"
|
||||
"google.golang.org/protobuf/types/known/fieldmaskpb"
|
||||
)
|
||||
|
||||
// NewEncryptFilter is a copy of event.NewEncryptFilter since importing it would
|
||||
// case circular deps. The primary reason for this test func is to make sure
|
||||
// the proper IgnoreTypes are included for testing.
|
||||
func NewEncryptFilter(t *testing.T, w wrapping.Wrapper) *encrypt.Filter {
|
||||
t.Helper()
|
||||
return &encrypt.Filter{
|
||||
Wrapper: w,
|
||||
IgnoreTypes: []reflect.Type{
|
||||
reflect.TypeOf(&fieldmaskpb.FieldMask{}),
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -57,8 +57,9 @@ TLS establishment is performed as follows:
|
||||
encrypted value.
|
||||
|
||||
5. The Controller decrypts this value via the shared KMS. If successful, the
|
||||
Controller validates that the nonce is not known, to ensure that this is not a
|
||||
replay.
|
||||
Controller validates that the nonce is not known to ensure that this is not a
|
||||
replay. The nonce value is checked against and stored into the database,
|
||||
ensuring that nonce replay cannot occur across controllers.
|
||||
|
||||
6. The Controller uses the decrypted parameters to configures its TLS stack with
|
||||
the same certificate and key.
|
||||
@@ -67,11 +68,6 @@ TLS establishment is performed as follows:
|
||||
self-signed CA certificate that was securely transmitted is configured as a
|
||||
valid root CA for validation checking.
|
||||
|
||||
8. If successful, the nonce is stored in the database along with an expiration
|
||||
time set several minutes past the actual expiration time of the certificate
|
||||
itself. This ensures that any replay attempt that occurs is detected and
|
||||
rejected until after the certificate is otherwise invalid.
|
||||
|
||||
## Client-to-Worker TLS
|
||||
|
||||
Workers do not require any configuration for their client-facing listeners to
|
||||
|
||||
Reference in new issue
Block a user