Skip to content
Draft
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
7 changes: 7 additions & 0 deletions provider/agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,13 @@ func agentResource() *schema.Resource {
"no_user_data",
}, false),
},
"dlp_policy": {
Type: schema.TypeString,
Optional: true,
ForceNew: true,
Description: "The id of a `coder_dlp_policy` resource that gates access to this agent. Omitted means no policy (permissive).",
ValidateFunc: validation.IsUUID,
},
"init_script": {
Type: schema.TypeString,
Computed: true,
Expand Down
51 changes: 51 additions & 0 deletions provider/agent_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,57 @@ func TestAgent(t *testing.T) {
})
}

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

t.Run("References", func(t *testing.T) {
t.Parallel()
resource.Test(t, resource.TestCase{
ProviderFactories: coderFactory(),
IsUnitTest: true,
Steps: []resource.TestStep{{
Config: `
resource "coder_dlp_policy" "strict" {
ssh_access = true
}
resource "coder_agent" "new" {
os = "linux"
arch = "amd64"
dlp_policy = coder_dlp_policy.strict.id
}
`,
Check: func(state *terraform.State) error {
require.Len(t, state.Modules, 1)
policy := state.Modules[0].Resources["coder_dlp_policy.strict"]
require.NotNil(t, policy)
agent := state.Modules[0].Resources["coder_agent.new"]
require.NotNil(t, agent)
require.Equal(t, policy.Primary.Attributes["id"], agent.Primary.Attributes["dlp_policy"])
return nil
},
}},
})
})

t.Run("InvalidUUID", func(t *testing.T) {
t.Parallel()
resource.Test(t, resource.TestCase{
ProviderFactories: coderFactory(),
IsUnitTest: true,
Steps: []resource.TestStep{{
Config: `
resource "coder_agent" "new" {
os = "linux"
arch = "amd64"
dlp_policy = "not-a-uuid"
}
`,
ExpectError: regexp.MustCompile(`expected "dlp_policy" to be a valid UUID`),
}},
})
})
}

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

Expand Down
71 changes: 71 additions & 0 deletions provider/dlp_policy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package provider

import (
"context"

"github.com/google/uuid"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)

type DLPPolicy struct {
ID string `mapstructure:"id"`
SSHAccess bool `mapstructure:"ssh_access"`
WebTerminalAccess bool `mapstructure:"web_terminal_access"`
PortForwardingAccess bool `mapstructure:"port_forwarding_access"`
AllowedApplications []string `mapstructure:"allowed_applications"`
}

func dlpPolicyResource() *schema.Resource {
return &schema.Resource{
SchemaVersion: 1,

Description: "Use this resource to declare a data loss prevention policy. " +
"Reference its `id` from a `coder_agent`'s `dlp_policy` attribute to apply " +
"it to that agent. A single policy may be referenced by any number of agents.",
CreateContext: func(_ context.Context, rd *schema.ResourceData, _ any) diag.Diagnostics {
rd.SetId(uuid.NewString())
return nil
},
ReadContext: schema.NoopContext,
DeleteContext: schema.NoopContext,
Schema: map[string]*schema.Schema{
"id": {
Type: schema.TypeString,
Description: "A unique identifier for this resource.",
Computed: true,
},
"ssh_access": {
Type: schema.TypeBool,
Description: "Whether workspace users may connect to the workspace over SSH.",
Optional: true,
Default: false,
ForceNew: true,
},
"web_terminal_access": {
Type: schema.TypeBool,
Description: "Whether workspace users may open the in-browser web terminal.",
Optional: true,
Default: false,
ForceNew: true,
},
"port_forwarding_access": {
Type: schema.TypeBool,
Description: "Whether workspace users may forward arbitrary TCP ports from the workspace.",
Optional: true,
Default: false,
ForceNew: true,
},
"allowed_applications": {
Type: schema.TypeList,
Description: "Slugs of coder_app resources workspace users are allowed to access. " +
"Apps whose slugs are not in this list are blocked.",
Optional: true,
ForceNew: true,
Elem: &schema.Schema{
Type: schema.TypeString,
},
},
},
}
}
79 changes: 79 additions & 0 deletions provider/dlp_policy_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package provider_test

import (
"testing"

"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
"github.com/stretchr/testify/require"
)

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

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

resource.Test(t, resource.TestCase{
ProviderFactories: coderFactory(),
IsUnitTest: true,
Steps: []resource.TestStep{{
Config: `
provider "coder" {
}
resource "coder_dlp_policy" "test" {
ssh_access = true
web_terminal_access = true
port_forwarding_access = true
allowed_applications = ["code-server", "vscode-desktop"]
}
`,
Check: func(state *terraform.State) error {
require.Len(t, state.Modules, 1)
res := state.Modules[0].Resources["coder_dlp_policy.test"]
require.NotNil(t, res)

require.NotEmpty(t, res.Primary.Attributes["id"])
require.Equal(t, "true", res.Primary.Attributes["ssh_access"])
require.Equal(t, "true", res.Primary.Attributes["web_terminal_access"])
require.Equal(t, "true", res.Primary.Attributes["port_forwarding_access"])
require.Equal(t, "2", res.Primary.Attributes["allowed_applications.#"])
require.Equal(t, "code-server", res.Primary.Attributes["allowed_applications.0"])
require.Equal(t, "vscode-desktop", res.Primary.Attributes["allowed_applications.1"])
return nil
},
}},
})
})

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

resource.Test(t, resource.TestCase{
ProviderFactories: coderFactory(),
IsUnitTest: true,
Steps: []resource.TestStep{{
Config: `
provider "coder" {
}
resource "coder_dlp_policy" "test" {
}
`,
Check: func(state *terraform.State) error {
require.Len(t, state.Modules, 1)
res := state.Modules[0].Resources["coder_dlp_policy.test"]
require.NotNil(t, res)

require.NotEmpty(t, res.Primary.Attributes["id"])
require.Equal(t, "false", res.Primary.Attributes["ssh_access"])
require.Equal(t, "false", res.Primary.Attributes["web_terminal_access"])
require.Equal(t, "false", res.Primary.Attributes["port_forwarding_access"])
// Omitting the field entirely leaves allowed_applications.# blank
// in state rather than "0"; both mean "no allowed apps".
require.Empty(t, res.Primary.Attributes["allowed_applications.#"])
return nil
},
}},
})
})
}
1 change: 1 addition & 0 deletions provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ func New() *schema.Provider {
"coder_env": envResource(),
"coder_devcontainer": devcontainerResource(),
"coder_external_agent": externalAgentResource(),
"coder_dlp_policy": dlpPolicyResource(),
},
}
}
Expand Down
Loading