mirror of
https://github.com/coder/coder.git
synced 2025-07-13 21:36:50 +00:00
feat: add keys to organization provision daemons (#14627)
This commit is contained in:
@ -82,56 +82,77 @@ type provisionerDaemonAuth struct {
|
||||
|
||||
// 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) {
|
||||
func (p *provisionerDaemonAuth) authorize(r *http.Request, orgID uuid.UUID, tags map[string]string) (uuid.UUID, 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")
|
||||
return uuid.Nil, 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.")
|
||||
return uuid.Nil, nil, xerrors.New("Both API key and provisioner key authentication provided. Only one is allowed.")
|
||||
}
|
||||
|
||||
// Provisioner Key Auth
|
||||
if pkOK {
|
||||
if pk.OrganizationID != orgID {
|
||||
return nil, xerrors.New("provisioner key unauthorized")
|
||||
return uuid.Nil, nil, xerrors.New("provisioner key unauthorized")
|
||||
}
|
||||
if tags != nil && !maps.Equal(tags, map[string]string{}) {
|
||||
return nil, xerrors.New("tags are not allowed when using a provisioner key")
|
||||
return uuid.Nil, nil, xerrors.New("tags are not allowed when using a provisioner key")
|
||||
}
|
||||
|
||||
// If using provisioner key / PSK auth, the daemon is, by definition, scoped to the organization.
|
||||
// Use the provisioner key tags here.
|
||||
tags = provisionersdk.MutateTags(uuid.Nil, pk.Tags)
|
||||
return tags, nil
|
||||
return pk.ID, tags, nil
|
||||
}
|
||||
|
||||
// User Auth
|
||||
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")
|
||||
if apiKeyOK {
|
||||
userKey, err := uuid.Parse(codersdk.ProvisionerKeyIDUserAuth)
|
||||
if err != nil {
|
||||
return uuid.Nil, nil, xerrors.Errorf("parse user provisioner key id: %w", err)
|
||||
}
|
||||
|
||||
// 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
|
||||
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 userKey, tags, nil
|
||||
}
|
||||
ua := httpmw.UserAuthorization(r)
|
||||
err = p.authorizer.Authorize(ctx, ua, policy.ActionCreate, rbac.ResourceProvisionerDaemon.InOrg(orgID))
|
||||
if err != nil {
|
||||
if !provAuth {
|
||||
return uuid.Nil, nil, xerrors.New("user unauthorized")
|
||||
}
|
||||
|
||||
pskKey, err := uuid.Parse(codersdk.ProvisionerKeyIDPSK)
|
||||
if err != nil {
|
||||
return uuid.Nil, nil, xerrors.Errorf("parse psk provisioner key id: %w", err)
|
||||
}
|
||||
|
||||
// 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 pskKey, tags, nil
|
||||
}
|
||||
|
||||
return userKey, tags, nil
|
||||
}
|
||||
|
||||
// User is allowed to create provisioner daemons
|
||||
return tags, nil
|
||||
// PSK Auth
|
||||
pskKey, err := uuid.Parse(codersdk.ProvisionerKeyIDPSK)
|
||||
if err != nil {
|
||||
return uuid.Nil, nil, xerrors.Errorf("parse psk provisioner key id: %w", err)
|
||||
}
|
||||
|
||||
tags = provisionersdk.MutateTags(uuid.Nil, tags)
|
||||
return pskKey, tags, nil
|
||||
}
|
||||
|
||||
// Serves the provisioner daemon protobuf API over a WebSocket.
|
||||
@ -194,7 +215,7 @@ 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)
|
||||
keyID, tags, err := api.provisionerDaemonAuth.authorize(r, organization.ID, tags)
|
||||
if err != nil {
|
||||
api.Logger.Warn(ctx, "unauthorized provisioner daemon serve request", slog.F("tags", tags), slog.Error(err))
|
||||
httpapi.Write(ctx, rw, http.StatusForbidden,
|
||||
@ -267,6 +288,7 @@ func (api *API) provisionerDaemonServe(rw http.ResponseWriter, r *http.Request)
|
||||
Version: versionHdrVal,
|
||||
APIVersion: apiVersion,
|
||||
OrganizationID: organization.ID,
|
||||
KeyID: keyID,
|
||||
})
|
||||
if err != nil {
|
||||
if !xerrors.Is(err, context.Canceled) {
|
||||
|
Reference in New Issue
Block a user