Skip to content

Commit c65996a

Browse files
feat: add user_secrets table (#19162)
Closes coder/internal#780 ## Summary of changes: - added `user_secrets` table - `user_secrets` table contains `env_name` and `file_path` fields which are not used at the moment, but will be used in later PRs - `user_secrets` table doesn't contain `value_key_id`, I will add it in a separate migration in a dbcrypt PR - on one hand I don't want to add fields which are not used (because it's a risk smth may change in implementation later), on the other hand I don't want to add too many migrations for user secrets table - added unique sql indexes - added sql queries for CRUD operations on user-secrets - introduced new `ResourceUserSecret` resource - basic unit-tests for CRUD ops and authorization behavior - Role updates: - owner: - remove `ResourceUserSecret` from site-wide perms - add `ResourceUserSecret` to user-wide perms - orgAdmin - remove `ResourceUserSecret` from org-wide perms; seems it's not strictly required, because `ResourceUserSecret` is not tied to organization in dbauthz wrappers? - memberRole - no need to change memberRole because it implicitly has access to user-secrets thanks to the `allPermsExcept` - is it enough changes to roles? Main questions: - [ ] We will have 2 migrations for user-secrets: - initial migration (in current PR) - adding `value_key_id` in dbcrypt PR - is this approach reasonable? - [ ] Are changes to roles's permissions are correct? - [ ] Are changes in roles_test.go are correct? --------- Co-authored-by: Steven Masley <Emyrk@users.noreply.github.com>
1 parent 34c46c0 commit c65996a

28 files changed

+913
-3
lines changed

coderd/apidoc/docs.go

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/apidoc/swagger.json

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/database/dbauthz/dbauthz.go

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1387,6 +1387,14 @@ func (q *querier) CountUnreadInboxNotificationsByUserID(ctx context.Context, use
13871387
return q.db.CountUnreadInboxNotificationsByUserID(ctx, userID)
13881388
}
13891389

1390+
func (q *querier) CreateUserSecret(ctx context.Context, arg database.CreateUserSecretParams) (database.UserSecret, error) {
1391+
obj := rbac.ResourceUserSecret.WithOwner(arg.UserID.String())
1392+
if err := q.authorizeContext(ctx, policy.ActionCreate, obj); err != nil {
1393+
return database.UserSecret{}, err
1394+
}
1395+
return q.db.CreateUserSecret(ctx, arg)
1396+
}
1397+
13901398
// TODO: Handle org scoped lookups
13911399
func (q *querier) CustomRoles(ctx context.Context, arg database.CustomRolesParams) ([]database.CustomRole, error) {
13921400
roleObject := rbac.ResourceAssignRole
@@ -1657,6 +1665,19 @@ func (q *querier) DeleteTailnetTunnel(ctx context.Context, arg database.DeleteTa
16571665
return q.db.DeleteTailnetTunnel(ctx, arg)
16581666
}
16591667

1668+
func (q *querier) DeleteUserSecret(ctx context.Context, id uuid.UUID) error {
1669+
// First get the secret to check ownership
1670+
secret, err := q.GetUserSecret(ctx, id)
1671+
if err != nil {
1672+
return err
1673+
}
1674+
1675+
if err := q.authorizeContext(ctx, policy.ActionDelete, secret); err != nil {
1676+
return err
1677+
}
1678+
return q.db.DeleteUserSecret(ctx, id)
1679+
}
1680+
16601681
func (q *querier) DeleteWebpushSubscriptionByUserIDAndEndpoint(ctx context.Context, arg database.DeleteWebpushSubscriptionByUserIDAndEndpointParams) error {
16611682
if err := q.authorizeContext(ctx, policy.ActionDelete, rbac.ResourceWebpushSubscription.WithOwner(arg.UserID.String())); err != nil {
16621683
return err
@@ -3075,6 +3096,28 @@ func (q *querier) GetUserNotificationPreferences(ctx context.Context, userID uui
30753096
return q.db.GetUserNotificationPreferences(ctx, userID)
30763097
}
30773098

3099+
func (q *querier) GetUserSecret(ctx context.Context, id uuid.UUID) (database.UserSecret, error) {
3100+
// First get the secret to check ownership
3101+
secret, err := q.db.GetUserSecret(ctx, id)
3102+
if err != nil {
3103+
return database.UserSecret{}, err
3104+
}
3105+
3106+
if err := q.authorizeContext(ctx, policy.ActionRead, secret); err != nil {
3107+
return database.UserSecret{}, err
3108+
}
3109+
return secret, nil
3110+
}
3111+
3112+
func (q *querier) GetUserSecretByUserIDAndName(ctx context.Context, arg database.GetUserSecretByUserIDAndNameParams) (database.UserSecret, error) {
3113+
obj := rbac.ResourceUserSecret.WithOwner(arg.UserID.String())
3114+
if err := q.authorizeContext(ctx, policy.ActionRead, obj); err != nil {
3115+
return database.UserSecret{}, err
3116+
}
3117+
3118+
return q.db.GetUserSecretByUserIDAndName(ctx, arg)
3119+
}
3120+
30783121
func (q *querier) GetUserStatusCounts(ctx context.Context, arg database.GetUserStatusCountsParams) ([]database.GetUserStatusCountsRow, error) {
30793122
if err := q.authorizeContext(ctx, policy.ActionRead, rbac.ResourceUser); err != nil {
30803123
return nil, err
@@ -4153,6 +4196,14 @@ func (q *querier) ListProvisionerKeysByOrganizationExcludeReserved(ctx context.C
41534196
return fetchWithPostFilter(q.auth, policy.ActionRead, q.db.ListProvisionerKeysByOrganizationExcludeReserved)(ctx, organizationID)
41544197
}
41554198

4199+
func (q *querier) ListUserSecrets(ctx context.Context, userID uuid.UUID) ([]database.UserSecret, error) {
4200+
obj := rbac.ResourceUserSecret.WithOwner(userID.String())
4201+
if err := q.authorizeContext(ctx, policy.ActionRead, obj); err != nil {
4202+
return nil, err
4203+
}
4204+
return q.db.ListUserSecrets(ctx, userID)
4205+
}
4206+
41564207
func (q *querier) ListWorkspaceAgentPortShares(ctx context.Context, workspaceID uuid.UUID) ([]database.WorkspaceAgentPortShare, error) {
41574208
workspace, err := q.db.GetWorkspaceByID(ctx, workspaceID)
41584209
if err != nil {
@@ -4866,6 +4917,19 @@ func (q *querier) UpdateUserRoles(ctx context.Context, arg database.UpdateUserRo
48664917
return q.db.UpdateUserRoles(ctx, arg)
48674918
}
48684919

4920+
func (q *querier) UpdateUserSecret(ctx context.Context, arg database.UpdateUserSecretParams) (database.UserSecret, error) {
4921+
// First get the secret to check ownership
4922+
secret, err := q.db.GetUserSecret(ctx, arg.ID)
4923+
if err != nil {
4924+
return database.UserSecret{}, err
4925+
}
4926+
4927+
if err := q.authorizeContext(ctx, policy.ActionUpdate, secret); err != nil {
4928+
return database.UserSecret{}, err
4929+
}
4930+
return q.db.UpdateUserSecret(ctx, arg)
4931+
}
4932+
48694933
func (q *querier) UpdateUserStatus(ctx context.Context, arg database.UpdateUserStatusParams) (database.User, error) {
48704934
fetch := func(ctx context.Context, arg database.UpdateUserStatusParams) (database.User, error) {
48714935
return q.db.GetUserByID(ctx, arg.ID)

coderd/database/dbauthz/dbauthz_test.go

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5883,3 +5883,64 @@ func (s *MethodTestSuite) TestAuthorizePrebuiltWorkspace() {
58835883
}).Asserts(w, policy.ActionUpdate, w.AsPrebuild(), policy.ActionUpdate)
58845884
}))
58855885
}
5886+
5887+
func (s *MethodTestSuite) TestUserSecrets() {
5888+
s.Run("GetUserSecretByUserIDAndName", s.Subtest(func(db database.Store, check *expects) {
5889+
user := dbgen.User(s.T(), db, database.User{})
5890+
userSecret := dbgen.UserSecret(s.T(), db, database.UserSecret{
5891+
UserID: user.ID,
5892+
})
5893+
arg := database.GetUserSecretByUserIDAndNameParams{
5894+
UserID: user.ID,
5895+
Name: userSecret.Name,
5896+
}
5897+
check.Args(arg).
5898+
Asserts(rbac.ResourceUserSecret.WithOwner(arg.UserID.String()), policy.ActionRead).
5899+
Returns(userSecret)
5900+
}))
5901+
s.Run("GetUserSecret", s.Subtest(func(db database.Store, check *expects) {
5902+
user := dbgen.User(s.T(), db, database.User{})
5903+
userSecret := dbgen.UserSecret(s.T(), db, database.UserSecret{
5904+
UserID: user.ID,
5905+
})
5906+
check.Args(userSecret.ID).
5907+
Asserts(userSecret, policy.ActionRead).
5908+
Returns(userSecret)
5909+
}))
5910+
s.Run("ListUserSecrets", s.Subtest(func(db database.Store, check *expects) {
5911+
user := dbgen.User(s.T(), db, database.User{})
5912+
userSecret := dbgen.UserSecret(s.T(), db, database.UserSecret{
5913+
UserID: user.ID,
5914+
})
5915+
check.Args(user.ID).
5916+
Asserts(rbac.ResourceUserSecret.WithOwner(user.ID.String()), policy.ActionRead).
5917+
Returns([]database.UserSecret{userSecret})
5918+
}))
5919+
s.Run("CreateUserSecret", s.Subtest(func(db database.Store, check *expects) {
5920+
user := dbgen.User(s.T(), db, database.User{})
5921+
arg := database.CreateUserSecretParams{
5922+
UserID: user.ID,
5923+
}
5924+
check.Args(arg).
5925+
Asserts(rbac.ResourceUserSecret.WithOwner(arg.UserID.String()), policy.ActionCreate)
5926+
}))
5927+
s.Run("UpdateUserSecret", s.Subtest(func(db database.Store, check *expects) {
5928+
user := dbgen.User(s.T(), db, database.User{})
5929+
userSecret := dbgen.UserSecret(s.T(), db, database.UserSecret{
5930+
UserID: user.ID,
5931+
})
5932+
arg := database.UpdateUserSecretParams{
5933+
ID: userSecret.ID,
5934+
}
5935+
check.Args(arg).
5936+
Asserts(userSecret, policy.ActionUpdate)
5937+
}))
5938+
s.Run("DeleteUserSecret", s.Subtest(func(db database.Store, check *expects) {
5939+
user := dbgen.User(s.T(), db, database.User{})
5940+
userSecret := dbgen.UserSecret(s.T(), db, database.UserSecret{
5941+
UserID: user.ID,
5942+
})
5943+
check.Args(userSecret.ID).
5944+
Asserts(userSecret, policy.ActionRead, userSecret, policy.ActionDelete)
5945+
}))
5946+
}

coderd/database/dbgen/dbgen.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1422,6 +1422,20 @@ func PresetParameter(t testing.TB, db database.Store, seed database.InsertPreset
14221422
return parameters
14231423
}
14241424

1425+
func UserSecret(t testing.TB, db database.Store, seed database.UserSecret) database.UserSecret {
1426+
userSecret, err := db.CreateUserSecret(genCtx, database.CreateUserSecretParams{
1427+
ID: takeFirst(seed.ID, uuid.New()),
1428+
UserID: takeFirst(seed.UserID, uuid.New()),
1429+
Name: takeFirst(seed.Name, "secret-name"),
1430+
Description: takeFirst(seed.Description, "secret description"),
1431+
Value: takeFirst(seed.Value, "secret value"),
1432+
EnvName: takeFirst(seed.EnvName, "SECRET_ENV_NAME"),
1433+
FilePath: takeFirst(seed.FilePath, "~/secret/file/path"),
1434+
})
1435+
require.NoError(t, err, "failed to insert user secret")
1436+
return userSecret
1437+
}
1438+
14251439
func ClaimPrebuild(t testing.TB, db database.Store, newUserID uuid.UUID, newName string, presetID uuid.UUID) database.ClaimPrebuiltWorkspaceRow {
14261440
claimedWorkspace, err := db.ClaimPrebuiltWorkspace(genCtx, database.ClaimPrebuiltWorkspaceParams{
14271441
NewUserID: newUserID,

coderd/database/dbmetrics/querymetrics.go

Lines changed: 42 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy