mirror of
https://github.com/gravitational/teleport.git
synced 2026-09-19 01:58:44 +08:00
Add scoped mode for the operator (#66762)
This work is not production ready yet. While this work makes the operator support running with a scoped token, this is not enough for a public release. Several failure modes must be addressed, including scoped name conflicts, and operators fighting for ownership of a resource.
This commit is contained in:
@@ -85,6 +85,18 @@ Setting this value is required when joining via the
|
||||
|
||||
`token` is the name of the token used by the operator to join the Teleport cluster.
|
||||
|
||||
## `scoped`
|
||||
|
||||
| Type | Default |
|
||||
|------|---------|
|
||||
| `bool` | `false` |
|
||||
|
||||
`scoped` makes the operator run in scoped mode.
|
||||
In scoped mode, the operator can only reconcile scoped resource such as
|
||||
`TeleportScopedTokenV1`, `TeleportScopedRoleV1`, or `TeleportScopedRoleAssignmentV1`.
|
||||
Scoped mode requires the operator to join using a scoped token and the experimental scope
|
||||
feature to be enabled on both Teleport Auth Service instances and Proxy Service instances.
|
||||
|
||||
## `teleportVersionOverride`
|
||||
|
||||
| Type | Default |
|
||||
|
||||
@@ -56,6 +56,9 @@ spec:
|
||||
- '{{ .Values.joinMethod }}'
|
||||
- -token
|
||||
- '{{ .Values.token }}'
|
||||
{{- if .Values.scoped }}
|
||||
- -scoped
|
||||
{{- end }}
|
||||
{{- if .Values.caPins }}
|
||||
- -ca-pin
|
||||
- '{{ join "," .Values.caPins }}'
|
||||
|
||||
@@ -47,6 +47,13 @@ teleportClusterName: ""
|
||||
# token(string) -- is the name of the token used by the operator to join the Teleport cluster.
|
||||
token: ""
|
||||
|
||||
# scoped(bool) -- makes the operator run in scoped mode.
|
||||
# In scoped mode, the operator can only reconcile scoped resource such as
|
||||
# `TeleportScopedTokenV1`, `TeleportScopedRoleV1`, or `TeleportScopedRoleAssignmentV1`.
|
||||
# Scoped mode requires the operator to join using a scoped token and the experimental scope
|
||||
# feature to be enabled on both Teleport Auth Service instances and Proxy Service instances.
|
||||
scoped: false
|
||||
|
||||
# teleportVersionOverride(string) -- controls the Teleport Kubernetes Operator
|
||||
# image version deployed by the chart.
|
||||
#
|
||||
|
||||
@@ -36,6 +36,7 @@ type operatorConfig struct {
|
||||
syncPeriod time.Duration
|
||||
namespace string
|
||||
logLevel string
|
||||
scoped bool
|
||||
}
|
||||
|
||||
// BindFlags binds operatorConfig fields to CLI flags.
|
||||
@@ -52,6 +53,7 @@ func (c *operatorConfig) BindFlags(fs *flag.FlagSet) {
|
||||
fs.StringVar(&c.namespace, "namespace", "", "The namespace containing the Teleport CRs.")
|
||||
fs.DurationVar(&c.syncPeriod, "sync-period", defaultSyncPeriod, "Operator sync period (format: https://pkg.go.dev/time#ParseDuration)")
|
||||
fs.StringVar(&c.logLevel, "log-level", "INFO", "Log level (DEBUG, INFO, WARN, ERROR).")
|
||||
fs.BoolVar(&c.scoped, "scoped", false, "Run operator in scoped mode. Only scoped resources will be reconciled.")
|
||||
}
|
||||
|
||||
// CheckAndSetDefaults checks the operatorConfig and populates unspecified
|
||||
|
||||
@@ -39,7 +39,44 @@ type reconcilerFactory struct {
|
||||
}
|
||||
|
||||
// SetupAllControllers sets up all controllers
|
||||
func SetupAllControllers(log logr.Logger, mgr manager.Manager, teleportClient *client.Client, features *proto.Features) error {
|
||||
func SetupAllControllers(log logr.Logger, mgr manager.Manager, teleportClient *client.Client, features *proto.Features, scoped bool) error {
|
||||
kubeClient := mgr.GetClient()
|
||||
for _, reconciler := range enabledReconcilers(log, features, scoped) {
|
||||
r, err := reconciler.factory(kubeClient, teleportClient)
|
||||
if err != nil {
|
||||
return trace.Wrap(err, "failed to create controller for %s", reconciler.cr)
|
||||
}
|
||||
err = r.SetupWithManager(mgr)
|
||||
if err != nil {
|
||||
return trace.Wrap(err, "failed to setup controller for: %s", reconciler.cr)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func enabledReconcilers(log logr.Logger, features *proto.Features, scoped bool) []reconcilerFactory {
|
||||
var reconcilers []reconcilerFactory
|
||||
|
||||
// We always run
|
||||
reconcilers = append(reconcilers, scopedReconcilers(log, features)...)
|
||||
if scoped {
|
||||
log.Info("Running in scoped mode. Unscoped resources will not be reconciled.")
|
||||
} else {
|
||||
reconcilers = append(reconcilers, unscopedReconcilers(log, features)...)
|
||||
}
|
||||
return reconcilers
|
||||
}
|
||||
|
||||
func scopedReconcilers(log logr.Logger, features *proto.Features) []reconcilerFactory {
|
||||
return []reconcilerFactory{
|
||||
{"TeleportScopedTokenV1", NewScopedTokenV1Reconciler},
|
||||
{"TeleportScopedRoleV1", NewScopedRoleV1Reconciler},
|
||||
{"TeleportScopedRoleAssignmentV1", NewScopedRoleAssignmentV1Reconciler},
|
||||
}
|
||||
}
|
||||
|
||||
func unscopedReconcilers(log logr.Logger, features *proto.Features) []reconcilerFactory {
|
||||
reconcilers := []reconcilerFactory{
|
||||
{"TeleportRole", NewRoleReconciler},
|
||||
{"TeleportRoleV6", NewRoleV6Reconciler},
|
||||
@@ -65,9 +102,6 @@ func SetupAllControllers(log logr.Logger, mgr manager.Manager, teleportClient *c
|
||||
// saml_idp_service_provider objects using tctl for any build. We
|
||||
// therefore enable it here unconditionally to mirror tctl behavior.
|
||||
{"TeleportSAMLIdPServiceProviderV1", NewSAMLIdPServiceProviderV1Reconciler},
|
||||
{"TeleportScopedTokenV1", NewScopedTokenV1Reconciler},
|
||||
{"TeleportScopedRoleV1", NewScopedRoleV1Reconciler},
|
||||
{"TeleportScopedRoleAssignmentV1", NewScopedRoleAssignmentV1Reconciler},
|
||||
}
|
||||
|
||||
oidc := modules.GetProtoEntitlement(features, entitlements.OIDC)
|
||||
@@ -110,17 +144,5 @@ func SetupAllControllers(log logr.Logger, mgr manager.Manager, teleportClient *c
|
||||
log.Info("The cluster license does not contain advanced workflows. TeleportAccessList, TeleportOktaImportRule resources won't be reconciled")
|
||||
}
|
||||
|
||||
kubeClient := mgr.GetClient()
|
||||
for _, reconciler := range reconcilers {
|
||||
r, err := reconciler.factory(kubeClient, teleportClient)
|
||||
if err != nil {
|
||||
return trace.Wrap(err, "failed to create controller for %s", reconciler.cr)
|
||||
}
|
||||
err = r.SetupWithManager(mgr)
|
||||
if err != nil {
|
||||
return trace.Wrap(err, "failed to setup controller for: %s", reconciler.cr)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
return reconcilers
|
||||
}
|
||||
|
||||
@@ -0,0 +1,156 @@
|
||||
/*
|
||||
Copyright 2026 Gravitational, Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package resources
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/gravitational/teleport/entitlements"
|
||||
"github.com/gravitational/teleport/lib/modules"
|
||||
"github.com/gravitational/teleport/lib/modules/modulestest"
|
||||
"github.com/gravitational/teleport/lib/utils/log/logtest"
|
||||
)
|
||||
|
||||
func TestEnabledReconcilers(t *testing.T) {
|
||||
// Test setup: create the fixtures
|
||||
ossFeatures := modulestest.OSSModules().Features()
|
||||
entFeatures := modulestest.EnterpriseModules().Features()
|
||||
entFeatures.Entitlements[entitlements.OIDC] = modules.EntitlementInfo{Enabled: true}
|
||||
entFeatures.Entitlements[entitlements.SAML] = modules.EntitlementInfo{Enabled: true}
|
||||
entFeatures.Entitlements[entitlements.Policy] = modules.EntitlementInfo{Enabled: true}
|
||||
entFeatures.AdvancedAccessWorkflows = true
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
scoped bool
|
||||
features modules.Features
|
||||
expectedReconcilers map[string]struct{}
|
||||
}{
|
||||
{
|
||||
name: "Unscoped, OSS",
|
||||
scoped: false,
|
||||
features: ossFeatures,
|
||||
expectedReconcilers: map[string]struct{}{
|
||||
// scoped
|
||||
"TeleportScopedTokenV1": {},
|
||||
"TeleportScopedRoleV1": {},
|
||||
"TeleportScopedRoleAssignmentV1": {},
|
||||
// unscoped oss
|
||||
"TeleportRole": {},
|
||||
"TeleportRoleV6": {},
|
||||
"TeleportRoleV7": {},
|
||||
"TeleportRoleV8": {},
|
||||
"TeleportUser": {},
|
||||
"TeleportGithubConnector": {},
|
||||
"TeleportLockV2": {},
|
||||
"TeleportProvisionToken": {},
|
||||
"TeleportOpenSSHServerV2": {},
|
||||
"TeleportOpenSSHEICEServerV2": {},
|
||||
"TeleportTrustedClusterV2": {},
|
||||
"TeleportBotV1": {},
|
||||
"TeleportWorkloadIdentityV1": {},
|
||||
"TeleportAutoupdateConfigV1": {},
|
||||
"TeleportAutoupdateVersionV1": {},
|
||||
"TeleportAppV3": {},
|
||||
"TeleportDatabaseV3": {},
|
||||
"TeleportAccessMonitoringRuleV1": {},
|
||||
"TeleportSAMLIdPServiceProviderV1": {},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Scoped, OSS",
|
||||
scoped: true,
|
||||
features: ossFeatures,
|
||||
expectedReconcilers: map[string]struct{}{
|
||||
"TeleportScopedTokenV1": {},
|
||||
"TeleportScopedRoleV1": {},
|
||||
"TeleportScopedRoleAssignmentV1": {},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Unscoped, enterprise",
|
||||
scoped: false,
|
||||
features: entFeatures,
|
||||
expectedReconcilers: map[string]struct{}{
|
||||
// scoped
|
||||
"TeleportScopedTokenV1": {},
|
||||
"TeleportScopedRoleV1": {},
|
||||
"TeleportScopedRoleAssignmentV1": {},
|
||||
// unscoped oss
|
||||
"TeleportRole": {},
|
||||
"TeleportRoleV6": {},
|
||||
"TeleportRoleV7": {},
|
||||
"TeleportRoleV8": {},
|
||||
"TeleportUser": {},
|
||||
"TeleportGithubConnector": {},
|
||||
"TeleportLockV2": {},
|
||||
"TeleportProvisionToken": {},
|
||||
"TeleportOpenSSHServerV2": {},
|
||||
"TeleportOpenSSHEICEServerV2": {},
|
||||
"TeleportTrustedClusterV2": {},
|
||||
"TeleportBotV1": {},
|
||||
"TeleportWorkloadIdentityV1": {},
|
||||
"TeleportAutoupdateConfigV1": {},
|
||||
"TeleportAutoupdateVersionV1": {},
|
||||
"TeleportAppV3": {},
|
||||
"TeleportDatabaseV3": {},
|
||||
"TeleportAccessMonitoringRuleV1": {},
|
||||
"TeleportSAMLIdPServiceProviderV1": {},
|
||||
// unscoped enterprise
|
||||
"TeleportOIDCConnector": {},
|
||||
"TeleportSAMLConnector": {},
|
||||
"TeleportInferenceModel": {},
|
||||
"TeleportInferencePolicy": {},
|
||||
"TeleportInferenceSecret": {},
|
||||
"TeleportRetrievalModelV1": {},
|
||||
"TeleportLoginRule": {},
|
||||
"TeleportAccessList": {},
|
||||
"TeleportOktaImportRule": {},
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
name: "Scoped, enterprise",
|
||||
scoped: true,
|
||||
features: entFeatures,
|
||||
expectedReconcilers: map[string]struct{}{
|
||||
"TeleportScopedTokenV1": {},
|
||||
"TeleportScopedRoleV1": {},
|
||||
"TeleportScopedRoleAssignmentV1": {},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
log := logr.FromSlogHandler(logtest.NewLogger().Handler())
|
||||
features := tt.features.ToProto()
|
||||
reconcilers := enabledReconcilers(log, features, tt.scoped)
|
||||
|
||||
// Test validation: check that the expected reconcilers were enabled
|
||||
reconcilersSet := make(map[string]struct{}, len(reconcilers))
|
||||
for _, reconciler := range reconcilers {
|
||||
reconcilersSet[reconciler.cr] = struct{}{}
|
||||
}
|
||||
require.Len(t, reconcilersSet, len(tt.expectedReconcilers))
|
||||
require.Equal(t, tt.expectedReconcilers, reconcilersSet)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -230,7 +230,9 @@ func (s *TestSetup) StartKubernetesOperator(t *testing.T) {
|
||||
|
||||
pong, err := s.TeleportClient.Ping(context.Background())
|
||||
require.NoError(t, err)
|
||||
err = resources.SetupAllControllers(setupLog, k8sManager, s.TeleportClient, pong.ServerFeatures)
|
||||
|
||||
const scoped = false
|
||||
err = resources.SetupAllControllers(setupLog, k8sManager, s.TeleportClient, pong.ServerFeatures, scoped)
|
||||
require.NoError(t, err)
|
||||
|
||||
ctx, ctxCancel := context.WithCancel(context.Background())
|
||||
|
||||
@@ -86,6 +86,7 @@ func main() {
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
botConfig.Scoped = config.scoped
|
||||
bot, err := embeddedtbot.New(botConfig, slogLogger.With(teleport.ComponentLabel, "embedded-tbot"))
|
||||
if err != nil {
|
||||
setupLog.Error(err, "unable to build tbot")
|
||||
@@ -132,7 +133,7 @@ func main() {
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if err = resources.SetupAllControllers(setupLog, mgr, client, pong.ServerFeatures); err != nil {
|
||||
if err = resources.SetupAllControllers(setupLog, mgr, client, pong.ServerFeatures, config.scoped); err != nil {
|
||||
setupLog.Error(err, "failed to setup controllers")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user