Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
8faed6e
new proto accessors for idp well-known info on SDK
jakedoublev Aug 15, 2024
709d197
remove condition no longer true since tokenEndpoint can be fetched fr…
jakedoublev Aug 16, 2024
63ae9c0
add public client to keycloak_data.yaml
jakedoublev Aug 16, 2024
4e71ac0
clean up keycloak provisioning
jakedoublev Aug 16, 2024
7cb6ec9
WIP
jakedoublev Aug 16, 2024
1f0fec1
set public_client_id from config to idp well-known configuration and …
jakedoublev Aug 16, 2024
c8b7549
handle access token source in separate PR
jakedoublev Aug 16, 2024
3f29f20
Merge branch 'main' into feat/cli-auth
jakedoublev Aug 16, 2024
2d18f54
lint
jakedoublev Aug 16, 2024
ace5c4f
lint
jakedoublev Aug 16, 2024
1ffe6d4
Merge branch 'main' into feat/cli-auth
jakedoublev Aug 16, 2024
4201591
Merge branch 'main' into feat/cli-auth
jakedoublev Aug 16, 2024
debcdbc
put back platform_issuer to avoid breaking java sdk
jakedoublev Aug 16, 2024
012cf09
expose idp accessors of well-known off platform config rather than to…
jakedoublev Aug 16, 2024
505671d
put back token_endpoint fetching if not provided
jakedoublev Aug 16, 2024
1d6ebb1
add comment
jakedoublev Aug 16, 2024
0d841be
use underscores for public_client_id in config
jakedoublev Aug 19, 2024
e667fbd
Merge branch 'main' into feat/cli-auth
jakedoublev Aug 19, 2024
a6a0c7c
use deprecated variable name
jakedoublev Aug 19, 2024
e66964f
comment for redirect uri usage
jakedoublev Aug 19, 2024
3371f23
cleanup
jakedoublev Aug 19, 2024
45130df
put back lib/fixtures
jakedoublev Aug 19, 2024
a58a2c0
Merge branch 'main' into feat/cli-auth
jakedoublev Aug 19, 2024
b729e00
yaml fmt
jakedoublev Aug 19, 2024
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
5 changes: 3 additions & 2 deletions opentdf-dev.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ server:
auth:
enabled: true
enforceDPoP: false
public_client_id: 'opentdf-public'
audience: 'http://localhost:8080'
issuer: http://localhost:8888/auth/realms/opentdf
policy:
Expand Down Expand Up @@ -75,10 +76,10 @@ server:
# 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'
# "*" to allow any origin or a specific domain like "https://yourdomain.com"
allowedorigins:
- '*'
# List of methods. Examples: 'GET,POST,PUT'
# List of methods. Examples: "GET,POST,PUT"
allowedmethods:
- GET
- POST
Expand Down
11 changes: 6 additions & 5 deletions opentdf-example-no-kas.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,15 @@ server:
auth:
enabled: false
enforceDPoP: false
audience: "http://localhost:8080"
public_client_id: 'opentdf-public'
audience: 'http://localhost:8080'
issuer: http://localhost:8888/auth/realms/tdf
cors:
enabled: false
# '*' to allow any origin or a specific domain like 'https://yourdomain.com'
allowedorigins:
- "*"
# List of methods. Examples: 'GET,POST,PUT'
# "*" to allow any origin or a specific domain like "https://yourdomain.com"
allowedorigins:
- '*'
# List of methods. Examples: "GET,POST,PUT"
allowedmethods:
- GET
- POST
Expand Down
19 changes: 10 additions & 9 deletions opentdf-example.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,16 @@ db:
# port: 5432
# user: postgres
# password: changeme
# mode: all
# mode: all
services:
kas:
eccertid: e1
rsacertid: r1
entityresolution:
url: http://keycloak:8888/auth
clientid: "tdf-entity-resolution"
clientsecret: "secret"
realm: "opentdf"
clientid: 'tdf-entity-resolution'
clientsecret: 'secret'
realm: 'opentdf'
legacykeycloak: true
inferid:
from:
Expand All @@ -27,7 +27,8 @@ server:
auth:
enabled: true
enforceDPoP: false
audience: "http://localhost:8080"
public_client_id: 'opentdf-public'
audience: 'http://localhost:8080'
issuer: http://keycloak:8888/auth/realms/opentdf
policy:
## Default policy for all requests
Expand Down Expand Up @@ -66,10 +67,10 @@ server:
# 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'
# "*" to allow any origin or a specific domain like "https://yourdomain.com"
allowedorigins:
- '*'
# List of methods. Examples: "GET,POST,PUT"
allowedmethods:
- GET
- POST
Expand Down
25 changes: 13 additions & 12 deletions opentdf-with-hsm.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,21 +18,22 @@ services:
entityresolution:
enabled: true
url: http://localhost:8888/auth
clientid: "tdf-entity-resolution"
clientsecret: "secret"
realm: "opentdf"
clientid: 'tdf-entity-resolution'
clientsecret: 'secret'
realm: 'opentdf'
legacykeycloak: true
authorization:
enabled: true
server:
auth:
enabled: true
enforceDPoP: false
audience: "http://localhost:8080"
public_client_id: 'opentdf-public'
audience: 'http://localhost:8080'
issuer: http://localhost:8888/auth/realms/opentdf
clients:
- "opentdf"
- "opentdf-sdk"
- 'opentdf'
- 'opentdf-sdk'
policy:
## Default policy for all requests
default: #"role:standard"
Expand Down Expand Up @@ -72,10 +73,10 @@ server:
# 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'
# "*" to allow any origin or a specific domain like "https://yourdomain.com"
allowedorigins:
- '*'
# List of methods. Examples: "GET,POST,PUT"
allowedmethods:
- GET
- POST
Expand All @@ -102,8 +103,8 @@ server:
type: hsm
hsm:
# As configured by init-temp-keys.sh --hsm
pin: "12345"
slotlabel: "dev-token"
pin: '12345'
slotlabel: 'dev-token'
keys:
- kid: r1
alg: rsa:2048
Expand Down
5 changes: 3 additions & 2 deletions sdk/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ type Option func(*config)

// Internal config struct for building SDK options.
type config struct {
// Platform configuration structure is subject to change. Consume via accessor methods.
PlatformConfiguration PlatformConfiguration
dialOption grpc.DialOption
tlsConfig *tls.Config
clientCredentials *oauth.ClientCredentials
Expand All @@ -24,7 +26,6 @@ type config struct {
scopes []string
extraDialOptions []grpc.DialOption
certExchange *oauth.CertExchangeInfo
platformConfiguration PlatformConfiguration
kasSessionKey *ocrypto.RsaKeyPair
dpopKey *ocrypto.RsaKeyPair
ipc bool
Expand Down Expand Up @@ -164,7 +165,7 @@ func WithCustomWellknownConnection(conn *grpc.ClientConn) Option {
// Use this option with caution, as it may lead to unexpected behavior
func WithPlatformConfiguration(platformConfiguration PlatformConfiguration) Option {
return func(c *config) {
c.platformConfiguration = platformConfiguration
c.PlatformConfiguration = platformConfiguration
}
}

Expand Down
59 changes: 47 additions & 12 deletions sdk/platformconfig.go
Original file line number Diff line number Diff line change
@@ -1,19 +1,54 @@
package sdk

import "log/slog"
import (
"log/slog"
)

func (s SDK) PlatformIssuer() string {
// This check is needed if we want to fetch platform configuration over ipc
if s.config.platformConfiguration == nil {
cfg, err := getPlatformConfiguration(s.conn)
if err != nil {
slog.Warn("failed to get platform configuration", slog.Any("error", err))
}
s.config.platformConfiguration = cfg
func (c PlatformConfiguration) getIdpConfig() map[string]interface{} {
idpCfg, err := c["idp"].(map[string]interface{})
if !err {
slog.Warn("idp configuration not found in well-known configuration")
idpCfg = map[string]interface{}{}
}
value, ok := s.config.platformConfiguration["platform_issuer"].(string)
return idpCfg
}

func (c PlatformConfiguration) Issuer() (string, error) {
idpCfg := c.getIdpConfig()
value, ok := idpCfg["issuer"].(string)
if !ok {
slog.Warn("issuer not found in well-known idp configuration")
return "", ErrPlatformIssuerNotFound
}
return value, nil
}

func (c PlatformConfiguration) AuthzEndpoint() (string, error) {
idpCfg := c.getIdpConfig()
value, ok := idpCfg["authorization_endpoint"].(string)
if !ok {
slog.Warn("authorization_endpoint not found in well-known idp configuration")
return "", ErrPlatformAuthzEndpointNotFound
}
return value, nil
}

func (c PlatformConfiguration) TokenEndpoint() (string, error) {
idpCfg := c.getIdpConfig()
value, ok := idpCfg["token_endpoint"].(string)
if !ok {
slog.Warn("token_endpoint not found in well-known idp configuration")
return "", ErrPlatformTokenEndpointNotFound
}
return value, nil
}

func (c PlatformConfiguration) PublicClientID() (string, error) {
idpCfg := c.getIdpConfig()
value, ok := idpCfg["public_client_id"].(string)
if !ok {
slog.Warn("platform_issuer not found in platform configuration")
slog.Warn("public_client_id not found in well-known idp configuration")
return "", ErrPlatformTokenEndpointNotFound
}
return value
return value, nil
}
29 changes: 13 additions & 16 deletions sdk/sdk.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import (
"errors"
"fmt"
"io"
"log/slog"
"net"
"net/http"
"net/url"
Expand All @@ -35,10 +34,14 @@ import (
const (
// Failure while connecting to a service.
// Check your configuration and/or retry.
ErrGrpcDialFailed = Error("failed to dial grpc endpoint")
ErrShutdownFailed = Error("failed to shutdown sdk")
ErrPlatformConfigFailed = Error("failed to retrieve platform configuration")
ErrPlatformEndpointMalformed = Error("platform endpoint is malformed")
ErrGrpcDialFailed = Error("failed to dial grpc endpoint")
ErrShutdownFailed = Error("failed to shutdown sdk")
ErrPlatformConfigFailed = Error("failed to retrieve platform configuration")
ErrPlatformEndpointMalformed = Error("platform endpoint is malformed")
ErrPlatformIssuerNotFound = Error("issuer not found in well-known idp configuration")
ErrPlatformAuthzEndpointNotFound = Error("authorization_endpoint not found in well-known idp configuration")
ErrPlatformTokenEndpointNotFound = Error("token_endpoint not found in well-known idp configuration")
ErrPlatformPublicClientIDNotFound = Error("public_client_id not found in well-known idp configuration")
)

type Error string
Expand Down Expand Up @@ -112,7 +115,7 @@ func New(platformEndpoint string, opts ...Option) (*SDK, error) {
}

// If platformConfiguration is not provided, fetch it from the platform
if cfg.platformConfiguration == nil && !cfg.ipc { //nolint:nestif // Most of checks are for errors
if cfg.PlatformConfiguration == nil && !cfg.ipc { //nolint:nestif // Most of checks are for errors
var pcfg PlatformConfiguration
var err error

Expand All @@ -127,7 +130,7 @@ func New(platformEndpoint string, opts ...Option) (*SDK, error) {
return nil, errors.Join(ErrPlatformConfigFailed, err)
}
}
cfg.platformConfiguration = pcfg
cfg.PlatformConfiguration = pcfg
if cfg.tokenEndpoint == "" {
cfg.tokenEndpoint, err = getTokenEndpoint(*cfg)
if err != nil {
Expand Down Expand Up @@ -212,14 +215,9 @@ func buildIDPTokenSource(c *config) (auth.AccessTokenSource, error) {
return c.customAccessTokenSource, nil
}

if (c.clientCredentials == nil) != (c.tokenEndpoint == "") {
return nil, errors.New("either both or neither of client credentials and token endpoint must be specified")
}

// at this point we have either both client credentials and a token endpoint or none of the above. if we don't have
// any just return a KAS client that can only get public keys
// If we don't have client-credentials, just return a KAS client that can only get public keys.
// There are uses for uncredentialed clients (i.e. consuming the well-known configuration).
if c.clientCredentials == nil {
slog.Info("no client credentials provided. GRPC requests to KAS and services will not be authenticated.")
return nil, nil //nolint:nilnil // not having credentials is not an error
}

Expand Down Expand Up @@ -336,7 +334,6 @@ func IsValidTdf(reader io.ReadSeeker) (bool, error) {
loader := gojsonschema.NewStringLoader(manifestSchemaString)
manifestStringLoader := gojsonschema.NewStringLoader(manifest)
result, err := gojsonschema.Validate(loader, manifestStringLoader)

if err != nil {
return false, errors.New("could not validate manifest.json")
}
Expand Down Expand Up @@ -383,7 +380,7 @@ func getPlatformConfiguration(conn *grpc.ClientConn) (PlatformConfiguration, err

// TODO: This should be moved to a separate package. We do discovery in ../service/internal/auth/discovery.go
func getTokenEndpoint(c config) (string, error) {
issuerURL, ok := c.platformConfiguration["platform_issuer"].(string)
issuerURL, ok := c.PlatformConfiguration["platform_issuer"].(string)

if !ok {
return "", errors.New("platform_issuer is not set, or is not a string")
Expand Down
64 changes: 62 additions & 2 deletions sdk/sdk_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,12 @@ func GetMethods(i interface{}) []string {
func TestNew_ShouldCreateSDK(t *testing.T) {
s, err := sdk.New(goodPlatformEndpoint,
sdk.WithPlatformConfiguration(sdk.PlatformConfiguration{
"platform_issuer": "https://example.org",
"idp": map[string]interface{}{
"issuer": "https://example.org",
"authorization_endpoint": "https://example.org/auth",
"token_endpoint": "https://example.org/token",
"public_client_id": "myclient",
},
}),
sdk.WithClientCredentials("myid", "mysecret", nil),
sdk.WithTokenEndpoint("https://example.org/token"),
Expand All @@ -41,7 +46,24 @@ func TestNew_ShouldCreateSDK(t *testing.T) {
require.NotNil(t, s)

// Check platform issuer
assert.Equal(t, "https://example.org", s.PlatformIssuer())
iss, err := s.PlatformConfiguration.Issuer()
assert.Equal(t, "https://example.org", iss)
require.NoError(t, err)

// Check platform authz endpoint
authzEndpoint, err := s.PlatformConfiguration.AuthzEndpoint()
assert.Equal(t, "https://example.org/auth", authzEndpoint)
require.NoError(t, err)

// Check platform token endpoint
tokenEndpoint, err := s.PlatformConfiguration.TokenEndpoint()
assert.Equal(t, "https://example.org/token", tokenEndpoint)
require.NoError(t, err)

// Check platform public client id
publicClientID, err := s.PlatformConfiguration.PublicClientID()
assert.Equal(t, "myclient", publicClientID)
require.NoError(t, err)

// check if the clients are available
assert.NotNil(t, s.Attributes)
Expand All @@ -50,6 +72,44 @@ func TestNew_ShouldCreateSDK(t *testing.T) {
assert.NotNil(t, s.KeyAccessServerRegistry)
}

func Test_PlatformConfiguration_BadCases(t *testing.T) {
assertions := func(t *testing.T, s *sdk.SDK) {
iss, err := s.PlatformConfiguration.Issuer()
assert.Equal(t, "", iss)
require.ErrorIs(t, err, sdk.ErrPlatformIssuerNotFound)

authzEndpoint, err := s.PlatformConfiguration.AuthzEndpoint()
assert.Equal(t, "", authzEndpoint)
require.ErrorIs(t, err, sdk.ErrPlatformAuthzEndpointNotFound)

tokenEndpoint, err := s.PlatformConfiguration.TokenEndpoint()
assert.Equal(t, "", tokenEndpoint)
require.ErrorIs(t, err, sdk.ErrPlatformTokenEndpointNotFound)

publicClientID, err := s.PlatformConfiguration.PublicClientID()
assert.Equal(t, "", publicClientID)
require.ErrorIs(t, err, sdk.ErrPlatformTokenEndpointNotFound)
}

noIdpValsSDK, err := sdk.New(goodPlatformEndpoint,
sdk.WithPlatformConfiguration(sdk.PlatformConfiguration{
"idp": map[string]interface{}{},
}),
)
require.NoError(t, err)
assert.NotNil(t, noIdpValsSDK)

assertions(t, noIdpValsSDK)

noIdpCfgSDK, err := sdk.New(goodPlatformEndpoint,
sdk.WithPlatformConfiguration(sdk.PlatformConfiguration{}),
)
require.NoError(t, err)
assert.NotNil(t, noIdpCfgSDK)

assertions(t, noIdpCfgSDK)
}

func Test_ShouldCreateNewSDK_NoCredentials(t *testing.T) {
// When
s, err := sdk.New(goodPlatformEndpoint,
Expand Down
Loading