Skip to content

Commit

Permalink
feat(core): Introduce ERS mode, ability to connect to remote ERS (#1735)
Browse files Browse the repository at this point in the history
  • Loading branch information
elizabethhealy authored Nov 26, 2024
1 parent 5dc1893 commit a118316
Show file tree
Hide file tree
Showing 14 changed files with 385 additions and 32 deletions.
2 changes: 1 addition & 1 deletion Contributing.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ For end-users/consumers, see [here](./Consuming.md).
1. Note: You will have to add the ``localhost.crt`` as a trusted certificate to do TLS authentication at ``localhost:8443``.
3. Create an OpenTDF config file: `opentdf.yaml`
1. The `opentdf-dev.yaml` file is the more secure starting point, but you will likely need to modify it to match your environment. This configuration is recommended as it is more secure but it does require valid development keypairs.
2. The `opentdf-example-no-kas.yaml` file is simpler to run but less secure. This file configures the platform to startup without a KAS instances and without endpoint authentication.
2. The `opentdf-core-mode.yaml` file is simpler to run but less secure. This file configures the platform to startup without a KAS instances, without a built-in ERS instance, and without endpoint authentication.
4. Provision keycloak: `go run github.com/opentdf/platform/service provision keycloak`. Updates the local Keycloak configuration for local testing and development by creating a realm, roles, a client, and users.
5. Run the server: `go run github.com/opentdf/platform/service start`. Runs the OpenTDF platform capabilities as a monolithic service.
1. _Alt_ use the hot-reload development environment `air`
Expand Down
8 changes: 6 additions & 2 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,12 @@ Root level key `sdk_config`

| Field | Description | Default | Environment Variable |
| -------- | -------------| -------- | -------------------- |
| `endpoint` | The core platform endpoint to connect to | | OPENTDF_SDK_CONFIG_ENDPOINT |
| `plaintext` | Use a plaintext grpc connection | `false` | OPENTDF_SDK_CONFIG_PLAINTEXT |
| `core.endpoint` | The core platform endpoint to connect to | | OPENTDF_SDK_CONFIG_ENDPOINT |
| `core.plaintext` | Use a plaintext grpc connection | `false` | OPENTDF_SDK_CONFIG_PLAINTEXT |
| `core.insecure` | Use an insecure tls connection | `false` | |
| `entityresolution.endpoint` | The entityresolution endpoint to connect to | | |
| `entityresolution.plaintext` | Use a plaintext ERS grpc connection | `false` | |
| `entityresolution.insecure` | Use an insecure tls connection | `false` | |
| `client_id` | OAuth client id | | OPENTDF_SDK_CONFIG_CLIENT_ID |
| `client_secret` | The clients credentials | | OPENTDF_SDK_CONFIG_CLIENT_SECRET |

Expand Down
10 changes: 9 additions & 1 deletion opentdf-example-no-kas.yaml → opentdf-core-mode.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
# configures the platform to startup without a KAS instances, without a built-in ERS instance, and without endpoint authentication
# build off of this config file if you are running your ERS and KAS instances seperately or if you only need the policy features
mode: core
sdk_config:
entityresolution:
endpoint: http://localhost:8181
plaintext: true
client_id: opentdf
client_secret: secret
logger:
level: debug
type: text
Expand Down Expand Up @@ -44,4 +52,4 @@ server:
maxage: 3600
grpc:
reflectionEnabled: true # Default is false
port: 8080
port: 8383
92 changes: 92 additions & 0 deletions opentdf-ers-mode.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# configures the platform to run just the entity resolution service and the well-known service
# primarily to be used for testing and development of external ERS connections
mode: entityresolution
logger:
level: debug
type: text
output: stdout
services:
entityresolution:
log_level: info
url: http://localhost:8888/auth
clientid: 'tdf-entity-resolution'
clientsecret: 'secret'
realm: 'opentdf'
legacykeycloak: true
inferid:
from:
email: true
username: true
server:
auth:
enabled: true
enforceDPoP: false
public_client_id: 'opentdf-public'
audience: 'http://localhost:8080'
issuer: http://localhost:8888/auth/realms/opentdf
policy:
## Default policy for all requests
default: #"role:standard"
## Dot notation is used to access nested claims (i.e. realm_access.roles)
claim: # realm_access.roles
## Maps the external role to the opentdf role
## Note: left side is used in the policy, right side is the external role
map:
# standard: opentdf-standard
# admin: opentdf-admin
# org-admin: opentdf-org-admin

## Custom policy (see examples https://github.com/casbin/casbin/tree/master/examples)
csv: #|
# p, role:org-admin, policy:attributes, *, *, allow
# p, role:org-admin, policy:subject-mappings, *, *, allow
# p, role:org-admin, policy:resource-mappings, *, *, allow
# p, role:org-admin, policy:kas-registry, *, *, allow
# p, role:org-admin, policy:unsafe, *, *, allow

## Custom model (see https://casbin.org/docs/syntax-for-models/)
model: #|
# [request_definition]
# r = sub, res, act, obj
#
# [policy_definition]
# p = sub, res, act, obj, eft
#
# [role_definition]
# g = _, _
#
# [policy_effect]
# e = some(where (p.eft == allow)) && !some(where (p.eft == deny))
#
# [matchers]
# m = g(r.sub, p.sub) && globOrRegexMatch(r.res, p.res) && globOrRegexMatch(r.act, p.act) && globOrRegexMatch(r.obj, p.obj)
cors:
enabled: false
# "*" to allow any origin or a specific domain like "https://yourdomain.com"
allowedorigins:
- '*'
# List of methods. Examples: "GET,POST,PUT"
allowedmethods:
- GET
- POST
- PATCH
- PUT
- DELETE
- OPTIONS
# List of headers that are allowed in a request
allowedheaders:
- ACCEPT
- Authorization
- Content-Type
- X-CSRF-Token
- X-Request-ID
# List of response headers that browsers are allowed to access
exposedheaders:
- Link
# Sets whether credentials are included in the CORS request
allowcredentials: true
# Sets the maximum age (in seconds) of a specific CORS preflight request
maxage: 3600
grpc:
reflectionEnabled: true # Default is false
port: 8282
110 changes: 110 additions & 0 deletions opentdf-kas-mode.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
# configures the platform to run only the KAS and well known service
# build off this config file if you intend on running a (or multiple) seperate kas instance(s)
mode: kas
sdk_config:
core:
endpoint: http://localhost:8080
plaintext: true
client_id: opentdf
client_secret: secret
logger:
level: debug
type: text
output: stdout
services:
kas:
keyring:
- kid: e1
alg: ec:secp256r1
- kid: e1
alg: ec:secp256r1
legacy: true
- kid: r1
alg: rsa:2048
- kid: r1
alg: rsa:2048
legacy: true
server:
tls:
enabled: false
cert: ./keys/platform.crt
key: ./keys/platform-key.pem
auth:
enabled: true
enforceDPoP: false
public_client_id: 'opentdf-public'
audience: 'http://localhost:8080'
issuer: http://localhost:8888/auth/realms/opentdf
policy:
## Default policy for all requests
default: #"role:standard"
## Dot notation is used to access nested claims (i.e. realm_access.roles)
claim: # realm_access.roles
## Maps the external role to the opentdf role
## Note: left side is used in the policy, right side is the external role
map:
# standard: opentdf-standard
# admin: opentdf-admin

## Custom policy (see examples https://github.com/casbin/casbin/tree/master/examples)
csv: #|
# p, role:admin, *, *, allow

## Custom model (see https://casbin.org/docs/syntax-for-models/)
model: #|
# [request_definition]
# r = sub, res, act, obj
#
# [policy_definition]
# p = sub, res, act, obj, eft
#
# [role_definition]
# g = _, _
#
# [policy_effect]
# e = some(where (p.eft == allow)) && !some(where (p.eft == deny))
#
# [matchers]
# m = g(r.sub, p.sub) && globOrRegexMatch(r.res, p.res) && globOrRegexMatch(r.act, p.act) && globOrRegexMatch(r.obj, p.obj)
cors:
enabled: false
# "*" to allow any origin or a specific domain like "https://yourdomain.com"
allowedorigins:
- '*'
# List of methods. Examples: "GET,POST,PUT"
allowedmethods:
- GET
- POST
- PATCH
- PUT
- DELETE
- OPTIONS
# List of headers that are allowed in a request
allowedheaders:
- ACCEPT
- Authorization
- Content-Type
- X-CSRF-Token
- X-Request-ID
# List of response headers that browsers are allowed to access
exposedheaders:
- Link
# Sets whether credentials are included in the CORS request
allowcredentials: true
# Sets the maximum age (in seconds) of a specific CORS preflight request
maxage: 3600
grpc:
reflectionEnabled: true # Default is false
cryptoProvider:
type: standard
standard:
keys:
- kid: r1
alg: rsa:2048
private: kas-private.pem
cert: kas-cert.pem
- kid: e1
alg: ec:secp256r1
private: kas-ec-private.pem
cert: kas-ec-cert.pem
port: 8181
4 changes: 2 additions & 2 deletions sdk/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ type config struct {
customAccessTokenSource auth.AccessTokenSource
oauthAccessTokenSource oauth2.TokenSource
coreConn *grpc.ClientConn
entityResolutionConn *grpc.ClientConn
collectionStore *collectionStore
}

Expand Down Expand Up @@ -135,10 +136,9 @@ func WithCustomAuthorizationConnection(conn *grpc.ClientConn) Option {
}
}

// Deprecated: Use WithCustomCoreConnection instead
func WithCustomEntityResolutionConnection(conn *grpc.ClientConn) Option {
return func(c *config) {
c.coreConn = conn
c.entityResolutionConn = conn
}
}

Expand Down
9 changes: 8 additions & 1 deletion sdk/sdk.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ type SDK struct {
func New(platformEndpoint string, opts ...Option) (*SDK, error) {
var (
platformConn *grpc.ClientConn // Connection to the platform
ersConn *grpc.ClientConn // Connection to ERS (possibly remote)
err error
)

Expand Down Expand Up @@ -169,6 +170,12 @@ func New(platformEndpoint string, opts ...Option) (*SDK, error) {
}
}

if cfg.entityResolutionConn != nil {
ersConn = cfg.entityResolutionConn
} else {
ersConn = platformConn
}

return &SDK{
config: *cfg,
collectionStore: cfg.collectionStore,
Expand All @@ -183,7 +190,7 @@ func New(platformEndpoint string, opts ...Option) (*SDK, error) {
Unsafe: unsafe.NewUnsafeServiceClient(platformConn),
KeyAccessServerRegistry: kasregistry.NewKeyAccessServerRegistryServiceClient(platformConn),
Authorization: authorization.NewAuthorizationServiceClient(platformConn),
EntityResoution: entityresolution.NewEntityResolutionServiceClient(platformConn),
EntityResoution: entityresolution.NewEntityResolutionServiceClient(ersConn),
wellknownConfiguration: wellknownconfiguration.NewWellKnownServiceClient(platformConn),
}, nil
}
Expand Down
2 changes: 1 addition & 1 deletion service/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ require (
github.com/opentdf/platform/lib/flattening v0.1.1
github.com/opentdf/platform/lib/ocrypto v0.1.7
github.com/opentdf/platform/protocol/go v0.2.20
github.com/opentdf/platform/sdk v0.3.21
github.com/opentdf/platform/sdk v0.3.22
github.com/pressly/goose/v3 v3.19.1
github.com/spf13/cobra v1.8.1
github.com/spf13/viper v1.18.2
Expand Down
4 changes: 2 additions & 2 deletions service/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -287,8 +287,8 @@ github.com/opentdf/platform/lib/ocrypto v0.1.7 h1:IcCYRrwmMqntqUE8frmUDg5EZ0WMdl
github.com/opentdf/platform/lib/ocrypto v0.1.7/go.mod h1:4bhKPbRFzURMerH5Vr/LlszHvcoXQbfJXa0bpY7/7yg=
github.com/opentdf/platform/protocol/go v0.2.20 h1:FPU1ZcXvPm/QeE2nqgbD/HMTOCICQSD0DoncQbAZ1ws=
github.com/opentdf/platform/protocol/go v0.2.20/go.mod h1:TWIuf387VeR3q0TL4nAMKQTWEqqID+8Yjao76EX9Dto=
github.com/opentdf/platform/sdk v0.3.21 h1:18oZk8t32luXBL2lhRa3qvjTY17Y3PmA0Wp1F8tdkqc=
github.com/opentdf/platform/sdk v0.3.21/go.mod h1:KpT/m5zXQ19WqhGePKfIC39Ly8LOipKdKGbJ1B/59a8=
github.com/opentdf/platform/sdk v0.3.22 h1:nxmu7i+dmKuRQKVi5EIjOVdEFzzu/zkaA5LmGPPtPzw=
github.com/opentdf/platform/sdk v0.3.22/go.mod h1:KpT/m5zXQ19WqhGePKfIC39Ly8LOipKdKGbJ1B/59a8=
github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs=
github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=
github.com/ory/dockertest/v3 v3.10.0 h1:4K3z2VMe8Woe++invjaTB7VRyQXQy5UY+loujO4aNE4=
Expand Down
29 changes: 23 additions & 6 deletions service/internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,11 @@ type Config struct {

// SDKConfig represents the configuration for the SDK.
type SDKConfig struct {
// Endpoint is the URL of the Core Platform endpoint.
Endpoint string `mapstructure:"endpoint" json:"endpoint"`
// Connection to the Core Platform
CorePlatformConnection Connection `mapstructure:"core" json:"core"`

// Plaintext specifies whether the SDK should use plaintext communication.
Plaintext bool `mapstructure:"plaintext" json:"plaintext" default:"false" validate:"boolean"`
// Connection to an ERS if not in the core platform
EntityResolutionConnection Connection `mapstructure:"entityresolution" json:"entityresolution"`

// ClientID is the client ID used for client credentials grant.
// It is required together with ClientSecret.
Expand All @@ -58,6 +58,17 @@ type SDKConfig struct {
ClientSecret string `mapstructure:"client_secret" json:"client_secret" validate:"required_with=ClientID"`
}

type Connection struct {
// Endpoint is the URL of the platform or service.
Endpoint string `mapstructure:"endpoint" json:"endpoint"`

// Plaintext specifies whether the SDK should use plaintext communication.
Plaintext bool `mapstructure:"plaintext" json:"plaintext" default:"false" validate:"boolean"`

// Insecure specifies whether the SDK should use insecure TLS communication.
Insecure bool `mapstructure:"insecure" json:"insecure" default:"false" validate:"boolean"`
}

type Error string

func (e Error) Error() string {
Expand Down Expand Up @@ -137,8 +148,14 @@ func (c *Config) LogValue() slog.Value {

func (c SDKConfig) LogValue() slog.Value {
return slog.GroupValue(
slog.String("endpoint", c.Endpoint),
slog.Bool("plaintext", c.Plaintext),
slog.Group("core",
"endpoint", c.CorePlatformConnection.Endpoint,
"plaintext", c.CorePlatformConnection.Plaintext,
"insecure", c.CorePlatformConnection.Insecure),
slog.Group("entityresolution",
"endpoint", c.EntityResolutionConnection.Endpoint,
"plaintext", c.EntityResolutionConnection.Plaintext,
"insecure", c.EntityResolutionConnection.Insecure),
slog.String("client_id", c.ClientID),
slog.String("client_secret", "[REDACTED]"),
)
Expand Down
8 changes: 7 additions & 1 deletion service/pkg/server/services.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ const (
modeALL = "all"
modeCore = "core"
modeKAS = "kas"
modeERS = "entityresolution"
modeEssential = "essential"

serviceKAS = "kas"
Expand Down Expand Up @@ -76,7 +77,6 @@ func registerCoreServices(reg serviceregistry.Registry, mode []string) ([]string
case "core":
registeredServices = append(registeredServices, []string{servicePolicy, serviceAuthorization, serviceWellKnown}...)
services = append(services, []serviceregistry.IService{
entityresolution.NewRegistration(),
authorization.NewRegistration(),
wellknown.NewRegistration(),
}...)
Expand All @@ -87,6 +87,12 @@ func registerCoreServices(reg serviceregistry.Registry, mode []string) ([]string
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
registeredServices = append(registeredServices, serviceEntityResolution)
if err := reg.RegisterService(entityresolution.NewRegistration(), modeERS); err != nil {
return nil, err //nolint:wrapcheck // We are all friends here
}
default:
continue
}
Expand Down
Loading

0 comments on commit a118316

Please sign in to comment.