Skip to content

Commit

Permalink
feat: update service registry in preperation for connectrpc migration (
Browse files Browse the repository at this point in the history
…#1715)

Depends On: #1709 

To transition to ConnectRPC and register service handlers effectively,
we need to utilize generics. The reason for this is that the generated
ConnectRPC code for creating handlers enforces strong typing:

`NewXServiceHandler(svc XServiceHandler, opts ...connect.HandlerOption)`

For reference, see an example here: 


https://github.com/opentdf/platform/blob/0ef65d410a8e1bc8b82f52b6a4f0f469a2f7f4fe/protocol/go/policy/kasregistry/kasregistryconnect/key_access_server_registry.connect.go#L190C51-L190C88

By leveraging generics, we can maintain type safety while also keeping
interceptors centrally managed. This approach ensures that no individual
service can inadvertently modify the interceptor list, helping maintain
consistent security and functionality across all services.

---------

Co-authored-by: Jake Van Vorhis <[email protected]>
  • Loading branch information
strantalis and jakedoublev authored Nov 6, 2024
1 parent ce3c0da commit ce289a4
Show file tree
Hide file tree
Showing 22 changed files with 410 additions and 352 deletions.
132 changes: 65 additions & 67 deletions service/authorization/authorization.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,92 +54,90 @@ type CustomRego struct {
Query string `mapstructure:"query" json:"query" default:"data.opentdf.entitlements.attributes"`
}

func NewRegistration() serviceregistry.Registration {
return serviceregistry.Registration{
Namespace: "authorization",
ServiceDesc: &authorization.AuthorizationService_ServiceDesc,
RegisterFunc: func(srp serviceregistry.RegistrationParams) (any, serviceregistry.HandlerServer) {
var (
err error
entitlementRego []byte
authZCfg = new(Config)
)

logger := srp.Logger

// default ERS endpoint
as := &AuthorizationService{sdk: srp.SDK, logger: logger}
if err := srp.RegisterReadinessCheck("authorization", as.IsReady); err != nil {
logger.Error("failed to register authorization readiness check", slog.String("error", err.Error()))
}
func NewRegistration() *serviceregistry.Service[AuthorizationService] {
return &serviceregistry.Service[AuthorizationService]{
ServiceOptions: serviceregistry.ServiceOptions[AuthorizationService]{
Namespace: "authorization",
ServiceDesc: &authorization.AuthorizationService_ServiceDesc,
RegisterFunc: func(srp serviceregistry.RegistrationParams) (*AuthorizationService, serviceregistry.HandlerServer) {
var (
err error
entitlementRego []byte
authZCfg = new(Config)
)

if err := defaults.Set(authZCfg); err != nil {
panic(fmt.Errorf("failed to set defaults for authorization service config: %w", err))
}
logger := srp.Logger

// Only decode config if it exists
if srp.Config != nil {
if err := mapstructure.Decode(srp.Config, &authZCfg); err != nil {
panic(fmt.Errorf("invalid auth svc cfg [%v] %w", srp.Config, err))
// default ERS endpoint
as := &AuthorizationService{sdk: srp.SDK, logger: logger}
if err := srp.RegisterReadinessCheck("authorization", as.IsReady); err != nil {
logger.Error("failed to register authorization readiness check", slog.String("error", err.Error()))
}
}

// Validate Config
validate := validator.New(validator.WithRequiredStructEnabled())
if err := validate.Struct(authZCfg); err != nil {
var invalidValidationError *validator.InvalidValidationError
if errors.As(err, &invalidValidationError) {
logger.Error("error validating authorization service config", slog.String("error", err.Error()))
panic(fmt.Errorf("error validating authorization service config: %w", err))
if err := defaults.Set(authZCfg); err != nil {
panic(fmt.Errorf("failed to set defaults for authorization service config: %w", err))
}

// Only decode config if it exists
if srp.Config != nil {
if err := mapstructure.Decode(srp.Config, &authZCfg); err != nil {
panic(fmt.Errorf("invalid auth svc cfg [%v] %w", srp.Config, err))
}
}

var validationErrors validator.ValidationErrors
if errors.As(err, &validationErrors) {
for _, err := range validationErrors {
// Validate Config
validate := validator.New(validator.WithRequiredStructEnabled())
if err := validate.Struct(authZCfg); err != nil {
var invalidValidationError *validator.InvalidValidationError
if errors.As(err, &invalidValidationError) {
logger.Error("error validating authorization service config", slog.String("error", err.Error()))
panic(fmt.Errorf("error validating authorization service config: %w", err))
}

var validationErrors validator.ValidationErrors
if errors.As(err, &validationErrors) {
for _, err := range validationErrors {
logger.Error("error validating authorization service config", slog.String("error", err.Error()))
panic(fmt.Errorf("error validating authorization service config: %w", err))
}
}
}
}

logger.Debug("authorization service config", slog.Any("config", *authZCfg))
logger.Debug("authorization service config", slog.Any("config", *authZCfg))

// Build Rego PreparedEvalQuery
// Build Rego PreparedEvalQuery

// Load rego from embedded file or custom path
if authZCfg.Rego.Path != "" {
entitlementRego, err = os.ReadFile(authZCfg.Rego.Path)
if err != nil {
panic(fmt.Errorf("failed to read custom entitlements.rego file: %w", err))
}
} else {
entitlementRego, err = policies.EntitlementsRego.ReadFile("entitlements/entitlements.rego")
if err != nil {
panic(fmt.Errorf("failed to read entitlements.rego file: %w", err))
// Load rego from embedded file or custom path
if authZCfg.Rego.Path != "" {
entitlementRego, err = os.ReadFile(authZCfg.Rego.Path)
if err != nil {
panic(fmt.Errorf("failed to read custom entitlements.rego file: %w", err))
}
} else {
entitlementRego, err = policies.EntitlementsRego.ReadFile("entitlements/entitlements.rego")
if err != nil {
panic(fmt.Errorf("failed to read entitlements.rego file: %w", err))
}
}
}

// Register builtin
subjectmappingbuiltin.SubjectMappingBuiltin()
// Register builtin
subjectmappingbuiltin.SubjectMappingBuiltin()

as.eval, err = rego.New(
rego.Query(authZCfg.Rego.Query),
rego.Module("entitlements.rego", string(entitlementRego)),
rego.StrictBuiltinErrors(true),
).PrepareForEval(context.Background())
if err != nil {
panic(fmt.Errorf("failed to prepare entitlements.rego for eval: %w", err))
}
as.eval, err = rego.New(
rego.Query(authZCfg.Rego.Query),
rego.Module("entitlements.rego", string(entitlementRego)),
rego.StrictBuiltinErrors(true),
).PrepareForEval(context.Background())
if err != nil {
panic(fmt.Errorf("failed to prepare entitlements.rego for eval: %w", err))
}

as.config = *authZCfg
as.config = *authZCfg

return as, func(ctx context.Context, mux *runtime.ServeMux, server any) error {
authServer, okAuth := server.(authorization.AuthorizationServiceServer)
if !okAuth {
return fmt.Errorf("failed to assert server type to authorization.AuthorizationServiceServer")
return as, func(ctx context.Context, mux *runtime.ServeMux) error {
return authorization.RegisterAuthorizationServiceHandlerServer(ctx, mux, as)
}
return authorization.RegisterAuthorizationServiceHandlerServer(ctx, mux, authServer)
}
},
},
}
}
Expand Down
11 changes: 6 additions & 5 deletions service/entityresolution/claims/claims_entity_resolution.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,11 @@ type ClaimsEntityResolutionService struct {
logger *logger.Logger
}

func RegisterClaimsERS(_ serviceregistry.ServiceConfig, logger *logger.Logger) (any, serviceregistry.HandlerServer) {
return &ClaimsEntityResolutionService{logger: logger},
func(ctx context.Context, mux *runtime.ServeMux, server any) error {
return entityresolution.RegisterEntityResolutionServiceHandlerServer(ctx, mux, server.(entityresolution.EntityResolutionServiceServer)) //nolint:forcetypeassert // allow type assert, following other services
func RegisterClaimsERS(_ serviceregistry.ServiceConfig, logger *logger.Logger) (ClaimsEntityResolutionService, serviceregistry.HandlerServer) {
claimsSVC := ClaimsEntityResolutionService{logger: logger}
return claimsSVC,
func(ctx context.Context, mux *runtime.ServeMux) error {
return entityresolution.RegisterEntityResolutionServiceHandlerServer(ctx, mux, claimsSVC)
}
}

Expand Down Expand Up @@ -64,7 +65,7 @@ func EntityResolution(_ context.Context,
var resolvedEntities []*entityresolution.EntityRepresentation

for idx, ident := range payload {
var entityStruct = &structpb.Struct{}
entityStruct := &structpb.Struct{}
switch ident.GetEntityType().(type) {
case *authorization.Entity_Claims:
claims := ident.GetClaims()
Expand Down
42 changes: 26 additions & 16 deletions service/entityresolution/entityresolution.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,25 +12,35 @@ type ERSConfig struct {
Mode string `mapstructure:"mode" json:"mode"`
}

const KeycloakMode = "keycloak"
const ClaimsMode = "claims"
const (
KeycloakMode = "keycloak"
ClaimsMode = "claims"
)

type EntityResolution struct {
entityresolution.EntityResolutionServiceServer
}

func NewRegistration() serviceregistry.Registration {
return serviceregistry.Registration{
Namespace: "entityresolution",
ServiceDesc: &entityresolution.EntityResolutionService_ServiceDesc,
RegisterFunc: func(srp serviceregistry.RegistrationParams) (any, serviceregistry.HandlerServer) {
var inputConfig ERSConfig
func NewRegistration() *serviceregistry.Service[EntityResolution] {
return &serviceregistry.Service[EntityResolution]{
ServiceOptions: serviceregistry.ServiceOptions[EntityResolution]{
Namespace: "entityresolution",
ServiceDesc: &entityresolution.EntityResolutionService_ServiceDesc,
RegisterFunc: func(srp serviceregistry.RegistrationParams) (*EntityResolution, serviceregistry.HandlerServer) {
var inputConfig ERSConfig

if err := mapstructure.Decode(srp.Config, &inputConfig); err != nil {
panic(err)
}
if inputConfig.Mode == ClaimsMode {
return claims.RegisterClaimsERS(srp.Config, srp.Logger)
}
if err := mapstructure.Decode(srp.Config, &inputConfig); err != nil {
panic(err)
}
if inputConfig.Mode == ClaimsMode {
claimsSVC, claimsHandler := claims.RegisterClaimsERS(srp.Config, srp.Logger)
return &EntityResolution{EntityResolutionServiceServer: claimsSVC}, claimsHandler
}

// Default to keyclaok ERS
return keycloak.RegisterKeycloakERS(srp.Config, srp.Logger)
// Default to keycloak ERS
kcSVC, kcHandler := keycloak.RegisterKeycloakERS(srp.Config, srp.Logger)
return &EntityResolution{EntityResolutionServiceServer: kcSVC}, kcHandler
},
},
}
}
10 changes: 5 additions & 5 deletions service/entityresolution/keycloak/keycloak_entity_resolution.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,16 +52,16 @@ type KeycloakConfig struct {
InferID InferredIdentityConfig `mapstructure:"inferid,omitempty" json:"inferid,omitempty"`
}

func RegisterKeycloakERS(config serviceregistry.ServiceConfig, logger *logger.Logger) (any, serviceregistry.HandlerServer) {
func RegisterKeycloakERS(config serviceregistry.ServiceConfig, logger *logger.Logger) (*KeycloakEntityResolutionService, serviceregistry.HandlerServer) {
var inputIdpConfig KeycloakConfig
if err := mapstructure.Decode(config, &inputIdpConfig); err != nil {
panic(err)
}
logger.Debug("entity_resolution configuration", "config", inputIdpConfig)

return &KeycloakEntityResolutionService{idpConfig: inputIdpConfig, logger: logger},
func(ctx context.Context, mux *runtime.ServeMux, server any) error {
return entityresolution.RegisterEntityResolutionServiceHandlerServer(ctx, mux, server.(entityresolution.EntityResolutionServiceServer)) //nolint:forcetypeassert // allow type assert, following other services
keycloakSVC := &KeycloakEntityResolutionService{idpConfig: inputIdpConfig, logger: logger}
return keycloakSVC,
func(ctx context.Context, mux *runtime.ServeMux) error {
return entityresolution.RegisterEntityResolutionServiceHandlerServer(ctx, mux, keycloakSVC)
}
}

Expand Down
1 change: 1 addition & 0 deletions service/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module github.com/opentdf/platform/service
go 1.22

require (
connectrpc.com/connect v1.17.0
github.com/Masterminds/squirrel v1.5.4
github.com/Nerzal/gocloak/v13 v13.9.0
github.com/bmatcuk/doublestar v1.3.4
Expand Down
2 changes: 2 additions & 0 deletions service/go.sum
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.34.1-20240508200655-46a4cf4ba109.1 h1:LEXWFH/xZ5oOWrC3oOtHbUyBdzRWMCPpAQmKC9v05mA=
buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.34.1-20240508200655-46a4cf4ba109.1/go.mod h1:XF+P8+RmfdufmIYpGUC+6bF7S+IlmHDEnCrO3OXaUAQ=
connectrpc.com/connect v1.17.0 h1:W0ZqMhtVzn9Zhn2yATuUokDLO5N+gIuBWMOnsQrfmZk=
connectrpc.com/connect v1.17.0/go.mod h1:0292hj1rnx8oFrStN7cB4jjVBeqs+Yx5yDIC2prWDO8=
dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9vkmnHYOMsOr4WLk+Vo07yKIzd94sVoIqshQ4bU=
Expand Down
34 changes: 17 additions & 17 deletions service/health/health.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,29 +12,29 @@ import (
"google.golang.org/grpc/status"
)

var (
serviceHealthChecks = make(map[string]func(context.Context) error)
)
var serviceHealthChecks = make(map[string]func(context.Context) error)

type HealthService struct { //nolint:revive // HealthService is a valid name for this struct
healthpb.UnimplementedHealthServer
logger *logger.Logger
}

func NewRegistration() serviceregistry.Registration {
return serviceregistry.Registration{
Namespace: "health",
ServiceDesc: &healthpb.Health_ServiceDesc,
RegisterFunc: func(srp serviceregistry.RegistrationParams) (any, serviceregistry.HandlerServer) {
err := srp.WellKnownConfig("health", map[string]any{
"endpoint": "/healthz",
})
if err != nil {
srp.Logger.Error("failed to set well-known config", slog.String("error", err.Error()))
}
return &HealthService{logger: srp.Logger}, func(_ context.Context, _ *runtime.ServeMux, _ any) error {
return nil
}
func NewRegistration() *serviceregistry.Service[HealthService] {
return &serviceregistry.Service[HealthService]{
ServiceOptions: serviceregistry.ServiceOptions[HealthService]{
Namespace: "health",
ServiceDesc: &healthpb.Health_ServiceDesc,
RegisterFunc: func(srp serviceregistry.RegistrationParams) (*HealthService, serviceregistry.HandlerServer) {
err := srp.WellKnownConfig("health", map[string]any{
"endpoint": "/healthz",
})
if err != nil {
srp.Logger.Error("failed to set well-known config", slog.String("error", err.Error()))
}
return &HealthService{logger: srp.Logger}, func(_ context.Context, _ *runtime.ServeMux) error {
return nil
}
},
},
}
}
Expand Down
Loading

0 comments on commit ce289a4

Please sign in to comment.