diff --git a/sdk/sdk.go b/sdk/sdk.go index dced9c9b20..9f435ee218 100644 --- a/sdk/sdk.go +++ b/sdk/sdk.go @@ -16,6 +16,7 @@ import ( "github.com/opentdf/platform/lib/ocrypto" "github.com/opentdf/platform/protocol/go/authorization" + authorizationV2 "github.com/opentdf/platform/protocol/go/authorization/v2" "github.com/opentdf/platform/protocol/go/entityresolution" entityresolutionV2 "github.com/opentdf/platform/protocol/go/entityresolution/v2" "github.com/opentdf/platform/protocol/go/policy" @@ -71,6 +72,7 @@ type SDK struct { Actions actions.ActionServiceClient Attributes attributes.AttributesServiceClient Authorization authorization.AuthorizationServiceClient + AuthorizationV2 authorizationV2.AuthorizationServiceClient EntityResoution entityresolution.EntityResolutionServiceClient EntityResolutionV2 entityresolutionV2.EntityResolutionServiceClient KeyAccessServerRegistry kasregistry.KeyAccessServerRegistryServiceClient @@ -208,16 +210,17 @@ func New(platformEndpoint string, opts ...Option) (*SDK, error) { tokenSource: accessTokenSource, Actions: actions.NewActionServiceClient(platformConn), Attributes: attributes.NewAttributesServiceClient(platformConn), + Authorization: authorization.NewAuthorizationServiceClient(platformConn), + AuthorizationV2: authorizationV2.NewAuthorizationServiceClient(platformConn), + EntityResoution: entityresolution.NewEntityResolutionServiceClient(ersConn), + EntityResolutionV2: entityresolutionV2.NewEntityResolutionServiceClient(ersConn), + KeyAccessServerRegistry: kasregistry.NewKeyAccessServerRegistryServiceClient(platformConn), + KeyManagement: keymanagement.NewKeyManagementServiceClient(platformConn), Namespaces: namespaces.NewNamespaceServiceClient(platformConn), RegisteredResources: registeredresources.NewRegisteredResourcesServiceClient(platformConn), ResourceMapping: resourcemapping.NewResourceMappingServiceClient(platformConn), SubjectMapping: subjectmapping.NewSubjectMappingServiceClient(platformConn), Unsafe: unsafe.NewUnsafeServiceClient(platformConn), - KeyAccessServerRegistry: kasregistry.NewKeyAccessServerRegistryServiceClient(platformConn), - Authorization: authorization.NewAuthorizationServiceClient(platformConn), - EntityResoution: entityresolution.NewEntityResolutionServiceClient(ersConn), - EntityResolutionV2: entityresolutionV2.NewEntityResolutionServiceClient(ersConn), - KeyManagement: keymanagement.NewKeyManagementServiceClient(platformConn), wellknownConfiguration: wellknownconfiguration.NewWellKnownServiceClient(platformConn), }, nil } diff --git a/service/authorization/v2/authorization.go b/service/authorization/v2/authorization.go new file mode 100644 index 0000000000..15e4d1b5a1 --- /dev/null +++ b/service/authorization/v2/authorization.go @@ -0,0 +1,80 @@ +package authorization + +import ( + "context" + "errors" + + "connectrpc.com/connect" + authzV2 "github.com/opentdf/platform/protocol/go/authorization/v2" + authzV2Connect "github.com/opentdf/platform/protocol/go/authorization/v2/authorizationv2connect" + otdf "github.com/opentdf/platform/sdk" + "github.com/opentdf/platform/service/logger" + "github.com/opentdf/platform/service/pkg/serviceregistry" + "go.opentelemetry.io/otel/trace" +) + +type Service struct { + sdk *otdf.SDK + config *Config + logger *logger.Logger + trace.Tracer +} + +type Config struct{} + +func NewRegistration() *serviceregistry.Service[authzV2Connect.AuthorizationServiceHandler] { + as := new(Service) + + return &serviceregistry.Service[authzV2Connect.AuthorizationServiceHandler]{ + ServiceOptions: serviceregistry.ServiceOptions[authzV2Connect.AuthorizationServiceHandler]{ + Namespace: "authorization", + Version: "v2", + ServiceDesc: &authzV2.AuthorizationService_ServiceDesc, + ConnectRPCFunc: authzV2Connect.NewAuthorizationServiceHandler, + RegisterFunc: func(srp serviceregistry.RegistrationParams) (authzV2Connect.AuthorizationServiceHandler, serviceregistry.HandlerServer) { + authZCfg := new(Config) + + logger := srp.Logger + + // default ERS endpoint + as.sdk = srp.SDK + as.logger = logger + // if err := srp.RegisterReadinessCheck("authorization", as.IsReady); err != nil { + // logger.Error("failed to register authorization readiness check", slog.String("error", err.Error())) + // } + + as.config = authZCfg + as.Tracer = srp.Tracer + logger.Debug("authorization v2 service register func") + + return as, nil + }, + }, + } +} + +// TODO: uncomment after v1 is deprecated, as cannot have more than one readiness check under a namespace +// func (as Service) IsReady(ctx context.Context) error { +// as.logger.TraceContext(ctx, "checking readiness of authorization service") +// return nil +// } + +// GetEntitlements for an entity chain +func (as *Service) GetEntitlements(_ context.Context, _ *connect.Request[authzV2.GetEntitlementsRequest]) (*connect.Response[authzV2.GetEntitlementsResponse], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("GetEntitlements not implemented")) +} + +// GetDecision for an entity chain and an action on a single resource +func (as *Service) GetDecision(_ context.Context, _ *connect.Request[authzV2.GetDecisionRequest]) (*connect.Response[authzV2.GetDecisionResponse], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("GetDecision not implemented")) +} + +// GetDecisionMultiResource for an entity chain and action on multiple resources +func (as *Service) GetDecisionMultiResource(_ context.Context, _ *connect.Request[authzV2.GetDecisionMultiResourceRequest]) (*connect.Response[authzV2.GetDecisionMultiResourceResponse], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("GetDecisionMultiResource not implemented")) +} + +// GetDecisionBulk for multiple requests, each comprising a combination of entity chain, action, and one or more resources +func (as *Service) GetDecisionBulk(_ context.Context, _ *connect.Request[authzV2.GetDecisionBulkRequest]) (*connect.Response[authzV2.GetDecisionBulkResponse], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("GetDecisionBulk not implemented")) +} diff --git a/service/entityresolution/v2/entity_resolution.go b/service/entityresolution/v2/entity_resolution.go index 51eca766c3..9fd485b2c0 100644 --- a/service/entityresolution/v2/entity_resolution.go +++ b/service/entityresolution/v2/entity_resolution.go @@ -28,6 +28,7 @@ func NewRegistration() *serviceregistry.Service[entityresolutionv2connect.Entity return &serviceregistry.Service[entityresolutionv2connect.EntityResolutionServiceHandler]{ ServiceOptions: serviceregistry.ServiceOptions[entityresolutionv2connect.EntityResolutionServiceHandler]{ Namespace: "entityresolution", + Version: "v2", ServiceDesc: &ersV2.EntityResolutionService_ServiceDesc, ConnectRPCFunc: entityresolutionv2connect.NewEntityResolutionServiceHandler, RegisterFunc: func(srp serviceregistry.RegistrationParams) (entityresolutionv2connect.EntityResolutionServiceHandler, serviceregistry.HandlerServer) { diff --git a/service/pkg/server/services.go b/service/pkg/server/services.go index 917909fe4b..e6be96c890 100644 --- a/service/pkg/server/services.go +++ b/service/pkg/server/services.go @@ -11,6 +11,7 @@ import ( "github.com/go-viper/mapstructure/v2" "github.com/opentdf/platform/sdk" "github.com/opentdf/platform/service/authorization" + authorizationV2 "github.com/opentdf/platform/service/authorization/v2" "github.com/opentdf/platform/service/entityresolution" entityresolutionV2 "github.com/opentdf/platform/service/entityresolution/v2" "github.com/opentdf/platform/service/health" @@ -70,6 +71,7 @@ func registerCoreServices(reg serviceregistry.Registry, mode []string) ([]string registeredServices = append(registeredServices, []string{servicePolicy, serviceAuthorization, serviceKAS, serviceWellKnown, serviceEntityResolution}...) services = append(services, []serviceregistry.IService{ authorization.NewRegistration(), + authorizationV2.NewRegistration(), kas.NewRegistration(), wellknown.NewRegistration(), entityresolution.NewRegistration(), @@ -80,6 +82,7 @@ func registerCoreServices(reg serviceregistry.Registry, mode []string) ([]string registeredServices = append(registeredServices, []string{servicePolicy, serviceAuthorization, serviceWellKnown}...) services = append(services, []serviceregistry.IService{ authorization.NewRegistration(), + authorizationV2.NewRegistration(), wellknown.NewRegistration(), }...) services = append(services, policy.NewRegistrations()...) @@ -165,6 +168,9 @@ func startServices(ctx context.Context, cfg *config.Config, otdf *server.OpenTDF return err } } + if svc.GetVersion() != "" { + svcLogger = svcLogger.With("version", svc.GetVersion()) + } err = svc.Start(ctx, serviceregistry.RegistrationParams{ Config: cfg.Services[svc.GetNamespace()], diff --git a/service/pkg/server/services_test.go b/service/pkg/server/services_test.go index a2048f2305..cb9c687067 100644 --- a/service/pkg/server/services_test.go +++ b/service/pkg/server/services_test.go @@ -26,7 +26,11 @@ type mockTestServiceOptions struct { dbRegister serviceregistry.DBRegister } -const numExpectedPolicyServices = 9 +const ( + numExpectedPolicyServices = 9 + numExpectedEntityResolutionServiceVersions = 2 + numExpectedAuthorizationServiceVersions = 2 +) func mockTestServiceRegistry(opts mockTestServiceOptions) (serviceregistry.IService, *spyTestService) { spy := &spyTestService{} @@ -104,7 +108,7 @@ func (suite *ServiceTestSuite) Test_RegisterCoreServices_In_Mode_ALL_Expect_All_ authz, err := registry.GetNamespace(serviceAuthorization) suite.Require().NoError(err) - suite.Len(authz.Services, 1) + suite.Len(authz.Services, numExpectedAuthorizationServiceVersions) suite.Equal(modeCore, authz.Mode) kas, err := registry.GetNamespace(serviceKAS) @@ -124,8 +128,7 @@ func (suite *ServiceTestSuite) Test_RegisterCoreServices_In_Mode_ALL_Expect_All_ ers, err := registry.GetNamespace(serviceEntityResolution) suite.Require().NoError(err) - ersServiceVersionsCount := 2 - suite.Len(ers.Services, ersServiceVersionsCount) + suite.Len(ers.Services, numExpectedEntityResolutionServiceVersions) suite.Equal(modeCore, ers.Mode) } @@ -137,7 +140,7 @@ func (suite *ServiceTestSuite) Test_RegisterCoreServices_In_Mode_Core_Expect_Cor authz, err := registry.GetNamespace(serviceAuthorization) suite.Require().NoError(err) - suite.Len(authz.Services, 1) + suite.Len(authz.Services, numExpectedAuthorizationServiceVersions) suite.Equal(modeCore, authz.Mode) _, err = registry.GetNamespace(serviceKAS) @@ -163,7 +166,7 @@ func (suite *ServiceTestSuite) Test_RegisterServices_In_Mode_Core_Plus_Kas_Expec authz, err := registry.GetNamespace(serviceAuthorization) suite.Require().NoError(err) - suite.Len(authz.Services, 1) + suite.Len(authz.Services, numExpectedAuthorizationServiceVersions) suite.Equal(modeCore, authz.Mode) kas, err := registry.GetNamespace(serviceKAS) @@ -190,7 +193,7 @@ func (suite *ServiceTestSuite) Test_RegisterServices_In_Mode_Core_Plus_Kas_Expec authz, err := registry.GetNamespace(serviceAuthorization) suite.Require().NoError(err) - suite.Len(authz.Services, 1) + suite.Len(authz.Services, numExpectedAuthorizationServiceVersions) suite.Equal(modeCore, authz.Mode) kas, err := registry.GetNamespace(serviceKAS) @@ -210,8 +213,7 @@ func (suite *ServiceTestSuite) Test_RegisterServices_In_Mode_Core_Plus_Kas_Expec ers, err := registry.GetNamespace(serviceEntityResolution) suite.Require().NoError(err) - ersServiceVersionsCount := 2 - suite.Len(ers.Services, ersServiceVersionsCount) + suite.Len(ers.Services, numExpectedEntityResolutionServiceVersions) suite.Equal(modeERS, ers.Mode) } diff --git a/service/pkg/serviceregistry/serviceregistry.go b/service/pkg/serviceregistry/serviceregistry.go index 91d3ebafb2..1e454c5a23 100644 --- a/service/pkg/serviceregistry/serviceregistry.go +++ b/service/pkg/serviceregistry/serviceregistry.go @@ -72,6 +72,7 @@ type IService interface { IsDBRequired() bool DBMigrations() *embed.FS GetNamespace() string + GetVersion() string GetServiceDesc() *grpc.ServiceDesc Start(ctx context.Context, params RegistrationParams) error IsStarted() bool @@ -99,6 +100,8 @@ type ServiceOptions[S any] struct { // Namespace is the namespace of the service. One or more gRPC services can be registered under // the same namespace. Namespace string + // Version is the major version of the service according to the protocol buffer definition. + Version string // ServiceDesc is the gRPC service descriptor. For non-gRPC services, this can be mocked out, // but at minimum, the ServiceName field must be set ServiceDesc *grpc.ServiceDesc @@ -120,6 +123,10 @@ func (s Service[S]) GetNamespace() string { return s.Namespace } +func (s Service[S]) GetVersion() string { + return s.Version +} + func (s Service[S]) GetServiceDesc() *grpc.ServiceDesc { return s.ServiceDesc } @@ -219,6 +226,7 @@ func (s Service[S]) RegisterGRPCGatewayHandler(ctx context.Context, mux *runtime // namespace represents a namespace in the service registry. type Namespace struct { Mode string + Version string Services []IService } @@ -258,7 +266,11 @@ func (reg Registry) RegisterService(svc IService, mode string) error { return fmt.Errorf("service already registered namespace:%s service:%s", svc.GetNamespace(), svc.GetServiceDesc().ServiceName) } - slog.Info("registered service", slog.String("namespace", svc.GetNamespace()), slog.String("service", svc.GetServiceDesc().ServiceName)) + slog.Info( + "registered service", + slog.String("namespace", svc.GetNamespace()), + slog.String("service", svc.GetServiceDesc().ServiceName), + ) copyNamespace.Services = append(copyNamespace.Services, svc) reg[svc.GetNamespace()] = copyNamespace