Skip to content
Merged
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
21 changes: 20 additions & 1 deletion docs/Configuring.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ The platform leverages [viper](https://github.com/spf13/viper) to help load conf

- [Platform Configuration](#platform-configuration)
- [Deployment Mode](#deployment-mode)
- [Service Negation](#service-negation)
- [SDK Configuration](#sdk-configuration)
- [Logger Configuration](#logger-configuration)
- [Server Configuration](#server-configuration)
Expand All @@ -31,11 +32,29 @@ The platform is designed as a modular monolith, meaning that all services are bu
- core: Runs essential services, including policy, authorization, and wellknown services.
- kas: Runs the Key Access Server (KAS) service.

### Service Negation

You can exclude specific services from any mode using the negation syntax `-servicename`:

- **Syntax**: `mode: <base-mode>,-<service1>,-<service2>`
- **Constraint**: At least one positive mode must be specified (negation-only modes like `-kas` will result in an error)
- **Available services**: `policy`, `authorization`, `kas`, `entityresolution`, `wellknown`

**Examples:**
```yaml
# Run all services except Entity Resolution Service
mode: all,-entityresolution

# Run core services except Policy Service
mode: core,-policy

# Run all services except both KAS and Entity Resolution
mode: all,-kas,-entityresolution
```

| Field | Description | Default | Environment Variable |
| ------ | ----------------------------------------------------------------------------- | ------- | -------------------- |
| `mode` | Drives which services to run. Following modes are supported. (all, core, kas) | `all` | OPENTDF_MODE |
| `mode` | Drives which services to run. Supported modes: `all`, `core`, `kas`. Use `-servicename` to exclude specific services (e.g., `all,-entityresolution`) | `all` | OPENTDF_MODE |

## SDK Configuration

Expand Down
151 changes: 62 additions & 89 deletions service/pkg/server/services.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ import (
"embed"
"fmt"
"log/slog"
"slices"
"strings"

"github.com/go-viper/mapstructure/v2"
"github.com/opentdf/platform/sdk"
Expand All @@ -30,92 +28,77 @@ import (
"go.opentelemetry.io/otel/trace"
)

const (
modeALL = "all"
modeCore = "core"
modeKAS = "kas"
modeERS = "entityresolution"
modeEssential = "essential"

serviceKAS = "kas"
servicePolicy = "policy"
serviceWellKnown = "wellknown"
serviceEntityResolution = "entityresolution"
serviceAuthorization = "authorization"
var (
ServiceHealth ServiceName = "health"
ServiceKAS ServiceName = "kas"
ServicePolicy ServiceName = "policy"
ServiceWellKnown ServiceName = "wellknown"
ServiceEntityResolution ServiceName = "entityresolution"
ServiceAuthorization ServiceName = "authorization"
)

// registerEssentialServices registers the essential services to the given service registry.
// It takes a serviceregistry.Registry as input and returns an error if registration fails.
func registerEssentialServices(reg *serviceregistry.Registry) error {
// getServiceConfigurations returns fresh service configurations each time it's called.
// This prevents state sharing between test runs by creating new service instances.
func getServiceConfigurations() []serviceregistry.ServiceConfiguration {
return []serviceregistry.ServiceConfiguration{
// Note: Health service is registered separately via RegisterEssentialServices
{
Name: ServicePolicy,
Modes: []serviceregistry.ModeName{serviceregistry.ModeALL, serviceregistry.ModeCore},
Services: policy.NewRegistrations(),
},
{
Name: ServiceAuthorization,
Modes: []serviceregistry.ModeName{serviceregistry.ModeALL, serviceregistry.ModeCore},
Services: []serviceregistry.IService{authorization.NewRegistration(), authorizationV2.NewRegistration()},
},
{
Name: ServiceKAS,
Modes: []serviceregistry.ModeName{serviceregistry.ModeALL, serviceregistry.ModeKAS},
Services: []serviceregistry.IService{kas.NewRegistration()},
},
{
Name: ServiceWellKnown,
Modes: []serviceregistry.ModeName{serviceregistry.ModeALL, serviceregistry.ModeCore},
Services: []serviceregistry.IService{wellknown.NewRegistration()},
},
{
Name: ServiceEntityResolution,
Modes: []serviceregistry.ModeName{serviceregistry.ModeALL, serviceregistry.ModeERS},
Services: []serviceregistry.IService{entityresolution.NewRegistration(), entityresolutionV2.NewRegistration()},
},
}
}

// RegisterEssentialServices registers the essential services directly
func RegisterEssentialServices(reg *serviceregistry.Registry) error {
essentialServices := []serviceregistry.IService{
health.NewRegistration(),
}
// Register the essential services
for _, s := range essentialServices {
if err := reg.RegisterService(s, modeEssential); err != nil {
return err //nolint:wrapcheck // We are all friends here
for _, svc := range essentialServices {
if err := reg.RegisterService(svc, serviceregistry.ModeEssential); err != nil {
return err
}
}
return nil
}

// registerCoreServices registers the core services based on the provided mode.
// It returns the list of registered services and any error encountered during registration.
func registerCoreServices(reg *serviceregistry.Registry, mode []string) ([]string, error) {
var (
services []serviceregistry.IService
registeredServices []string
)

for _, m := range mode {
switch m {
case "all":
registeredServices = append(registeredServices, []string{servicePolicy, serviceAuthorization, serviceKAS, serviceWellKnown, serviceEntityResolution}...)
services = append(services, []serviceregistry.IService{
authorization.NewRegistration(),
authorizationV2.NewRegistration(),
kas.NewRegistration(),
wellknown.NewRegistration(),
entityresolution.NewRegistration(),
entityresolutionV2.NewRegistration(),
}...)
services = append(services, policy.NewRegistrations()...)
case "core":
registeredServices = append(registeredServices, []string{servicePolicy, serviceAuthorization, serviceWellKnown}...)
services = append(services, []serviceregistry.IService{
authorization.NewRegistration(),
authorizationV2.NewRegistration(),
wellknown.NewRegistration(),
}...)
services = append(services, policy.NewRegistrations()...)
case "kas":
// If the mode is "kas", register only the KAS service
registeredServices = append(registeredServices, serviceKAS)
if err := reg.RegisterService(kas.NewRegistration(), modeKAS); err != nil {
return nil, err //nolint:wrapcheck // We are all friends here
}
case "entityresolution":
// If the mode is "entityresolution", register only the ERS service (v1 and v2)
registeredServices = append(registeredServices, serviceEntityResolution)
if err := reg.RegisterService(entityresolution.NewRegistration(), modeERS); err != nil {
return nil, err //nolint:wrapcheck // We are all friends here
}
if err := reg.RegisterService(entityresolutionV2.NewRegistration(), modeERS); err != nil {
return nil, err //nolint:wrapcheck // We are all friends here
}
default:
continue
}
// RegisterCoreServices registers the core services using declarative configuration
func RegisterCoreServices(reg *serviceregistry.Registry, modes []serviceregistry.ModeName) ([]string, error) {
// Convert ModeName slice to string slice
stringModes := make([]string, len(modes))
for i, mode := range modes {
stringModes[i] = mode.String()
}
return reg.RegisterServicesFromConfiguration(stringModes, getServiceConfigurations())
}

// Register the services
for _, s := range services {
if err := reg.RegisterCoreService(s); err != nil {
return nil, err //nolint:wrapcheck // We are all friends here
}
}
// ServiceName represents a typed service identifier
type ServiceName string

return registeredServices, nil
// String returns the string representation of ServiceName
func (s ServiceName) String() string {
return string(s)
}

type startServicesParams struct {
Expand Down Expand Up @@ -143,20 +126,10 @@ func startServices(ctx context.Context, params startServicesParams) (func(), err
cacheManager := params.cacheManager
keyManagerFactories := params.keyManagerFactories

for _, ns := range reg.GetNamespaces() {
namespace, err := reg.GetNamespace(ns)
if err != nil {
// This is an internal inconsistency and should not happen.
return nil, fmt.Errorf("namespace not found: %w", err)
}
// modeEnabled checks if the mode is enabled based on the configuration and namespace mode.
// It returns true if the mode is "all" or "essential" in the configuration, or if it matches the namespace mode.
modeEnabled := slices.ContainsFunc(cfg.Mode, func(m string) bool {
if strings.EqualFold(m, modeALL) || strings.EqualFold(namespace.Mode, modeEssential) {
return true
}
return strings.EqualFold(m, namespace.Mode)
})
// Iterate through the registered namespaces
for ns, namespace := range reg.GetNamespaces() {
// Check if this namespace should be enabled based on configured modes
modeEnabled := namespace.IsEnabled(cfg.Mode)

// Skip the namespace if the mode is not enabled
if !modeEnabled {
Expand Down
Loading
Loading