- Notifications
You must be signed in to change notification settings - Fork924
feat: accept provisioner keys for provisioner auth#13972
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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.
Already on GitHub?Sign in to your account
Uh oh!
There was an error while loading.Please reload this page.
Changes fromall commits
abe253d
6837b57
56a880d
32ff000
c9376a0
1e9b79a
1214b62
c7c00e7
5d470f0
1a45338
File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading.Please reload this page.
Jump to
Uh oh!
There was an error while loading.Please reload this page.
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -189,6 +189,8 @@ type ServeProvisionerDaemonRequest struct { | ||
Tags map[string]string `json:"tags"` | ||
// PreSharedKey is an authentication key to use on the API instead of the normal session token from the client. | ||
PreSharedKey string `json:"pre_shared_key"` | ||
// ProvisionerKey is an authentication key to use on the API instead of the normal session token from the client. | ||
ProvisionerKey string `json:"provisioner_key"` | ||
} | ||
// ServeProvisionerDaemon returns the gRPC service for a provisioner daemon | ||
@@ -223,8 +225,15 @@ func (c *Client) ServeProvisionerDaemon(ctx context.Context, req ServeProvisione | ||
headers := http.Header{} | ||
headers.Set(BuildVersionHeader, buildinfo.Version()) | ||
if req.ProvisionerKey != "" { | ||
headers.Set(ProvisionerDaemonKey, req.ProvisionerKey) | ||
} | ||
if req.PreSharedKey != "" { | ||
headers.Set(ProvisionerDaemonPSK, req.PreSharedKey) | ||
} | ||
Comment on lines +229 to +234 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more. Should these be mutually exclusive? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more. I wanted it to fail at the API layer instead of silently taking one or the other. I could do a client error but thought it was cleaner to just have the server handle it. | ||
if req.ProvisionerKey == "" && req.PreSharedKey == "" { | ||
// use session token if we don't have a PSK or provisioner key. | ||
jar, err := cookiejar.New(nil) | ||
if err != nil { | ||
return nil, xerrors.Errorf("create cookie jar: %w", err) | ||
@@ -234,8 +243,6 @@ func (c *Client) ServeProvisionerDaemon(ctx context.Context, req ServeProvisione | ||
Value: c.SessionToken(), | ||
}}) | ||
httpClient.Jar = jar | ||
} | ||
conn, res, err := websocket.Dial(ctx, serverURL.String(), &websocket.DialOptions{ | ||
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -79,36 +79,58 @@ func (api *API) provisionerDaemons(rw http.ResponseWriter, r *http.Request) { | ||
type provisionerDaemonAuth struct { | ||
psk string | ||
db database.Store | ||
authorizer rbac.Authorizer | ||
} | ||
// authorize returns mutated tags if the given HTTP request is authorized to access the provisioner daemon | ||
// protobuf API, and returns nil,err otherwise. | ||
func (p *provisionerDaemonAuth) authorize(r *http.Request, orgID uuid.UUID, tags map[string]string) (map[string]string,error) { | ||
ctx := r.Context() | ||
apiKey, apiKeyOK := httpmw.APIKeyOptional(r) | ||
pk, pkOK := httpmw.ProvisionerKeyAuthOptional(r) | ||
provAuth := httpmw.ProvisionerDaemonAuthenticated(r) | ||
if !provAuth && !apiKeyOK { | ||
return nil, xerrors.New("no API key or provisioner key provided") | ||
} | ||
if apiKeyOK && pkOK { | ||
return nil, xerrors.New("Both API key and provisioner key authentication provided. Only one is allowed.") | ||
} | ||
if apiKeyOK { | ||
tags = provisionersdk.MutateTags(apiKey.UserID, tags) | ||
if tags[provisionersdk.TagScope] == provisionersdk.ScopeUser { | ||
// Any authenticated user can create provisioner daemons scoped | ||
// for jobs that they own, | ||
return tags,nil | ||
} | ||
ua := httpmw.UserAuthorization(r) | ||
err := p.authorizer.Authorize(ctx, ua, policy.ActionCreate, rbac.ResourceProvisionerDaemon.InOrg(orgID)) | ||
if err != nil { | ||
if !provAuth { | ||
return nil, xerrors.New("user unauthorized") | ||
} | ||
// Allow fallback to PSK auth if the user is not allowed to create provisioner daemons. | ||
// This is to preserve backwards compatibility with existing user provisioner daemons. | ||
// If using PSK auth, the daemon is, by definition, scoped to the organization. | ||
tags = provisionersdk.MutateTags(uuid.Nil, tags) | ||
return tags, nil | ||
} | ||
f0ssel marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||
// User is allowed to create provisioner daemons | ||
return tags, nil | ||
} | ||
if pkOK { | ||
if pk.OrganizationID != orgID { | ||
return nil, xerrors.New("provisioner key unauthorized") | ||
} | ||
} | ||
// If using provisioner key / PSK auth, the daemon is, by definition, scoped to the organization. | ||
tags = provisionersdk.MutateTags(uuid.Nil, tags) | ||
return tags, nil | ||
} | ||
// Serves the provisioner daemon protobuf API over a WebSocket. | ||
@@ -171,12 +193,13 @@ func (api *API) provisionerDaemonServe(rw http.ResponseWriter, r *http.Request) | ||
api.Logger.Warn(ctx, "unnamed provisioner daemon") | ||
} | ||
tags,err := api.provisionerDaemonAuth.authorize(r, organization.ID, tags) | ||
iferr != nil { | ||
api.Logger.Warn(ctx, "unauthorized provisioner daemon serve request", slog.F("tags", tags), slog.Error(err)) | ||
httpapi.Write(ctx, rw, http.StatusForbidden, | ||
codersdk.Response{ | ||
Message: fmt.Sprintf("You aren't allowed to create provisioner daemons with scope %q", tags[provisionersdk.TagScope]), | ||
Detail: err.Error(), | ||
}, | ||
) | ||
return | ||
@@ -209,7 +232,7 @@ func (api *API) provisionerDaemonServe(rw http.ResponseWriter, r *http.Request) | ||
) | ||
authCtx := ctx | ||
if r.Header.Get(codersdk.ProvisionerDaemonPSK) != ""|| r.Header.Get(codersdk.ProvisionerDaemonKey) != ""{ | ||
//nolint:gocritic // PSK auth means no actor in request, | ||
// so use system restricted. | ||
authCtx = dbauthz.AsSystemRestricted(ctx) | ||
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.