Skip to content

Commit e098250

Browse files
committed
feat: no longer require org flag for provisioners
1 parent 37885e2 commit e098250

File tree

2 files changed

+54
-93
lines changed

2 files changed

+54
-93
lines changed

enterprise/cli/provisionerdaemonstart.go

Lines changed: 19 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,7 @@ package cli
44

55
import (
66
"context"
7-
"errors"
87
"fmt"
9-
"net/http"
108
"os"
119
"regexp"
1210
"time"
@@ -24,7 +22,6 @@ import (
2422
"github.com/coder/coder/v2/cli/cliui"
2523
"github.com/coder/coder/v2/cli/cliutil"
2624
"github.com/coder/coder/v2/coderd/database"
27-
"github.com/coder/coder/v2/coderd/provisionerkey"
2825
"github.com/coder/coder/v2/codersdk"
2926
"github.com/coder/coder/v2/codersdk/drpc"
3027
"github.com/coder/coder/v2/provisioner/terraform"
@@ -73,32 +70,17 @@ func (r *RootCmd) provisionerDaemonStart() *serpent.Command {
7370
interruptCtx, interruptCancel := inv.SignalNotifyContext(ctx, agpl.InterruptSignals...)
7471
defer interruptCancel()
7572

76-
// This can fail to get the current organization
77-
// if the client is not authenticated as a user,
78-
// like when only PSK is provided.
79-
// This will be cleaner once PSK is replaced
80-
// with org scoped authentication tokens.
81-
org, err := orgContext.Selected(inv, client)
82-
if err != nil {
83-
var cErr *codersdk.Error
84-
if !errors.As(err, &cErr) || cErr.StatusCode() != http.StatusUnauthorized {
85-
return xerrors.Errorf("current organization: %w", err)
86-
}
87-
88-
if preSharedKey == "" && provisionerKey == "" {
89-
return xerrors.New("must provide a pre-shared key or provisioner key when not authenticated as a user")
73+
orgID := uuid.Nil
74+
if preSharedKey == "" && provisionerKey == "" {
75+
// We can only select an organization if using user auth
76+
org, err := orgContext.Selected(inv, client)
77+
if err != nil {
78+
return xerrors.Errorf("get organization: %w", err)
9079
}
91-
92-
org = codersdk.Organization{MinimalOrganization: codersdk.MinimalOrganization{ID: uuid.Nil}}
80+
orgID = org.ID
81+
} else {
9382
if orgContext.FlagSelect != "" {
94-
// If we are using PSK, we can't fetch the organization
95-
// to validate org name so we need the user to provide
96-
// a valid organization ID.
97-
orgID, err := uuid.Parse(orgContext.FlagSelect)
98-
if err != nil {
99-
return xerrors.New("must provide an org ID when not authenticated as a user and organization is specified")
100-
}
101-
org = codersdk.Organization{MinimalOrganization: codersdk.MinimalOrganization{ID: orgID}}
83+
return xerrors.New("cannot provide --org value with --psk or --key flags")
10284
}
10385
}
10486

@@ -115,19 +97,6 @@ func (r *RootCmd) provisionerDaemonStart() *serpent.Command {
11597
return err
11698
}
11799

118-
if provisionerKey != "" {
119-
if preSharedKey != "" {
120-
return xerrors.New("cannot provide both provisioner key --key and pre-shared key --psk")
121-
}
122-
if len(rawTags) > 0 {
123-
return xerrors.New("cannot provide tags when using provisioner key")
124-
}
125-
err = provisionerkey.Validate(provisionerKey)
126-
if err != nil {
127-
return xerrors.Errorf("validate provisioner key: %w", err)
128-
}
129-
}
130-
131100
logOpts := []clilog.Option{
132101
clilog.WithFilter(logFilter...),
133102
clilog.WithHuman(logHuman),
@@ -232,7 +201,7 @@ func (r *RootCmd) provisionerDaemonStart() *serpent.Command {
232201
},
233202
Tags: tags,
234203
PreSharedKey: preSharedKey,
235-
Organization: org.ID,
204+
Organization: orgID,
236205
ProvisionerKey: provisionerKey,
237206
})
238207
}, &provisionerd.Options{
@@ -281,6 +250,13 @@ func (r *RootCmd) provisionerDaemonStart() *serpent.Command {
281250
},
282251
}
283252

253+
keyOption := serpent.Option{
254+
Flag: "key",
255+
Env: "CODER_PROVISIONER_DAEMON_KEY",
256+
Description: "Provisioner key to authenticate with Coder server.",
257+
Value: serpent.StringOf(&provisionerKey),
258+
Hidden: true,
259+
}
284260
cmd.Options = serpent.OptionSet{
285261
{
286262
Flag: "cache-dir",
@@ -316,14 +292,9 @@ func (r *RootCmd) provisionerDaemonStart() *serpent.Command {
316292
Env: "CODER_PROVISIONER_DAEMON_PSK",
317293
Description: "Pre-shared key to authenticate with Coder server.",
318294
Value: serpent.StringOf(&preSharedKey),
295+
UseInstead: []serpent.Option{keyOption},
319296
},
320-
{
321-
Flag: "key",
322-
Env: "CODER_PROVISIONER_DAEMON_KEY",
323-
Description: "Provisioner key to authenticate with Coder server.",
324-
Value: serpent.StringOf(&provisionerKey),
325-
Hidden: true,
326-
},
297+
keyOption,
327298
{
328299
Flag: "name",
329300
Env: "CODER_PROVISIONER_DAEMON_NAME",

enterprise/coderd/provisionerdaemons.go

Lines changed: 35 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -82,77 +82,68 @@ type provisionerDaemonAuth struct {
8282

8383
// authorize returns mutated tags if the given HTTP request is authorized to access the provisioner daemon
8484
// protobuf API, and returns nil, err otherwise.
85-
func (p *provisionerDaemonAuth) authorize(r *http.Request, orgID uuid.UUID, tags map[string]string) (uuid.UUID, map[string]string, error) {
85+
func (p *provisionerDaemonAuth) authorize(r *http.Request, org database.Organization, tags map[string]string) (uuid.UUID, uuid.UUID, map[string]string, error) {
8686
ctx := r.Context()
8787
apiKey, apiKeyOK := httpmw.APIKeyOptional(r)
8888
pk, pkOK := httpmw.ProvisionerKeyAuthOptional(r)
8989
provAuth := httpmw.ProvisionerDaemonAuthenticated(r)
9090
if !provAuth && !apiKeyOK {
91-
return uuid.Nil, nil, xerrors.New("no API key or provisioner key provided")
91+
return uuid.Nil, uuid.Nil, nil, xerrors.New("no API key or provisioner key provided")
9292
}
9393
if apiKeyOK && pkOK {
94-
return uuid.Nil, nil, xerrors.New("Both API key and provisioner key authentication provided. Only one is allowed.")
94+
return uuid.Nil, uuid.Nil, nil, xerrors.New("Both API key and provisioner key authentication provided. Only one is allowed.")
9595
}
9696

9797
// Provisioner Key Auth
9898
if pkOK {
99-
if pk.OrganizationID != orgID {
100-
return uuid.Nil, nil, xerrors.New("provisioner key unauthorized")
101-
}
10299
if tags != nil && !maps.Equal(tags, map[string]string{}) {
103-
return uuid.Nil, nil, xerrors.New("tags are not allowed when using a provisioner key")
100+
return uuid.Nil, uuid.Nil, nil, xerrors.New("tags are not allowed when using a provisioner key")
104101
}
105102

106103
// If using provisioner key / PSK auth, the daemon is, by definition, scoped to the organization.
107104
// Use the provisioner key tags here.
108105
tags = provisionersdk.MutateTags(uuid.Nil, pk.Tags)
109-
return pk.ID, tags, nil
106+
return pk.ID, pk.OrganizationID, tags, nil
110107
}
111108

112-
// User Auth
113-
if apiKeyOK {
114-
userKey, err := uuid.Parse(codersdk.ProvisionerKeyIDUserAuth)
115-
if err != nil {
116-
return uuid.Nil, nil, xerrors.Errorf("parse user provisioner key id: %w", err)
109+
// PSK Auth
110+
if provAuth {
111+
if !org.IsDefault {
112+
return uuid.Nil, uuid.Nil, nil, xerrors.Errorf("PSK auth is only allowed for the default organization '%s'", org.Name)
117113
}
118114

119-
tags = provisionersdk.MutateTags(apiKey.UserID, tags)
120-
if tags[provisionersdk.TagScope] == provisionersdk.ScopeUser {
121-
// Any authenticated user can create provisioner daemons scoped
122-
// for jobs that they own,
123-
return userKey, tags, nil
124-
}
125-
ua := httpmw.UserAuthorization(r)
126-
err = p.authorizer.Authorize(ctx, ua, policy.ActionCreate, rbac.ResourceProvisionerDaemon.InOrg(orgID))
115+
pskKey, err := uuid.Parse(codersdk.ProvisionerKeyIDPSK)
127116
if err != nil {
128-
if !provAuth {
129-
return uuid.Nil, nil, xerrors.New("user unauthorized")
130-
}
131-
132-
pskKey, err := uuid.Parse(codersdk.ProvisionerKeyIDPSK)
133-
if err != nil {
134-
return uuid.Nil, nil, xerrors.Errorf("parse psk provisioner key id: %w", err)
135-
}
117+
return uuid.Nil, uuid.Nil, nil, xerrors.Errorf("parse psk provisioner key id: %w", err)
118+
}
136119

137-
// Allow fallback to PSK auth if the user is not allowed to create provisioner daemons.
138-
// This is to preserve backwards compatibility with existing user provisioner daemons.
139-
// If using PSK auth, the daemon is, by definition, scoped to the organization.
140-
tags = provisionersdk.MutateTags(uuid.Nil, tags)
120+
tags = provisionersdk.MutateTags(uuid.Nil, tags)
121+
return pskKey, org.ID, tags, nil
122+
}
141123

142-
return pskKey, tags, nil
143-
}
124+
// User Auth
125+
if !apiKeyOK {
126+
return uuid.Nil, uuid.Nil, nil, xerrors.New("no API key provided")
127+
}
144128

145-
return userKey, tags, nil
129+
userKey, err := uuid.Parse(codersdk.ProvisionerKeyIDUserAuth)
130+
if err != nil {
131+
return uuid.Nil, uuid.Nil, nil, xerrors.Errorf("parse user provisioner key id: %w", err)
146132
}
147133

148-
// PSK Auth
149-
pskKey, err := uuid.Parse(codersdk.ProvisionerKeyIDPSK)
134+
tags = provisionersdk.MutateTags(apiKey.UserID, tags)
135+
if tags[provisionersdk.TagScope] == provisionersdk.ScopeUser {
136+
// Any authenticated user can create provisioner daemons scoped
137+
// for jobs that they own,
138+
return userKey, org.ID, tags, nil
139+
}
140+
ua := httpmw.UserAuthorization(r)
141+
err = p.authorizer.Authorize(ctx, ua, policy.ActionCreate, rbac.ResourceProvisionerDaemon.InOrg(org.ID))
150142
if err != nil {
151-
return uuid.Nil, nil, xerrors.Errorf("parse psk provisioner key id: %w", err)
143+
return uuid.Nil, uuid.Nil, nil, xerrors.New("user unauthorized")
152144
}
153145

154-
tags = provisionersdk.MutateTags(uuid.Nil, tags)
155-
return pskKey, tags, nil
146+
return userKey, org.ID, tags, nil
156147
}
157148

158149
// Serves the provisioner daemon protobuf API over a WebSocket.
@@ -166,7 +157,6 @@ func (p *provisionerDaemonAuth) authorize(r *http.Request, orgID uuid.UUID, tags
166157
// @Router /organizations/{organization}/provisionerdaemons/serve [get]
167158
func (api *API) provisionerDaemonServe(rw http.ResponseWriter, r *http.Request) {
168159
ctx := r.Context()
169-
organization := httpmw.OrganizationParam(r)
170160

171161
tags := map[string]string{}
172162
if r.URL.Query().Has("tag") {
@@ -215,7 +205,7 @@ func (api *API) provisionerDaemonServe(rw http.ResponseWriter, r *http.Request)
215205
api.Logger.Warn(ctx, "unnamed provisioner daemon")
216206
}
217207

218-
keyID, tags, err := api.provisionerDaemonAuth.authorize(r, organization.ID, tags)
208+
keyID, orgID, tags, err := api.provisionerDaemonAuth.authorize(r, httpmw.OrganizationParam(r), tags)
219209
if err != nil {
220210
api.Logger.Warn(ctx, "unauthorized provisioner daemon serve request", slog.F("tags", tags), slog.Error(err))
221211
httpapi.Write(ctx, rw, http.StatusForbidden,
@@ -287,7 +277,7 @@ func (api *API) provisionerDaemonServe(rw http.ResponseWriter, r *http.Request)
287277
LastSeenAt: sql.NullTime{Time: now, Valid: true},
288278
Version: versionHdrVal,
289279
APIVersion: apiVersion,
290-
OrganizationID: organization.ID,
280+
OrganizationID: orgID,
291281
KeyID: keyID,
292282
})
293283
if err != nil {
@@ -351,7 +341,7 @@ func (api *API) provisionerDaemonServe(rw http.ResponseWriter, r *http.Request)
351341
srvCtx,
352342
api.AccessURL,
353343
daemon.ID,
354-
organization.ID,
344+
orgID,
355345
logger,
356346
provisioners,
357347
tags,

0 commit comments

Comments
 (0)