Skip to content

Commit 3619a3a

Browse files
authored
feat: add disabling of default 'everyone' group access to template (#7982)
* feat: add disabling of default 'everyone' group access to template * add FE to disable everyone group * require entitlement to uncheck box
1 parent e4b6f56 commit 3619a3a

16 files changed

+208
-74
lines changed

cli/templatecreate.go

+13-5
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ func (r *RootCmd) templateCreate() *clibase.Cmd {
2828
provisionerTags []string
2929
variablesFile string
3030
variables []string
31+
disableEveryone bool
3132
defaultTTL time.Duration
3233
failureTTL time.Duration
3334
inactivityTTL time.Duration
@@ -121,11 +122,12 @@ func (r *RootCmd) templateCreate() *clibase.Cmd {
121122
}
122123

123124
createReq := codersdk.CreateTemplateRequest{
124-
Name: templateName,
125-
VersionID: job.ID,
126-
DefaultTTLMillis: ptr.Ref(defaultTTL.Milliseconds()),
127-
FailureTTLMillis: ptr.Ref(failureTTL.Milliseconds()),
128-
InactivityTTLMillis: ptr.Ref(inactivityTTL.Milliseconds()),
125+
Name: templateName,
126+
VersionID: job.ID,
127+
DefaultTTLMillis: ptr.Ref(defaultTTL.Milliseconds()),
128+
FailureTTLMillis: ptr.Ref(failureTTL.Milliseconds()),
129+
InactivityTTLMillis: ptr.Ref(inactivityTTL.Milliseconds()),
130+
DisableEveryoneGroupAccess: disableEveryone,
129131
}
130132

131133
_, err = client.CreateTemplate(inv.Context(), organization.ID, createReq)
@@ -144,6 +146,12 @@ func (r *RootCmd) templateCreate() *clibase.Cmd {
144146
},
145147
}
146148
cmd.Options = clibase.OptionSet{
149+
{
150+
Flag: "private",
151+
Description: "Disable the default behavior of granting template access to the 'everyone' group. " +
152+
"The template permissions must be updated to allow non-admin users to use this template.",
153+
Value: clibase.BoolOf(&disableEveryone),
154+
},
147155
{
148156
Flag: "variables-file",
149157
Description: "Specify a file path with values for Terraform-managed variables.",

cli/testdata/coder_templates_create_--help.golden

+5
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@ Create a template from the current directory or as specified by flag
1717
Specify an inactivity TTL for workspaces created from this template.
1818
This licensed feature's default is 0h (off).
1919

20+
--private bool
21+
Disable the default behavior of granting template access to the
22+
'everyone' group. The template permissions must be updated to allow
23+
non-admin users to use this template.
24+
2025
--provisioner-tag string-array
2126
Specify a set of tags to target provisioner daemons.
2227

coderd/apidoc/docs.go

+4
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/apidoc/swagger.json

+4
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/audit/audit.go

+8
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,14 @@ type MockAuditor struct {
4242
auditLogs []database.AuditLog
4343
}
4444

45+
// ResetLogs removes all audit logs from the mock auditor.
46+
// This is helpful for testing to get a clean slate.
47+
func (a *MockAuditor) ResetLogs() {
48+
a.mutex.Lock()
49+
defer a.mutex.Unlock()
50+
a.auditLogs = make([]database.AuditLog, 0)
51+
}
52+
4553
func (a *MockAuditor) AuditLogs() []database.AuditLog {
4654
a.mutex.Lock()
4755
defer a.mutex.Unlock()

coderd/templates.go

+17-13
Original file line numberDiff line numberDiff line change
@@ -274,22 +274,26 @@ func (api *API) postTemplateByOrganization(rw http.ResponseWriter, r *http.Reque
274274
allowUserAutostop = ptr.NilToDefault(createTemplate.AllowUserAutostop, true)
275275
)
276276

277+
defaultsGroups := database.TemplateACL{}
278+
if !createTemplate.DisableEveryoneGroupAccess {
279+
// The organization ID is used as the group ID for the everyone group
280+
// in this organization.
281+
defaultsGroups[organization.ID.String()] = []rbac.Action{rbac.ActionRead}
282+
}
277283
err = api.Database.InTx(func(tx database.Store) error {
278284
now := database.Now()
279285
dbTemplate, err = tx.InsertTemplate(ctx, database.InsertTemplateParams{
280-
ID: uuid.New(),
281-
CreatedAt: now,
282-
UpdatedAt: now,
283-
OrganizationID: organization.ID,
284-
Name: createTemplate.Name,
285-
Provisioner: importJob.Provisioner,
286-
ActiveVersionID: templateVersion.ID,
287-
Description: createTemplate.Description,
288-
CreatedBy: apiKey.UserID,
289-
UserACL: database.TemplateACL{},
290-
GroupACL: database.TemplateACL{
291-
organization.ID.String(): []rbac.Action{rbac.ActionRead},
292-
},
286+
ID: uuid.New(),
287+
CreatedAt: now,
288+
UpdatedAt: now,
289+
OrganizationID: organization.ID,
290+
Name: createTemplate.Name,
291+
Provisioner: importJob.Provisioner,
292+
ActiveVersionID: templateVersion.ID,
293+
Description: createTemplate.Description,
294+
CreatedBy: apiKey.UserID,
295+
UserACL: database.TemplateACL{},
296+
GroupACL: defaultsGroups,
293297
DisplayName: createTemplate.DisplayName,
294298
Icon: createTemplate.Icon,
295299
AllowUserCancelWorkspaceJobs: allowUserCancelWorkspaceJobs,

coderd/templates_test.go

+33-9
Original file line numberDiff line numberDiff line change
@@ -48,25 +48,28 @@ func TestPostTemplateByOrganization(t *testing.T) {
4848
t.Parallel()
4949
auditor := audit.NewMock()
5050
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true, Auditor: auditor})
51-
user := coderdtest.CreateFirstUser(t, client)
52-
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
51+
owner := coderdtest.CreateFirstUser(t, client)
52+
// By default, everyone in the org can read the template.
53+
user, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID)
54+
auditor.ResetLogs()
55+
56+
version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, nil)
5357

54-
expected := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
58+
expected := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID)
5559

5660
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
5761
defer cancel()
5862

59-
got, err := client.Template(ctx, expected.ID)
63+
got, err := user.Template(ctx, expected.ID)
6064
require.NoError(t, err)
6165

6266
assert.Equal(t, expected.Name, got.Name)
6367
assert.Equal(t, expected.Description, got.Description)
6468

65-
require.Len(t, auditor.AuditLogs(), 4)
66-
assert.Equal(t, database.AuditActionLogin, auditor.AuditLogs()[0].Action)
67-
assert.Equal(t, database.AuditActionCreate, auditor.AuditLogs()[1].Action)
68-
assert.Equal(t, database.AuditActionWrite, auditor.AuditLogs()[2].Action)
69-
assert.Equal(t, database.AuditActionCreate, auditor.AuditLogs()[3].Action)
69+
require.Len(t, auditor.AuditLogs(), 3)
70+
assert.Equal(t, database.AuditActionCreate, auditor.AuditLogs()[0].Action)
71+
assert.Equal(t, database.AuditActionWrite, auditor.AuditLogs()[1].Action)
72+
assert.Equal(t, database.AuditActionCreate, auditor.AuditLogs()[2].Action)
7073
})
7174

7275
t.Run("AlreadyExists", func(t *testing.T) {
@@ -126,6 +129,27 @@ func TestPostTemplateByOrganization(t *testing.T) {
126129
require.Zero(t, got.DefaultTTLMillis)
127130
})
128131

132+
t.Run("DisableEveryone", func(t *testing.T) {
133+
t.Parallel()
134+
auditor := audit.NewMock()
135+
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true, Auditor: auditor})
136+
owner := coderdtest.CreateFirstUser(t, client)
137+
user, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID)
138+
version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, nil)
139+
140+
expected := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID, func(request *codersdk.CreateTemplateRequest) {
141+
request.DisableEveryoneGroupAccess = true
142+
})
143+
144+
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
145+
defer cancel()
146+
147+
_, err := user.Template(ctx, expected.ID)
148+
var apiErr *codersdk.Error
149+
require.ErrorAs(t, err, &apiErr)
150+
require.Equal(t, http.StatusNotFound, apiErr.StatusCode())
151+
})
152+
129153
t.Run("Unauthorized", func(t *testing.T) {
130154
t.Parallel()
131155
client := coderdtest.New(t, nil)

codersdk/organizations.go

+7
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,13 @@ type CreateTemplateRequest struct {
108108
// InactivityTTLMillis allows optionally specifying the max lifetime before Coder
109109
// deletes inactive workspaces created from this template.
110110
InactivityTTLMillis *int64 `json:"inactivity_ttl_ms,omitempty"`
111+
112+
// DisableEveryoneGroupAccess allows optionally disabling the default
113+
// behavior of granting the 'everyone' group access to use the template.
114+
// If this is set to true, the template will not be available to all users,
115+
// and must be explicitly granted to users or groups in the permissions settings
116+
// of the template.
117+
DisableEveryoneGroupAccess bool `json:"disable_everyone_group_access"`
111118
}
112119

113120
// CreateWorkspaceRequest provides options for creating a new workspace.

0 commit comments

Comments
 (0)