Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
-- Restore effective chat access when rolling back to a version that requires
-- the role. The previous assignments cannot be recovered after migration up.
UPDATE organization_members
SET roles = array_append(roles, 'agents-access')
WHERE NOT ('agents-access' = ANY(roles));

UPDATE organizations
SET default_org_member_roles = array_append(default_org_member_roles, 'agents-access')
WHERE NOT ('agents-access' = ANY(default_org_member_roles));
Comment on lines +8 to +9

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Exclude service accounts from rollback access grants

When this migration is rolled back, adding agents-access to default_org_member_roles grants it to service accounts as well, because GetAuthorizationUserRoles unions organization defaults into every membership, including accounts receiving the separate organization-service-account role. This defeats the new explicit omission of chat permissions for service accounts and gives machine credentials Coder Agents access after a production rollback. Restore the role only on regular users' membership rows, rather than making it an organization default.

Useful? React with 👍 / 👎.

11 changes: 11 additions & 0 deletions coderd/database/migrations/000570_remove_agents_access_role.up.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
UPDATE users
SET rbac_roles = array_remove(rbac_roles, 'agents-access')
WHERE 'agents-access' = ANY(rbac_roles);

UPDATE organization_members
SET roles = array_remove(roles, 'agents-access')
WHERE 'agents-access' = ANY(roles);

UPDATE organizations
SET default_org_member_roles = array_remove(default_org_member_roles, 'agents-access')
WHERE 'agents-access' = ANY(default_org_member_roles);
50 changes: 50 additions & 0 deletions coderd/database/migrations/migrate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1190,6 +1190,56 @@ func TestMigration000475AgentsAccessOrgRole(t *testing.T) {
)
}

func TestMigration000570RemoveAgentsAccessRole(t *testing.T) {
t.Parallel()

const migrationVersion = 570

sqlDB := testSQLDB(t)
next, err := migrations.Stepper(sqlDB)
require.NoError(t, err)
for {
version, more, err := next()
require.NoError(t, err)
if !more {
t.Fatalf("migration %d not found", migrationVersion)
}
if version == migrationVersion-1 {
break
}
}

db := database.New(sqlDB)
user := dbgen.User(t, db, database.User{
RBACRoles: []string{"auditor", "agents-access"},
})
org := dbgen.Organization(t, db, database.Organization{
DefaultOrgMemberRoles: []string{"organization-workspace-access", "agents-access"},
})
dbgen.OrganizationMember(t, db, database.OrganizationMember{
OrganizationID: org.ID,
UserID: user.ID,
Roles: []string{"organization-auditor", "agents-access"},
})

version, _, err := next()
require.NoError(t, err)
require.EqualValues(t, migrationVersion, version)

ctx := testutil.Context(t, testutil.WaitLong)
var siteRoles, orgRoles, defaultRoles pq.StringArray
err = sqlDB.QueryRowContext(ctx, "SELECT rbac_roles FROM users WHERE id = $1", user.ID).Scan(&siteRoles)
require.NoError(t, err)
err = sqlDB.QueryRowContext(ctx, "SELECT roles FROM organization_members WHERE organization_id = $1 AND user_id = $2", org.ID, user.ID).Scan(&orgRoles)
require.NoError(t, err)
err = sqlDB.QueryRowContext(ctx, "SELECT default_org_member_roles FROM organizations WHERE id = $1", org.ID).Scan(&defaultRoles)
require.NoError(t, err)

require.Equal(t, []string{"auditor"}, []string(siteRoles))
require.Equal(t, []string{"organization-auditor"}, []string(orgRoles))
require.Equal(t, []string{"organization-workspace-access"}, []string(defaultRoles))
}

func TestMigration000504AIProvidersBackfill(t *testing.T) {
t.Parallel()

Expand Down
12 changes: 3 additions & 9 deletions coderd/database/querier_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1464,8 +1464,8 @@ func TestGetAuthorizedChats(t *testing.T) {

org := dbgen.Organization(t, db, database.Organization{})
dbgen.OrganizationMember(t, db, database.OrganizationMember{UserID: owner.ID, OrganizationID: org.ID})
dbgen.OrganizationMember(t, db, database.OrganizationMember{UserID: member.ID, OrganizationID: org.ID, Roles: []string{rbac.RoleAgentsAccess()}})
dbgen.OrganizationMember(t, db, database.OrganizationMember{UserID: secondMember.ID, OrganizationID: org.ID, Roles: []string{rbac.RoleAgentsAccess()}})
dbgen.OrganizationMember(t, db, database.OrganizationMember{UserID: member.ID, OrganizationID: org.ID})
dbgen.OrganizationMember(t, db, database.OrganizationMember{UserID: secondMember.ID, OrganizationID: org.ID})

// Create FK dependencies: a chat provider and model config.
_ = dbgen.ChatProvider(t, db, database.ChatProvider{
Expand Down Expand Up @@ -1652,7 +1652,7 @@ func TestGetAuthorizedChats(t *testing.T) {
// Use a dedicated user for pagination to avoid interference
// with the other parallel subtests.
paginationUser := dbgen.User(t, db, database.User{})
dbgen.OrganizationMember(t, db, database.OrganizationMember{UserID: paginationUser.ID, OrganizationID: org.ID, Roles: []string{rbac.RoleAgentsAccess()}})
dbgen.OrganizationMember(t, db, database.OrganizationMember{UserID: paginationUser.ID, OrganizationID: org.ID})
for i := range 7 {
dbgen.Chat(t, db, database.Chat{
OrganizationID: org.ID,
Expand Down Expand Up @@ -1726,12 +1726,10 @@ func TestGetAuthorizedChatsACLSharing(t *testing.T) {
dbgen.OrganizationMember(t, db, database.OrganizationMember{
UserID: owner.ID,
OrganizationID: org.ID,
Roles: []string{rbac.RoleAgentsAccess()},
})
dbgen.OrganizationMember(t, db, database.OrganizationMember{
UserID: recipient.ID,
OrganizationID: org.ID,
Roles: []string{rbac.RoleAgentsAccess()},
})

dbgen.ChatProvider(t, db, database.ChatProvider{Provider: "openai", DisplayName: "OpenAI"})
Expand Down Expand Up @@ -1844,12 +1842,10 @@ func TestGetAuthorizedChatsACLSharingGroupACL(t *testing.T) {
dbgen.OrganizationMember(t, db, database.OrganizationMember{
UserID: owner.ID,
OrganizationID: org.ID,
Roles: []string{rbac.RoleAgentsAccess()},
})
dbgen.OrganizationMember(t, db, database.OrganizationMember{
UserID: recipient.ID,
OrganizationID: org.ID,
Roles: []string{rbac.RoleAgentsAccess()},
})
group := dbgen.Group(t, db, database.Group{OrganizationID: org.ID})
dbgen.GroupMember(t, db, database.GroupMemberTable{UserID: recipient.ID, GroupID: group.ID})
Expand Down Expand Up @@ -1948,12 +1944,10 @@ func TestGetAuthorizedChatsByChatFileIDACLSharing(t *testing.T) {
dbgen.OrganizationMember(t, db, database.OrganizationMember{
UserID: owner.ID,
OrganizationID: org.ID,
Roles: []string{rbac.RoleAgentsAccess()},
})
dbgen.OrganizationMember(t, db, database.OrganizationMember{
UserID: recipient.ID,
OrganizationID: org.ID,
Roles: []string{rbac.RoleAgentsAccess()},
})

dbgen.ChatProvider(t, db, database.ChatProvider{Provider: "openai", DisplayName: "OpenAI"})
Expand Down
4 changes: 2 additions & 2 deletions coderd/exp_chats_acl_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -463,7 +463,7 @@ func TestListChatsSharedScope(t *testing.T) {
client, db := newChatClientWithDatabase(t)
firstUser := coderdtest.CreateFirstUser(t, client.Client)
modelConfig := createChatModelConfig(t, client)
viewerClient, viewer := coderdtest.CreateAnotherUser(t, client.Client, firstUser.OrganizationID, rbac.ScopedRoleAgentsAccess(firstUser.OrganizationID))
viewerClient, viewer := coderdtest.CreateAnotherUser(t, client.Client, firstUser.OrganizationID)
viewerClientExp := codersdk.NewExperimentalClient(viewerClient)
sharedChat := dbgen.Chat(t, db, database.Chat{
OrganizationID: firstUser.OrganizationID,
Expand Down Expand Up @@ -562,7 +562,7 @@ func TestChatSharingDisabled(t *testing.T) {
})
firstUser := coderdtest.CreateFirstUser(t, client.Client)
modelConfig := createChatModelConfig(t, client)
viewerClient, viewer := coderdtest.CreateAnotherUser(t, client.Client, firstUser.OrganizationID, rbac.ScopedRoleAgentsAccess(firstUser.OrganizationID))
viewerClient, viewer := coderdtest.CreateAnotherUser(t, client.Client, firstUser.OrganizationID)
viewerClientExp := codersdk.NewExperimentalClient(viewerClient)

chat := dbgen.Chat(t, store, database.Chat{
Expand Down
Loading
Loading