Prevent running plan or apply without permissions
This commit is contained in:
parent
53d322ec69
commit
ffc67a8e90
|
@ -22,6 +22,11 @@ func (b *Remote) opApply(stopCtx, cancelCtx context.Context, op *backend.Operati
|
|||
return nil, generalError("error retrieving workspace", err)
|
||||
}
|
||||
|
||||
if !w.Permissions.CanUpdate {
|
||||
return nil, fmt.Errorf(strings.TrimSpace(
|
||||
fmt.Sprintf(applyErrNoUpdateRights, b.hostname, b.organization, op.Workspace)))
|
||||
}
|
||||
|
||||
if w.VCSRepo != nil {
|
||||
return nil, fmt.Errorf(strings.TrimSpace(applyErrVCSNotSupported))
|
||||
}
|
||||
|
@ -76,6 +81,9 @@ func (b *Remote) opApply(stopCtx, cancelCtx context.Context, op *backend.Operati
|
|||
return r, nil
|
||||
}
|
||||
|
||||
// Since we already checked the permissions before creating the run
|
||||
// this should never happen. But it doesn't hurt to keep this in as
|
||||
// a safeguard for any unexpected situations.
|
||||
if !r.Permissions.CanApply {
|
||||
// Make sure we discard the run if possible.
|
||||
if r.Actions.IsDiscardable {
|
||||
|
@ -261,10 +269,20 @@ func (b *Remote) confirm(stopCtx context.Context, op *backend.Operation, opts *t
|
|||
return nil
|
||||
}
|
||||
|
||||
const applyErrVCSNotSupported = `
|
||||
Apply not allowed for workspaces with a VCS connection!
|
||||
const applyErrNoUpdateRights = `
|
||||
Insufficient rights to apply changes!
|
||||
|
||||
A workspace that is connected to a VCS requires the VCS based workflow
|
||||
[reset][yellow]The provided credentials have insufficient rights to apply changes. In order
|
||||
to apply changes at least write permissions on the workspace are required. To
|
||||
queue a run that can be approved by someone else, please use the 'Queue Plan'
|
||||
button in the web UI:
|
||||
https://%s/app/%s/%s/runs[reset]
|
||||
`
|
||||
|
||||
const applyErrVCSNotSupported = `
|
||||
Apply not allowed for workspaces with a VCS connection.
|
||||
|
||||
A workspace that is connected to a VCS requires the VCS-driven workflow
|
||||
to ensure that the VCS remains the single source of truth.
|
||||
`
|
||||
|
||||
|
@ -293,10 +311,10 @@ does not require any configuration files.
|
|||
const applyErrNoApplyRights = `
|
||||
Insufficient rights to approve the pending changes!
|
||||
|
||||
[reset][yellow]There are pending changes, but the used credentials have insufficient rights
|
||||
to approve them. The run will be discarded to prevent it from blocking the
|
||||
queue waiting for external approval. To trigger a run that can be approved by
|
||||
someone else, please use the 'Queue Plan' button in the web UI:
|
||||
[reset][yellow]There are pending changes, but the provided credentials have insufficient rights
|
||||
to approve them. The run will be discarded to prevent it from blocking the queue
|
||||
waiting for external approval. To queue a run that can be approved by someone
|
||||
else, please use the 'Queue Plan' button in the web UI:
|
||||
https://%s/app/%s/%s/runs[reset]
|
||||
`
|
||||
|
||||
|
|
|
@ -61,10 +61,47 @@ func TestRemote_applyBasic(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestRemote_applyWithoutPermissions(t *testing.T) {
|
||||
b := testBackendNoDefault(t)
|
||||
|
||||
// Create a named workspace without permissions.
|
||||
w, err := b.client.Workspaces.Create(
|
||||
context.Background(),
|
||||
b.organization,
|
||||
tfe.WorkspaceCreateOptions{
|
||||
Name: tfe.String(b.prefix + "prod"),
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("error creating named workspace: %v", err)
|
||||
}
|
||||
w.Permissions.CanUpdate = false
|
||||
|
||||
mod, modCleanup := module.TestTree(t, "./test-fixtures/apply")
|
||||
defer modCleanup()
|
||||
|
||||
op := testOperationApply()
|
||||
op.Module = mod
|
||||
op.Workspace = "prod"
|
||||
|
||||
run, err := b.Operation(context.Background(), op)
|
||||
if err != nil {
|
||||
t.Fatalf("error starting operation: %v", err)
|
||||
}
|
||||
<-run.Done()
|
||||
|
||||
if run.Err == nil {
|
||||
t.Fatalf("expected an apply error, got: %v", run.Err)
|
||||
}
|
||||
if !strings.Contains(run.Err.Error(), "insufficient rights to apply changes") {
|
||||
t.Fatalf("expected a permissions error, got: %v", run.Err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemote_applyWithVCS(t *testing.T) {
|
||||
b := testBackendNoDefault(t)
|
||||
|
||||
// Create the named workspace with a VCS.
|
||||
// Create a named workspace with a VCS.
|
||||
_, err := b.client.Workspaces.Create(
|
||||
context.Background(),
|
||||
b.organization,
|
||||
|
|
|
@ -853,6 +853,10 @@ func (m *mockWorkspaces) Create(ctx context.Context, organization string, option
|
|||
w := &tfe.Workspace{
|
||||
ID: generateID("ws-"),
|
||||
Name: *options.Name,
|
||||
Permissions: &tfe.WorkspacePermissions{
|
||||
CanQueueRun: true,
|
||||
CanUpdate: true,
|
||||
},
|
||||
}
|
||||
if options.VCSRepo != nil {
|
||||
w.VCSRepo = &tfe.VCSRepo{}
|
||||
|
|
|
@ -20,6 +20,16 @@ import (
|
|||
func (b *Remote) opPlan(stopCtx, cancelCtx context.Context, op *backend.Operation) (*tfe.Run, error) {
|
||||
log.Printf("[INFO] backend/remote: starting Plan operation")
|
||||
|
||||
// Retrieve the workspace used to run this operation in.
|
||||
w, err := b.client.Workspaces.Read(stopCtx, b.organization, op.Workspace)
|
||||
if err != nil {
|
||||
return nil, generalError("error retrieving workspace", err)
|
||||
}
|
||||
|
||||
if !w.Permissions.CanQueueRun {
|
||||
return nil, fmt.Errorf(strings.TrimSpace(fmt.Sprintf(planErrNoQueueRunRights)))
|
||||
}
|
||||
|
||||
if op.Plan != nil {
|
||||
return nil, fmt.Errorf(strings.TrimSpace(planErrPlanNotSupported))
|
||||
}
|
||||
|
@ -36,12 +46,6 @@ func (b *Remote) opPlan(stopCtx, cancelCtx context.Context, op *backend.Operatio
|
|||
return nil, fmt.Errorf(strings.TrimSpace(planErrNoConfig))
|
||||
}
|
||||
|
||||
// Retrieve the workspace used to run this operation in.
|
||||
w, err := b.client.Workspaces.Read(stopCtx, b.organization, op.Workspace)
|
||||
if err != nil {
|
||||
return nil, generalError("error retrieving workspace", err)
|
||||
}
|
||||
|
||||
return b.plan(stopCtx, cancelCtx, op, w)
|
||||
}
|
||||
|
||||
|
@ -192,6 +196,13 @@ func (b *Remote) plan(stopCtx, cancelCtx context.Context, op *backend.Operation,
|
|||
return r, nil
|
||||
}
|
||||
|
||||
const planErrNoQueueRunRights = `
|
||||
Insufficient rights to generate a plan!
|
||||
|
||||
[reset][yellow]The provided credentials have insufficient rights to generate a plan. In order
|
||||
to generate plans, at least plan permissions on the workspace are required.[reset]
|
||||
`
|
||||
|
||||
const planErrPlanNotSupported = `
|
||||
Displaying a saved plan is currently not supported!
|
||||
|
||||
|
|
|
@ -48,6 +48,43 @@ func TestRemote_planBasic(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestRemote_planWithoutPermissions(t *testing.T) {
|
||||
b := testBackendNoDefault(t)
|
||||
|
||||
// Create a named workspace without permissions.
|
||||
w, err := b.client.Workspaces.Create(
|
||||
context.Background(),
|
||||
b.organization,
|
||||
tfe.WorkspaceCreateOptions{
|
||||
Name: tfe.String(b.prefix + "prod"),
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("error creating named workspace: %v", err)
|
||||
}
|
||||
w.Permissions.CanQueueRun = false
|
||||
|
||||
mod, modCleanup := module.TestTree(t, "./test-fixtures/plan")
|
||||
defer modCleanup()
|
||||
|
||||
op := testOperationPlan()
|
||||
op.Module = mod
|
||||
op.Workspace = "prod"
|
||||
|
||||
run, err := b.Operation(context.Background(), op)
|
||||
if err != nil {
|
||||
t.Fatalf("error starting operation: %v", err)
|
||||
}
|
||||
<-run.Done()
|
||||
|
||||
if run.Err == nil {
|
||||
t.Fatalf("expected a plan error, got: %v", run.Err)
|
||||
}
|
||||
if !strings.Contains(run.Err.Error(), "insufficient rights to generate a plan") {
|
||||
t.Fatalf("expected a permissions error, got: %v", run.Err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemote_planWithPlan(t *testing.T) {
|
||||
b := testBackendDefault(t)
|
||||
|
||||
|
|
Loading…
Reference in New Issue