Skip to content

chore: add provisioner key crud apis #13857

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 20 commits into from
Jul 16, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
Next Next commit
move to enterprise
  • Loading branch information
f0ssel committed Jul 16, 2024
commit 76cce3063dc1186afc07f5075b592e0503640750
10 changes: 0 additions & 10 deletions coderd/coderd.go
Original file line number Diff line number Diff line change
Expand Up @@ -915,16 +915,6 @@ func New(options *Options) *API {
})
})
})
r.Route("/provisionerkeys", func(r chi.Router) {
r.Get("/", api.provisionerKeys)
r.Post("/", api.postProvisionerKey)
r.Route("/{provisionerkey}", func(r chi.Router) {
r.Use(
httpmw.ExtractProvisionerKeyParam(options.Database),
)
r.Delete("/", api.deleteProvisionerKey)
})
})
})
})
r.Route("/templates", func(r chi.Router) {
Expand Down
2 changes: 2 additions & 0 deletions codersdk/deployment.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ const (
FeatureAccessControl FeatureName = "access_control"
FeatureControlSharedPorts FeatureName = "control_shared_ports"
FeatureCustomRoles FeatureName = "custom_roles"
FeatureMultipleOrganizations FeatureName = "multiple_organizations"
)

// FeatureNames must be kept in-sync with the Feature enum above.
Expand All @@ -77,6 +78,7 @@ var FeatureNames = []FeatureName{
FeatureAccessControl,
FeatureControlSharedPorts,
FeatureCustomRoles,
FeatureMultipleOrganizations,
}

// Humanize returns the feature name in a human-readable format.
Expand Down
23 changes: 22 additions & 1 deletion enterprise/coderd/coderd.go
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ func New(ctx context.Context, options *Options) (_ *API, err error) {
})
r.Route("/workspaceproxies", func(r chi.Router) {
r.Use(
api.moonsEnabledMW,
api.RequireFeatureMW(codersdk.FeatureWorkspaceProxy),
)
r.Group(func(r chi.Router) {
r.Use(
Expand Down Expand Up @@ -254,6 +254,21 @@ func New(ctx context.Context, options *Options) (_ *API, err error) {
r.Get("/", api.groupByOrganization)
})
})
r.Route("/organizations/{organization}/provisionerkeys", func(r chi.Router) {
r.Use(
apiKeyMiddleware,
httpmw.ExtractOrganizationParam(api.Database),
api.RequireFeatureMW(codersdk.FeatureMultipleOrganizations),
)
r.Get("/", api.provisionerKeys)
r.Post("/", api.postProvisionerKey)
r.Route("/{provisionerkey}", func(r chi.Router) {
r.Use(
httpmw.ExtractProvisionerKeyParam(options.Database),
)
r.Delete("/", api.deleteProvisionerKey)
})
})
// TODO: provisioner daemons are not scoped to organizations in the database, so placing them
// under an organization route doesn't make sense. In order to allow the /serve endpoint to
// work with a pre-shared key (PSK) without an API key, these routes will simply ignore the
Expand Down Expand Up @@ -566,6 +581,7 @@ func (api *API) updateEntitlements(ctx context.Context) error {
codersdk.FeatureUserRoleManagement: true,
codersdk.FeatureAccessControl: true,
codersdk.FeatureControlSharedPorts: true,
codersdk.FeatureMultipleOrganizations: true,
})
if err != nil {
return err
Expand Down Expand Up @@ -751,6 +767,11 @@ func (api *API) updateEntitlements(ctx context.Context) error {
api.AGPL.CustomRoleHandler.Store(&handler)
}

if initial, changed, enabled := featureChanged(codersdk.FeatureMultipleOrganizations); shouldUpdate(initial, changed, enabled) {
var handler coderd.CustomRoleHandler = &enterpriseCustomRoleHandler{API: api, Enabled: enabled}
api.AGPL.CustomRoleHandler.Store(&handler)
}

// External token encryption is soft-enforced
featureExternalTokenEncryption := entitlements.Features[codersdk.FeatureExternalTokenEncryption]
featureExternalTokenEncryption.Enabled = len(api.ExternalTokenEncryption) > 0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import (
// @Tags Enterprise
// @Param organization path string true "Organization ID"
// @Success 201 {object} codersdk.CreateProvisionerKeyResponse
// @Router /organizations/{organization}/provisionerkey [post]
// @Router /organizations/{organization}/provisionerkeys [post]
func (api *API) postProvisionerKey(rw http.ResponseWriter, r *http.Request) {
ctx := r.Context()
organization := httpmw.OrganizationParam(r)
Expand Down Expand Up @@ -86,7 +86,7 @@ func (api *API) postProvisionerKey(rw http.ResponseWriter, r *http.Request) {
// @Tags Enterprise
// @Param organization path string true "Organization ID"
// @Success 200 {object} []codersdk.ProvisionerKey
// @Router /organizations/{organization}/provisionerkey [get]
// @Router /organizations/{organization}/provisionerkeys [get]
func (api *API) provisionerKeys(rw http.ResponseWriter, r *http.Request) {
ctx := r.Context()
organization := httpmw.OrganizationParam(r)
Expand All @@ -108,7 +108,7 @@ func (api *API) provisionerKeys(rw http.ResponseWriter, r *http.Request) {
// @Param organization path string true "Organization ID"
// @Param provisionerkey path string true "Provisioner key name"
// @Success 204
// @Router /organizations/{organization}/provisionerkey/{provisionerkey} [delete]
// @Router /organizations/{organization}/provisionerkeys/{provisionerkey} [delete]
func (api *API) deleteProvisionerKey(rw http.ResponseWriter, r *http.Request) {
ctx := r.Context()
organization := httpmw.OrganizationParam(r)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import (
"github.com/coder/coder/v2/coderd/coderdtest"
"github.com/coder/coder/v2/coderd/rbac"
"github.com/coder/coder/v2/codersdk"
"github.com/coder/coder/v2/enterprise/coderd/coderdenttest"
"github.com/coder/coder/v2/enterprise/coderd/license"
"github.com/coder/coder/v2/testutil"
)

Expand All @@ -17,18 +19,18 @@ func TestProvisionerKeys(t *testing.T) {

ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong*10)
t.Cleanup(cancel)
client := coderdtest.New(t, nil)
owner := coderdtest.CreateFirstUser(t, client)
client, owner := coderdenttest.New(t, &coderdenttest.Options{LicenseOptions: &coderdenttest.LicenseOptions{
Features: license.Features{
codersdk.FeatureMultipleOrganizations: 1,
},
}})
orgAdmin, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID, rbac.ScopedRoleOrgAdmin(owner.OrganizationID))
member, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID)
otherOrg, err := client.CreateOrganization(ctx, codersdk.CreateOrganizationRequest{
Name: "other",
})
require.NoError(t, err, "create org")
otherOrg := coderdtest.CreateOrganization(t, client, coderdtest.CreateOrganizationOptions{})
outsideOrgAdmin, _ := coderdtest.CreateAnotherUser(t, client, otherOrg.ID, rbac.ScopedRoleOrgAdmin(otherOrg.ID))

// member cannot create a provisioner key
_, err = member.CreateProvisionerKey(ctx, otherOrg.ID, codersdk.CreateProvisionerKeyRequest{
_, err := member.CreateProvisionerKey(ctx, otherOrg.ID, codersdk.CreateProvisionerKeyRequest{
Name: "key",
})
require.ErrorContains(t, err, "Resource not found")
Expand Down
32 changes: 17 additions & 15 deletions enterprise/coderd/templates.go
Original file line number Diff line number Diff line change
Expand Up @@ -327,7 +327,7 @@ func convertSDKTemplateRole(role codersdk.TemplateRole) []policy.Action {
return nil
}

// TODO reduce the duplication across all of these.
// TODO move to api.RequireFeatureMW when we are OK with changing the behavior.
func (api *API) templateRBACEnabledMW(next http.Handler) http.Handler {
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
api.entitlementsMu.RLock()
Expand All @@ -343,19 +343,21 @@ func (api *API) templateRBACEnabledMW(next http.Handler) http.Handler {
})
}

func (api *API) moonsEnabledMW(next http.Handler) http.Handler {
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
// Entitlement must be enabled.
api.entitlementsMu.RLock()
proxy := api.entitlements.Features[codersdk.FeatureWorkspaceProxy].Enabled
api.entitlementsMu.RUnlock()
if !proxy {
httpapi.Write(r.Context(), rw, http.StatusForbidden, codersdk.Response{
Message: "External workspace proxies is an Enterprise feature. Contact sales!",
})
return
}
func (api *API) RequireFeatureMW(feat codersdk.FeatureName) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
// Entitlement must be enabled.
api.entitlementsMu.RLock()
enabled := api.entitlements.Features[feat].Enabled
api.entitlementsMu.RUnlock()
if !enabled {
httpapi.Write(r.Context(), rw, http.StatusForbidden, codersdk.Response{
Message: fmt.Sprintf("%s is an Enterprise feature. Contact sales!", feat.Humanize()),
})
return
}

next.ServeHTTP(rw, r)
})
next.ServeHTTP(rw, r)
})
}
}