Prevent users from creating apps for the beam app service (#66937)

Beams runs an app_service, configured to look for apps with
the "teleport.internal/beams/app-type" label.

Users should not be able to create apps with this label - we expect
these apps are only created via static beams config or via the
`tsh beams publish` command.

It's safe to block these requests at the RPC layer because beam
apps are written directly to storage and don't go through these
auth RPCs.
This commit is contained in:
Zac Bergquist
2026-05-22 17:26:19 +00:00
committed by GitHub
parent c118891fb0
commit fad450b4ea
3 changed files with 49 additions and 5 deletions
+7 -4
View File
@@ -1355,20 +1355,23 @@ const (
// AppSubKindLabel is the label that has the same value of "app.sub_kind".
AppSubKindLabel = TeleportInternalLabelPrefix + "app-sub-kind"
// BeamsInternalLabelPrefix is the prefix used by internal beams labels.
BeamsInternalLabelPrefix = TeleportInternalLabelPrefix + "beams/"
// BeamIDLabel is the label used to track which Beam a resource belongs to.
BeamIDLabel = TeleportInternalLabelPrefix + "beams/id"
BeamIDLabel = BeamsInternalLabelPrefix + "id"
// BeamOwnerLabel is the label used to track which user's Beam a resource
// belongs to.
BeamOwnerLabel = TeleportInternalLabelPrefix + "beams/owner"
BeamOwnerLabel = BeamsInternalLabelPrefix + "owner"
// BeamAliasLabel is the label used to track the alias of the Beam a
// resource belongs to.
BeamAliasLabel = TeleportInternalLabelPrefix + "beams/alias"
BeamAliasLabel = BeamsInternalLabelPrefix + "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"
BeamAppTypeLabel = BeamsInternalLabelPrefix + "app-type"
)
const (
+23
View File
@@ -6957,6 +6957,15 @@ func sessionTypeFromStartEvent(sessionStart apievents.AuditEvent) types.SessionK
}
}
func hasInternalBeamsLabel(labels map[string]string) bool {
for key := range maps.Keys(labels) {
if strings.HasPrefix(key, types.BeamsInternalLabelPrefix) {
return true
}
}
return false
}
// CreateApp creates a new application resource.
func (a *ServerWithRoles) CreateApp(ctx context.Context, app types.Application) error {
if err := a.authorizeAction(types.KindApp, types.VerbCreate); err != nil {
@@ -6973,6 +6982,13 @@ func (a *ServerWithRoles) CreateApp(ctx context.Context, app types.Application)
if err := a.checkAccessToApp(app); err != nil {
return trace.Wrap(err)
}
// Users are not allowed to create apps that would be picked up
// by the beams app_service.
if hasInternalBeamsLabel(app.GetAllLabels()) {
return trace.AccessDenied("access denied")
}
return trace.Wrap(a.authServer.CreateApp(ctx, app))
}
@@ -6998,6 +7014,13 @@ func (a *ServerWithRoles) UpdateApp(ctx context.Context, app types.Application)
if err := a.checkAccessToApp(app); err != nil {
return trace.Wrap(err)
}
// Users are not allowed to create apps that would be picked up
// by the beams app_service.
if hasInternalBeamsLabel(app.GetAllLabels()) {
return trace.AccessDenied("access denied")
}
return trace.Wrap(a.authServer.UpdateApp(ctx, app))
}
+19 -1
View File
@@ -4085,7 +4085,7 @@ func TestListSAMLIdPServiceProviderAndListResources(t *testing.T) {
// TestApps verifies RBAC is applied to app resources.
func TestApps(t *testing.T) {
t.Parallel()
ctx := context.Background()
ctx := t.Context()
srv := newTestTLSServer(t)
// Setup a couple of users:
@@ -4093,19 +4093,25 @@ func TestApps(t *testing.T) {
// - "admin" has access to all apps
dev, devRole, err := authtest.CreateUserAndRole(srv.Auth(), "dev", nil, nil)
require.NoError(t, err)
devRole.SetAppLabels(types.Allow, types.Labels{"env": {"dev"}})
_, err = srv.Auth().UpsertRole(ctx, devRole)
require.NoError(t, err)
devClt, err := srv.NewClient(authtest.TestUser(dev.GetName()))
require.NoError(t, err)
t.Cleanup(func() { devClt.Close() })
admin, adminRole, err := authtest.CreateUserAndRole(srv.Auth(), "admin", nil, nil)
require.NoError(t, err)
adminRole.SetAppLabels(types.Allow, types.Labels{types.Wildcard: {types.Wildcard}})
_, err = srv.Auth().UpsertRole(ctx, adminRole)
require.NoError(t, err)
adminClt, err := srv.NewClient(authtest.TestUser(admin.GetName()))
require.NoError(t, err)
t.Cleanup(func() { adminClt.Close() })
// Prepare a couple of app resources.
devApp, err := types.NewAppV3(types.Metadata{
@@ -4115,6 +4121,7 @@ func TestApps(t *testing.T) {
URI: "localhost1",
})
require.NoError(t, err)
adminApp, err := types.NewAppV3(types.Metadata{
Name: "admin",
Labels: map[string]string{"env": "prod", types.OriginLabel: types.OriginDynamic},
@@ -4172,6 +4179,17 @@ func TestApps(t *testing.T) {
cmpopts.IgnoreFields(types.Metadata{}, "Revision"),
))
// Clients can't create/update apps for the beams service.
adminApp.GetMetadata().Labels["teleport.internal/beams/app-type"] = "llm"
err = adminClt.UpdateApp(ctx, adminApp)
require.True(t, trace.IsAccessDenied(err))
delete(adminApp.GetMetadata().Labels, "teleport.internal/beams/app-type")
beamApp := devApp.Copy()
beamApp.GetMetadata().Labels["teleport.internal/beams/app-type"] = "llm"
err = devClt.CreateApp(ctx, beamApp)
require.True(t, trace.IsAccessDenied(err))
// When listing apps, dev should only see one.
apps, err := devClt.GetApps(ctx)
require.NoError(t, err)