Skip to content

Commit 0d1096d

Browse files
authored
feat: add auditing for groups (coder#4527)
- Clean up `database.TemplateACL` implementation.
1 parent d4585fe commit 0d1096d

27 files changed

+405
-213
lines changed

coderd/audit/diff.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ type Auditable interface {
1515
database.TemplateVersion |
1616
database.User |
1717
database.Workspace |
18-
database.GitSSHKey
18+
database.GitSSHKey |
19+
database.Group
1920
}
2021

2122
// Map is a map of changed fields in an audited resource. It maps field names to

coderd/audit/request.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ func ResourceTarget[T Auditable](tgt T) string {
4545
return typed.Name
4646
case database.GitSSHKey:
4747
return typed.PublicKey
48+
case database.Group:
49+
return typed.Name
4850
default:
4951
panic(fmt.Sprintf("unknown resource %T", tgt))
5052
}
@@ -64,6 +66,8 @@ func ResourceID[T Auditable](tgt T) uuid.UUID {
6466
return typed.ID
6567
case database.GitSSHKey:
6668
return typed.UserID
69+
case database.Group:
70+
return typed.ID
6771
default:
6872
panic(fmt.Sprintf("unknown resource %T", tgt))
6973
}
@@ -83,6 +87,8 @@ func ResourceType[T Auditable](tgt T) database.ResourceType {
8387
return database.ResourceTypeWorkspace
8488
case database.GitSSHKey:
8589
return database.ResourceTypeGitSshKey
90+
case database.Group:
91+
return database.ResourceTypeGroup
8692
default:
8793
panic(fmt.Sprintf("unknown resource %T", tgt))
8894
}

coderd/database/databasefake/databasefake.go

Lines changed: 23 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1457,34 +1457,6 @@ func (q *fakeQuerier) GetTemplates(_ context.Context) ([]database.Template, erro
14571457
return templates, nil
14581458
}
14591459

1460-
func (q *fakeQuerier) UpdateTemplateUserACLByID(_ context.Context, id uuid.UUID, acl database.TemplateACL) error {
1461-
q.mutex.RLock()
1462-
defer q.mutex.RUnlock()
1463-
1464-
for i, t := range q.templates {
1465-
if t.ID == id {
1466-
t = t.SetUserACL(acl)
1467-
q.templates[i] = t
1468-
return nil
1469-
}
1470-
}
1471-
return sql.ErrNoRows
1472-
}
1473-
1474-
func (q *fakeQuerier) UpdateTemplateGroupACLByID(_ context.Context, id uuid.UUID, acl database.TemplateACL) error {
1475-
q.mutex.RLock()
1476-
defer q.mutex.RUnlock()
1477-
1478-
for i, t := range q.templates {
1479-
if t.ID == id {
1480-
t = t.SetGroupACL(acl)
1481-
q.templates[i] = t
1482-
return nil
1483-
}
1484-
}
1485-
return sql.ErrNoRows
1486-
}
1487-
14881460
func (q *fakeQuerier) GetTemplateUserRoles(_ context.Context, id uuid.UUID) ([]database.TemplateUser, error) {
14891461
q.mutex.RLock()
14901462
defer q.mutex.RUnlock()
@@ -1501,10 +1473,8 @@ func (q *fakeQuerier) GetTemplateUserRoles(_ context.Context, id uuid.UUID) ([]d
15011473
return nil, sql.ErrNoRows
15021474
}
15031475

1504-
acl := template.UserACL()
1505-
1506-
users := make([]database.TemplateUser, 0, len(acl))
1507-
for k, v := range acl {
1476+
users := make([]database.TemplateUser, 0, len(template.UserACL))
1477+
for k, v := range template.UserACL {
15081478
user, err := q.GetUserByID(context.Background(), uuid.MustParse(k))
15091479
if err != nil && xerrors.Is(err, sql.ErrNoRows) {
15101480
return nil, xerrors.Errorf("get user by ID: %w", err)
@@ -1544,10 +1514,8 @@ func (q *fakeQuerier) GetTemplateGroupRoles(_ context.Context, id uuid.UUID) ([]
15441514
return nil, sql.ErrNoRows
15451515
}
15461516

1547-
acl := template.GroupACL()
1548-
1549-
groups := make([]database.TemplateGroup, 0, len(acl))
1550-
for k, v := range acl {
1517+
groups := make([]database.TemplateGroup, 0, len(template.GroupACL))
1518+
for k, v := range template.GroupACL {
15511519
group, err := q.GetGroupByID(context.Background(), uuid.MustParse(k))
15521520
if err != nil && !xerrors.Is(err, sql.ErrNoRows) {
15531521
return nil, xerrors.Errorf("get group by ID: %w", err)
@@ -2047,11 +2015,9 @@ func (q *fakeQuerier) InsertTemplate(_ context.Context, arg database.InsertTempl
20472015
MaxTtl: arg.MaxTtl,
20482016
MinAutostartInterval: arg.MinAutostartInterval,
20492017
CreatedBy: arg.CreatedBy,
2018+
UserACL: arg.UserACL,
2019+
GroupACL: arg.GroupACL,
20502020
}
2051-
template = template.SetUserACL(database.TemplateACL{})
2052-
template = template.SetGroupACL(database.TemplateACL{
2053-
arg.OrganizationID.String(): []rbac.Action{rbac.ActionRead},
2054-
})
20552021
q.templates = append(q.templates, template)
20562022
return template, nil
20572023
}
@@ -2470,6 +2436,23 @@ func (q *fakeQuerier) UpdateTemplateDeletedByID(_ context.Context, arg database.
24702436
return sql.ErrNoRows
24712437
}
24722438

2439+
func (q *fakeQuerier) UpdateTemplateACLByID(_ context.Context, arg database.UpdateTemplateACLByIDParams) (database.Template, error) {
2440+
q.mutex.Lock()
2441+
defer q.mutex.Unlock()
2442+
2443+
for i, template := range q.templates {
2444+
if template.ID == arg.ID {
2445+
template.GroupACL = arg.GroupACL
2446+
template.UserACL = arg.UserACL
2447+
2448+
q.templates[i] = template
2449+
return template, nil
2450+
}
2451+
}
2452+
2453+
return database.Template{}, sql.ErrNoRows
2454+
}
2455+
24732456
func (q *fakeQuerier) UpdateTemplateVersionByID(_ context.Context, arg database.UpdateTemplateVersionByIDParams) error {
24742457
q.mutex.Lock()
24752458
defer q.mutex.Unlock()

coderd/database/drivers.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,22 @@ func (a *Actions) Scan(src interface{}) error {
2424
func (a *Actions) Value() (driver.Value, error) {
2525
return json.Marshal(a)
2626
}
27+
28+
// TemplateACL is a map of ids to permissions.
29+
type TemplateACL map[string][]rbac.Action
30+
31+
func (t *TemplateACL) Scan(src interface{}) error {
32+
switch v := src.(type) {
33+
case string:
34+
return json.Unmarshal([]byte(v), &t)
35+
case []byte, json.RawMessage:
36+
//nolint
37+
return json.Unmarshal(v.([]byte), &t)
38+
}
39+
40+
return xerrors.Errorf("unexpected type %T", src)
41+
}
42+
43+
func (t TemplateACL) Value() (driver.Value, error) {
44+
return json.Marshal(t)
45+
}

coderd/database/dump.sql

Lines changed: 2 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
-- You cannot safely remove values from enums https://www.postgresql.org/docs/current/datatype-enum.html
2+
-- You cannot create a new type and do a rename because objects depend on this type now.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
BEGIN;
2+
3+
ALTER TYPE resource_type ADD VALUE IF NOT EXISTS 'group';
4+
5+
COMMIT;

coderd/database/modelmethods.go

Lines changed: 2 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,65 +1,11 @@
11
package database
22

33
import (
4-
"encoding/json"
5-
"fmt"
6-
74
"github.com/coder/coder/coderd/rbac"
85
)
96

107
const AllUsersGroup = "Everyone"
118

12-
// TemplateACL is a map of user_ids to permissions.
13-
type TemplateACL map[string][]rbac.Action
14-
15-
func (t Template) UserACL() TemplateACL {
16-
var acl TemplateACL
17-
if len(t.userACL) == 0 {
18-
return acl
19-
}
20-
21-
err := json.Unmarshal(t.userACL, &acl)
22-
if err != nil {
23-
panic(fmt.Sprintf("failed to unmarshal template.userACL: %v", err.Error()))
24-
}
25-
26-
return acl
27-
}
28-
29-
func (t Template) GroupACL() TemplateACL {
30-
var acl TemplateACL
31-
if len(t.groupACL) == 0 {
32-
return acl
33-
}
34-
35-
err := json.Unmarshal(t.groupACL, &acl)
36-
if err != nil {
37-
panic(fmt.Sprintf("failed to unmarshal template.userACL: %v", err.Error()))
38-
}
39-
40-
return acl
41-
}
42-
43-
func (t Template) SetGroupACL(acl TemplateACL) Template {
44-
raw, err := json.Marshal(acl)
45-
if err != nil {
46-
panic(fmt.Sprintf("marshal user acl: %v", err))
47-
}
48-
49-
t.groupACL = raw
50-
return t
51-
}
52-
53-
func (t Template) SetUserACL(acl TemplateACL) Template {
54-
raw, err := json.Marshal(acl)
55-
if err != nil {
56-
panic(fmt.Sprintf("marshal user acl: %v", err))
57-
}
58-
59-
t.userACL = raw
60-
return t
61-
}
62-
639
func (s APIKeyScope) ToRBAC() rbac.Scope {
6410
switch s {
6511
case APIKeyScopeAll:
@@ -74,8 +20,8 @@ func (s APIKeyScope) ToRBAC() rbac.Scope {
7420
func (t Template) RBACObject() rbac.Object {
7521
obj := rbac.ResourceTemplate
7622
return obj.InOrg(t.OrganizationID).
77-
WithACLUserList(t.UserACL()).
78-
WithGroupACL(t.GroupACL())
23+
WithACLUserList(t.UserACL).
24+
WithGroupACL(t.GroupACL)
7925
}
8026

8127
func (TemplateVersion) RBACObject(template Template) rbac.Object {

coderd/database/modelqueries.go

Lines changed: 0 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package database
22

33
import (
44
"context"
5-
"encoding/json"
65
"fmt"
76
"strings"
87

@@ -23,8 +22,6 @@ type customQuerier interface {
2322
}
2423

2524
type templateQuerier interface {
26-
UpdateTemplateUserACLByID(ctx context.Context, id uuid.UUID, acl TemplateACL) error
27-
UpdateTemplateGroupACLByID(ctx context.Context, id uuid.UUID, acl TemplateACL) error
2825
GetTemplateGroupRoles(ctx context.Context, id uuid.UUID) ([]TemplateGroup, error)
2926
GetTemplateUserRoles(ctx context.Context, id uuid.UUID) ([]TemplateUser, error)
3027
}
@@ -34,28 +31,6 @@ type TemplateUser struct {
3431
Actions Actions `db:"actions"`
3532
}
3633

37-
func (q *sqlQuerier) UpdateTemplateUserACLByID(ctx context.Context, id uuid.UUID, acl TemplateACL) error {
38-
raw, err := json.Marshal(acl)
39-
if err != nil {
40-
return xerrors.Errorf("marshal user acl: %w", err)
41-
}
42-
43-
const query = `
44-
UPDATE
45-
templates
46-
SET
47-
user_acl = $2
48-
WHERE
49-
id = $1`
50-
51-
_, err = q.db.ExecContext(ctx, query, id.String(), raw)
52-
if err != nil {
53-
return xerrors.Errorf("update user acl: %w", err)
54-
}
55-
56-
return nil
57-
}
58-
5934
func (q *sqlQuerier) GetTemplateUserRoles(ctx context.Context, id uuid.UUID) ([]TemplateUser, error) {
6035
const query = `
6136
SELECT
@@ -100,28 +75,6 @@ type TemplateGroup struct {
10075
Actions Actions `db:"actions"`
10176
}
10277

103-
func (q *sqlQuerier) UpdateTemplateGroupACLByID(ctx context.Context, id uuid.UUID, acl TemplateACL) error {
104-
raw, err := json.Marshal(acl)
105-
if err != nil {
106-
return xerrors.Errorf("marshal user acl: %w", err)
107-
}
108-
109-
const query = `
110-
UPDATE
111-
templates
112-
SET
113-
group_acl = $2
114-
WHERE
115-
id = $1`
116-
117-
_, err = q.db.ExecContext(ctx, query, id.String(), raw)
118-
if err != nil {
119-
return xerrors.Errorf("update user acl: %w", err)
120-
}
121-
122-
return nil
123-
}
124-
12578
func (q *sqlQuerier) GetTemplateGroupRoles(ctx context.Context, id uuid.UUID) ([]TemplateGroup, error) {
12679
const query = `
12780
SELECT

coderd/database/models.go

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

coderd/database/querier.go

Lines changed: 1 addition & 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)