diff --git a/CHANGELOG.md b/CHANGELOG.md index a21c2aa99e..64b5441727 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,12 +2,16 @@ Canonical reference for changes, improvements, and bugfixes for Boundary. -## 0.10.1 () +## 0.10.1 (2022/08/11) ### Bug Fixes -* Managed Groups: fixes an issue where the `filter` field is not sent by - admin UI ([PR](https://github.com/hashicorp/boundary-ui/pull/1238)) +* db: Fix an issue with migrations affecting clusters that contain + credential libraries or static credentials. + ([Issue](https://github.com/hashicorp/boundary/issues/2349)), + ([PR](https://github.com/hashicorp/boundary/pull/2351)). +* Managed Groups: Fix an issue where the `filter` field is not sent by + admin UI ([PR](https://github.com/hashicorp/boundary-ui/pull/1238)). ## 0.10.0 (2022/08/10) diff --git a/internal/db/schema/migrations/oss/postgres/40/01_credential.up.sql b/internal/db/schema/migrations/oss/postgres/40/01_credential.up.sql index 7cbca9ac63..5a588c3cee 100644 --- a/internal/db/schema/migrations/oss/postgres/40/01_credential.up.sql +++ b/internal/db/schema/migrations/oss/postgres/40/01_credential.up.sql @@ -4,6 +4,12 @@ begin; alter table credential_purpose_enm drop constraint only_predefined_credential_purposes_allowed; + -- drop immutable columns so we can migrate enm values + drop trigger immutable_columns on target_credential_library; + drop trigger immutable_columns on session_credential_dynamic; + drop trigger immutable_columns on target_static_credential; + drop trigger immutable_columns on session_credential_static; + -- update egress to injected_application update credential_purpose_enm set name = 'injected_application' @@ -34,4 +40,15 @@ begin; 'credential_purpose_enm is an enumeration table for credential purposes. ' 'It contains rows for representing the brokered, and injected_application credential purposes.'; + -- replace the immutable columns + create trigger immutable_columns before update on target_credential_library + for each row execute procedure immutable_columns('target_id', 'credential_library_id', 'credential_purpose', 'create_time'); + create trigger immutable_columns before update on session_credential_dynamic + for each row execute procedure immutable_columns('session_id', 'library_id', 'credential_purpose', 'create_time'); + + create trigger immutable_columns before update on target_static_credential + for each row execute procedure immutable_columns('target_id', 'credential_static_id', 'credential_purpose', 'create_time'); + create trigger immutable_columns before update on session_credential_static + for each row execute procedure immutable_columns('session_id', 'credential_static_id', 'credential_purpose', 'create_time'); + commit; diff --git a/internal/db/schema/migrations/oss/postgres_40_01_test.go b/internal/db/schema/migrations/oss/postgres_40_01_test.go new file mode 100644 index 0000000000..529013954e --- /dev/null +++ b/internal/db/schema/migrations/oss/postgres_40_01_test.go @@ -0,0 +1,204 @@ +package oss_test + +import ( + "context" + "testing" + + "github.com/hashicorp/boundary/internal/authtoken" + "github.com/hashicorp/boundary/internal/credential/static" + "github.com/hashicorp/boundary/internal/credential/vault" + "github.com/hashicorp/boundary/internal/db" + "github.com/hashicorp/boundary/internal/db/common" + "github.com/hashicorp/boundary/internal/db/schema" + hoststatic "github.com/hashicorp/boundary/internal/host/static" + "github.com/hashicorp/boundary/internal/iam" + "github.com/hashicorp/boundary/internal/kms" + "github.com/hashicorp/boundary/internal/session" + "github.com/hashicorp/boundary/internal/target" + "github.com/hashicorp/boundary/internal/target/targettest" + "github.com/hashicorp/boundary/testing/dbtest" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestMigrations_Credential_Purpose_Refactor(t *testing.T) { + const ( + priorMigration = 39002 + currentMigration = 40001 + ) + + t.Parallel() + ctx := context.Background() + dialect := dbtest.Postgres + + c, u, _, err := dbtest.StartUsingTemplate(dialect, dbtest.WithTemplate(dbtest.Template1)) + require.NoError(t, err) + t.Cleanup(func() { + require.NoError(t, c()) + }) + d, err := common.SqlOpen(dialect, u) + require.NoError(t, 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(t, err) + + require.NoError(t, m.ApplyMigrations(ctx)) + state, err := m.CurrentState(ctx) + require.NoError(t, err) + want := &schema.State{ + Initialized: true, + Editions: []schema.EditionState{ + { + Name: "oss", + BinarySchemaVersion: priorMigration, + DatabaseSchemaVersion: priorMigration, + DatabaseSchemaState: schema.Equal, + }, + }, + } + require.Equal(t, want, state) + + // Get a connection + dbType, err := db.StringToDbType(dialect) + require.NoError(t, err) + conn, err := db.Open(ctx, dbType, u) + require.NoError(t, err) + rw := db.New(conn) + + // Create project + wrapper := db.TestWrapper(t) + iamRepo := iam.TestRepo(t, conn, wrapper) + org, proj := iam.TestScopes(t, iamRepo) + kmsCache := kms.TestKms(t, conn, wrapper) + + at := authtoken.TestAuthToken(t, conn, kmsCache, org.GetPublicId()) + uId := at.GetIamUserId() + hc := hoststatic.TestCatalogs(t, conn, proj.GetPublicId(), 1)[0] + hs := hoststatic.TestSets(t, conn, hc.GetPublicId(), 1)[0] + h := hoststatic.TestHosts(t, conn, hc.GetPublicId(), 1)[0] + hoststatic.TestSetMembers(t, conn, hs.GetPublicId(), []*hoststatic.Host{h}) + + // Create a target + tar := targettest.TestNewTestTarget(ctx, t, conn, proj.PublicId, "my-credential-sources", target.WithHostSources([]string{hs.GetPublicId()})) + + vaultStoreId := "csvlt_vaultid123" + num, err := rw.Exec(ctx, ` +insert into credential_vault_store + (public_id, scope_id, vault_address) +values + ($1, $2, $3); +`, []interface{}{vaultStoreId, proj.GetPublicId(), "http://vault"}) + require.NoError(t, err) + assert.Equal(t, 1, num) + + credLibs := vault.TestCredentialLibraries(t, conn, wrapper, vaultStoreId, 2) + lib1 := credLibs[0] + lib2 := credLibs[1] + + storeStatic := static.TestCredentialStores(t, conn, wrapper, proj.GetPublicId(), 1)[0] + credsStatic := static.TestUsernamePasswordCredentials(t, conn, wrapper, "u", "p", storeStatic.GetPublicId(), proj.GetPublicId(), 2) + cred1 := credsStatic[0] + cred2 := credsStatic[1] + + appCredLib, err := target.NewCredentialLibrary(tar.GetPublicId(), lib1.GetPublicId(), "application") + require.NoError(t, err) + egressCredLib, err := target.NewCredentialLibrary(tar.GetPublicId(), lib2.GetPublicId(), "egress") + require.NoError(t, err) + appCred, err := target.NewStaticCredential(tar.GetPublicId(), cred1.PublicId, "application") + require.NoError(t, err) + egressCred, err := target.NewStaticCredential(tar.GetPublicId(), cred2.PublicId, "egress") + require.NoError(t, err) + + err = rw.CreateItems(ctx, []interface{}{appCredLib, egressCredLib}) + require.NoError(t, err) + err = rw.CreateItems(ctx, []interface{}{appCred, egressCred}) + require.NoError(t, err) + + dynCreds := []*session.DynamicCredential{ + session.NewDynamicCredential(appCredLib.GetCredentialLibraryId(), "application"), + session.NewDynamicCredential(egressCredLib.GetCredentialLibraryId(), "egress"), + } + staticCreds := []*session.StaticCredential{ + session.NewStaticCredential(appCred.GetCredentialId(), "application"), + session.NewStaticCredential(egressCred.GetCredentialId(), "egress"), + } + + // Create a test session + sess := session.TestSession(t, conn, wrapper, session.ComposedOf{ + UserId: uId, + HostId: h.GetPublicId(), + TargetId: tar.GetPublicId(), + HostSetId: hs.GetPublicId(), + AuthTokenId: at.GetPublicId(), + ScopeId: proj.GetPublicId(), + Endpoint: "tcp://127.0.0.1:22", + DynamicCredentials: dynCreds, + StaticCredentials: staticCreds, + }) + + // 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(t, err) + + require.NoError(t, m.ApplyMigrations(ctx)) + state, err = m.CurrentState(ctx) + require.NoError(t, err) + want = &schema.State{ + Initialized: true, + Editions: []schema.EditionState{ + { + Name: "oss", + BinarySchemaVersion: currentMigration, + DatabaseSchemaVersion: currentMigration, + DatabaseSchemaState: schema.Equal, + }, + }, + } + require.Equal(t, want, state) + + // Validate migrations + lookupLib := &target.CredentialLibrary{} + err = rw.LookupWhere(ctx, lookupLib, "credential_library_id = ?", []interface{}{appCredLib.GetCredentialLibraryId()}) + require.NoError(t, err) + assert.Equal(t, "brokered", lookupLib.CredentialPurpose) + + lookupLib = &target.CredentialLibrary{} + err = rw.LookupWhere(ctx, lookupLib, "credential_library_id = ?", []interface{}{egressCredLib.GetCredentialLibraryId()}) + require.NoError(t, err) + assert.Equal(t, "injected_application", lookupLib.CredentialPurpose) + + lookupCred := &target.StaticCredential{} + err = rw.LookupWhere(ctx, lookupCred, "credential_static_id = ?", []interface{}{appCred.GetCredentialId()}) + require.NoError(t, err) + assert.Equal(t, "brokered", lookupCred.CredentialPurpose) + + lookupCred = &target.StaticCredential{} + err = rw.LookupWhere(ctx, lookupCred, "credential_static_id = ?", []interface{}{egressCred.GetCredentialId()}) + require.NoError(t, err) + assert.Equal(t, "injected_application", lookupCred.CredentialPurpose) + + lookupDynCred := &session.DynamicCredential{} + err = rw.LookupWhere(ctx, lookupDynCred, "session_id = ? and library_id = ?", []interface{}{sess.GetPublicId(), appCredLib.GetCredentialLibraryId()}) + require.NoError(t, err) + assert.Equal(t, "brokered", lookupDynCred.CredentialPurpose) + + lookupDynCred = &session.DynamicCredential{} + err = rw.LookupWhere(ctx, lookupDynCred, "session_id = ? and library_id = ?", []interface{}{sess.GetPublicId(), egressCredLib.GetCredentialLibraryId()}) + require.NoError(t, err) + assert.Equal(t, "injected_application", lookupDynCred.CredentialPurpose) + + lookupStaticCred := &session.StaticCredential{} + err = rw.LookupWhere(ctx, lookupStaticCred, "session_id = ? and credential_static_id = ?", []interface{}{sess.GetPublicId(), appCred.GetCredentialId()}) + require.NoError(t, err) + assert.Equal(t, "brokered", lookupStaticCred.CredentialPurpose) + + lookupStaticCred = &session.StaticCredential{} + err = rw.LookupWhere(ctx, lookupStaticCred, "session_id = ? and credential_static_id = ?", []interface{}{sess.GetPublicId(), egressCred.GetCredentialId()}) + require.NoError(t, err) + assert.Equal(t, "injected_application", lookupStaticCred.CredentialPurpose) +} diff --git a/internal/session/testing.go b/internal/session/testing.go index 4d7b9aecf5..e9b346fc37 100644 --- a/internal/session/testing.go +++ b/internal/session/testing.go @@ -104,6 +104,12 @@ func TestSession(t testing.TB, conn *db.DB, wrapper wrapping.Wrapper, c Composed require.NoError(err) } + for _, cred := range s.StaticCredentials { + cred.SessionId = s.PublicId + err := rw.Create(ctx, cred) + require.NoError(err) + } + ss, err := fetchStates(ctx, rw, s.PublicId, append(opts.withDbOpts, db.WithOrder("start_time desc"))...) require.NoError(err) s.States = ss