feat: remove org flag requirement for provisioners (#14722)

This commit is contained in:
Garrett Delfosse
2024-09-20 12:45:31 -04:00
committed by GitHub
parent 96e9a4f85c
commit 50124fefdc
4 changed files with 102 additions and 166 deletions
+69 -54
View File
@@ -74,6 +74,12 @@ func (api *API) provisionerDaemons(rw http.ResponseWriter, r *http.Request) {
httpapi.Write(ctx, rw, http.StatusOK, db2sdk.List(daemons, db2sdk.ProvisionerDaemon))
}
type provisiionerDaemonAuthResponse struct {
keyID uuid.UUID
orgID uuid.UUID
tags map[string]string
}
type provisionerDaemonAuth struct {
psk string
db database.Store
@@ -82,77 +88,85 @@ 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) (uuid.UUID, map[string]string, error) {
func (p *provisionerDaemonAuth) authorize(r *http.Request, org database.Organization, tags map[string]string) (provisiionerDaemonAuthResponse, error) {
ctx := r.Context()
apiKey, apiKeyOK := httpmw.APIKeyOptional(r)
pk, pkOK := httpmw.ProvisionerKeyAuthOptional(r)
provAuth := httpmw.ProvisionerDaemonAuthenticated(r)
if !provAuth && !apiKeyOK {
return uuid.Nil, nil, xerrors.New("no API key or provisioner key provided")
return provisiionerDaemonAuthResponse{}, xerrors.New("no API key or provisioner key provided")
}
if apiKeyOK && pkOK {
return uuid.Nil, nil, xerrors.New("Both API key and provisioner key authentication provided. Only one is allowed.")
return provisiionerDaemonAuthResponse{}, xerrors.New("Both API key and provisioner key authentication provided. Only one is allowed.")
}
// Provisioner Key Auth
if pkOK {
if pk.OrganizationID != orgID {
return uuid.Nil, nil, xerrors.New("provisioner key unauthorized")
}
if tags != nil && !maps.Equal(tags, map[string]string{}) {
return uuid.Nil, nil, xerrors.New("tags are not allowed when using a provisioner key")
return provisiionerDaemonAuthResponse{}, 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 pk.ID, tags, nil
}
// User Auth
if apiKeyOK {
userKey, err := uuid.Parse(codersdk.ProvisionerKeyIDUserAuth)
if err != nil {
return uuid.Nil, nil, xerrors.Errorf("parse user provisioner key id: %w", err)
}
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
return provisiionerDaemonAuthResponse{
keyID: pk.ID,
orgID: pk.OrganizationID,
tags: 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)
if provAuth {
if !org.IsDefault {
return provisiionerDaemonAuthResponse{}, xerrors.Errorf("PSK auth is only allowed for the default organization '%s'", org.Name)
}
pskKey, err := uuid.Parse(codersdk.ProvisionerKeyIDPSK)
if err != nil {
return provisiionerDaemonAuthResponse{}, xerrors.Errorf("parse psk provisioner key id: %w", err)
}
tags = provisionersdk.MutateTags(uuid.Nil, tags)
return provisiionerDaemonAuthResponse{
keyID: pskKey,
orgID: org.ID,
tags: tags,
}, nil
}
tags = provisionersdk.MutateTags(uuid.Nil, tags)
return pskKey, tags, nil
// User Auth
if !apiKeyOK {
return provisiionerDaemonAuthResponse{}, xerrors.New("no API key provided")
}
userKey, err := uuid.Parse(codersdk.ProvisionerKeyIDUserAuth)
if err != nil {
return provisiionerDaemonAuthResponse{}, xerrors.Errorf("parse user provisioner key id: %w", err)
}
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 provisiionerDaemonAuthResponse{
keyID: userKey,
orgID: org.ID,
tags: tags,
}, nil
}
ua := httpmw.UserAuthorization(r)
err = p.authorizer.Authorize(ctx, ua, policy.ActionCreate, rbac.ResourceProvisionerDaemon.InOrg(org.ID))
if err != nil {
return provisiionerDaemonAuthResponse{}, xerrors.New("user unauthorized")
}
return provisiionerDaemonAuthResponse{
keyID: userKey,
orgID: org.ID,
tags: tags,
}, nil
}
// Serves the provisioner daemon protobuf API over a WebSocket.
@@ -166,7 +180,6 @@ func (p *provisionerDaemonAuth) authorize(r *http.Request, orgID uuid.UUID, tags
// @Router /organizations/{organization}/provisionerdaemons/serve [get]
func (api *API) provisionerDaemonServe(rw http.ResponseWriter, r *http.Request) {
ctx := r.Context()
organization := httpmw.OrganizationParam(r)
tags := map[string]string{}
if r.URL.Query().Has("tag") {
@@ -215,7 +228,7 @@ func (api *API) provisionerDaemonServe(rw http.ResponseWriter, r *http.Request)
api.Logger.Warn(ctx, "unnamed provisioner daemon")
}
keyID, tags, err := api.provisionerDaemonAuth.authorize(r, organization.ID, tags)
authRes, err := api.provisionerDaemonAuth.authorize(r, httpmw.OrganizationParam(r), 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,
@@ -226,6 +239,8 @@ func (api *API) provisionerDaemonServe(rw http.ResponseWriter, r *http.Request)
)
return
}
tags = authRes.tags
api.Logger.Debug(ctx, "provisioner authorized", slog.F("tags", tags))
if err := provisionerdserver.Tags(tags).Valid(); err != nil {
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
@@ -287,8 +302,8 @@ func (api *API) provisionerDaemonServe(rw http.ResponseWriter, r *http.Request)
LastSeenAt: sql.NullTime{Time: now, Valid: true},
Version: versionHdrVal,
APIVersion: apiVersion,
OrganizationID: organization.ID,
KeyID: keyID,
OrganizationID: authRes.orgID,
KeyID: authRes.keyID,
})
if err != nil {
if !xerrors.Is(err, context.Canceled) {
@@ -351,7 +366,7 @@ func (api *API) provisionerDaemonServe(rw http.ResponseWriter, r *http.Request)
srvCtx,
api.AccessURL,
daemon.ID,
organization.ID,
authRes.orgID,
logger,
provisioners,
tags,