Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 68 additions & 0 deletions coderd/agentapi/subagent_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ import (
"github.com/coder/coder/v2/coderd/database/dbauthz"
"github.com/coder/coder/v2/coderd/database/dbgen"
"github.com/coder/coder/v2/coderd/database/dbtestutil"
"github.com/coder/coder/v2/coderd/database/dbtime"
"github.com/coder/coder/v2/coderd/dlppolicy"
"github.com/coder/coder/v2/coderd/rbac"
"github.com/coder/coder/v2/coderd/util/ptr"
"github.com/coder/coder/v2/codersdk"
Expand Down Expand Up @@ -220,6 +222,72 @@ func TestSubAgentAPI(t *testing.T) {
assert.Equal(t, parentAgent.ID, lookedUp.ID, "instance ID lookup should still return the parent agent")
})

t.Run("CreateSubAgentInheritsDLPPolicy", func(t *testing.T) {
t.Parallel()

var (
log = testutil.Logger(t)
clock = quartz.NewMock(t)

db, org = newDatabaseWithOrg(t)
user, agent = newUserWithWorkspaceAgent(t, db, org)
)

ctx := testutil.Context(t, testutil.WaitShort)

// Attach a DLP policy with ssh_access disabled to the parent agent.
ao, err := db.GetAuthenticatedWorkspaceAgentAndBuildByAuthToken(dbauthz.AsSystemRestricted(ctx), agent.AuthToken)
require.NoError(t, err)
build, err := db.GetWorkspaceBuildByID(dbauthz.AsSystemRestricted(ctx), ao.WorkspaceBuild.ID)
require.NoError(t, err)

policy, err := db.InsertTemplateVersionDLPPolicy(dbauthz.AsProvisionerd(ctx), database.InsertTemplateVersionDLPPolicyParams{
ID: uuid.New(),
TemplateVersionID: build.TemplateVersionID,
Name: "strict",
SshAccess: false,
WebTerminalAccess: true,
PortForwardingAccess: true,
AllowedApplications: []string{},
CreatedAt: dbtime.Now(),
})
require.NoError(t, err)
dbgen.SetWorkspaceAgentDLPPolicy(t, db, agent.ID, policy.ID)

// Re-fetch the parent so the API sees the updated DlpPolicyID.
parentAgent, err := db.GetWorkspaceAgentByID(dbauthz.AsSystemRestricted(ctx), agent.ID)
require.NoError(t, err)
require.True(t, parentAgent.DlpPolicyID.Valid, "parent should have the DLP policy attached")

api := newAgentAPI(t, log, db, clock, user, org, parentAgent)

createResp, err := api.CreateSubAgent(ctx, &proto.CreateSubAgentRequest{
Name: "sub-agent",
Directory: "/workspaces/test",
Architecture: "amd64",
OperatingSystem: "linux",
})
require.NoError(t, err)

subAgentID, err := uuid.FromBytes(createResp.Agent.Id)
require.NoError(t, err)

// The sub-agent row must carry the parent's DLP policy id so that
// gates keyed on the agent ID return the same policy.
subAgent, err := db.GetWorkspaceAgentByID(dbauthz.AsSystemRestricted(ctx), subAgentID)
require.NoError(t, err)
require.True(t, subAgent.DlpPolicyID.Valid, "sub-agent should inherit the parent's DLP policy")
require.Equal(t, parentAgent.DlpPolicyID.UUID, subAgent.DlpPolicyID.UUID)

// And dlppolicy.ForAgent must resolve the same policy when looked up
// via the sub-agent's id, which is what the enforcement gates do.
resolved, err := dlppolicy.ForAgent(ctx, db, subAgentID)
require.NoError(t, err)
require.NotNil(t, resolved)
require.Equal(t, policy.ID, resolved.ID)
require.False(t, resolved.SshAccess, "sub-agent must inherit ssh_access=false")
})

type expectedAppError struct {
index int32
field string
Expand Down
7 changes: 7 additions & 0 deletions coderd/database/dbauthz/dbauthz.go
Original file line number Diff line number Diff line change
Expand Up @@ -7532,6 +7532,13 @@ func (q *querier) UpdateWorkspaceAgentConnectionByID(ctx context.Context, arg da
return q.db.UpdateWorkspaceAgentConnectionByID(ctx, arg)
}

func (q *querier) UpdateWorkspaceAgentDLPPolicyByID(ctx context.Context, arg database.UpdateWorkspaceAgentDLPPolicyByIDParams) error {
if err := q.authorizeContext(ctx, policy.ActionUpdate, rbac.ResourceTemplate); err != nil {
return err
}
return q.db.UpdateWorkspaceAgentDLPPolicyByID(ctx, arg)
}

func (q *querier) UpdateWorkspaceAgentDirectoryByID(ctx context.Context, arg database.UpdateWorkspaceAgentDirectoryByIDParams) error {
workspace, err := q.db.GetWorkspaceByAgentID(ctx, arg.ID)
if err != nil {
Expand Down
5 changes: 5 additions & 0 deletions coderd/database/dbauthz/dbauthz_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2136,6 +2136,11 @@ func (s *MethodTestSuite) TestOrganization() {
dbm.EXPECT().InsertTemplateVersionDLPPolicy(gomock.Any(), arg).Return(database.TemplateVersionDlpPolicy{}, nil).AnyTimes()
check.Args(arg).Asserts(rbac.ResourceTemplate, policy.ActionUpdate)
}))
s.Run("UpdateWorkspaceAgentDLPPolicyByID", s.Mocked(func(dbm *dbmock.MockStore, _ *gofakeit.Faker, check *expects) {
arg := database.UpdateWorkspaceAgentDLPPolicyByIDParams{ID: uuid.New(), DlpPolicyID: uuid.NullUUID{UUID: uuid.New(), Valid: true}}
dbm.EXPECT().UpdateWorkspaceAgentDLPPolicyByID(gomock.Any(), arg).Return(nil).AnyTimes()
check.Args(arg).Asserts(rbac.ResourceTemplate, policy.ActionUpdate)
}))
s.Run("GetTemplateVersionDLPPoliciesByTemplateVersionID", s.Mocked(func(dbm *dbmock.MockStore, faker *gofakeit.Faker, check *expects) {
tpl := testutil.Fake(s.T(), faker, database.Template{})
tv := testutil.Fake(s.T(), faker, database.TemplateVersion{TemplateID: uuid.NullUUID{UUID: tpl.ID, Valid: true}, OrganizationID: tpl.OrganizationID, CreatedBy: tpl.CreatedBy})
Expand Down
11 changes: 11 additions & 0 deletions coderd/database/dbgen/dbgen.go
Original file line number Diff line number Diff line change
Expand Up @@ -514,6 +514,7 @@ func WorkspaceAgent(t testing.TB, db database.Store, orig database.WorkspaceAgen
DisplayApps: append([]database.DisplayApp{}, orig.DisplayApps...),
DisplayOrder: takeFirst(orig.DisplayOrder, 1),
APIKeyScope: takeFirst(orig.APIKeyScope, database.AgentKeyScopeEnumAll),
DlpPolicyID: orig.DlpPolicyID,
})
require.NoError(t, err, "insert workspace agent")
if orig.FirstConnectedAt.Valid || orig.LastConnectedAt.Valid || orig.DisconnectedAt.Valid || orig.LastConnectedReplicaID.Valid {
Expand Down Expand Up @@ -583,6 +584,16 @@ func WorkspaceSubAgent(t testing.TB, db database.Store, parentAgent database.Wor
return subAgt
}

// SetWorkspaceAgentDLPPolicy sets the DLP policy reference on an existing
// workspace agent. Helpful for tests that need to attach a policy to an
// agent that was created via dbfake or other helpers.
func SetWorkspaceAgentDLPPolicy(t testing.TB, db database.Store, agentID uuid.UUID, policyID uuid.UUID) {
require.NoError(t, db.UpdateWorkspaceAgentDLPPolicyByID(genCtx, database.UpdateWorkspaceAgentDLPPolicyByIDParams{
ID: agentID,
DlpPolicyID: uuid.NullUUID{UUID: policyID, Valid: true},
}))
}

func WorkspaceAgentScript(t testing.TB, db database.Store, orig database.WorkspaceAgentScript) database.WorkspaceAgentScript {
scripts, err := db.InsertWorkspaceAgentScripts(genCtx, database.InsertWorkspaceAgentScriptsParams{
WorkspaceAgentID: takeFirst(orig.WorkspaceAgentID, uuid.New()),
Expand Down
8 changes: 8 additions & 0 deletions coderd/database/dbmetrics/querymetrics.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 14 additions & 0 deletions coderd/database/dbmock/dbmock.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions coderd/database/querier.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

22 changes: 22 additions & 0 deletions coderd/database/queries.sql.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 11 additions & 0 deletions coderd/database/queries/workspaceagents.sql
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,17 @@ INSERT INTO
VALUES
($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21) RETURNING *;

-- name: UpdateWorkspaceAgentDLPPolicyByID :exec
-- UpdateWorkspaceAgentDLPPolicyByID is intended for tests and admin-tooling
-- only. In normal operation `dlp_policy_id` is set at agent insert time and
-- not modified afterwards.
UPDATE
workspace_agents
SET
dlp_policy_id = $2
WHERE
id = $1;

-- name: UpdateWorkspaceAgentConnectionByID :exec
UPDATE
workspace_agents
Expand Down
42 changes: 42 additions & 0 deletions coderd/dlppolicy/dlppolicy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// Package dlppolicy resolves an agent's data loss prevention policy for
// coderd enforcement gates.
package dlppolicy

import (
"context"
"database/sql"
"errors"

"github.com/google/uuid"
"golang.org/x/xerrors"

"github.com/coder/coder/v2/coderd/database"
"github.com/coder/coder/v2/coderd/database/dbauthz"
)

// ForAgent returns the DLP policy attached to the given workspace agent, or
// nil if the agent has no policy configured. A nil result means no policy is
// in effect (default-permissive).
//
// Callers must have already authorized the request against the workspace.
// This function reads the policy under a system-restricted context because
// the policy lookup is system-internal post-authz. Policies exist to
// constrain the actor, so the lookup must not depend on the actor's
// permissions.
func ForAgent(ctx context.Context, db database.Store, agentID uuid.UUID) (*database.TemplateVersionDlpPolicy, error) {
// The escalation is intentional. See the doc comment above.
//nolint:gocritic // post-authz system-internal lookup, see doc comment.
p, err := db.GetTemplateVersionDLPPolicyByAgentID(dbauthz.AsSystemRestricted(ctx), agentID)
if errors.Is(err, sql.ErrNoRows) {
// A nil policy with a nil error is the documented "no policy" signal;
// callers check dlp != nil. A sentinel error would force every gate
// to branch on errors.Is, which is noisier than the existing
// nil-policy check.
//nolint:nilnil // nil policy is the no-policy signal; see doc comment.
return nil, nil
}
if err != nil {
return nil, xerrors.Errorf("get dlp policy for agent %q: %w", agentID, err)
}
return &p, nil
}
93 changes: 93 additions & 0 deletions coderd/dlppolicy/dlppolicy_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package dlppolicy_test

import (
"context"
"testing"
"time"

"github.com/google/uuid"
"github.com/stretchr/testify/require"

"github.com/coder/coder/v2/coderd/database"
"github.com/coder/coder/v2/coderd/database/dbgen"
"github.com/coder/coder/v2/coderd/database/dbtestutil"
"github.com/coder/coder/v2/coderd/dlppolicy"
"github.com/coder/coder/v2/testutil"
)

func TestForAgent(t *testing.T) {
t.Parallel()

t.Run("AgentWithPolicy", func(t *testing.T) {
t.Parallel()

ctx := testutil.Context(t, testutil.WaitShort)
db, _ := dbtestutil.NewDB(t)
org := dbgen.Organization(t, db, database.Organization{})
user := dbgen.User(t, db, database.User{})
job := dbgen.ProvisionerJob(t, db, nil, database.ProvisionerJob{
Type: database.ProvisionerJobTypeTemplateVersionImport,
OrganizationID: org.ID,
})
tv := dbgen.TemplateVersion(t, db, database.TemplateVersion{
JobID: job.ID,
OrganizationID: org.ID,
CreatedBy: user.ID,
})

policyID := uuid.New()
_, err := db.InsertTemplateVersionDLPPolicy(ctx, database.InsertTemplateVersionDLPPolicyParams{
ID: policyID,
TemplateVersionID: tv.ID,
Name: "strict",
SshAccess: true,
WebTerminalAccess: false,
PortForwardingAccess: true,
AllowedApplications: []string{"code-server"},
CreatedAt: time.Now(),
})
require.NoError(t, err)

res := dbgen.WorkspaceResource(t, db, database.WorkspaceResource{JobID: job.ID})
agent := dbgen.WorkspaceAgent(t, db, database.WorkspaceAgent{
ResourceID: res.ID,
DlpPolicyID: uuid.NullUUID{UUID: policyID, Valid: true},
})

got, err := dlppolicy.ForAgent(context.Background(), db, agent.ID)
require.NoError(t, err)
require.NotNil(t, got)
require.Equal(t, "strict", got.Name)
require.True(t, got.SshAccess)
require.False(t, got.WebTerminalAccess)
})

t.Run("AgentWithoutPolicy", func(t *testing.T) {
t.Parallel()

db, _ := dbtestutil.NewDB(t)
org := dbgen.Organization(t, db, database.Organization{})
job := dbgen.ProvisionerJob(t, db, nil, database.ProvisionerJob{
Type: database.ProvisionerJobTypeWorkspaceBuild,
OrganizationID: org.ID,
})
res := dbgen.WorkspaceResource(t, db, database.WorkspaceResource{JobID: job.ID})
agent := dbgen.WorkspaceAgent(t, db, database.WorkspaceAgent{
ResourceID: res.ID,
})

got, err := dlppolicy.ForAgent(context.Background(), db, agent.ID)
require.NoError(t, err)
require.Nil(t, got)
})

t.Run("AgentNotFound", func(t *testing.T) {
t.Parallel()

db, _ := dbtestutil.NewDB(t)

got, err := dlppolicy.ForAgent(context.Background(), db, uuid.New())
require.NoError(t, err)
require.Nil(t, got)
})
}
Loading
Loading