Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions api/v1alpha1/platformmesh_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,12 @@ type ProviderConnection struct {
// Scoped mode requires exactly one of endpointSliceName (virtual workspace server from slice) or apiExportName (workspace server for Path).
// +optional
AdminAuth *bool `json:"adminAuth,omitempty"`
// ProviderRBACPreset selects an in-tree preset shipped with the operator.
// When set, PMO writes a scoped kubeconfig whose server URL shape and RBAC
// come from the preset instead of being derived from an APIExport. Mutually
// exclusive with APIExportName and EndpointSliceName.
// +optional
ProviderRBACPreset *string `json:"providerRBACPreset,omitempty"`
}

// PlatformMeshStatus defines the observed state of PlatformMesh
Expand Down
2 changes: 1 addition & 1 deletion cmd/operator.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ func RunController(_ *cobra.Command, _ []string) { // coverage-ignore
}
imageVersionStore := subroutines.NewImageVersionStore()

pmReconciler, err := controller.NewPlatformMeshReconciler(mgr, &operatorCfg, defaultCfg, operatorCfg.WorkspaceDir, clientInfra, imageVersionStore)
pmReconciler, err := controller.NewPlatformMeshReconciler(mgr, &operatorCfg, defaultCfg, operatorCfg.WorkspaceDir, clientInfra, imageVersionStore, nil)
if err != nil {
setupLog.Error(err, "unable to create PlatformMesh reconciler")
os.Exit(1)
Expand Down
14 changes: 14 additions & 0 deletions config/crd/core.platform-mesh.io_platformmeshes.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,13 @@ spec:
type: string
path:
type: string
providerRBACPreset:
description: |-
ProviderRBACPreset selects an in-tree preset shipped with the operator.
When set, PMO writes a scoped kubeconfig whose server URL shape and RBAC
come from the preset instead of being derived from an APIExport. Mutually
exclusive with APIExportName and EndpointSliceName.
type: string
rawPath:
type: string
secret:
Expand Down Expand Up @@ -191,6 +198,13 @@ spec:
type: string
path:
type: string
providerRBACPreset:
description: |-
ProviderRBACPreset selects an in-tree preset shipped with the operator.
When set, PMO writes a scoped kubeconfig whose server URL shape and RBAC
come from the preset instead of being derived from an APIExport. Mutually
exclusive with APIExportName and EndpointSliceName.
type: string
rawPath:
type: string
secret:
Expand Down
12 changes: 6 additions & 6 deletions internal/controller/controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -386,7 +386,7 @@ func (s *NewPlatformMeshReconcilerTestSuite) Test_allSubroutinesDisabled_returns
}
commonCfg := &pmconfig.CommonServiceConfig{}

r, err := NewPlatformMeshReconciler(mgr, cfg, commonCfg, "/tmp", fakeClient, subroutines.NewImageVersionStore())
r, err := NewPlatformMeshReconciler(mgr, cfg, commonCfg, "/tmp", fakeClient, subroutines.NewImageVersionStore(), nil)
s.Require().NoError(err)
s.NotNil(r)
s.NotNil(r.lifecycle)
Expand All @@ -403,7 +403,7 @@ func (s *NewPlatformMeshReconcilerTestSuite) Test_deploymentSubroutineEnabled_re
}
commonCfg := &pmconfig.CommonServiceConfig{}

r, err := NewPlatformMeshReconciler(mgr, cfg, commonCfg, "/tmp", fakeClient, subroutines.NewImageVersionStore())
r, err := NewPlatformMeshReconciler(mgr, cfg, commonCfg, "/tmp", fakeClient, subroutines.NewImageVersionStore(), nil)
s.Require().NoError(err)
s.NotNil(r)
s.NotNil(r.lifecycle)
Expand All @@ -419,7 +419,7 @@ func (s *NewPlatformMeshReconcilerTestSuite) Test_kcpSetupSubroutineEnabled_retu
}
commonCfg := &pmconfig.CommonServiceConfig{}

r, err := NewPlatformMeshReconciler(mgr, cfg, commonCfg, "/tmp", fakeClient, nil)
r, err := NewPlatformMeshReconciler(mgr, cfg, commonCfg, "/tmp", fakeClient, nil, nil)
s.Require().NoError(err)
s.NotNil(r)
s.NotNil(r.lifecycle)
Expand All @@ -435,7 +435,7 @@ func (s *NewPlatformMeshReconcilerTestSuite) Test_waitSubroutineEnabled_returnsV
}
commonCfg := &pmconfig.CommonServiceConfig{}

r, err := NewPlatformMeshReconciler(mgr, cfg, commonCfg, "/tmp", fakeClient, nil)
r, err := NewPlatformMeshReconciler(mgr, cfg, commonCfg, "/tmp", fakeClient, nil, nil)
s.Require().NoError(err)
s.NotNil(r)
s.NotNil(r.lifecycle)
Expand All @@ -451,7 +451,7 @@ func (s *NewPlatformMeshReconcilerTestSuite) Test_providerSecretSubroutineEnable
}
commonCfg := &pmconfig.CommonServiceConfig{}

r, err := NewPlatformMeshReconciler(mgr, cfg, commonCfg, "/tmp", fakeClient, nil)
r, err := NewPlatformMeshReconciler(mgr, cfg, commonCfg, "/tmp", fakeClient, nil, nil)
s.Require().NoError(err)
s.NotNil(r)
s.NotNil(r.lifecycle)
Expand All @@ -467,7 +467,7 @@ func (s *NewPlatformMeshReconcilerTestSuite) Test_featureTogglesSubroutineEnable
}
commonCfg := &pmconfig.CommonServiceConfig{}

r, err := NewPlatformMeshReconciler(mgr, cfg, commonCfg, "/tmp", fakeClient, nil)
r, err := NewPlatformMeshReconciler(mgr, cfg, commonCfg, "/tmp", fakeClient, nil, nil)
s.Require().NoError(err)
s.NotNil(r)
s.NotNil(r.lifecycle)
Expand Down
9 changes: 7 additions & 2 deletions internal/controller/platformmesh_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import (
corev1alpha1 "github.com/platform-mesh/platform-mesh-operator/api/v1alpha1"
"github.com/platform-mesh/platform-mesh-operator/internal/config"
"github.com/platform-mesh/platform-mesh-operator/internal/metrics"
"github.com/platform-mesh/platform-mesh-operator/pkg/rbacpresets"
pmsubs "github.com/platform-mesh/platform-mesh-operator/pkg/subroutines"
)

Expand Down Expand Up @@ -127,7 +128,11 @@ func (r *PlatformMeshReconciler) mapConfigMapToPlatformMesh(ctx context.Context,
return requests
}

func NewPlatformMeshReconciler(mgr mcmanager.Manager, cfg *config.OperatorConfig, commonCfg *pmconfig.CommonServiceConfig, dir string, clientInfra client.Client, imageVersionStore *pmsubs.ImageVersionStore) (*PlatformMeshReconciler, error) {
func NewPlatformMeshReconciler(mgr mcmanager.Manager, cfg *config.OperatorConfig, commonCfg *pmconfig.CommonServiceConfig, dir string, clientInfra client.Client, imageVersionStore *pmsubs.ImageVersionStore, presetLoader *rbacpresets.Loader,
) (*PlatformMeshReconciler, error) {
if presetLoader == nil {
presetLoader = rbacpresets.NewLoader(rbacpresets.EmbeddedProvidersFS())
}
kcpUrl := fmt.Sprintf("https://%s-front-proxy.%s:%s", cfg.KCP.FrontProxyName, cfg.KCP.Namespace, cfg.KCP.FrontProxyPort)
if cfg.KCP.Url != "" {
kcpUrl = cfg.KCP.Url
Expand All @@ -145,7 +150,7 @@ func NewPlatformMeshReconciler(mgr mcmanager.Manager, cfg *config.OperatorConfig
subs = append(subs, pmsubs.NewKcpsetupSubroutine(localCl, &pmsubs.Helper{}, cfg, dir+"/manifests/kcp", kcpUrl))
}
if cfg.Subroutines.ProviderSecret.Enabled {
subs = append(subs, pmsubs.NewProviderSecretSubroutine(localCl, &pmsubs.Helper{}, pmsubs.DefaultHelmGetter{}, kcpUrl))
subs = append(subs, pmsubs.NewProviderSecretSubroutine(localCl, &pmsubs.Helper{}, pmsubs.DefaultHelmGetter{}, kcpUrl, presetLoader))
}
if cfg.Subroutines.FeatureToggles.Enabled {
subs = append(subs, pmsubs.NewFeatureToggleSubroutine(localCl, &pmsubs.Helper{}, cfg, kcpUrl))
Expand Down
63 changes: 63 additions & 0 deletions pkg/rbacpresets/loader.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package rbacpresets

import (
"embed"
"fmt"
"io/fs"
"path/filepath"
"strings"
)

//go:embed providers/*.yaml
var providerPresetFilesEmbedded embed.FS

// EmbeddedProvidersFS returns the embedded production preset files (providers/*.yaml).
func EmbeddedProvidersFS() fs.FS {
return providerPresetFilesEmbedded
}

// Loader reads provider RBAC preset YAML from an io/fs.FS (expected layout: providers/<name>.yaml).
type Loader struct {
FS fs.FS
}

// NewLoader returns a Loader that reads presets from f. f must not be nil.
func NewLoader(f fs.FS) *Loader {
if f == nil {
panic("rbacpresets: NewLoader: fs.FS is nil")
}
return &Loader{FS: f}
}

// MergePresetFS returns an fs.FS that resolves paths from overlay first, then base.
func MergePresetFS(base, overlay fs.FS) fs.FS {
return mergedPresetFS{base: base, overlay: overlay}
}

type mergedPresetFS struct {
base, overlay fs.FS
}

func (m mergedPresetFS) Open(name string) (fs.File, error) {
f, err := m.overlay.Open(name)
if err == nil {
return f, nil
}
return m.base.Open(name)
}

// LoadPreset reads providers/<name>.yaml from l.FS and renders the preset.
func (l *Loader) LoadPreset(name string, data PresetTemplateData) (*RenderedPreset, error) {
presetName := strings.TrimSpace(name)
if presetName == "" {
return nil, fmt.Errorf("preset name is empty")
}
if strings.Contains(presetName, "/") || strings.Contains(presetName, "\\") || strings.Contains(presetName, "..") {
return nil, fmt.Errorf("invalid preset name %q", name)
}
raw, err := fs.ReadFile(l.FS, filepath.ToSlash(filepath.Join("providers", presetName+".yaml")))
if err != nil {
return nil, fmt.Errorf("load provider RBAC preset %q: %w", presetName, err)
}
return RenderPreset(presetName, raw, data)
}
28 changes: 28 additions & 0 deletions pkg/rbacpresets/loader_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package rbacpresets

import (
"testing"

"github.com/stretchr/testify/require"
)

func TestLoadPresetLoadsEmbeddedPreset(t *testing.T) {
t.Parallel()

preset, err := NewLoader(EmbeddedProvidersFS()).LoadPreset("sample", PresetTemplateData{
ProviderPath: "root:platform-mesh-system",
Suffix: "sample-kubeconfig",
})
require.NoError(t, err)
require.Equal(t, ServerTargetWorkspaceCluster, preset.Spec.ServerTarget.Type)
require.Equal(t, "platform-mesh-provider-sample-kubeconfig", preset.Spec.ServiceAccountName)
require.Len(t, preset.ByWorkspace, 1)
require.Len(t, preset.ByWorkspace[0].Manifests, 3)
}

func TestLoadPresetRejectsUnsafeNames(t *testing.T) {
t.Parallel()

_, err := NewLoader(EmbeddedProvidersFS()).LoadPreset("../init-agent", PresetTemplateData{})
require.ErrorContains(t, err, "invalid preset name")
}
52 changes: 52 additions & 0 deletions pkg/rbacpresets/preset.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// Package rbacpresets defines the in-tree provider RBAC preset format. Preset
// files use a ProviderRBACPreset header document (kind only) followed by RBAC
// manifests; this is not a Kubernetes CRD and is not registered with the
// controller-runtime scheme. ProviderConnection.providerRBACPreset (in
// api/v1alpha1) references a preset by name only.
package rbacpresets

const (
KindProviderRBACPreset = "ProviderRBACPreset"
AnnotationWorkspace = "rbacpresets.platform-mesh.io/workspace"
LabelPreset = "rbacpresets.platform-mesh.io/preset"
LabelProviderSecret = "rbacpresets.platform-mesh.io/provider-secret"
LabelManagedBy = "app.kubernetes.io/managed-by"
ManagedByPlatformMesh = "platform-mesh-operator"
)

type ServerTargetType string

const (
ServerTargetWorkspaceCluster ServerTargetType = "workspaceCluster"
ServerTargetRawPath ServerTargetType = "rawPath"
ServerTargetWorkspaceTypeVirtualWorkspace ServerTargetType = "workspaceTypeVirtualWorkspace"
ServerTargetPathRawPath ServerTargetType = "pathRawPath"
)

type ProviderRBACPresetSpec struct {
ServerTarget ServerTarget `yaml:"serverTarget" json:"serverTarget"`
// Defaulted to the provider connection path when empty.
ServiceAccountWorkspace string `yaml:"serviceAccountWorkspace,omitempty" json:"serviceAccountWorkspace,omitempty"`
// Defaulted to "platform-mesh-provider-<provider secret>" when empty.
ServiceAccountName string `yaml:"serviceAccountName,omitempty" json:"serviceAccountName,omitempty"`
}

type ServerTarget struct {
Type ServerTargetType `yaml:"type" json:"type"`
// For rawPath: optional preset-declared rawPath that overrides pc.RawPath when set.
RawPath string `yaml:"rawPath,omitempty" json:"rawPath,omitempty"`
// For workspaceTypeVirtualWorkspace.
WorkspaceTypeName string `yaml:"workspaceTypeName,omitempty" json:"workspaceTypeName,omitempty"`
WorkspaceTypePath string `yaml:"workspaceTypePath,omitempty" json:"workspaceTypePath,omitempty"`
}

type presetDocument struct {
Spec ProviderRBACPresetSpec `yaml:"spec"`
}

var allowedManifestKinds = map[string]struct{}{
"ServiceAccount": {},
"ClusterRole": {},
"ClusterRoleBinding": {},
"RoleBinding": {},
}
56 changes: 56 additions & 0 deletions pkg/rbacpresets/providers/sample.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# Minimal reference preset: workspace-cluster server URL, SA + ClusterRole + ClusterRoleBinding.
# Copy and extend for operator-specific presets
kind: ProviderRBACPreset
metadata:
name: sample
spec:
serverTarget:
type: workspaceCluster
serviceAccountWorkspace: "{{ .ProviderPath }}"
serviceAccountName: "platform-mesh-provider-{{ .Suffix }}"
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: "{{ .SAName }}"
namespace: default
annotations:
rbacpresets.platform-mesh.io/workspace: "{{ .ProviderPath }}"
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: "{{ .SAName }}"
annotations:
rbacpresets.platform-mesh.io/workspace: "{{ .ProviderPath }}"
rules:
- apiGroups: ["core.platform-mesh.io"]
resources: ["contentconfigurations"]
verbs: ["get", "list", "watch"]
- apiGroups: ["apis.kcp.io"]
resources: ["apiexportendpointslices"]
verbs: ["get", "list", "watch"]
- nonResourceURLs:
- "/api"
- "/api/*"
- "/apis"
- "/apis/*"
- "/clusters/*"
- "/services"
- "/services/*"
verbs: ["get"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: "{{ .SAName }}"
annotations:
rbacpresets.platform-mesh.io/workspace: "{{ .ProviderPath }}"
subjects:
- kind: ServiceAccount
name: "{{ .SAName }}"
namespace: default
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: "{{ .SAName }}"
Loading
Loading