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.promptExample(),
|
||||||
r.rptyCommand(),
|
r.rptyCommand(),
|
||||||
r.syncCommand(),
|
r.syncCommand(),
|
||||||
r.boundary(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -333,6 +332,12 @@ func (r *RootCmd) Command(subcommands []*serpent.Command) (*serpent.Command, err
|
|||||||
// support links.
|
// support links.
|
||||||
return
|
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 = errors.Join(
|
||||||
merr,
|
merr,
|
||||||
xerrors.Errorf("option %q in %q should have a flag or env", opt.Name, cmd.FullName()),
|
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"
|
FeatureManagedAgentLimit FeatureName = "managed_agent_limit"
|
||||||
FeatureWorkspaceExternalAgent FeatureName = "workspace_external_agent"
|
FeatureWorkspaceExternalAgent FeatureName = "workspace_external_agent"
|
||||||
FeatureAIBridge FeatureName = "aibridge"
|
FeatureAIBridge FeatureName = "aibridge"
|
||||||
|
FeatureBoundary FeatureName = "boundary"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -119,6 +120,7 @@ var (
|
|||||||
FeatureManagedAgentLimit,
|
FeatureManagedAgentLimit,
|
||||||
FeatureWorkspaceExternalAgent,
|
FeatureWorkspaceExternalAgent,
|
||||||
FeatureAIBridge,
|
FeatureAIBridge,
|
||||||
|
FeatureBoundary,
|
||||||
}
|
}
|
||||||
|
|
||||||
// FeatureNamesMap is a map of all feature names for quick lookups.
|
// FeatureNamesMap is a map of all feature names for quick lookups.
|
||||||
@@ -163,6 +165,7 @@ func (n FeatureName) AlwaysEnable() bool {
|
|||||||
FeatureMultipleOrganizations: true,
|
FeatureMultipleOrganizations: true,
|
||||||
FeatureWorkspacePrebuilds: true,
|
FeatureWorkspacePrebuilds: true,
|
||||||
FeatureWorkspaceExternalAgent: true,
|
FeatureWorkspaceExternalAgent: true,
|
||||||
|
FeatureBoundary: true,
|
||||||
}[n]
|
}[n]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1344,6 +1344,11 @@
|
|||||||
"description": "Toggle auto-update policy for a workspace",
|
"description": "Toggle auto-update policy for a workspace",
|
||||||
"path": "reference/cli/autoupdate.md"
|
"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",
|
"title": "coder",
|
||||||
"path": "reference/cli/index.md"
|
"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>support</code>](./support.md) | Commands for troubleshooting issues with a Coder deployment. |
|
||||||
| [<code>server</code>](./server.md) | Start a Coder server |
|
| [<code>server</code>](./server.md) | Start a Coder server |
|
||||||
| [<code>provisioner</code>](./provisioner.md) | View and manage provisioner daemons and jobs |
|
| [<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>features</code>](./features.md) | List Enterprise features |
|
||||||
| [<code>licenses</code>](./licenses.md) | Add, delete, and list licenses |
|
| [<code>licenses</code>](./licenses.md) | Add, delete, and list licenses |
|
||||||
| [<code>groups</code>](./groups.md) | Manage groups |
|
| [<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()...)),
|
agplcli.ExperimentalCommand(append(r.AGPLExperimental(), r.enterpriseExperimental()...)),
|
||||||
|
|
||||||
// New commands that don't exist in AGPL:
|
// New commands that don't exist in AGPL:
|
||||||
|
r.boundary(),
|
||||||
r.workspaceProxy(),
|
r.workspaceProxy(),
|
||||||
r.features(),
|
r.features(),
|
||||||
r.licenses(),
|
r.licenses(),
|
||||||
|
|||||||
+2
@@ -15,6 +15,8 @@ USAGE:
|
|||||||
|
|
||||||
SUBCOMMANDS:
|
SUBCOMMANDS:
|
||||||
aibridge Manage AI Bridge.
|
aibridge Manage AI Bridge.
|
||||||
|
boundary Network isolation tool for monitoring and restricting
|
||||||
|
HTTP/HTTPS requests
|
||||||
external-workspaces Create or manage external workspaces
|
external-workspaces Create or manage external workspaces
|
||||||
features List Enterprise features
|
features List Enterprise features
|
||||||
groups Manage groups
|
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
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b // indirect
|
||||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||||
howett.net/plist v1.0.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
|
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/agentapi-sdk-go v0.0.0-20250505131810-560d1d88d225
|
||||||
github.com/coder/aibridge v0.3.1-0.20260121122740-e164b504fc52
|
github.com/coder/aibridge v0.3.1-0.20260121122740-e164b504fc52
|
||||||
github.com/coder/aisdk-go v0.0.9
|
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/coder/preview v1.0.4
|
||||||
github.com/danieljoos/wincred v1.2.3
|
github.com/danieljoos/wincred v1.2.3
|
||||||
github.com/dgraph-io/ristretto/v2 v2.3.0
|
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/jackmordaunt/icns/v3 v3.0.1 // indirect
|
||||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
|
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
|
||||||
github.com/klauspost/cpuid/v2 v2.2.10 // 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/mattn/go-shellwords v1.0.12 // indirect
|
||||||
github.com/moby/sys/user v0.4.0 // indirect
|
github.com/moby/sys/user v0.4.0 // indirect
|
||||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // 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/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 h1:Vzo/k2qwVGLTR10ESDeP2Ecek1SdPfZlEjtTfMveiVo=
|
||||||
github.com/coder/aisdk-go v0.0.9/go.mod h1:KF6/Vkono0FJJOtWtveh5j7yfNrSctVTpwgweYWSp5M=
|
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.6.0 h1:DfYVBIH8/6EBfg9I0qz7rX2jo+4blUx4P4amd13nib8=
|
||||||
github.com/coder/boundary v0.0.1-alpha/go.mod h1:d1AMFw81rUgrGHuZzWdPNhkY0G8w7pvLNLYF0e3ceC4=
|
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 h1:SBN/DA63+ZHwuWwPHPYoCZ/KLAjHv5g4h2MS4f2/MTI=
|
||||||
github.com/coder/bubbletea v1.2.2-0.20241212190825-007a1cdb2c41/go.mod h1:I9ULxr64UaOSUv7hcb3nX4kowodJCVS7vt7VVJk/kW4=
|
github.com/coder/bubbletea v1.2.2-0.20241212190825-007a1cdb2c41/go.mod h1:I9ULxr64UaOSUv7hcb3nX4kowodJCVS7vt7VVJk/kW4=
|
||||||
github.com/coder/clistat v1.2.0 h1:37KJKqiCllJsRvWqTHf3qiLIXX0JB6oqE5oxcqgdLkY=
|
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/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 h1:GhTfQa67venUUvmleTNFnb+bi7S3aocF7ZCXU9fSO7U=
|
||||||
github.com/kyokomi/emoji/v2 v2.2.13/go.mod h1:JUcn42DTdsXJo1SWanHh4HKDEyPaR5CqkmoirZZP9qE=
|
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 h1:6Yzfa6GP0rIo/kULo2bwGEkFvCePZ3qHDDTC3/J9Swo=
|
||||||
github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs=
|
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=
|
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=
|
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 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/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.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.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
|
||||||
lukechampine.com/uint128 v1.2.0/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=
|
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"
|
| "advanced_template_scheduling"
|
||||||
| "appearance"
|
| "appearance"
|
||||||
| "audit_log"
|
| "audit_log"
|
||||||
|
| "boundary"
|
||||||
| "browser_only"
|
| "browser_only"
|
||||||
| "connection_log"
|
| "connection_log"
|
||||||
| "control_shared_ports"
|
| "control_shared_ports"
|
||||||
@@ -2136,6 +2137,7 @@ export const FeatureNames: FeatureName[] = [
|
|||||||
"advanced_template_scheduling",
|
"advanced_template_scheduling",
|
||||||
"appearance",
|
"appearance",
|
||||||
"audit_log",
|
"audit_log",
|
||||||
|
"boundary",
|
||||||
"browser_only",
|
"browser_only",
|
||||||
"connection_log",
|
"connection_log",
|
||||||
"control_shared_ports",
|
"control_shared_ports",
|
||||||
|
|||||||
Reference in New Issue
Block a user