Skip to content

feat: add disabling of default 'everyone' group access to template #7982

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 16 commits into from
Jun 14, 2023
Merged
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
18 changes: 13 additions & 5 deletions cli/templatecreate.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ func (r *RootCmd) templateCreate() *clibase.Cmd {
provisionerTags []string
variablesFile string
variables []string
disableEveryone bool
defaultTTL time.Duration
failureTTL time.Duration
inactivityTTL time.Duration
Expand Down Expand Up @@ -121,11 +122,12 @@ func (r *RootCmd) templateCreate() *clibase.Cmd {
}

createReq := codersdk.CreateTemplateRequest{
Name: templateName,
VersionID: job.ID,
DefaultTTLMillis: ptr.Ref(defaultTTL.Milliseconds()),
FailureTTLMillis: ptr.Ref(failureTTL.Milliseconds()),
InactivityTTLMillis: ptr.Ref(inactivityTTL.Milliseconds()),
Name: templateName,
VersionID: job.ID,
DefaultTTLMillis: ptr.Ref(defaultTTL.Milliseconds()),
FailureTTLMillis: ptr.Ref(failureTTL.Milliseconds()),
InactivityTTLMillis: ptr.Ref(inactivityTTL.Milliseconds()),
DisableEveryoneGroupAccess: disableEveryone,
}

_, err = client.CreateTemplate(inv.Context(), organization.ID, createReq)
Expand All @@ -144,6 +146,12 @@ func (r *RootCmd) templateCreate() *clibase.Cmd {
},
}
cmd.Options = clibase.OptionSet{
{
Flag: "private",
Description: "Disable the default behavior of granting template access to the 'everyone' group. " +
"The template permissions must be updated to allow non-admin users to use this template.",
Value: clibase.BoolOf(&disableEveryone),
},
{
Flag: "variables-file",
Description: "Specify a file path with values for Terraform-managed variables.",
Expand Down
5 changes: 5 additions & 0 deletions cli/testdata/coder_templates_create_--help.golden
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ Create a template from the current directory or as specified by flag
Specify an inactivity TTL for workspaces created from this template.
This licensed feature's default is 0h (off).

--private bool
Disable the default behavior of granting template access to the
'everyone' group. The template permissions must be updated to allow
non-admin users to use this template.

--provisioner-tag string-array
Specify a set of tags to target provisioner daemons.

Expand Down
4 changes: 4 additions & 0 deletions coderd/apidoc/docs.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions coderd/apidoc/swagger.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions coderd/audit/audit.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,14 @@ type MockAuditor struct {
auditLogs []database.AuditLog
}

// ResetLogs removes all audit logs from the mock auditor.
// This is helpful for testing to get a clean slate.
func (a *MockAuditor) ResetLogs() {
a.mutex.Lock()
defer a.mutex.Unlock()
a.auditLogs = make([]database.AuditLog, 0)
}

func (a *MockAuditor) AuditLogs() []database.AuditLog {
a.mutex.Lock()
defer a.mutex.Unlock()
Expand Down
30 changes: 17 additions & 13 deletions coderd/templates.go
Original file line number Diff line number Diff line change
Expand Up @@ -274,22 +274,26 @@ func (api *API) postTemplateByOrganization(rw http.ResponseWriter, r *http.Reque
allowUserAutostop = ptr.NilToDefault(createTemplate.AllowUserAutostop, true)
)

defaultsGroups := database.TemplateACL{}
if !createTemplate.DisableEveryoneGroupAccess {
// The organization ID is used as the group ID for the everyone group
// in this organization.
defaultsGroups[organization.ID.String()] = []rbac.Action{rbac.ActionRead}
}
err = api.Database.InTx(func(tx database.Store) error {
now := database.Now()
dbTemplate, err = tx.InsertTemplate(ctx, database.InsertTemplateParams{
ID: uuid.New(),
CreatedAt: now,
UpdatedAt: now,
OrganizationID: organization.ID,
Name: createTemplate.Name,
Provisioner: importJob.Provisioner,
ActiveVersionID: templateVersion.ID,
Description: createTemplate.Description,
CreatedBy: apiKey.UserID,
UserACL: database.TemplateACL{},
GroupACL: database.TemplateACL{
organization.ID.String(): []rbac.Action{rbac.ActionRead},
},
ID: uuid.New(),
CreatedAt: now,
UpdatedAt: now,
OrganizationID: organization.ID,
Name: createTemplate.Name,
Provisioner: importJob.Provisioner,
ActiveVersionID: templateVersion.ID,
Description: createTemplate.Description,
CreatedBy: apiKey.UserID,
UserACL: database.TemplateACL{},
GroupACL: defaultsGroups,
DisplayName: createTemplate.DisplayName,
Icon: createTemplate.Icon,
AllowUserCancelWorkspaceJobs: allowUserCancelWorkspaceJobs,
Expand Down
42 changes: 33 additions & 9 deletions coderd/templates_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,25 +48,28 @@ func TestPostTemplateByOrganization(t *testing.T) {
t.Parallel()
auditor := audit.NewMock()
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true, Auditor: auditor})
user := coderdtest.CreateFirstUser(t, client)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
owner := coderdtest.CreateFirstUser(t, client)
// By default, everyone in the org can read the template.
user, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID)
auditor.ResetLogs()

version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, nil)

expected := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
expected := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID)

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

got, err := client.Template(ctx, expected.ID)
got, err := user.Template(ctx, expected.ID)
require.NoError(t, err)

assert.Equal(t, expected.Name, got.Name)
assert.Equal(t, expected.Description, got.Description)

require.Len(t, auditor.AuditLogs(), 4)
assert.Equal(t, database.AuditActionLogin, auditor.AuditLogs()[0].Action)
assert.Equal(t, database.AuditActionCreate, auditor.AuditLogs()[1].Action)
assert.Equal(t, database.AuditActionWrite, auditor.AuditLogs()[2].Action)
assert.Equal(t, database.AuditActionCreate, auditor.AuditLogs()[3].Action)
require.Len(t, auditor.AuditLogs(), 3)
assert.Equal(t, database.AuditActionCreate, auditor.AuditLogs()[0].Action)
assert.Equal(t, database.AuditActionWrite, auditor.AuditLogs()[1].Action)
assert.Equal(t, database.AuditActionCreate, auditor.AuditLogs()[2].Action)
})

t.Run("AlreadyExists", func(t *testing.T) {
Expand Down Expand Up @@ -126,6 +129,27 @@ func TestPostTemplateByOrganization(t *testing.T) {
require.Zero(t, got.DefaultTTLMillis)
})

t.Run("DisableEveryone", func(t *testing.T) {
t.Parallel()
auditor := audit.NewMock()
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true, Auditor: auditor})
owner := coderdtest.CreateFirstUser(t, client)
user, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID)
version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, nil)

expected := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID, func(request *codersdk.CreateTemplateRequest) {
request.DisableEveryoneGroupAccess = true
})

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

_, err := user.Template(ctx, expected.ID)
var apiErr *codersdk.Error
require.ErrorAs(t, err, &apiErr)
require.Equal(t, http.StatusNotFound, apiErr.StatusCode())
})

t.Run("Unauthorized", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, nil)
Expand Down
7 changes: 7 additions & 0 deletions codersdk/organizations.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,13 @@ type CreateTemplateRequest struct {
// InactivityTTLMillis allows optionally specifying the max lifetime before Coder
// deletes inactive workspaces created from this template.
InactivityTTLMillis *int64 `json:"inactivity_ttl_ms,omitempty"`

// DisableEveryoneGroupAccess allows optionally disabling the default
// behavior of granting the 'everyone' group access to use the template.
// If this is set to true, the template will not be available to all users,
// and must be explicitly granted to users or groups in the permissions settings
// of the template.
DisableEveryoneGroupAccess bool `json:"disable_everyone_group_access"`
}

// CreateWorkspaceRequest provides options for creating a new workspace.
Expand Down
Loading