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
1 change: 1 addition & 0 deletions Taskfile.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ tasks:
- kubectl rollout restart deployment/security-operator-generator -n {{.DEPLOYMENT_NAMESPACE}}
- kubectl rollout restart deployment/security-operator-initializer -n {{.DEPLOYMENT_NAMESPACE}}
- kubectl rollout restart deployment/security-operator-terminator -n {{.DEPLOYMENT_NAMESPACE}}
- kubectl rollout restart deployment/security-operator-system -n {{.DEPLOYMENT_NAMESPACE}}
- echo "All deployments restarted"
docker:kind:
desc: "Build container image, load it into kind cluster, and restart deployments"
Expand Down
2 changes: 1 addition & 1 deletion cmd/model_generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ var modelGeneratorCmd = &cobra.Command{
return fmt.Errorf("scheme should not be nil")
}

provider, err := apiexport.New(restCfg, operatorCfg.APIExportEndpointSliceName, apiexport.Options{
provider, err := apiexport.New(restCfg, generatorCfg.APIExportEndpointSlices.CorePlatformMeshIO, apiexport.Options{
Scheme: mgrOpts.Scheme,
})
if err != nil {
Expand Down
11 changes: 1 addition & 10 deletions cmd/operator.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ var operatorCmd = &cobra.Command{
return fmt.Errorf("scheme should not be nil")
}

provider, err := apiexport.New(restCfg, operatorCfg.APIExportEndpointSliceName, apiexport.Options{
provider, err := apiexport.New(restCfg, operatorCfg.APIExportEndpointSlices.CorePlatformMeshIO, apiexport.Options{
Scheme: mgrOpts.Scheme,
})
if err != nil {
Expand All @@ -161,11 +161,6 @@ var operatorCmd = &cobra.Command{
return err
}

orgClient, err := logicalClusterClientFromKey(mgr.GetLocalManager().GetConfig(), log)(logicalcluster.Name("root:orgs"))
if err != nil {
log.Error().Err(err).Msg("Failed to create org client")
return err
}

fga := openfgav1.NewOpenFGAServiceClient(conn)

Expand All @@ -180,10 +175,6 @@ var operatorCmd = &cobra.Command{
log.Error().Err(err).Str("controller", "authorizationmodel").Msg("unable to create controller")
return err
}
if err = controller.NewIdentityProviderConfigurationReconciler(ctx, mgr, orgClient, &operatorCfg, log).SetupWithManager(mgr, defaultCfg, log); err != nil {
log.Error().Err(err).Str("controller", "identityprovider").Msg("unable to create controller")
return err
}
if err = controller.NewInviteReconciler(ctx, mgr, &operatorCfg, log).SetupWithManager(mgr, defaultCfg, log); err != nil {
log.Error().Err(err).Str("controller", "invite").Msg("unable to create controller")
return err
Expand Down
4 changes: 4 additions & 0 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ var (
terminatorCfg config.Config
operatorCfg config.Config
generatorCfg config.Config
systemCfg config.Config
log *logger.Logger
setupLog logr.Logger
)
Expand All @@ -34,19 +35,22 @@ func init() {
rootCmd.AddCommand(operatorCmd)
rootCmd.AddCommand(modelGeneratorCmd)
rootCmd.AddCommand(initContainerCmd)
rootCmd.AddCommand(systemCmd)

defaultCfg = platformeshconfig.NewDefaultConfig()
operatorCfg = config.NewConfig()
generatorCfg = config.NewConfig()
initializerCfg = config.NewConfig()
terminatorCfg = config.NewConfig()
systemCfg = config.NewConfig()
initContainerCfg = config.NewInitContainerConfig()

defaultCfg.AddFlags(rootCmd.PersistentFlags())
operatorCfg.AddFlags(operatorCmd.Flags())
generatorCfg.AddFlags(modelGeneratorCmd.Flags())
initializerCfg.AddFlags(initializerCmd.Flags())
terminatorCfg.AddFlags(terminatorCmd.Flags())
systemCfg.AddFlags(systemCmd.Flags())
initContainerCfg.AddFlags(initContainerCmd.Flags())

cobra.OnInitialize(initLog)
Expand Down
98 changes: 98 additions & 0 deletions cmd/system.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package cmd

import (
"context"
"crypto/tls"

platformeshcontext "github.com/platform-mesh/golang-commons/context"
"github.com/platform-mesh/security-operator/internal/controller"
"github.com/spf13/cobra"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/healthz"
metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server"
mcmanager "sigs.k8s.io/multicluster-runtime/pkg/manager"

"k8s.io/client-go/rest"

"github.com/kcp-dev/logicalcluster/v3"
"github.com/kcp-dev/multicluster-provider/apiexport"
)

var systemCmd = &cobra.Command{
Use: "system",
Short: "System controllers for system.platform-mesh.io apiexport resources",
RunE: func(cmd *cobra.Command, args []string) error {
ctrl.SetLogger(log.ComponentLogger("controller-runtime").Logr())

ctx, _, shutdown := platformeshcontext.StartContext(log, defaultCfg, defaultCfg.ShutdownTimeout)
defer shutdown()

restCfg, err := getKubeconfigFromPath(systemCfg.KCP.Kubeconfig)
if err != nil {
log.Error().Err(err).Msg("unable to get KCP kubeconfig")
return err
}

opts := ctrl.Options{
Scheme: scheme,
Metrics: metricsserver.Options{
BindAddress: defaultCfg.Metrics.BindAddress,
TLSOpts: []func(*tls.Config){
func(c *tls.Config) {
c.NextProtos = []string{"http/1.1"}
},
},
},
HealthProbeBindAddress: defaultCfg.HealthProbeBindAddress,
LeaderElection: defaultCfg.LeaderElectionEnabled,
LeaderElectionID: "security-operator-system.platform-mesh.io",
BaseContext: func() context.Context { return ctx },
}

if defaultCfg.LeaderElectionEnabled {
inClusterCfg, err := rest.InClusterConfig()
if err != nil {
setupLog.Error(err, "unable to get in-cluster config for leader election")
return err
}
opts.LeaderElectionConfig = inClusterCfg
}

provider, err := apiexport.New(restCfg, systemCfg.APIExportEndpointSlices.SystemPlatformMeshIO, apiexport.Options{
Scheme: scheme,
})
if err != nil {
setupLog.Error(err, "unable to create apiexport provider")
return err
}

mgr, err := mcmanager.New(restCfg, provider, opts)
if err != nil {
setupLog.Error(err, "unable to create manager")
return err
}
orgClient, err := logicalClusterClientFromKey(mgr.GetLocalManager().GetConfig(), log)(logicalcluster.Name("root:orgs"))
if err != nil {
log.Error().Err(err).Msg("Failed to create org client")
return err
}

if err = controller.NewIdentityProviderConfigurationReconciler(ctx, mgr, orgClient, &systemCfg, log).SetupWithManager(mgr, defaultCfg, log); err != nil {
log.Error().Err(err).Str("controller", "identityprovider").Msg("unable to create controller")
return err
}

if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil {
log.Error().Err(err).Msg("unable to set up health check")
return err
}
if err := mgr.AddReadyzCheck("readyz", healthz.Ping); err != nil {
log.Error().Err(err).Msg("unable to set up ready check")
return err
}

setupLog.Info("starting system manager")

return mgr.Start(ctx)
},
}
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module github.com/platform-mesh/security-operator

go 1.25.7
go 1.26.1

require (
github.com/coreos/go-oidc v2.5.0+incompatible
Expand Down
27 changes: 18 additions & 9 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,16 @@ type IDPConfig struct {
RegistrationAllowed bool
}

type APIExportEndpointSlices struct {
CorePlatformMeshIO string
SystemPlatformMeshIO string
}

// Config struct to hold the app config
type Config struct {
FGA FGAConfig
KCP KCPConfig
APIExportEndpointSliceName string
APIExportEndpointSlices APIExportEndpointSlices
CoreModulePath string
BaseDomain string
GroupClaim string
Expand Down Expand Up @@ -89,13 +94,16 @@ func NewConfig() Config {
KCP: KCPConfig{
Kubeconfig: "/api-kubeconfig/kubeconfig",
},
APIExportEndpointSliceName: "core.platform-mesh.io",
BaseDomain: "portal.dev.local:8443",
GroupClaim: "groups",
UserClaim: "email",
WorkspacePath: "root",
WorkspaceTypeName: "security",
HttpClientTimeoutSeconds: 30,
APIExportEndpointSlices: APIExportEndpointSlices{
CorePlatformMeshIO: "core.platform-mesh.io",
SystemPlatformMeshIO: "system.platform-mesh.io",
},
BaseDomain: "portal.dev.local:8443",
GroupClaim: "groups",
UserClaim: "email",
WorkspacePath: "root",
WorkspaceTypeName: "security",
HttpClientTimeoutSeconds: 30,
IDP: IDPConfig{
KubectlClientRedirectURLs: []string{"http://localhost:8000", "http://localhost:18000"},
AccessTokenLifespan: 28800,
Expand Down Expand Up @@ -123,7 +131,8 @@ func (c *Config) AddFlags(fs *pflag.FlagSet) {
fs.StringVar(&c.FGA.ParentRelation, "fga-parent-relation", c.FGA.ParentRelation, "Set the OpenFGA parent relation name")
fs.StringVar(&c.FGA.CreatorRelation, "fga-creator-relation", c.FGA.CreatorRelation, "Set the OpenFGA creator relation name")
fs.StringVar(&c.KCP.Kubeconfig, "kcp-kubeconfig", c.KCP.Kubeconfig, "Set the KCP kubeconfig path")
fs.StringVar(&c.APIExportEndpointSliceName, "api-export-endpoint-slice-name", c.APIExportEndpointSliceName, "Set the APIExportEndpointSlice name")
fs.StringVar(&c.APIExportEndpointSlices.CorePlatformMeshIO, "api-export-endpoint-slice-name", c.APIExportEndpointSlices.CorePlatformMeshIO, "Set the core.platform-mesh.io APIExportEndpointSlice name")
fs.StringVar(&c.APIExportEndpointSlices.SystemPlatformMeshIO, "system-api-export-endpoint-slice-name", c.APIExportEndpointSlices.SystemPlatformMeshIO, "Set the system.platform-mesh.io APIExportEndpointSlice name")
fs.StringVar(&c.CoreModulePath, "core-module-path", c.CoreModulePath, "Set the path to the core module FGA model file")
fs.StringVar(&c.BaseDomain, "base-domain", c.BaseDomain, "Set the base domain used to construct issuer URLs")
fs.StringVar(&c.GroupClaim, "group-claim", c.GroupClaim, "Set the ID token group claim")
Expand Down
3 changes: 2 additions & 1 deletion internal/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ func TestNewConfig(t *testing.T) {

assert.Equal(t, "core_platform-mesh_io_account", cfg.FGA.ObjectType)
assert.Equal(t, "/api-kubeconfig/kubeconfig", cfg.KCP.Kubeconfig)
assert.Equal(t, "core.platform-mesh.io", cfg.APIExportEndpointSliceName)
assert.Equal(t, "core.platform-mesh.io", cfg.APIExportEndpointSlices.CorePlatformMeshIO)
assert.Equal(t, "system.platform-mesh.io", cfg.APIExportEndpointSlices.SystemPlatformMeshIO)
assert.Equal(t, "security-operator", cfg.Keycloak.ClientID)
assert.Equal(t, 9443, cfg.Webhooks.Port)
assert.Equal(t, []string{"http://localhost:8000", "http://localhost:18000"}, cfg.IDP.KubectlClientRedirectURLs)
Expand Down
7 changes: 5 additions & 2 deletions internal/subroutine/authorization_model_generation.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"bytes"
"context"
"fmt"
"slices"
"strings"
"text/template"

Expand Down Expand Up @@ -210,8 +211,10 @@ func (a *AuthorizationModelGenerationSubroutine) GetName() string {
func (a *AuthorizationModelGenerationSubroutine) Process(ctx context.Context, instance lifecyclecontrollerruntime.RuntimeObject) (ctrl.Result, errors.OperatorError) {
binding := instance.(*kcpapisv1alpha2.APIBinding)

if binding.Spec.Reference.Export.Name == "core.platform-mesh.io" || strings.HasSuffix(binding.Spec.Reference.Export.Name, "kcp.io") {
// If the APIExport is the core.platform-mesh.io, we can skip the model generation.
internalAPIBindings := []string{"core.platform-mesh.io", "system.platform-mesh.io"}

if slices.Contains(internalAPIBindings, binding.Spec.Reference.Export.Name) || strings.HasSuffix(binding.Spec.Reference.Export.Name, "kcp.io") {
// If the APIExport is the core.platform-mesh.io, system.platform-mesh.io we can skip the model generation.
return ctrl.Result{}, nil
}

Expand Down
4 changes: 2 additions & 2 deletions internal/subroutine/idp.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ func (i *IDPSubroutine) Initialize(ctx context.Context, instance runtimeobject.R
}

idp := &v1alpha1.IdentityProviderConfiguration{ObjectMeta: metav1.ObjectMeta{Name: workspaceName}}
_, err = controllerutil.CreateOrPatch(ctx, cl.GetClient(), idp, func() error {
_, err = controllerutil.CreateOrPatch(ctx, i.orgsClient, idp, func() error {
idp.Spec.RegistrationAllowed = i.registrationAllowed

for _, desired := range clients {
Expand All @@ -141,7 +141,7 @@ func (i *IDPSubroutine) Initialize(ctx context.Context, instance runtimeobject.R

log.Info().Str("workspace", workspaceName).Msg("idp configuration resource is created")

if err := cl.GetClient().Get(ctx, types.NamespacedName{Name: workspaceName}, idp); err != nil {
if err := i.orgsClient.Get(ctx, types.NamespacedName{Name: workspaceName}, idp); err != nil {
return ctrl.Result{}, errors.NewOperatorError(fmt.Errorf("failed to get idp resource %w", err), true, true)
}

Expand Down
26 changes: 11 additions & 15 deletions internal/subroutine/workspace_authorization.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (
"github.com/platform-mesh/golang-commons/controller/lifecycle/runtimeobject"
lifecyclesubroutine "github.com/platform-mesh/golang-commons/controller/lifecycle/subroutine"
"github.com/platform-mesh/golang-commons/errors"
"github.com/platform-mesh/security-operator/api/v1alpha1"
accountsv1alpha1 "github.com/platform-mesh/account-operator/api/v1alpha1"
"github.com/platform-mesh/security-operator/internal/config"
"github.com/rs/zerolog/log"
ctrl "sigs.k8s.io/controller-runtime"
Expand Down Expand Up @@ -84,26 +84,22 @@ func (r *workspaceAuthSubroutine) Initialize(ctx context.Context, instance runti
return ctrl.Result{}, errors.NewOperatorError(fmt.Errorf("failed to get cluster from context %w", err), true, true)
}

var idpConfig v1alpha1.IdentityProviderConfiguration
err = cluster.GetClient().Get(ctx, types.NamespacedName{Name: workspaceName}, &idpConfig)
var accountInfo accountsv1alpha1.AccountInfo
err = cluster.GetClient().Get(ctx, types.NamespacedName{Name: "account"}, &accountInfo)
if err != nil {
return ctrl.Result{}, errors.NewOperatorError(fmt.Errorf("failed to get IdentityProviderConfiguration: %w", err), true, true)
return ctrl.Result{}, errors.NewOperatorError(fmt.Errorf("failed to get AccountInfo: %w", err), true, true)
}

if len(idpConfig.Spec.Clients) == 0 || len(idpConfig.Status.ManagedClients) == 0 {
return ctrl.Result{}, errors.NewOperatorError(fmt.Errorf("IdentityProviderConfiguration %s has no clients in spec or status", workspaceName), true, false)
if accountInfo.Spec.OIDC == nil || len(accountInfo.Spec.OIDC.Clients) == 0 {
return ctrl.Result{}, errors.NewOperatorError(fmt.Errorf("AccountInfo %s has no OIDC clients", workspaceName), true, false)
}

audiences := make([]string, 0, len(idpConfig.Spec.Clients))
for _, specClient := range idpConfig.Spec.Clients {
managedClient, ok := idpConfig.Status.ManagedClients[specClient.ClientName]
if !ok {
return ctrl.Result{}, errors.NewOperatorError(fmt.Errorf("managed client %s not found in IdentityProviderConfiguration status", specClient.ClientName), true, false)
audiences := make([]string, 0, len(accountInfo.Spec.OIDC.Clients))
for clientName, clientInfo := range accountInfo.Spec.OIDC.Clients {
if clientInfo.ClientID == "" {
return ctrl.Result{}, errors.NewOperatorError(fmt.Errorf("OIDC client %s has empty ClientID in AccountInfo", clientName), true, false)
}
if managedClient.ClientID == "" {
return ctrl.Result{}, errors.NewOperatorError(fmt.Errorf("managed client %s has empty ClientID in IdentityProviderConfiguration status", specClient.ClientName), true, false)
}
audiences = append(audiences, managedClient.ClientID)
audiences = append(audiences, clientInfo.ClientID)
}

jwtAuthenticationConfiguration := kcptenancyv1alphav1.JWTAuthenticator{
Expand Down
Loading
Loading