mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat: add port-sharing backend (#11939)
This commit is contained in:
@@ -15,6 +15,8 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/coder/coder/v2/coderd/appearance"
|
||||
agplportsharing "github.com/coder/coder/v2/coderd/portsharing"
|
||||
"github.com/coder/coder/v2/enterprise/coderd/portsharing"
|
||||
|
||||
"golang.org/x/xerrors"
|
||||
"tailscale.com/tailcfg"
|
||||
@@ -533,6 +535,7 @@ func (api *API) updateEntitlements(ctx context.Context) error {
|
||||
codersdk.FeatureWorkspaceProxy: true,
|
||||
codersdk.FeatureUserRoleManagement: true,
|
||||
codersdk.FeatureAccessControl: true,
|
||||
codersdk.FeatureControlSharedPorts: true,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -690,6 +693,14 @@ func (api *API) updateEntitlements(ctx context.Context) error {
|
||||
}
|
||||
}
|
||||
|
||||
if initial, changed, enabled := featureChanged(codersdk.FeatureControlSharedPorts); shouldUpdate(initial, changed, enabled) {
|
||||
var ps agplportsharing.PortSharer = agplportsharing.DefaultPortSharer
|
||||
if enabled {
|
||||
ps = portsharing.NewEnterprisePortSharer()
|
||||
}
|
||||
api.AGPL.PortSharer.Store(&ps)
|
||||
}
|
||||
|
||||
// External token encryption is soft-enforced
|
||||
featureExternalTokenEncryption := entitlements.Features[codersdk.FeatureExternalTokenEncryption]
|
||||
featureExternalTokenEncryption.Enabled = len(api.ExternalTokenEncryption) > 0
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
package portsharing
|
||||
|
||||
import (
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/coder/coder/v2/coderd/database"
|
||||
"github.com/coder/coder/v2/codersdk"
|
||||
)
|
||||
|
||||
type EnterprisePortSharer struct{}
|
||||
|
||||
func NewEnterprisePortSharer() *EnterprisePortSharer {
|
||||
return &EnterprisePortSharer{}
|
||||
}
|
||||
|
||||
func (EnterprisePortSharer) AuthorizedPortSharingLevel(template database.Template, level codersdk.WorkspaceAgentPortShareLevel) error {
|
||||
max := codersdk.WorkspaceAgentPortShareLevel(template.MaxPortSharingLevel)
|
||||
switch level {
|
||||
case codersdk.WorkspaceAgentPortShareLevelPublic:
|
||||
if max != codersdk.WorkspaceAgentPortShareLevelPublic {
|
||||
return xerrors.Errorf("port sharing level not allowed. Max level is '%s'", max)
|
||||
}
|
||||
case codersdk.WorkspaceAgentPortShareLevelAuthenticated:
|
||||
if max == codersdk.WorkspaceAgentPortShareLevelOwner {
|
||||
return xerrors.Errorf("port sharing level not allowed. Max level is '%s'", max)
|
||||
}
|
||||
default:
|
||||
return xerrors.New("port sharing level is invalid.")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (EnterprisePortSharer) ValidateTemplateMaxPortSharingLevel(level codersdk.WorkspaceAgentPortShareLevel) error {
|
||||
if !level.ValidMaxLevel() {
|
||||
return xerrors.New("invalid max port sharing level, value must be 'authenticated' or 'public'.")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -140,6 +140,43 @@ func TestTemplates(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("MaxPortShareLevel", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
owner, user := coderdenttest.New(t, &coderdenttest.Options{
|
||||
Options: &coderdtest.Options{
|
||||
IncludeProvisionerDaemon: true,
|
||||
},
|
||||
LicenseOptions: &coderdenttest.LicenseOptions{
|
||||
Features: license.Features{
|
||||
codersdk.FeatureControlSharedPorts: 1,
|
||||
},
|
||||
},
|
||||
})
|
||||
client, _ := coderdtest.CreateAnotherUser(t, owner, user.OrganizationID, rbac.RoleTemplateAdmin())
|
||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
|
||||
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
||||
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
|
||||
defer cancel()
|
||||
|
||||
// OK
|
||||
var level codersdk.WorkspaceAgentPortShareLevel = codersdk.WorkspaceAgentPortShareLevelPublic
|
||||
updated, err := client.UpdateTemplateMeta(ctx, template.ID, codersdk.UpdateTemplateMeta{
|
||||
MaxPortShareLevel: &level,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, level, updated.MaxPortShareLevel)
|
||||
|
||||
// Invalid level
|
||||
level = "invalid"
|
||||
_, err = client.UpdateTemplateMeta(ctx, template.ID, codersdk.UpdateTemplateMeta{
|
||||
MaxPortShareLevel: &level,
|
||||
})
|
||||
require.ErrorContains(t, err, "invalid max port sharing level")
|
||||
})
|
||||
|
||||
t.Run("BlockDisablingAutoOffWithMaxTTL", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
client, user := coderdenttest.New(t, &coderdenttest.Options{
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
package coderd_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/coder/coder/v2/coderd/coderdtest"
|
||||
"github.com/coder/coder/v2/coderd/rbac"
|
||||
"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/testutil"
|
||||
)
|
||||
|
||||
func TestWorkspacePortShare(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
dep := coderdtest.DeploymentValues(t)
|
||||
dep.Experiments = append(dep.Experiments, string(codersdk.ExperimentSharedPorts))
|
||||
ownerClient, owner := coderdenttest.New(t, &coderdenttest.Options{
|
||||
Options: &coderdtest.Options{
|
||||
IncludeProvisionerDaemon: true,
|
||||
DeploymentValues: dep,
|
||||
},
|
||||
LicenseOptions: &coderdenttest.LicenseOptions{
|
||||
Features: license.Features{
|
||||
codersdk.FeatureControlSharedPorts: 1,
|
||||
},
|
||||
},
|
||||
})
|
||||
client, user := coderdtest.CreateAnotherUser(t, ownerClient, owner.OrganizationID, rbac.RoleTemplateAdmin())
|
||||
workspace, agent := setupWorkspaceAgent(t, client, codersdk.CreateFirstUserResponse{
|
||||
UserID: user.ID,
|
||||
OrganizationID: owner.OrganizationID,
|
||||
}, 0)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitShort)
|
||||
defer cancel()
|
||||
|
||||
// try to update port share with template max port share level owner
|
||||
_, err := client.UpsertWorkspaceAgentPortShare(ctx, workspace.ID, codersdk.UpsertWorkspaceAgentPortShareRequest{
|
||||
AgentName: agent.Name,
|
||||
Port: 8080,
|
||||
ShareLevel: codersdk.WorkspaceAgentPortShareLevelPublic,
|
||||
})
|
||||
require.Error(t, err, "Port sharing level not allowed")
|
||||
|
||||
// update the template max port share level to public
|
||||
var level codersdk.WorkspaceAgentPortShareLevel = codersdk.WorkspaceAgentPortShareLevelPublic
|
||||
client.UpdateTemplateMeta(ctx, workspace.TemplateID, codersdk.UpdateTemplateMeta{
|
||||
MaxPortShareLevel: &level,
|
||||
})
|
||||
|
||||
// OK
|
||||
ps, err := client.UpsertWorkspaceAgentPortShare(ctx, workspace.ID, codersdk.UpsertWorkspaceAgentPortShareRequest{
|
||||
AgentName: agent.Name,
|
||||
Port: 8080,
|
||||
ShareLevel: codersdk.WorkspaceAgentPortShareLevelPublic,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.EqualValues(t, codersdk.WorkspaceAgentPortShareLevelPublic, ps.ShareLevel)
|
||||
}
|
||||
Reference in New Issue
Block a user