mirror of
https://github.com/coder/coder.git
synced 2026-06-02 20:48:20 +00:00
feat: add boundary premium feature (#21589)
Source code changes: - Added a wrapper for the boundary subcommand that checks feature entitlement before executing the underlying command. - Added a helper that returns the Boundary version using the runtime/debug package, which reads this information from the go.mod file. - Added FeatureBoundary to the corresponding enum. - Move boundary command from AGPL to enterprise. `NOTE`: From now on, the Boundary version will be specified in go.mod instead of being defined in AI modules.
This commit is contained in:
committed by
GitHub
parent
b82693d4cc
commit
9b14fd3adc
@@ -1,12 +0,0 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
boundarycli "github.com/coder/boundary/cli"
|
||||
"github.com/coder/serpent"
|
||||
)
|
||||
|
||||
func (*RootCmd) boundary() *serpent.Command {
|
||||
cmd := boundarycli.BaseCommand() // Package coder/boundary/cli exports a "base command" designed to be integrated as a subcommand.
|
||||
cmd.Use += " [args...]" // The base command looks like `boundary -- command`. Serpent adds the flags piece, but we need to add the args.
|
||||
return cmd
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
package cli_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
boundarycli "github.com/coder/boundary/cli"
|
||||
"github.com/coder/coder/v2/cli/clitest"
|
||||
"github.com/coder/coder/v2/pty/ptytest"
|
||||
"github.com/coder/coder/v2/testutil"
|
||||
)
|
||||
|
||||
// Actually testing the functionality of coder/boundary takes place in the
|
||||
// coder/boundary repo, since it's a dependency of coder.
|
||||
// Here we want to test basically that integrating it as a subcommand doesn't break anything.
|
||||
func TestBoundarySubcommand(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx := testutil.Context(t, testutil.WaitShort)
|
||||
|
||||
inv, _ := clitest.New(t, "exp", "boundary", "--help")
|
||||
pty := ptytest.New(t).Attach(inv)
|
||||
|
||||
go func() {
|
||||
err := inv.WithContext(ctx).Run()
|
||||
assert.NoError(t, err)
|
||||
}()
|
||||
|
||||
// Expect the --help output to include the short description.
|
||||
// We're simply confirming that `coder boundary --help` ran without a runtime error as
|
||||
// a good chunk of serpents self validation logic happens at runtime.
|
||||
pty.ExpectMatch(boundarycli.BaseCommand().Short)
|
||||
}
|
||||
+6
-1
@@ -151,7 +151,6 @@ func (r *RootCmd) AGPLExperimental() []*serpent.Command {
|
||||
r.promptExample(),
|
||||
r.rptyCommand(),
|
||||
r.syncCommand(),
|
||||
r.boundary(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -333,6 +332,12 @@ func (r *RootCmd) Command(subcommands []*serpent.Command) (*serpent.Command, err
|
||||
// support links.
|
||||
return
|
||||
}
|
||||
if cmd.Name() == "boundary" {
|
||||
// The boundary command is integrated from the boundary package
|
||||
// and has YAML-only options (e.g., allowlist from config file)
|
||||
// that don't have flags or env vars.
|
||||
return
|
||||
}
|
||||
merr = errors.Join(
|
||||
merr,
|
||||
xerrors.Errorf("option %q in %q should have a flag or env", opt.Name, cmd.FullName()),
|
||||
|
||||
@@ -90,6 +90,7 @@ const (
|
||||
FeatureManagedAgentLimit FeatureName = "managed_agent_limit"
|
||||
FeatureWorkspaceExternalAgent FeatureName = "workspace_external_agent"
|
||||
FeatureAIBridge FeatureName = "aibridge"
|
||||
FeatureBoundary FeatureName = "boundary"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -119,6 +120,7 @@ var (
|
||||
FeatureManagedAgentLimit,
|
||||
FeatureWorkspaceExternalAgent,
|
||||
FeatureAIBridge,
|
||||
FeatureBoundary,
|
||||
}
|
||||
|
||||
// FeatureNamesMap is a map of all feature names for quick lookups.
|
||||
@@ -163,6 +165,7 @@ func (n FeatureName) AlwaysEnable() bool {
|
||||
FeatureMultipleOrganizations: true,
|
||||
FeatureWorkspacePrebuilds: true,
|
||||
FeatureWorkspaceExternalAgent: true,
|
||||
FeatureBoundary: true,
|
||||
}[n]
|
||||
}
|
||||
|
||||
|
||||
@@ -1344,6 +1344,11 @@
|
||||
"description": "Toggle auto-update policy for a workspace",
|
||||
"path": "reference/cli/autoupdate.md"
|
||||
},
|
||||
{
|
||||
"title": "boundary",
|
||||
"description": "Network isolation tool for monitoring and restricting HTTP/HTTPS requests",
|
||||
"path": "reference/cli/boundary.md"
|
||||
},
|
||||
{
|
||||
"title": "coder",
|
||||
"path": "reference/cli/index.md"
|
||||
|
||||
Generated
+147
@@ -0,0 +1,147 @@
|
||||
<!-- DO NOT EDIT | GENERATED CONTENT -->
|
||||
# boundary
|
||||
|
||||
Network isolation tool for monitoring and restricting HTTP/HTTPS requests
|
||||
|
||||
## Usage
|
||||
|
||||
```console
|
||||
coder boundary [flags] [args...]
|
||||
```
|
||||
|
||||
## Description
|
||||
|
||||
```console
|
||||
boundary creates an isolated network environment for target processes, intercepting HTTP/HTTPS traffic through a transparent proxy that enforces user-defined allow rules.
|
||||
```
|
||||
|
||||
## Options
|
||||
|
||||
### --config
|
||||
|
||||
| | |
|
||||
|-------------|-------------------------------|
|
||||
| Type | <code>yaml-config-path</code> |
|
||||
| Environment | <code>$BOUNDARY_CONFIG</code> |
|
||||
|
||||
Path to YAML config file.
|
||||
|
||||
### --allow
|
||||
|
||||
| | |
|
||||
|-------------|------------------------------|
|
||||
| Type | <code>string</code> |
|
||||
| Environment | <code>$BOUNDARY_ALLOW</code> |
|
||||
|
||||
Allow rule (repeatable). These are merged with allowlist from config file. Format: "pattern" or "METHOD[,METHOD] pattern".
|
||||
|
||||
### --
|
||||
|
||||
| | |
|
||||
|------|---------------------------|
|
||||
| Type | <code>string-array</code> |
|
||||
| YAML | <code>allowlist</code> |
|
||||
|
||||
Allowlist rules from config file (YAML only).
|
||||
|
||||
### --log-level
|
||||
|
||||
| | |
|
||||
|-------------|----------------------------------|
|
||||
| Type | <code>string</code> |
|
||||
| Environment | <code>$BOUNDARY_LOG_LEVEL</code> |
|
||||
| YAML | <code>log_level</code> |
|
||||
| Default | <code>warn</code> |
|
||||
|
||||
Set log level (error, warn, info, debug).
|
||||
|
||||
### --log-dir
|
||||
|
||||
| | |
|
||||
|-------------|--------------------------------|
|
||||
| Type | <code>string</code> |
|
||||
| Environment | <code>$BOUNDARY_LOG_DIR</code> |
|
||||
| YAML | <code>log_dir</code> |
|
||||
|
||||
Set a directory to write logs to rather than stderr.
|
||||
|
||||
### --proxy-port
|
||||
|
||||
| | |
|
||||
|-------------|--------------------------|
|
||||
| Type | <code>int</code> |
|
||||
| Environment | <code>$PROXY_PORT</code> |
|
||||
| YAML | <code>proxy_port</code> |
|
||||
| Default | <code>8080</code> |
|
||||
|
||||
Set a port for HTTP proxy.
|
||||
|
||||
### --pprof
|
||||
|
||||
| | |
|
||||
|-------------|------------------------------|
|
||||
| Type | <code>bool</code> |
|
||||
| Environment | <code>$BOUNDARY_PPROF</code> |
|
||||
| YAML | <code>pprof_enabled</code> |
|
||||
|
||||
Enable pprof profiling server.
|
||||
|
||||
### --pprof-port
|
||||
|
||||
| | |
|
||||
|-------------|-----------------------------------|
|
||||
| Type | <code>int</code> |
|
||||
| Environment | <code>$BOUNDARY_PPROF_PORT</code> |
|
||||
| YAML | <code>pprof_port</code> |
|
||||
| Default | <code>6060</code> |
|
||||
|
||||
Set port for pprof profiling server.
|
||||
|
||||
### --configure-dns-for-local-stub-resolver
|
||||
|
||||
| | |
|
||||
|-------------|--------------------------------------------------------------|
|
||||
| Type | <code>bool</code> |
|
||||
| Environment | <code>$BOUNDARY_CONFIGURE_DNS_FOR_LOCAL_STUB_RESOLVER</code> |
|
||||
| YAML | <code>configure_dns_for_local_stub_resolver</code> |
|
||||
|
||||
Configure DNS for local stub resolver (e.g., systemd-resolved). Only needed when /etc/resolv.conf contains nameserver 127.0.0.53.
|
||||
|
||||
### --jail-type
|
||||
|
||||
| | |
|
||||
|-------------|----------------------------------|
|
||||
| Type | <code>string</code> |
|
||||
| Environment | <code>$BOUNDARY_JAIL_TYPE</code> |
|
||||
| YAML | <code>jail_type</code> |
|
||||
| Default | <code>nsjail</code> |
|
||||
|
||||
Jail type to use for network isolation. Options: nsjail (default), landjail.
|
||||
|
||||
### --disable-audit-logs
|
||||
|
||||
| | |
|
||||
|-------------|----------------------------------|
|
||||
| Type | <code>bool</code> |
|
||||
| Environment | <code>$DISABLE_AUDIT_LOGS</code> |
|
||||
| YAML | <code>disable_audit_logs</code> |
|
||||
|
||||
Disable sending of audit logs to the workspace agent when set to true.
|
||||
|
||||
### --log-proxy-socket-path
|
||||
|
||||
| | |
|
||||
|-------------|----------------------------------------------------------|
|
||||
| Type | <code>string</code> |
|
||||
| Environment | <code>$CODER_AGENT_BOUNDARY_LOG_PROXY_SOCKET_PATH</code> |
|
||||
| Default | <code>/tmp/boundary-audit.sock</code> |
|
||||
|
||||
Path to the socket where the boundary log proxy server listens for audit logs.
|
||||
|
||||
### --version
|
||||
|
||||
| | |
|
||||
|------|-------------------|
|
||||
| Type | <code>bool</code> |
|
||||
|
||||
Print version information and exit.
|
||||
Generated
+1
@@ -65,6 +65,7 @@ Coder — A tool for provisioning self-hosted development environments with Terr
|
||||
| [<code>support</code>](./support.md) | Commands for troubleshooting issues with a Coder deployment. |
|
||||
| [<code>server</code>](./server.md) | Start a Coder server |
|
||||
| [<code>provisioner</code>](./provisioner.md) | View and manage provisioner daemons and jobs |
|
||||
| [<code>boundary</code>](./boundary.md) | Network isolation tool for monitoring and restricting HTTP/HTTPS requests |
|
||||
| [<code>features</code>](./features.md) | List Enterprise features |
|
||||
| [<code>licenses</code>](./licenses.md) | Add, delete, and list licenses |
|
||||
| [<code>groups</code>](./groups.md) | Manage groups |
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"os"
|
||||
"runtime/debug"
|
||||
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
boundarycli "github.com/coder/boundary/cli"
|
||||
"github.com/coder/coder/v2/codersdk"
|
||||
"github.com/coder/serpent"
|
||||
)
|
||||
|
||||
func isChild() bool {
|
||||
return os.Getenv("CHILD") == "true"
|
||||
}
|
||||
|
||||
func getBoundaryVersion() string {
|
||||
const boundaryModulePath = "github.com/coder/boundary"
|
||||
|
||||
buildInfo, ok := debug.ReadBuildInfo()
|
||||
if !ok {
|
||||
return "unknown"
|
||||
}
|
||||
|
||||
for _, module := range buildInfo.Deps {
|
||||
if module.Path == boundaryModulePath {
|
||||
return module.Version
|
||||
}
|
||||
}
|
||||
|
||||
return "unknown"
|
||||
}
|
||||
|
||||
func (r *RootCmd) verifyLicense(inv *serpent.Invocation) error {
|
||||
client, err := r.InitClient(inv)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
entitlements, err := client.Entitlements(inv.Context())
|
||||
if cerr, ok := codersdk.AsError(err); ok && cerr.StatusCode() == http.StatusNotFound {
|
||||
return xerrors.Errorf("your deployment appears to be an AGPL deployment, so you cannot use the boundary command")
|
||||
} else if err != nil {
|
||||
return xerrors.Errorf("failed to get entitlements: %w", err)
|
||||
}
|
||||
|
||||
feature := entitlements.Features[codersdk.FeatureBoundary]
|
||||
if feature.Entitlement == codersdk.EntitlementNotEntitled {
|
||||
return xerrors.Errorf("your license is not entitled to use the boundary feature")
|
||||
}
|
||||
if !feature.Enabled {
|
||||
// Feature is entitled but disabled (shouldn't happen for FeatureBoundary
|
||||
// since it's in AlwaysEnable(), but handle it gracefully).
|
||||
return xerrors.Errorf("the boundary feature is disabled in your deployment configuration")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *RootCmd) boundary() *serpent.Command {
|
||||
version := getBoundaryVersion()
|
||||
cmd := boundarycli.BaseCommand(version) // Package coder/boundary/cli exports a "base command" designed to be integrated as a subcommand.
|
||||
cmd.Use += " [args...]" // The base command looks like `boundary -- command`. Serpent adds the flags piece, but we need to add the args.
|
||||
|
||||
// Wrap the handler to check for FeatureBoundary entitlement.
|
||||
originalHandler := cmd.Handler
|
||||
cmd.Handler = func(inv *serpent.Invocation) error {
|
||||
// Boundary re-executes itself with CHILD=true to run the target process
|
||||
// inside a jailed network namespace. Skip the license check for child
|
||||
// processes since the parent already verified entitlement.
|
||||
if isChild() {
|
||||
return originalHandler(inv)
|
||||
}
|
||||
|
||||
if err := r.verifyLicense(inv); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Call the original handler if entitlement check passes.
|
||||
return originalHandler(inv)
|
||||
}
|
||||
|
||||
return cmd
|
||||
}
|
||||
@@ -0,0 +1,305 @@
|
||||
package cli_test
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
boundarycli "github.com/coder/boundary/cli"
|
||||
"github.com/coder/coder/v2/cli/clitest"
|
||||
"github.com/coder/coder/v2/coderd/coderdtest"
|
||||
"github.com/coder/coder/v2/coderd/httpapi"
|
||||
"github.com/coder/coder/v2/codersdk"
|
||||
"github.com/coder/coder/v2/enterprise/coderd/coderdenttest"
|
||||
"github.com/coder/coder/v2/enterprise/coderd/license"
|
||||
"github.com/coder/coder/v2/pty/ptytest"
|
||||
"github.com/coder/coder/v2/testutil"
|
||||
)
|
||||
|
||||
// Actually testing the functionality of coder/boundary takes place in the
|
||||
// coder/boundary repo, since it's a dependency of coder.
|
||||
// Here we want to test basically that integrating it as a subcommand doesn't break anything.
|
||||
func TestBoundarySubcommand(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx := testutil.Context(t, testutil.WaitShort)
|
||||
|
||||
inv, _ := newCLI(t, "boundary", "--help")
|
||||
pty := ptytest.New(t).Attach(inv)
|
||||
|
||||
go func() {
|
||||
err := inv.WithContext(ctx).Run()
|
||||
assert.NoError(t, err)
|
||||
}()
|
||||
|
||||
// Expect the --help output to include the short description.
|
||||
// We're simply confirming that `coder boundary --help` ran without a runtime error as
|
||||
// a good chunk of serpents self validation logic happens at runtime.
|
||||
pty.ExpectMatch(boundarycli.BaseCommand("dev").Short)
|
||||
}
|
||||
|
||||
func TestBoundaryLicenseVerification(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("EntitledAndEnabled", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
client, _ := coderdenttest.New(t, &coderdenttest.Options{
|
||||
LicenseOptions: &coderdenttest.LicenseOptions{
|
||||
Features: license.Features{
|
||||
codersdk.FeatureBoundary: 1,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
inv, conf := newCLI(t, "boundary", "--version")
|
||||
//nolint:gocritic // requires owner
|
||||
clitest.SetupConfig(t, client, conf)
|
||||
|
||||
ctx := testutil.Context(t, testutil.WaitShort)
|
||||
err := inv.WithContext(ctx).Run()
|
||||
// Should succeed - boundary --version should work with valid license.
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("NotEntitled", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Create a proxy server that returns entitlements without boundary feature.
|
||||
client, _ := coderdenttest.New(t, &coderdenttest.Options{
|
||||
LicenseOptions: &coderdenttest.LicenseOptions{
|
||||
Features: license.Features{
|
||||
// No FeatureBoundary
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
proxy := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path == "/api/v2/entitlements" {
|
||||
res := codersdk.Entitlements{
|
||||
Features: map[codersdk.FeatureName]codersdk.Feature{},
|
||||
Warnings: []string{},
|
||||
Errors: []string{},
|
||||
HasLicense: true,
|
||||
Trial: false,
|
||||
RequireTelemetry: false,
|
||||
}
|
||||
// Set boundary to not entitled, all other features to entitled.
|
||||
for _, feature := range codersdk.FeatureNames {
|
||||
if feature == codersdk.FeatureBoundary {
|
||||
// Explicitly set boundary to not entitled.
|
||||
res.Features[feature] = codersdk.Feature{
|
||||
Entitlement: codersdk.EntitlementNotEntitled,
|
||||
Enabled: false,
|
||||
}
|
||||
} else {
|
||||
res.Features[feature] = codersdk.Feature{
|
||||
Entitlement: codersdk.EntitlementEntitled,
|
||||
Enabled: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
httpapi.Write(r.Context(), w, http.StatusOK, res)
|
||||
return
|
||||
}
|
||||
|
||||
// Otherwise, proxy the request to the real API server.
|
||||
rp := httputil.NewSingleHostReverseProxy(client.URL)
|
||||
tp := &http.Transport{}
|
||||
defer tp.CloseIdleConnections()
|
||||
rp.Transport = tp
|
||||
rp.ServeHTTP(w, r)
|
||||
}))
|
||||
defer proxy.Close()
|
||||
|
||||
proxyURL, err := url.Parse(proxy.URL)
|
||||
require.NoError(t, err)
|
||||
proxyClient := codersdk.New(proxyURL)
|
||||
proxyClient.SetSessionToken(client.SessionToken())
|
||||
t.Cleanup(proxyClient.HTTPClient.CloseIdleConnections)
|
||||
|
||||
inv, conf := newCLI(t, "boundary", "--version")
|
||||
clitest.SetupConfig(t, proxyClient, conf)
|
||||
|
||||
ctx := testutil.Context(t, testutil.WaitShort)
|
||||
err = inv.WithContext(ctx).Run()
|
||||
require.Error(t, err)
|
||||
require.ErrorContains(t, err, "your license is not entitled to use the boundary feature")
|
||||
})
|
||||
|
||||
t.Run("FeatureDisabled", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Create a proxy server that returns entitlements with boundary disabled.
|
||||
client, _ := coderdenttest.New(t, &coderdenttest.Options{
|
||||
LicenseOptions: &coderdenttest.LicenseOptions{
|
||||
Features: license.Features{
|
||||
codersdk.FeatureBoundary: 1,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
proxy := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path == "/api/v2/entitlements" {
|
||||
res := codersdk.Entitlements{
|
||||
Features: map[codersdk.FeatureName]codersdk.Feature{},
|
||||
Warnings: []string{},
|
||||
Errors: []string{},
|
||||
HasLicense: true,
|
||||
Trial: false,
|
||||
RequireTelemetry: false,
|
||||
}
|
||||
for _, feature := range codersdk.FeatureNames {
|
||||
if feature == codersdk.FeatureBoundary {
|
||||
// Feature is entitled but disabled.
|
||||
res.Features[feature] = codersdk.Feature{
|
||||
Entitlement: codersdk.EntitlementEntitled,
|
||||
Enabled: false,
|
||||
}
|
||||
} else {
|
||||
res.Features[feature] = codersdk.Feature{
|
||||
Entitlement: codersdk.EntitlementEntitled,
|
||||
Enabled: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
httpapi.Write(r.Context(), w, http.StatusOK, res)
|
||||
return
|
||||
}
|
||||
|
||||
// Otherwise, proxy the request to the real API server.
|
||||
rp := httputil.NewSingleHostReverseProxy(client.URL)
|
||||
tp := &http.Transport{}
|
||||
defer tp.CloseIdleConnections()
|
||||
rp.Transport = tp
|
||||
rp.ServeHTTP(w, r)
|
||||
}))
|
||||
defer proxy.Close()
|
||||
|
||||
proxyURL, err := url.Parse(proxy.URL)
|
||||
require.NoError(t, err)
|
||||
proxyClient := codersdk.New(proxyURL)
|
||||
proxyClient.SetSessionToken(client.SessionToken())
|
||||
t.Cleanup(proxyClient.HTTPClient.CloseIdleConnections)
|
||||
|
||||
inv, conf := newCLI(t, "boundary", "--version")
|
||||
clitest.SetupConfig(t, proxyClient, conf)
|
||||
|
||||
ctx := testutil.Context(t, testutil.WaitShort)
|
||||
err = inv.WithContext(ctx).Run()
|
||||
require.Error(t, err)
|
||||
require.ErrorContains(t, err, "the boundary feature is disabled in your deployment configuration")
|
||||
})
|
||||
|
||||
t.Run("AGPLDeployment", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Create an AGPL server (no enterprise features).
|
||||
client := coderdtest.New(t, &coderdtest.Options{})
|
||||
|
||||
proxy := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path == "/api/v2/entitlements" {
|
||||
// AGPL deployments return 404 for entitlements endpoint.
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
// Otherwise, proxy the request to the real API server.
|
||||
rp := httputil.NewSingleHostReverseProxy(client.URL)
|
||||
tp := &http.Transport{}
|
||||
defer tp.CloseIdleConnections()
|
||||
rp.Transport = tp
|
||||
rp.ServeHTTP(w, r)
|
||||
}))
|
||||
defer proxy.Close()
|
||||
|
||||
proxyURL, err := url.Parse(proxy.URL)
|
||||
require.NoError(t, err)
|
||||
proxyClient := codersdk.New(proxyURL)
|
||||
proxyClient.SetSessionToken(client.SessionToken())
|
||||
t.Cleanup(proxyClient.HTTPClient.CloseIdleConnections)
|
||||
|
||||
inv, conf := newCLI(t, "boundary", "--version")
|
||||
clitest.SetupConfig(t, proxyClient, conf)
|
||||
|
||||
ctx := testutil.Context(t, testutil.WaitShort)
|
||||
err = inv.WithContext(ctx).Run()
|
||||
require.Error(t, err)
|
||||
require.ErrorContains(t, err, "your deployment appears to be an AGPL deployment")
|
||||
})
|
||||
}
|
||||
|
||||
// TestBoundaryChildProcessSkipsCheck verifies that when CHILD=true, the license
|
||||
// check is skipped. This simulates boundary re-executing itself to run the
|
||||
// target process. We use a proxy that would fail the license check to verify
|
||||
// it's skipped.
|
||||
func TestBoundaryChildProcessSkipsCheck(t *testing.T) {
|
||||
// Cannot use t.Parallel() with t.Setenv().
|
||||
client, _ := coderdenttest.New(t, &coderdenttest.Options{
|
||||
LicenseOptions: &coderdenttest.LicenseOptions{
|
||||
Features: license.Features{
|
||||
// No FeatureBoundary - would normally fail
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
proxy := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path == "/api/v2/entitlements" {
|
||||
// Return not entitled for boundary - this would normally cause failure.
|
||||
res := codersdk.Entitlements{
|
||||
Features: map[codersdk.FeatureName]codersdk.Feature{},
|
||||
Warnings: []string{},
|
||||
Errors: []string{},
|
||||
HasLicense: true,
|
||||
Trial: false,
|
||||
RequireTelemetry: false,
|
||||
}
|
||||
for _, feature := range codersdk.FeatureNames {
|
||||
if feature == codersdk.FeatureBoundary {
|
||||
res.Features[feature] = codersdk.Feature{
|
||||
Entitlement: codersdk.EntitlementNotEntitled,
|
||||
Enabled: false,
|
||||
}
|
||||
} else {
|
||||
res.Features[feature] = codersdk.Feature{
|
||||
Entitlement: codersdk.EntitlementEntitled,
|
||||
Enabled: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
httpapi.Write(r.Context(), w, http.StatusOK, res)
|
||||
return
|
||||
}
|
||||
|
||||
// Otherwise, proxy the request to the real API server.
|
||||
rp := httputil.NewSingleHostReverseProxy(client.URL)
|
||||
tp := &http.Transport{}
|
||||
defer tp.CloseIdleConnections()
|
||||
rp.Transport = tp
|
||||
rp.ServeHTTP(w, r)
|
||||
}))
|
||||
defer proxy.Close()
|
||||
|
||||
proxyURL, err := url.Parse(proxy.URL)
|
||||
require.NoError(t, err)
|
||||
proxyClient := codersdk.New(proxyURL)
|
||||
proxyClient.SetSessionToken(client.SessionToken())
|
||||
t.Cleanup(proxyClient.HTTPClient.CloseIdleConnections)
|
||||
|
||||
inv, conf := newCLI(t, "boundary", "--version")
|
||||
clitest.SetupConfig(t, proxyClient, conf)
|
||||
|
||||
// Set CHILD=true to simulate boundary re-execution. This should skip the
|
||||
// license check, so the command should succeed even though the proxy would
|
||||
// return "not entitled".
|
||||
t.Setenv("CHILD", "true")
|
||||
|
||||
ctx := testutil.Context(t, testutil.WaitShort)
|
||||
err = inv.WithContext(ctx).Run()
|
||||
// Should succeed because license check is skipped for child processes.
|
||||
require.NoError(t, err)
|
||||
}
|
||||
@@ -18,6 +18,7 @@ func (r *RootCmd) enterpriseOnly() []*serpent.Command {
|
||||
agplcli.ExperimentalCommand(append(r.AGPLExperimental(), r.enterpriseExperimental()...)),
|
||||
|
||||
// New commands that don't exist in AGPL:
|
||||
r.boundary(),
|
||||
r.workspaceProxy(),
|
||||
r.features(),
|
||||
r.licenses(),
|
||||
|
||||
+2
@@ -15,6 +15,8 @@ USAGE:
|
||||
|
||||
SUBCOMMANDS:
|
||||
aibridge Manage AI Bridge.
|
||||
boundary Network isolation tool for monitoring and restricting
|
||||
HTTP/HTTPS requests
|
||||
external-workspaces Create or manage external workspaces
|
||||
features List Enterprise features
|
||||
groups Manage groups
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
coder v0.0.0-devel
|
||||
|
||||
USAGE:
|
||||
coder boundary [flags] [args...]
|
||||
|
||||
Network isolation tool for monitoring and restricting HTTP/HTTPS requests
|
||||
|
||||
boundary creates an isolated network environment for target processes,
|
||||
intercepting HTTP/HTTPS traffic through a transparent proxy that enforces
|
||||
user-defined allow rules.
|
||||
|
||||
OPTIONS:
|
||||
--allow string, $BOUNDARY_ALLOW
|
||||
Allow rule (repeatable). These are merged with allowlist from config
|
||||
file. Format: "pattern" or "METHOD[,METHOD] pattern".
|
||||
|
||||
string-array
|
||||
Allowlist rules from config file (YAML only).
|
||||
|
||||
--config yaml-config-path, $BOUNDARY_CONFIG
|
||||
Path to YAML config file.
|
||||
|
||||
--configure-dns-for-local-stub-resolver bool, $BOUNDARY_CONFIGURE_DNS_FOR_LOCAL_STUB_RESOLVER
|
||||
Configure DNS for local stub resolver (e.g., systemd-resolved). Only
|
||||
needed when /etc/resolv.conf contains nameserver 127.0.0.53.
|
||||
|
||||
--disable-audit-logs bool, $DISABLE_AUDIT_LOGS
|
||||
Disable sending of audit logs to the workspace agent when set to true.
|
||||
|
||||
--jail-type string, $BOUNDARY_JAIL_TYPE (default: nsjail)
|
||||
Jail type to use for network isolation. Options: nsjail (default),
|
||||
landjail.
|
||||
|
||||
--log-dir string, $BOUNDARY_LOG_DIR
|
||||
Set a directory to write logs to rather than stderr.
|
||||
|
||||
--log-level string, $BOUNDARY_LOG_LEVEL (default: warn)
|
||||
Set log level (error, warn, info, debug).
|
||||
|
||||
--log-proxy-socket-path string, $CODER_AGENT_BOUNDARY_LOG_PROXY_SOCKET_PATH (default: /tmp/boundary-audit.sock)
|
||||
Path to the socket where the boundary log proxy server listens for
|
||||
audit logs.
|
||||
|
||||
--pprof bool, $BOUNDARY_PPROF
|
||||
Enable pprof profiling server.
|
||||
|
||||
--pprof-port int, $BOUNDARY_PPROF_PORT (default: 6060)
|
||||
Set port for pprof profiling server.
|
||||
|
||||
--proxy-port int, $PROXY_PORT (default: 8080)
|
||||
Set a port for HTTP proxy.
|
||||
|
||||
--version bool
|
||||
Print version information and exit.
|
||||
|
||||
———
|
||||
Run `coder --help` for a list of global options.
|
||||
@@ -453,7 +453,7 @@ require (
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
howett.net/plist v1.0.0 // indirect
|
||||
kernel.org/pub/linux/libs/security/libcap/psx v1.2.73 // indirect
|
||||
kernel.org/pub/linux/libs/security/libcap/psx v1.2.77 // indirect
|
||||
sigs.k8s.io/yaml v1.5.0 // indirect
|
||||
)
|
||||
|
||||
@@ -475,7 +475,7 @@ require (
|
||||
github.com/coder/agentapi-sdk-go v0.0.0-20250505131810-560d1d88d225
|
||||
github.com/coder/aibridge v0.3.1-0.20260121122740-e164b504fc52
|
||||
github.com/coder/aisdk-go v0.0.9
|
||||
github.com/coder/boundary v0.0.1-alpha
|
||||
github.com/coder/boundary v0.6.0
|
||||
github.com/coder/preview v1.0.4
|
||||
github.com/danieljoos/wincred v1.2.3
|
||||
github.com/dgraph-io/ristretto/v2 v2.3.0
|
||||
@@ -541,6 +541,7 @@ require (
|
||||
github.com/jackmordaunt/icns/v3 v3.0.1 // indirect
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.10 // indirect
|
||||
github.com/landlock-lsm/go-landlock v0.0.0-20251103212306-430f8e5cd97c // indirect
|
||||
github.com/mattn/go-shellwords v1.0.12 // indirect
|
||||
github.com/moby/sys/user v0.4.0 // indirect
|
||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect
|
||||
|
||||
@@ -931,8 +931,8 @@ github.com/coder/aibridge v0.3.1-0.20260121122740-e164b504fc52 h1:UcsOXQH881tXPp
|
||||
github.com/coder/aibridge v0.3.1-0.20260121122740-e164b504fc52/go.mod h1:x45BE/NNDesDN1eWy4bsg81QsL6ou7xXPIeQr0ePETQ=
|
||||
github.com/coder/aisdk-go v0.0.9 h1:Vzo/k2qwVGLTR10ESDeP2Ecek1SdPfZlEjtTfMveiVo=
|
||||
github.com/coder/aisdk-go v0.0.9/go.mod h1:KF6/Vkono0FJJOtWtveh5j7yfNrSctVTpwgweYWSp5M=
|
||||
github.com/coder/boundary v0.0.1-alpha h1:6shUQ2zkrWrfbgVcqWvpV2ibljOQvPvYqTctWBkKoUA=
|
||||
github.com/coder/boundary v0.0.1-alpha/go.mod h1:d1AMFw81rUgrGHuZzWdPNhkY0G8w7pvLNLYF0e3ceC4=
|
||||
github.com/coder/boundary v0.6.0 h1:DfYVBIH8/6EBfg9I0qz7rX2jo+4blUx4P4amd13nib8=
|
||||
github.com/coder/boundary v0.6.0/go.mod h1:jEXVbTGQP9JFoXkyzsnitj2rsWJuTt+VVej1Yzr2CkQ=
|
||||
github.com/coder/bubbletea v1.2.2-0.20241212190825-007a1cdb2c41 h1:SBN/DA63+ZHwuWwPHPYoCZ/KLAjHv5g4h2MS4f2/MTI=
|
||||
github.com/coder/bubbletea v1.2.2-0.20241212190825-007a1cdb2c41/go.mod h1:I9ULxr64UaOSUv7hcb3nX4kowodJCVS7vt7VVJk/kW4=
|
||||
github.com/coder/clistat v1.2.0 h1:37KJKqiCllJsRvWqTHf3qiLIXX0JB6oqE5oxcqgdLkY=
|
||||
@@ -1544,6 +1544,8 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0
|
||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||
github.com/kyokomi/emoji/v2 v2.2.13 h1:GhTfQa67venUUvmleTNFnb+bi7S3aocF7ZCXU9fSO7U=
|
||||
github.com/kyokomi/emoji/v2 v2.2.13/go.mod h1:JUcn42DTdsXJo1SWanHh4HKDEyPaR5CqkmoirZZP9qE=
|
||||
github.com/landlock-lsm/go-landlock v0.0.0-20251103212306-430f8e5cd97c h1:QcKqiunpt7hooa/xIx0iyepA6Cs2BgKexaYOxHvHNCs=
|
||||
github.com/landlock-lsm/go-landlock v0.0.0-20251103212306-430f8e5cd97c/go.mod h1:stwyhp9tfeEy3A4bRJLdOEvjW/CetRJg/vcijNG8M5A=
|
||||
github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80 h1:6Yzfa6GP0rIo/kULo2bwGEkFvCePZ3qHDDTC3/J9Swo=
|
||||
github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs=
|
||||
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||
@@ -2847,8 +2849,9 @@ k8s.io/utils v0.0.0-20241210054802-24370beab758 h1:sdbE21q2nlQtFh65saZY+rRM6x6aJ
|
||||
k8s.io/utils v0.0.0-20241210054802-24370beab758/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
|
||||
kernel.org/pub/linux/libs/security/libcap/cap v1.2.73 h1:Th2b8jljYqkyZKS3aD3N9VpYsQpHuXLgea+SZUIfODA=
|
||||
kernel.org/pub/linux/libs/security/libcap/cap v1.2.73/go.mod h1:hbeKwKcboEsxARYmcy/AdPVN11wmT/Wnpgv4k4ftyqY=
|
||||
kernel.org/pub/linux/libs/security/libcap/psx v1.2.73 h1:SEAEUiPVylTD4vqqi+vtGkSnXeP2FcRO3FoZB1MklMw=
|
||||
kernel.org/pub/linux/libs/security/libcap/psx v1.2.73/go.mod h1:+l6Ee2F59XiJ2I6WR5ObpC1utCQJZ/VLsEbQCD8RG24=
|
||||
kernel.org/pub/linux/libs/security/libcap/psx v1.2.77 h1:Z06sMOzc0GNCwp6efaVrIrz4ywGJ1v+DP0pjVkOfDuA=
|
||||
kernel.org/pub/linux/libs/security/libcap/psx v1.2.77/go.mod h1:+l6Ee2F59XiJ2I6WR5ObpC1utCQJZ/VLsEbQCD8RG24=
|
||||
lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
|
||||
lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
|
||||
modernc.org/cc/v3 v3.36.0/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI=
|
||||
|
||||
Generated
+2
@@ -2110,6 +2110,7 @@ export type FeatureName =
|
||||
| "advanced_template_scheduling"
|
||||
| "appearance"
|
||||
| "audit_log"
|
||||
| "boundary"
|
||||
| "browser_only"
|
||||
| "connection_log"
|
||||
| "control_shared_ports"
|
||||
@@ -2136,6 +2137,7 @@ export const FeatureNames: FeatureName[] = [
|
||||
"advanced_template_scheduling",
|
||||
"appearance",
|
||||
"audit_log",
|
||||
"boundary",
|
||||
"browser_only",
|
||||
"connection_log",
|
||||
"control_shared_ports",
|
||||
|
||||
Reference in New Issue
Block a user