Skip to content

Commit ce289a4

Browse files
feat: update service registry in preperation for connectrpc migration (#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]>
1 parent ce3c0da commit ce289a4

File tree

22 files changed

+410
-352
lines changed

22 files changed

+410
-352
lines changed

service/authorization/authorization.go

Lines changed: 65 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -54,92 +54,90 @@ type CustomRego struct {
5454
Query string `mapstructure:"query" json:"query" default:"data.opentdf.entitlements.attributes"`
5555
}
5656

57-
func NewRegistration() serviceregistry.Registration {
58-
return serviceregistry.Registration{
59-
Namespace: "authorization",
60-
ServiceDesc: &authorization.AuthorizationService_ServiceDesc,
61-
RegisterFunc: func(srp serviceregistry.RegistrationParams) (any, serviceregistry.HandlerServer) {
62-
var (
63-
err error
64-
entitlementRego []byte
65-
authZCfg = new(Config)
66-
)
67-
68-
logger := srp.Logger
69-
70-
// default ERS endpoint
71-
as := &AuthorizationService{sdk: srp.SDK, logger: logger}
72-
if err := srp.RegisterReadinessCheck("authorization", as.IsReady); err != nil {
73-
logger.Error("failed to register authorization readiness check", slog.String("error", err.Error()))
74-
}
57+
func NewRegistration() *serviceregistry.Service[AuthorizationService] {
58+
return &serviceregistry.Service[AuthorizationService]{
59+
ServiceOptions: serviceregistry.ServiceOptions[AuthorizationService]{
60+
Namespace: "authorization",
61+
ServiceDesc: &authorization.AuthorizationService_ServiceDesc,
62+
RegisterFunc: func(srp serviceregistry.RegistrationParams) (*AuthorizationService, serviceregistry.HandlerServer) {
63+
var (
64+
err error
65+
entitlementRego []byte
66+
authZCfg = new(Config)
67+
)
7568

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

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

87-
// Validate Config
88-
validate := validator.New(validator.WithRequiredStructEnabled())
89-
if err := validate.Struct(authZCfg); err != nil {
90-
var invalidValidationError *validator.InvalidValidationError
91-
if errors.As(err, &invalidValidationError) {
92-
logger.Error("error validating authorization service config", slog.String("error", err.Error()))
93-
panic(fmt.Errorf("error validating authorization service config: %w", err))
77+
if err := defaults.Set(authZCfg); err != nil {
78+
panic(fmt.Errorf("failed to set defaults for authorization service config: %w", err))
79+
}
80+
81+
// Only decode config if it exists
82+
if srp.Config != nil {
83+
if err := mapstructure.Decode(srp.Config, &authZCfg); err != nil {
84+
panic(fmt.Errorf("invalid auth svc cfg [%v] %w", srp.Config, err))
85+
}
9486
}
9587

96-
var validationErrors validator.ValidationErrors
97-
if errors.As(err, &validationErrors) {
98-
for _, err := range validationErrors {
88+
// Validate Config
89+
validate := validator.New(validator.WithRequiredStructEnabled())
90+
if err := validate.Struct(authZCfg); err != nil {
91+
var invalidValidationError *validator.InvalidValidationError
92+
if errors.As(err, &invalidValidationError) {
9993
logger.Error("error validating authorization service config", slog.String("error", err.Error()))
10094
panic(fmt.Errorf("error validating authorization service config: %w", err))
10195
}
96+
97+
var validationErrors validator.ValidationErrors
98+
if errors.As(err, &validationErrors) {
99+
for _, err := range validationErrors {
100+
logger.Error("error validating authorization service config", slog.String("error", err.Error()))
101+
panic(fmt.Errorf("error validating authorization service config: %w", err))
102+
}
103+
}
102104
}
103-
}
104105

105-
logger.Debug("authorization service config", slog.Any("config", *authZCfg))
106+
logger.Debug("authorization service config", slog.Any("config", *authZCfg))
106107

107-
// Build Rego PreparedEvalQuery
108+
// Build Rego PreparedEvalQuery
108109

109-
// Load rego from embedded file or custom path
110-
if authZCfg.Rego.Path != "" {
111-
entitlementRego, err = os.ReadFile(authZCfg.Rego.Path)
112-
if err != nil {
113-
panic(fmt.Errorf("failed to read custom entitlements.rego file: %w", err))
114-
}
115-
} else {
116-
entitlementRego, err = policies.EntitlementsRego.ReadFile("entitlements/entitlements.rego")
117-
if err != nil {
118-
panic(fmt.Errorf("failed to read entitlements.rego file: %w", err))
110+
// Load rego from embedded file or custom path
111+
if authZCfg.Rego.Path != "" {
112+
entitlementRego, err = os.ReadFile(authZCfg.Rego.Path)
113+
if err != nil {
114+
panic(fmt.Errorf("failed to read custom entitlements.rego file: %w", err))
115+
}
116+
} else {
117+
entitlementRego, err = policies.EntitlementsRego.ReadFile("entitlements/entitlements.rego")
118+
if err != nil {
119+
panic(fmt.Errorf("failed to read entitlements.rego file: %w", err))
120+
}
119121
}
120-
}
121122

122-
// Register builtin
123-
subjectmappingbuiltin.SubjectMappingBuiltin()
123+
// Register builtin
124+
subjectmappingbuiltin.SubjectMappingBuiltin()
124125

125-
as.eval, err = rego.New(
126-
rego.Query(authZCfg.Rego.Query),
127-
rego.Module("entitlements.rego", string(entitlementRego)),
128-
rego.StrictBuiltinErrors(true),
129-
).PrepareForEval(context.Background())
130-
if err != nil {
131-
panic(fmt.Errorf("failed to prepare entitlements.rego for eval: %w", err))
132-
}
126+
as.eval, err = rego.New(
127+
rego.Query(authZCfg.Rego.Query),
128+
rego.Module("entitlements.rego", string(entitlementRego)),
129+
rego.StrictBuiltinErrors(true),
130+
).PrepareForEval(context.Background())
131+
if err != nil {
132+
panic(fmt.Errorf("failed to prepare entitlements.rego for eval: %w", err))
133+
}
133134

134-
as.config = *authZCfg
135+
as.config = *authZCfg
135136

136-
return as, func(ctx context.Context, mux *runtime.ServeMux, server any) error {
137-
authServer, okAuth := server.(authorization.AuthorizationServiceServer)
138-
if !okAuth {
139-
return fmt.Errorf("failed to assert server type to authorization.AuthorizationServiceServer")
137+
return as, func(ctx context.Context, mux *runtime.ServeMux) error {
138+
return authorization.RegisterAuthorizationServiceHandlerServer(ctx, mux, as)
140139
}
141-
return authorization.RegisterAuthorizationServiceHandlerServer(ctx, mux, authServer)
142-
}
140+
},
143141
},
144142
}
145143
}

service/entityresolution/claims/claims_entity_resolution.go

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,11 @@ type ClaimsEntityResolutionService struct {
2222
logger *logger.Logger
2323
}
2424

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

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

6667
for idx, ident := range payload {
67-
var entityStruct = &structpb.Struct{}
68+
entityStruct := &structpb.Struct{}
6869
switch ident.GetEntityType().(type) {
6970
case *authorization.Entity_Claims:
7071
claims := ident.GetClaims()

service/entityresolution/entityresolution.go

Lines changed: 26 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -12,25 +12,35 @@ type ERSConfig struct {
1212
Mode string `mapstructure:"mode" json:"mode"`
1313
}
1414

15-
const KeycloakMode = "keycloak"
16-
const ClaimsMode = "claims"
15+
const (
16+
KeycloakMode = "keycloak"
17+
ClaimsMode = "claims"
18+
)
19+
20+
type EntityResolution struct {
21+
entityresolution.EntityResolutionServiceServer
22+
}
1723

18-
func NewRegistration() serviceregistry.Registration {
19-
return serviceregistry.Registration{
20-
Namespace: "entityresolution",
21-
ServiceDesc: &entityresolution.EntityResolutionService_ServiceDesc,
22-
RegisterFunc: func(srp serviceregistry.RegistrationParams) (any, serviceregistry.HandlerServer) {
23-
var inputConfig ERSConfig
24+
func NewRegistration() *serviceregistry.Service[EntityResolution] {
25+
return &serviceregistry.Service[EntityResolution]{
26+
ServiceOptions: serviceregistry.ServiceOptions[EntityResolution]{
27+
Namespace: "entityresolution",
28+
ServiceDesc: &entityresolution.EntityResolutionService_ServiceDesc,
29+
RegisterFunc: func(srp serviceregistry.RegistrationParams) (*EntityResolution, serviceregistry.HandlerServer) {
30+
var inputConfig ERSConfig
2431

25-
if err := mapstructure.Decode(srp.Config, &inputConfig); err != nil {
26-
panic(err)
27-
}
28-
if inputConfig.Mode == ClaimsMode {
29-
return claims.RegisterClaimsERS(srp.Config, srp.Logger)
30-
}
32+
if err := mapstructure.Decode(srp.Config, &inputConfig); err != nil {
33+
panic(err)
34+
}
35+
if inputConfig.Mode == ClaimsMode {
36+
claimsSVC, claimsHandler := claims.RegisterClaimsERS(srp.Config, srp.Logger)
37+
return &EntityResolution{EntityResolutionServiceServer: claimsSVC}, claimsHandler
38+
}
3139

32-
// Default to keyclaok ERS
33-
return keycloak.RegisterKeycloakERS(srp.Config, srp.Logger)
40+
// Default to keycloak ERS
41+
kcSVC, kcHandler := keycloak.RegisterKeycloakERS(srp.Config, srp.Logger)
42+
return &EntityResolution{EntityResolutionServiceServer: kcSVC}, kcHandler
43+
},
3444
},
3545
}
3646
}

service/entityresolution/keycloak/keycloak_entity_resolution.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -52,16 +52,16 @@ type KeycloakConfig struct {
5252
InferID InferredIdentityConfig `mapstructure:"inferid,omitempty" json:"inferid,omitempty"`
5353
}
5454

55-
func RegisterKeycloakERS(config serviceregistry.ServiceConfig, logger *logger.Logger) (any, serviceregistry.HandlerServer) {
55+
func RegisterKeycloakERS(config serviceregistry.ServiceConfig, logger *logger.Logger) (*KeycloakEntityResolutionService, serviceregistry.HandlerServer) {
5656
var inputIdpConfig KeycloakConfig
5757
if err := mapstructure.Decode(config, &inputIdpConfig); err != nil {
5858
panic(err)
5959
}
6060
logger.Debug("entity_resolution configuration", "config", inputIdpConfig)
61-
62-
return &KeycloakEntityResolutionService{idpConfig: inputIdpConfig, logger: logger},
63-
func(ctx context.Context, mux *runtime.ServeMux, server any) error {
64-
return entityresolution.RegisterEntityResolutionServiceHandlerServer(ctx, mux, server.(entityresolution.EntityResolutionServiceServer)) //nolint:forcetypeassert // allow type assert, following other services
61+
keycloakSVC := &KeycloakEntityResolutionService{idpConfig: inputIdpConfig, logger: logger}
62+
return keycloakSVC,
63+
func(ctx context.Context, mux *runtime.ServeMux) error {
64+
return entityresolution.RegisterEntityResolutionServiceHandlerServer(ctx, mux, keycloakSVC)
6565
}
6666
}
6767

service/go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ module github.com/opentdf/platform/service
33
go 1.22
44

55
require (
6+
connectrpc.com/connect v1.17.0
67
github.com/Masterminds/squirrel v1.5.4
78
github.com/Nerzal/gocloak/v13 v13.9.0
89
github.com/bmatcuk/doublestar v1.3.4

service/go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.34.1-20240508200655-46a4cf4ba109.1 h1:LEXWFH/xZ5oOWrC3oOtHbUyBdzRWMCPpAQmKC9v05mA=
22
buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.34.1-20240508200655-46a4cf4ba109.1/go.mod h1:XF+P8+RmfdufmIYpGUC+6bF7S+IlmHDEnCrO3OXaUAQ=
3+
connectrpc.com/connect v1.17.0 h1:W0ZqMhtVzn9Zhn2yATuUokDLO5N+gIuBWMOnsQrfmZk=
4+
connectrpc.com/connect v1.17.0/go.mod h1:0292hj1rnx8oFrStN7cB4jjVBeqs+Yx5yDIC2prWDO8=
35
dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
46
dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
57
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9vkmnHYOMsOr4WLk+Vo07yKIzd94sVoIqshQ4bU=

service/health/health.go

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -12,29 +12,29 @@ import (
1212
"google.golang.org/grpc/status"
1313
)
1414

15-
var (
16-
serviceHealthChecks = make(map[string]func(context.Context) error)
17-
)
15+
var serviceHealthChecks = make(map[string]func(context.Context) error)
1816

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

24-
func NewRegistration() serviceregistry.Registration {
25-
return serviceregistry.Registration{
26-
Namespace: "health",
27-
ServiceDesc: &healthpb.Health_ServiceDesc,
28-
RegisterFunc: func(srp serviceregistry.RegistrationParams) (any, serviceregistry.HandlerServer) {
29-
err := srp.WellKnownConfig("health", map[string]any{
30-
"endpoint": "/healthz",
31-
})
32-
if err != nil {
33-
srp.Logger.Error("failed to set well-known config", slog.String("error", err.Error()))
34-
}
35-
return &HealthService{logger: srp.Logger}, func(_ context.Context, _ *runtime.ServeMux, _ any) error {
36-
return nil
37-
}
22+
func NewRegistration() *serviceregistry.Service[HealthService] {
23+
return &serviceregistry.Service[HealthService]{
24+
ServiceOptions: serviceregistry.ServiceOptions[HealthService]{
25+
Namespace: "health",
26+
ServiceDesc: &healthpb.Health_ServiceDesc,
27+
RegisterFunc: func(srp serviceregistry.RegistrationParams) (*HealthService, serviceregistry.HandlerServer) {
28+
err := srp.WellKnownConfig("health", map[string]any{
29+
"endpoint": "/healthz",
30+
})
31+
if err != nil {
32+
srp.Logger.Error("failed to set well-known config", slog.String("error", err.Error()))
33+
}
34+
return &HealthService{logger: srp.Logger}, func(_ context.Context, _ *runtime.ServeMux) error {
35+
return nil
36+
}
37+
},
3838
},
3939
}
4040
}

0 commit comments

Comments
 (0)