mirror of
https://github.com/gravitational/teleport.git
synced 2026-10-24 02:25:42 +08:00
Add beam-user, beam-admin, and beam roles (#66708)
* Add `beam-user`, `beam-admin`, and `beam` roles Migrates the beam roles from the cloud tenant controller, adds descriptions, and marks `beam` as a "system" role so it does not show in the role selector. Fixes https://github.com/gravitational/beams/issues/137 * Make `host_cert` permissions more restrictive * Disable beams roles in OSS * Extract beams login into a constant * Fix stray quote * Further lock down the host cert permissions * Fix role preset tests * Using `host_cert.role` in expressions doesn't work
This commit is contained in:
@@ -1365,6 +1365,10 @@ const (
|
||||
// BeamAliasLabel is the label used to track the alias of the Beam a
|
||||
// resource belongs to.
|
||||
BeamAliasLabel = TeleportInternalLabelPrefix + "beams/alias"
|
||||
|
||||
// BeamAppTypeLabel is the label used to denote the type of app created for
|
||||
// Beams. Valid values: "ingress" and "llm".
|
||||
BeamAppTypeLabel = TeleportInternalLabelPrefix + "beams/app-type"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -1950,3 +1954,6 @@ const (
|
||||
// BuiltInAutomaticReview is used within access monitoring rules and indicates
|
||||
// that the automatic_review rule should be monitored by Teleport.
|
||||
const BuiltInAutomaticReview = "builtin"
|
||||
|
||||
// BeamsLogin is the login that should be used when SSHing into beams.
|
||||
const BeamsLogin = "beams"
|
||||
|
||||
@@ -747,6 +747,10 @@ const (
|
||||
// Access Requests.
|
||||
SystemIdentityCenterAccessRoleName = "aws-ic-access"
|
||||
|
||||
// SystemBeamRoleName specifies the name of a system role that grants the
|
||||
// beam bot permission to issue itself credentials.
|
||||
SystemBeamRoleName = "beam"
|
||||
|
||||
// PresetWildcardWorkloadIdentityIssuerRoleName is a name of a preset role
|
||||
// that includes the permissions necessary to issue workload identity
|
||||
// credentials using any workload_identity resource. This exists to simplify
|
||||
@@ -764,6 +768,14 @@ const (
|
||||
// PresetMCPUserRoleName is a name of a preset role that allows
|
||||
// accessing MCP servers.
|
||||
PresetMCPUserRoleName = "mcp-user"
|
||||
|
||||
// PresetBeamUserRoleName is a name of a preset role that allows users to use
|
||||
// the Beams feature.
|
||||
PresetBeamUserRoleName = "beam-user"
|
||||
|
||||
// PresetBeamAdminRoleName is a name of a preset role that allows users to
|
||||
// administer beams belonging to other users.
|
||||
PresetBeamAdminRoleName = "beam-admin"
|
||||
)
|
||||
|
||||
var PresetRoles = []string{PresetEditorRoleName, PresetAccessRoleName, PresetAuditorRoleName}
|
||||
|
||||
@@ -1432,6 +1432,9 @@ func GetPresetRoles(buildType string) []types.Role {
|
||||
services.NewPresetAccessPluginRole(),
|
||||
services.NewPresetListAccessRequestResourcesRole(),
|
||||
services.NewPresetMCPUserRole(),
|
||||
services.NewSystemBeamRole(buildType),
|
||||
services.NewPresetBeamUserRole(buildType),
|
||||
services.NewPresetBeamAdminRole(buildType),
|
||||
}
|
||||
|
||||
// Certain `New$FooRole()` functions will return a nil role if the
|
||||
|
||||
@@ -1293,12 +1293,15 @@ func TestPresets(t *testing.T) {
|
||||
teleport.PresetDeviceEnrollRoleName,
|
||||
teleport.PresetRequireTrustedDeviceRoleName,
|
||||
teleport.SystemOktaRequesterRoleName, // This is treated as a preset
|
||||
teleport.PresetBeamUserRoleName,
|
||||
teleport.PresetBeamAdminRoleName,
|
||||
}, presetRoleNames...)
|
||||
|
||||
enterpriseSystemRoleNames := []string{
|
||||
teleport.SystemAutomaticAccessApprovalRoleName,
|
||||
teleport.SystemOktaAccessRoleName,
|
||||
teleport.SystemIdentityCenterAccessRoleName,
|
||||
teleport.SystemBeamRoleName,
|
||||
}
|
||||
|
||||
enterpriseUsers := []types.User{
|
||||
|
||||
@@ -20,8 +20,10 @@ package services
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/gravitational/trace"
|
||||
|
||||
@@ -882,6 +884,135 @@ func NewPresetMCPUserRole() types.Role {
|
||||
return role
|
||||
}
|
||||
|
||||
// NewPresetBeamUserRole returns a new pre-defined role for accessing your own
|
||||
// beam resources.
|
||||
func NewPresetBeamUserRole(buildType string) types.Role {
|
||||
if buildType != modules.BuildEnterprise {
|
||||
return nil
|
||||
}
|
||||
|
||||
allowLLMApps := fmt.Sprintf(`labels[%q] == %q`, types.BeamAppTypeLabel, types.SubKindLLM)
|
||||
allowBeamApps := fmt.Sprintf(`labels[%q] == user.metadata.name`, types.BeamOwnerLabel)
|
||||
|
||||
role := &types.RoleV6{
|
||||
Kind: types.KindRole,
|
||||
Version: types.V8,
|
||||
Metadata: types.Metadata{
|
||||
Name: teleport.PresetBeamUserRoleName,
|
||||
Namespace: apidefaults.Namespace,
|
||||
Description: "Use the Beams feature",
|
||||
Labels: map[string]string{
|
||||
types.TeleportInternalResourceType: types.PresetResource,
|
||||
},
|
||||
},
|
||||
Spec: types.RoleSpecV6{
|
||||
Allow: types.RoleConditions{
|
||||
Logins: []string{types.BeamsLogin},
|
||||
AppLabelsExpression: strings.Join([]string{allowLLMApps, allowBeamApps}, " || "),
|
||||
NodeLabels: types.Labels{
|
||||
types.BeamOwnerLabel: {"{{user.metadata.name}}"},
|
||||
},
|
||||
BeamLabels: types.Labels{
|
||||
types.BeamOwnerLabel: {"{{user.metadata.name}}"},
|
||||
},
|
||||
Rules: []types.Rule{
|
||||
{
|
||||
Resources: []string{types.KindBeam},
|
||||
Verbs: []string{types.Wildcard},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
return role
|
||||
}
|
||||
|
||||
// NewPresetBeamAdminRole returns a new pre-defined role for administering beams
|
||||
// belonging to other users.
|
||||
func NewPresetBeamAdminRole(buildType string) types.Role {
|
||||
if buildType != modules.BuildEnterprise {
|
||||
return nil
|
||||
}
|
||||
|
||||
role := &types.RoleV6{
|
||||
Kind: types.KindRole,
|
||||
Version: types.V8,
|
||||
Metadata: types.Metadata{
|
||||
Name: teleport.PresetBeamAdminRoleName,
|
||||
Namespace: apidefaults.Namespace,
|
||||
Description: "Administer beams belonging to other users",
|
||||
Labels: map[string]string{
|
||||
types.TeleportInternalResourceType: types.PresetResource,
|
||||
},
|
||||
},
|
||||
Spec: types.RoleSpecV6{
|
||||
Allow: types.RoleConditions{
|
||||
BeamLabels: types.Labels{
|
||||
types.BeamOwnerLabel: {types.Wildcard},
|
||||
},
|
||||
Rules: []types.Rule{
|
||||
{
|
||||
Resources: []string{types.KindBeam},
|
||||
Verbs: []string{types.Wildcard},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
return role
|
||||
}
|
||||
|
||||
// NewSystemBeamRole returns a new pre-defined role for the beam to issue itself
|
||||
// credentials.
|
||||
func NewSystemBeamRole(buildType string) types.Role {
|
||||
if buildType != modules.BuildEnterprise {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Only allow the bot to generate host certificates for the Beam's OpenSSH
|
||||
// server. tbot explicitly sends a blank HostID, HostName and the Node role.
|
||||
hostCertConstraints := strings.Join([]string{
|
||||
fmt.Sprintf(`contains_all(user.spec.traits[%q], host_cert.principals)`, types.BeamIDLabel),
|
||||
`host_cert.host_id == ""`,
|
||||
`host_cert.node_name == ""`,
|
||||
}, " && ")
|
||||
|
||||
role := &types.RoleV6{
|
||||
Kind: types.KindRole,
|
||||
Version: types.V8,
|
||||
Metadata: types.Metadata{
|
||||
Name: teleport.SystemBeamRoleName,
|
||||
Namespace: apidefaults.Namespace,
|
||||
Description: "Used by a beam to issue itself credentials",
|
||||
Labels: map[string]string{
|
||||
types.TeleportInternalResourceType: types.SystemResource,
|
||||
},
|
||||
},
|
||||
Spec: types.RoleSpecV6{
|
||||
Allow: types.RoleConditions{
|
||||
Rules: []types.Rule{
|
||||
{
|
||||
Resources: []string{types.KindHostCert},
|
||||
Verbs: []string{types.VerbCreate},
|
||||
Where: hostCertConstraints,
|
||||
},
|
||||
{
|
||||
Resources: []string{types.KindWorkloadIdentity},
|
||||
Verbs: []string{
|
||||
types.VerbList,
|
||||
types.VerbRead,
|
||||
},
|
||||
},
|
||||
},
|
||||
WorkloadIdentityLabels: types.Labels{
|
||||
types.BeamIDLabel: []string{fmt.Sprintf(`{{external[%q]}}`, types.BeamIDLabel)},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
return role
|
||||
}
|
||||
|
||||
// VirtualDefaultHealthCheckConfigDB returns a health_check_config enabling
|
||||
// health checks for all databases resources, and is intended to be used as a
|
||||
// virtual default resource. Its name is "default" for historical reasons.
|
||||
|
||||
@@ -33,8 +33,6 @@ import (
|
||||
"github.com/gravitational/teleport/lib/utils"
|
||||
)
|
||||
|
||||
const beamsLogin = "beams"
|
||||
|
||||
type beamsCommands struct {
|
||||
ls *beamsLSCommand
|
||||
add *beamsAddCommand
|
||||
|
||||
@@ -28,6 +28,7 @@ import (
|
||||
|
||||
"github.com/gravitational/teleport"
|
||||
beamsv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/beams/v1"
|
||||
"github.com/gravitational/teleport/api/types"
|
||||
"github.com/gravitational/teleport/lib/auth/authclient"
|
||||
"github.com/gravitational/teleport/lib/client"
|
||||
"github.com/gravitational/teleport/lib/defaults"
|
||||
@@ -158,7 +159,7 @@ func (t *beamCopyTarget) toSFTP() string {
|
||||
}
|
||||
return fmt.Sprintf(
|
||||
"%s@%s:%s",
|
||||
beamsLogin,
|
||||
types.BeamsLogin,
|
||||
t.beam.GetStatus().GetNodeId(),
|
||||
t.path,
|
||||
)
|
||||
|
||||
@@ -26,6 +26,7 @@ import (
|
||||
"github.com/gravitational/trace"
|
||||
|
||||
beamsv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/beams/v1"
|
||||
"github.com/gravitational/teleport/api/types"
|
||||
"github.com/gravitational/teleport/api/utils/retryutils"
|
||||
"github.com/gravitational/teleport/lib/client"
|
||||
)
|
||||
@@ -85,7 +86,7 @@ func sshBeam(cf *CLIConf, tc *client.TeleportClient, beam *beamsv1.Beam, command
|
||||
}
|
||||
|
||||
target := fmt.Sprintf("%s:0", beam.GetStatus().GetNodeId())
|
||||
tc.HostLogin = beamsLogin
|
||||
tc.HostLogin = types.BeamsLogin
|
||||
tc.Stdin = cf.Stdin()
|
||||
|
||||
retry, err := retryutils.NewLinear(retryutils.LinearConfig{
|
||||
|
||||
Reference in New Issue
Block a user