From 8faed6eb447096801b96dd51ee1a6fb64021eb31 Mon Sep 17 00:00:00 2001 From: jakedoublev Date: Thu, 15 Aug 2024 16:57:26 -0700 Subject: [PATCH 01/19] new proto accessors for idp well-known info on SDK --- lib/fixtures/keycloak.go | 16 ++++++++++ sdk/platformconfig.go | 41 +++++++++++++++++++++++--- sdk/sdk.go | 20 ++++++++----- sdk/sdk_test.go | 54 ++++++++++++++++++++++++++++++++-- sdk/tdf_test.go | 12 +++++--- service/internal/auth/authn.go | 5 ---- 6 files changed, 126 insertions(+), 22 deletions(-) diff --git a/lib/fixtures/keycloak.go b/lib/fixtures/keycloak.go index 57e860e1c7..129760646e 100644 --- a/lib/fixtures/keycloak.go +++ b/lib/fixtures/keycloak.go @@ -126,6 +126,7 @@ func SetupKeycloak(ctx context.Context, kcConnectParams KeycloakConnectParams) e testingOnlyRoleName := "opentdf-testing-role" opentdfERSClientID := "tdf-entity-resolution" opentdfAuthorizationClientID := "tdf-authorization-svc" + opentdfPublicClientID := "opentdf-public" realmMangementClientName := "realm-management" protocolMappers := []gocloak.ProtocolMapperRepresentation{ @@ -305,6 +306,21 @@ func SetupKeycloak(ctx context.Context, kcConnectParams KeycloakConnectParams) e return err } + // Create OpenTDF Public Client + _, err = createClient(ctx, client, token, &kcConnectParams, gocloak.Client{ + ClientID: gocloak.StringP(opentdfPublicClientID), + Enabled: gocloak.BoolP(true), + Name: gocloak.StringP(opentdfPublicClientID), + PublicClient: gocloak.BoolP(true), + ProtocolMappers: &protocolMappers, + // TODO: take via CLI arguments? + // Note: otdfctl CLI auth code flow runs from localhost:9000) + RedirectURIs: &[]string{"http://localhost:9000/*"}, + }, nil, nil) + if err != nil { + return err + } + // opentdfSdkClientNumericId, err := getIDOfClient(ctx, client, token, &kcConnectParams, &opentdfClientId) // if err != nil { // slog.Error(fmt.Sprintf("Error getting the SDK id: %s", err)) diff --git a/sdk/platformconfig.go b/sdk/platformconfig.go index 1b780b0999..78f121a4e8 100644 --- a/sdk/platformconfig.go +++ b/sdk/platformconfig.go @@ -2,10 +2,43 @@ package sdk import "log/slog" -func (s SDK) PlatformIssuer() string { - value, ok := s.config.platformConfiguration["platform_issuer"].(string) + + +func (s SDK) getIdpConfig() map[string]interface{} { + idpCfg, err := s.config.platformConfiguration["idp"].(map[string]interface{}) + if !err { + slog.Warn("idp configuration not found in well-known configuration") + idpCfg = map[string]interface{}{} + } + return idpCfg +} + +func (s SDK) PlatformIssuer() (string, error) { + idpCfg := s.getIdpConfig() + value, ok := idpCfg["issuer"].(string) + if !ok { + slog.Warn("issuer not found in well-known idp configuration") + return "", ErrPlatformIssuerNotFound + } + return value, nil +} + +func (s SDK) PlatformAuthzEndpoint() (string, error) { + idpCfg := s.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 (s SDK) PlatformTokenEndpoint() (string, error) { + idpCfg := s.getIdpConfig() + value, ok := idpCfg["token_endpoint"].(string) if !ok { - slog.Warn("platform_issuer not found in platform configuration") + slog.Warn("token_endpoint not found in well-known idp configuration") + return "", ErrPlatformTokenEndpointNotFound } - return value + return value, nil } diff --git a/sdk/sdk.go b/sdk/sdk.go index 9eeaf1a645..0dab525944 100644 --- a/sdk/sdk.go +++ b/sdk/sdk.go @@ -35,10 +35,13 @@ 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") ) type Error string @@ -326,7 +329,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") } @@ -373,10 +375,14 @@ 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) + idpCfg, ok := c.platformConfiguration["idp"].(map[string]interface{}) + if !ok { + return "", errors.New("'idp' config not found in well-known configuration") + } + issuerURL, ok := idpCfg["issuer"].(string) if !ok { - return "", errors.New("platform_issuer is not set, or is not a string") + return "", errors.New("'idp' config 'issuer' is not set, or is not a string in well-known configuration") } oidcConfigURL := issuerURL + "/.well-known/openid-configuration" diff --git a/sdk/sdk_test.go b/sdk/sdk_test.go index 454aab0553..c8bb1c7612 100644 --- a/sdk/sdk_test.go +++ b/sdk/sdk_test.go @@ -32,7 +32,11 @@ 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", + }, }), sdk.WithClientCredentials("myid", "mysecret", nil), sdk.WithTokenEndpoint("https://example.org/token"), @@ -41,7 +45,19 @@ func TestNew_ShouldCreateSDK(t *testing.T) { require.NotNil(t, s) // Check platform issuer - assert.Equal(t, "https://example.org", s.PlatformIssuer()) + iss, err := s.PlatformIssuer() + assert.Equal(t, "https://example.org", iss) + assert.Nil(t, err) + + // Check platform authz endpoint + authzEndpoint, err := s.PlatformAuthzEndpoint() + assert.Equal(t, "https://example.org/auth", authzEndpoint) + assert.Nil(t, err) + + // Check platform token endpoint + tokenEndpoint, err := s.PlatformTokenEndpoint() + assert.Equal(t, "https://example.org/token", tokenEndpoint) + assert.Nil(t, err) // check if the clients are available assert.NotNil(t, s.Attributes) @@ -50,6 +66,40 @@ 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.PlatformIssuer() + assert.Equal(t, "", iss) + assert.ErrorIs(t, err, sdk.ErrPlatformIssuerNotFound) + + authzEndpoint, err := s.PlatformAuthzEndpoint() + assert.Equal(t, "", authzEndpoint) + assert.ErrorIs(t, err, sdk.ErrPlatformAuthzEndpointNotFound) + + tokenEndpoint, err := s.PlatformTokenEndpoint() + assert.Equal(t, "", tokenEndpoint) + assert.ErrorIs(t, err, sdk.ErrPlatformTokenEndpointNotFound) + } + + noIdpValsSDK, err := sdk.New(goodPlatformEndpoint, + sdk.WithPlatformConfiguration(sdk.PlatformConfiguration{ + "idp": map[string]interface{}{}, + }), + ) + assert.NoError(t, err) + assert.NotNil(t, noIdpValsSDK) + + assertions(t, noIdpValsSDK) + + noIdpCfgSDK, err := sdk.New(goodPlatformEndpoint, + sdk.WithPlatformConfiguration(sdk.PlatformConfiguration{}), + ) + assert.NoError(t, err) + assert.NotNil(t, noIdpCfgSDK) + + assertions(t, noIdpCfgSDK) +} + func Test_ShouldCreateNewSDK_NoCredentials(t *testing.T) { // When s, err := sdk.New(goodPlatformEndpoint, diff --git a/sdk/tdf_test.go b/sdk/tdf_test.go index 29f44f10fb..2aee76969c 100644 --- a/sdk/tdf_test.go +++ b/sdk/tdf_test.go @@ -260,7 +260,6 @@ func TestTDF(t *testing.T) { func (s *TDFSuite) Test_SimpleTDF() { metaData := []byte(`{"displayName" : "openTDF go sdk"}`) attributes := []string{ - "https://example.com/attr/Classification/value/S", "https://example.com/attr/Classification/value/X", } @@ -1218,7 +1217,9 @@ func (s *TDFSuite) startBackend() { "health": map[string]interface{}{ "endpoint": "/healthz", }, - "platform_issuer": "http://localhost:65432/auth", + "idp": map[string]interface{}{ + "issuer": "http://localhost:65432/auth", + }, }, } @@ -1268,8 +1269,10 @@ func (s *TDFSuite) startBackend() { listeners[origin] = grpcListener grpcServer := grpc.NewServer() - s.kases[i] = FakeKas{s: s, privateKey: ki.private, KASInfo: KASInfo{ - URL: ki.url, PublicKey: ki.public, KID: "r1", Algorithm: "rsa:2048"}, + s.kases[i] = FakeKas{ + s: s, privateKey: ki.private, KASInfo: KASInfo{ + URL: ki.url, PublicKey: ki.public, KID: "r1", Algorithm: "rsa:2048", + }, legakeys: map[string]keyInfo{}, } attributespb.RegisterAttributesServiceServer(grpcServer, fa) @@ -1365,6 +1368,7 @@ func mockAttributeFor(fqn autoconfigure.AttributeNameFQN) *policy.Attribute { } return nil } + func mockValueFor(fqn autoconfigure.AttributeValueFQN) *policy.Value { an := fqn.Prefix() a := mockAttributeFor(an) diff --git a/service/internal/auth/authn.go b/service/internal/auth/authn.go index cf1dc47bd4..5a835addf1 100644 --- a/service/internal/auth/authn.go +++ b/service/internal/auth/authn.go @@ -156,11 +156,6 @@ func NewAuthenticator(ctx context.Context, cfg Config, logger *logger.Logger, we a.oidcConfiguration = cfg.AuthNConfig - // Try an register oidc issuer to wellknown service but don't return an error if it fails - if err := wellknownRegistration("platform_issuer", cfg.Issuer); err != nil { - logger.Warn("failed to register platform issuer", slog.String("error", err.Error())) - } - var oidcConfigMap map[string]any // Create a map of the oidc configuration From 709d1972fe2d1b9164e5c812683b980f237ab09d Mon Sep 17 00:00:00 2001 From: jakedoublev Date: Thu, 15 Aug 2024 17:36:45 -0700 Subject: [PATCH 02/19] remove condition no longer true since tokenEndpoint can be fetched from well-known --- sdk/sdk.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/sdk/sdk.go b/sdk/sdk.go index 0dab525944..11de6bda80 100644 --- a/sdk/sdk.go +++ b/sdk/sdk.go @@ -205,10 +205,6 @@ 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 c.clientCredentials == nil { From 63ae9c0637628dd828ddd651f43e4056c9263faf Mon Sep 17 00:00:00 2001 From: jakedoublev Date: Thu, 15 Aug 2024 17:37:08 -0700 Subject: [PATCH 03/19] add public client to keycloak_data.yaml --- service/cmd/keycloak_data.yaml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/service/cmd/keycloak_data.yaml b/service/cmd/keycloak_data.yaml index 42121366b0..849a18463f 100644 --- a/service/cmd/keycloak_data.yaml +++ b/service/cmd/keycloak_data.yaml @@ -71,6 +71,16 @@ realms: secret: secret protocolMappers: - *customAudMapper + - client: + clientID: opentdf-public + enabled: true + name: opentdf-public + serviceAccountsEnabled: false + publicClient: true + redirectUris: + - 'http://localhost:9000/*' + protocolMappers: + - *customAudMapper users: - username: sample-user enabled: true From 4e71ac0b4481a1ed262aa1ce8871acc8fe0e273c Mon Sep 17 00:00:00 2001 From: jakedoublev Date: Thu, 15 Aug 2024 17:40:08 -0700 Subject: [PATCH 04/19] clean up keycloak provisioning --- service/cmd/provisionKeycloak.go | 1 + service/cmd/provisionKeycloakFromConfig.go | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/service/cmd/provisionKeycloak.go b/service/cmd/provisionKeycloak.go index 909fbad24b..2b25633baf 100644 --- a/service/cmd/provisionKeycloak.go +++ b/service/cmd/provisionKeycloak.go @@ -13,6 +13,7 @@ const ( provKcUsernameFlag = "username" provKcPasswordFlag = "password" provKcRealmFlag = "realm" + provKcFilenameFlag = "file" provKcInsecure = "insecure" ) diff --git a/service/cmd/provisionKeycloakFromConfig.go b/service/cmd/provisionKeycloakFromConfig.go index f93996c53b..985dbe0b65 100644 --- a/service/cmd/provisionKeycloakFromConfig.go +++ b/service/cmd/provisionKeycloakFromConfig.go @@ -14,7 +14,7 @@ import ( ) var ( - provKeycloakFilename = "./cmd/keycloak_data.yaml" + provKeycloakFilename = "./service/cmd/keycloak_data.yaml" keycloakData fixtures.KeycloakData ) @@ -115,7 +115,7 @@ func init() { provisionKeycloakFromConfigCmd.Flags().StringP(provKcEndpointFlag, "e", "http://localhost:8888/auth", "Keycloak endpoint") provisionKeycloakFromConfigCmd.Flags().StringP(provKcUsernameFlag, "u", "admin", "Keycloak username") provisionKeycloakFromConfigCmd.Flags().StringP(provKcPasswordFlag, "p", "changeme", "Keycloak password") - provisionKeycloakFromConfigCmd.Flags().StringP(provKeycloakFilename, "f", "./cmd/keycloak_data.yaml", "Keycloak config file") + provisionKeycloakFromConfigCmd.Flags().StringP(provKcFilenameFlag, "f", provKeycloakFilename, "Keycloak config file") // provisionKeycloakFromConfigCmd.Flags().StringP(provKcRealm, "r", "opentdf", "OpenTDF Keycloak Realm name") provisionCmd.AddCommand(provisionKeycloakFromConfigCmd) From 7cb6ec9c8ad881ab2cbc8dceffc165c2d5cf01bc Mon Sep 17 00:00:00 2001 From: jakedoublev Date: Thu, 15 Aug 2024 19:57:46 -0700 Subject: [PATCH 05/19] WIP --- sdk/options.go | 2 +- sdk/sdk.go | 6 ++---- sdk/tdf_test.go | 2 +- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/sdk/options.go b/sdk/options.go index 7b7cacb7d5..32b94eea72 100644 --- a/sdk/options.go +++ b/sdk/options.go @@ -93,7 +93,7 @@ func WithTokenEndpoint(tokenEndpoint string) Option { } } -func withCustomAccessTokenSource(a auth.AccessTokenSource) Option { +func WithCustomAccessTokenSource(a auth.AccessTokenSource) Option { return func(c *config) { c.customAccessTokenSource = a } diff --git a/sdk/sdk.go b/sdk/sdk.go index 11de6bda80..d0e3db087d 100644 --- a/sdk/sdk.go +++ b/sdk/sdk.go @@ -8,7 +8,6 @@ import ( "errors" "fmt" "io" - "log/slog" "net" "net/http" "net/url" @@ -205,10 +204,9 @@ func buildIDPTokenSource(c *config) (auth.AccessTokenSource, error) { return c.customAccessTokenSource, nil } - // 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 } diff --git a/sdk/tdf_test.go b/sdk/tdf_test.go index 2aee76969c..d4ef798e2d 100644 --- a/sdk/tdf_test.go +++ b/sdk/tdf_test.go @@ -1288,7 +1288,7 @@ func (s *TDFSuite) startBackend() { sdk, err := New("localhost:65432", WithClientCredentials("test", "test", nil), - withCustomAccessTokenSource(&ats), + WithCustomAccessTokenSource(&ats), WithTokenEndpoint("http://localhost:65432/auth/token"), WithInsecurePlaintextConn(), WithExtraDialOptions(grpc.WithContextDialer(dialer))) From 1f0fec107990e48c91070b6d6ee5f9e740dd8b93 Mon Sep 17 00:00:00 2001 From: jakedoublev Date: Fri, 16 Aug 2024 11:54:20 -0700 Subject: [PATCH 06/19] set public_client_id from config to idp well-known configuration and update configs --- opentdf-dev.yaml | 1 + opentdf-example-no-kas.yaml | 1 + opentdf-example.yaml | 1 + opentdf-with-hsm.yaml | 1 + sdk/platformconfig.go | 10 ++++++++++ sdk/sdk.go | 15 ++++++++------- sdk/sdk_test.go | 10 ++++++++++ service/internal/auth/authn.go | 2 ++ service/internal/auth/config.go | 19 ++++++++++++------- service/internal/auth/discovery.go | 1 + 10 files changed, 47 insertions(+), 14 deletions(-) diff --git a/opentdf-dev.yaml b/opentdf-dev.yaml index c095a3cbbe..eb4b7edccc 100644 --- a/opentdf-dev.yaml +++ b/opentdf-dev.yaml @@ -35,6 +35,7 @@ server: auth: enabled: true enforceDPoP: false + publicclientid: "opentdf-public" audience: 'http://localhost:8080' issuer: http://localhost:8888/auth/realms/opentdf policy: diff --git a/opentdf-example-no-kas.yaml b/opentdf-example-no-kas.yaml index 7d1849a18b..60530bc8fb 100644 --- a/opentdf-example-no-kas.yaml +++ b/opentdf-example-no-kas.yaml @@ -13,6 +13,7 @@ server: auth: enabled: false enforceDPoP: false + publicclientid: "opentdf-public" audience: "http://localhost:8080" issuer: http://localhost:8888/auth/realms/tdf cors: diff --git a/opentdf-example.yaml b/opentdf-example.yaml index 7c765e80ec..41b992ff9a 100644 --- a/opentdf-example.yaml +++ b/opentdf-example.yaml @@ -27,6 +27,7 @@ server: auth: enabled: true enforceDPoP: false + publicclientid: "opentdf-public" audience: "http://localhost:8080" issuer: http://keycloak:8888/auth/realms/opentdf policy: diff --git a/opentdf-with-hsm.yaml b/opentdf-with-hsm.yaml index 3bde88b14d..e47c013697 100644 --- a/opentdf-with-hsm.yaml +++ b/opentdf-with-hsm.yaml @@ -28,6 +28,7 @@ server: auth: enabled: true enforceDPoP: false + publicclientid: "opentdf-public" audience: "http://localhost:8080" issuer: http://localhost:8888/auth/realms/opentdf clients: diff --git a/sdk/platformconfig.go b/sdk/platformconfig.go index 78f121a4e8..777b491c8e 100644 --- a/sdk/platformconfig.go +++ b/sdk/platformconfig.go @@ -42,3 +42,13 @@ func (s SDK) PlatformTokenEndpoint() (string, error) { } return value, nil } + +func (s SDK) PlatformPublicClientID() (string, error) { + idpCfg := s.getIdpConfig() + value, ok := idpCfg["public_client_id"].(string) + if !ok { + slog.Warn("public_client_id not found in well-known idp configuration") + return "", ErrPlatformTokenEndpointNotFound + } + return value, nil +} diff --git a/sdk/sdk.go b/sdk/sdk.go index d0e3db087d..dbec970e04 100644 --- a/sdk/sdk.go +++ b/sdk/sdk.go @@ -34,13 +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") - 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") + 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 diff --git a/sdk/sdk_test.go b/sdk/sdk_test.go index c8bb1c7612..c00c50d32d 100644 --- a/sdk/sdk_test.go +++ b/sdk/sdk_test.go @@ -36,6 +36,7 @@ func TestNew_ShouldCreateSDK(t *testing.T) { "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), @@ -59,6 +60,11 @@ func TestNew_ShouldCreateSDK(t *testing.T) { assert.Equal(t, "https://example.org/token", tokenEndpoint) assert.Nil(t, err) + // Check platform public client id + publicClientID, err := s.PlatformPublicClientID() + assert.Equal(t, "myclient", publicClientID) + assert.Nil(t, err) + // check if the clients are available assert.NotNil(t, s.Attributes) assert.NotNil(t, s.ResourceMapping) @@ -79,6 +85,10 @@ func Test_PlatformConfiguration_BadCases(t *testing.T) { tokenEndpoint, err := s.PlatformTokenEndpoint() assert.Equal(t, "", tokenEndpoint) assert.ErrorIs(t, err, sdk.ErrPlatformTokenEndpointNotFound) + + publicClientID, err := s.PlatformPublicClientID() + assert.Equal(t, "", publicClientID) + assert.ErrorIs(t, err, sdk.ErrPlatformTokenEndpointNotFound) } noIdpValsSDK, err := sdk.New(goodPlatformEndpoint, diff --git a/service/internal/auth/authn.go b/service/internal/auth/authn.go index 5a835addf1..1e3aaf01d3 100644 --- a/service/internal/auth/authn.go +++ b/service/internal/auth/authn.go @@ -114,6 +114,8 @@ func NewAuthenticator(ctx context.Context, cfg Config, logger *logger.Logger, we if err != nil { return nil, err } + // Assign configured public_client_id + oidcConfig.PublicClientID = cfg.PublicClientID // If the issuer is different from the one in the configuration, update the configuration // This could happen if we are hitting an internal endpoint. Example we might point to https://keycloak.opentdf.svc/realms/opentdf diff --git a/service/internal/auth/config.go b/service/internal/auth/config.go index cf9fd2b8a5..084ec30ce8 100644 --- a/service/internal/auth/config.go +++ b/service/internal/auth/config.go @@ -16,13 +16,14 @@ type Config struct { // AuthNConfig is the configuration need for the platform to validate tokens type AuthNConfig struct { //nolint:revive // AuthNConfig is a valid name - EnforceDPoP bool `yaml:"enforceDPoP" json:"enforceDPoP" mapstructure:"enforceDPoP" default:"false"` - Issuer string `yaml:"issuer" json:"issuer"` - Audience string `yaml:"audience" json:"audience"` - Policy PolicyConfig `yaml:"policy" json:"policy" mapstructure:"policy"` - CacheRefresh string `mapstructure:"cache_refresh_interval"` - DPoPSkew time.Duration `mapstructure:"dpopskew" default:"1h"` - TokenSkew time.Duration `mapstructure:"skew" default:"1m"` + EnforceDPoP bool `yaml:"enforceDPoP" json:"enforceDPoP" mapstructure:"enforceDPoP" default:"false"` + Issuer string `yaml:"issuer" json:"issuer"` + Audience string `yaml:"audience" json:"audience"` + Policy PolicyConfig `yaml:"policy" json:"policy" mapstructure:"policy"` + CacheRefresh string `mapstructure:"cache_refresh_interval"` + DPoPSkew time.Duration `mapstructure:"dpopskew" default:"1h"` + TokenSkew time.Duration `mapstructure:"skew" default:"1m"` + PublicClientID string `yaml:"public_client_id" json:"public_client_id,omitempty" mapstructure:"publicclientid"` } type PolicyConfig struct { @@ -42,6 +43,10 @@ func (c AuthNConfig) validateAuthNConfig(logger *logger.Logger) error { return fmt.Errorf("config Auth.Audience is required") } + if c.PublicClientID == "" { + logger.Warn("config Auth.PublicClientID is empty and is required for discovery via well-known configuration.") + } + if !c.EnforceDPoP { logger.Warn("config Auth.EnforceDPoP is false. DPoP will not be enforced.") } diff --git a/service/internal/auth/discovery.go b/service/internal/auth/discovery.go index 9cbff5a3b7..e77988626a 100644 --- a/service/internal/auth/discovery.go +++ b/service/internal/auth/discovery.go @@ -26,6 +26,7 @@ type OIDCConfiguration struct { SubjectTypesSupported []string `json:"subject_types_supported"` IDTokenSigningAlgValuesSupported []string `json:"id_token_signing_alg_values_supported"` RequireRequestURIRegistration bool `json:"require_request_uri_registration"` + PublicClientID string `json:"public_client_id,omitempty"` } // DiscoverOPENIDConfiguration discovers the openid configuration for the issuer provided From c8b75490c6d292097e02f480dbd810d21b2e06a3 Mon Sep 17 00:00:00 2001 From: jakedoublev Date: Fri, 16 Aug 2024 11:58:11 -0700 Subject: [PATCH 07/19] handle access token source in separate PR --- sdk/options.go | 2 +- sdk/tdf_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/sdk/options.go b/sdk/options.go index 32b94eea72..7b7cacb7d5 100644 --- a/sdk/options.go +++ b/sdk/options.go @@ -93,7 +93,7 @@ func WithTokenEndpoint(tokenEndpoint string) Option { } } -func WithCustomAccessTokenSource(a auth.AccessTokenSource) Option { +func withCustomAccessTokenSource(a auth.AccessTokenSource) Option { return func(c *config) { c.customAccessTokenSource = a } diff --git a/sdk/tdf_test.go b/sdk/tdf_test.go index d4ef798e2d..2aee76969c 100644 --- a/sdk/tdf_test.go +++ b/sdk/tdf_test.go @@ -1288,7 +1288,7 @@ func (s *TDFSuite) startBackend() { sdk, err := New("localhost:65432", WithClientCredentials("test", "test", nil), - WithCustomAccessTokenSource(&ats), + withCustomAccessTokenSource(&ats), WithTokenEndpoint("http://localhost:65432/auth/token"), WithInsecurePlaintextConn(), WithExtraDialOptions(grpc.WithContextDialer(dialer))) From 2d18f5480a6d90990d67a14162f7da90c10b3890 Mon Sep 17 00:00:00 2001 From: jakedoublev Date: Fri, 16 Aug 2024 12:08:30 -0700 Subject: [PATCH 08/19] lint --- sdk/sdk_test.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/sdk/sdk_test.go b/sdk/sdk_test.go index c00c50d32d..257f49b709 100644 --- a/sdk/sdk_test.go +++ b/sdk/sdk_test.go @@ -48,22 +48,22 @@ func TestNew_ShouldCreateSDK(t *testing.T) { // Check platform issuer iss, err := s.PlatformIssuer() assert.Equal(t, "https://example.org", iss) - assert.Nil(t, err) + assert.NoError(t, err) // Check platform authz endpoint authzEndpoint, err := s.PlatformAuthzEndpoint() assert.Equal(t, "https://example.org/auth", authzEndpoint) - assert.Nil(t, err) + assert.NoError(t, err) // Check platform token endpoint tokenEndpoint, err := s.PlatformTokenEndpoint() assert.Equal(t, "https://example.org/token", tokenEndpoint) - assert.Nil(t, err) + assert.NoError(t, err) // Check platform public client id publicClientID, err := s.PlatformPublicClientID() assert.Equal(t, "myclient", publicClientID) - assert.Nil(t, err) + assert.NoError(t, err) // check if the clients are available assert.NotNil(t, s.Attributes) @@ -76,19 +76,19 @@ func Test_PlatformConfiguration_BadCases(t *testing.T) { assertions := func(t *testing.T, s *sdk.SDK) { iss, err := s.PlatformIssuer() assert.Equal(t, "", iss) - assert.ErrorIs(t, err, sdk.ErrPlatformIssuerNotFound) + require.ErrorIs(t, err, sdk.ErrPlatformIssuerNotFound) authzEndpoint, err := s.PlatformAuthzEndpoint() assert.Equal(t, "", authzEndpoint) - assert.ErrorIs(t, err, sdk.ErrPlatformAuthzEndpointNotFound) + require.ErrorIs(t, err, sdk.ErrPlatformAuthzEndpointNotFound) tokenEndpoint, err := s.PlatformTokenEndpoint() assert.Equal(t, "", tokenEndpoint) - assert.ErrorIs(t, err, sdk.ErrPlatformTokenEndpointNotFound) + require.ErrorIs(t, err, sdk.ErrPlatformTokenEndpointNotFound) publicClientID, err := s.PlatformPublicClientID() assert.Equal(t, "", publicClientID) - assert.ErrorIs(t, err, sdk.ErrPlatformTokenEndpointNotFound) + require.ErrorIs(t, err, sdk.ErrPlatformTokenEndpointNotFound) } noIdpValsSDK, err := sdk.New(goodPlatformEndpoint, From ace5c4f772143cfc28c0153c27fa9894feb244a3 Mon Sep 17 00:00:00 2001 From: jakedoublev Date: Fri, 16 Aug 2024 12:23:07 -0700 Subject: [PATCH 09/19] lint --- sdk/sdk_test.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/sdk/sdk_test.go b/sdk/sdk_test.go index 257f49b709..53dcf08bc2 100644 --- a/sdk/sdk_test.go +++ b/sdk/sdk_test.go @@ -48,22 +48,22 @@ func TestNew_ShouldCreateSDK(t *testing.T) { // Check platform issuer iss, err := s.PlatformIssuer() assert.Equal(t, "https://example.org", iss) - assert.NoError(t, err) + require.NoError(t, err) // Check platform authz endpoint authzEndpoint, err := s.PlatformAuthzEndpoint() assert.Equal(t, "https://example.org/auth", authzEndpoint) - assert.NoError(t, err) + require.NoError(t, err) // Check platform token endpoint tokenEndpoint, err := s.PlatformTokenEndpoint() assert.Equal(t, "https://example.org/token", tokenEndpoint) - assert.NoError(t, err) + require.NoError(t, err) // Check platform public client id publicClientID, err := s.PlatformPublicClientID() assert.Equal(t, "myclient", publicClientID) - assert.NoError(t, err) + require.NoError(t, err) // check if the clients are available assert.NotNil(t, s.Attributes) @@ -96,7 +96,7 @@ func Test_PlatformConfiguration_BadCases(t *testing.T) { "idp": map[string]interface{}{}, }), ) - assert.NoError(t, err) + require.NoError(t, err) assert.NotNil(t, noIdpValsSDK) assertions(t, noIdpValsSDK) @@ -104,7 +104,7 @@ func Test_PlatformConfiguration_BadCases(t *testing.T) { noIdpCfgSDK, err := sdk.New(goodPlatformEndpoint, sdk.WithPlatformConfiguration(sdk.PlatformConfiguration{}), ) - assert.NoError(t, err) + require.NoError(t, err) assert.NotNil(t, noIdpCfgSDK) assertions(t, noIdpCfgSDK) From debcdbc7fd274c13060ada453370206cd00cacef Mon Sep 17 00:00:00 2001 From: jakedoublev Date: Fri, 16 Aug 2024 13:26:54 -0700 Subject: [PATCH 10/19] put back platform_issuer to avoid breaking java sdk --- service/internal/auth/authn.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/service/internal/auth/authn.go b/service/internal/auth/authn.go index 1e3aaf01d3..cf8cdcef2f 100644 --- a/service/internal/auth/authn.go +++ b/service/internal/auth/authn.go @@ -158,6 +158,11 @@ func NewAuthenticator(ctx context.Context, cfg Config, logger *logger.Logger, we a.oidcConfiguration = cfg.AuthNConfig + // Try an register oidc issuer to wellknown service but don't return an error if it fails + if err := wellknownRegistration("platform_issuer", cfg.Issuer); err != nil { + logger.Warn("failed to register platform issuer", slog.String("error", err.Error())) + } + var oidcConfigMap map[string]any // Create a map of the oidc configuration From 012cf09eb8d5ba0549bc6ad8b494fa9c4e9d78f7 Mon Sep 17 00:00:00 2001 From: jakedoublev Date: Fri, 16 Aug 2024 15:27:25 -0700 Subject: [PATCH 11/19] expose idp accessors of well-known off platform config rather than top-level methods --- sdk/options.go | 4 +-- sdk/platformconfig.go | 32 +++++++++-------------- sdk/sdk.go | 61 ++----------------------------------------- sdk/sdk_test.go | 16 ++++++------ 4 files changed, 25 insertions(+), 88 deletions(-) diff --git a/sdk/options.go b/sdk/options.go index 88daabac90..f2c7e24bcf 100644 --- a/sdk/options.go +++ b/sdk/options.go @@ -24,7 +24,7 @@ type config struct { scopes []string extraDialOptions []grpc.DialOption certExchange *oauth.CertExchangeInfo - platformConfiguration PlatformConfiguration + PlatformConfiguration PlatformConfiguration kasSessionKey *ocrypto.RsaKeyPair dpopKey *ocrypto.RsaKeyPair ipc bool @@ -164,7 +164,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 } } diff --git a/sdk/platformconfig.go b/sdk/platformconfig.go index fe33abd2fa..9afe05fc61 100644 --- a/sdk/platformconfig.go +++ b/sdk/platformconfig.go @@ -1,17 +1,11 @@ package sdk -import "log/slog" +import ( + "log/slog" +) -func (s SDK) getIdpConfig() map[string]interface{} { - // 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 - } - idpCfg, err := s.config.platformConfiguration["idp"].(map[string]interface{}) +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{}{} @@ -19,8 +13,8 @@ func (s SDK) getIdpConfig() map[string]interface{} { return idpCfg } -func (s SDK) PlatformIssuer() (string, error) { - idpCfg := s.getIdpConfig() +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") @@ -29,8 +23,8 @@ func (s SDK) PlatformIssuer() (string, error) { return value, nil } -func (s SDK) PlatformAuthzEndpoint() (string, error) { - idpCfg := s.getIdpConfig() +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") @@ -39,8 +33,8 @@ func (s SDK) PlatformAuthzEndpoint() (string, error) { return value, nil } -func (s SDK) PlatformTokenEndpoint() (string, error) { - idpCfg := s.getIdpConfig() +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") @@ -49,8 +43,8 @@ func (s SDK) PlatformTokenEndpoint() (string, error) { return value, nil } -func (s SDK) PlatformPublicClientID() (string, error) { - idpCfg := s.getIdpConfig() +func (c PlatformConfiguration) PublicClientID() (string, error) { + idpCfg := c.getIdpConfig() value, ok := idpCfg["public_client_id"].(string) if !ok { slog.Warn("public_client_id not found in well-known idp configuration") diff --git a/sdk/sdk.go b/sdk/sdk.go index 1484d46b5c..09f101ef21 100644 --- a/sdk/sdk.go +++ b/sdk/sdk.go @@ -4,12 +4,10 @@ import ( "context" "crypto/tls" _ "embed" - "encoding/json" "errors" "fmt" "io" "net" - "net/http" "net/url" "regexp" @@ -115,7 +113,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 @@ -130,13 +128,7 @@ func New(platformEndpoint string, opts ...Option) (*SDK, error) { return nil, errors.Join(ErrPlatformConfigFailed, err) } } - cfg.platformConfiguration = pcfg - if cfg.tokenEndpoint == "" { - cfg.tokenEndpoint, err = getTokenEndpoint(*cfg) - if err != nil { - return nil, err - } - } + cfg.PlatformConfiguration = pcfg } var uci []grpc.UnaryClientInterceptor @@ -377,52 +369,3 @@ func getPlatformConfiguration(conn *grpc.ClientConn) (PlatformConfiguration, err return configuration.AsMap(), nil } - -// TODO: This should be moved to a separate package. We do discovery in ../service/internal/auth/discovery.go -func getTokenEndpoint(c config) (string, error) { - idpCfg, ok := c.platformConfiguration["idp"].(map[string]interface{}) - if !ok { - return "", errors.New("'idp' config not found in well-known configuration") - } - - issuerURL, ok := idpCfg["issuer"].(string) - if !ok { - return "", errors.New("'idp' config 'issuer' is not set, or is not a string in well-known configuration") - } - - oidcConfigURL := issuerURL + "/.well-known/openid-configuration" - - req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, oidcConfigURL, nil) - if err != nil { - return "", err - } - - httpClient := &http.Client{ - Transport: &http.Transport{ - TLSClientConfig: c.tlsConfig, - }, - } - - resp, err := httpClient.Do(req) - if err != nil { - return "", err - } - defer resp.Body.Close() - - body, err := io.ReadAll(resp.Body) - if err != nil { - return "", err - } - - var config map[string]interface{} - - if err = json.Unmarshal(body, &config); err != nil { - return "", err - } - tokenEndpoint, ok := config["token_endpoint"].(string) - if !ok { - return "", errors.New("token_endpoint not found in well-known configuration") - } - - return tokenEndpoint, nil -} diff --git a/sdk/sdk_test.go b/sdk/sdk_test.go index 53dcf08bc2..3f4e35b361 100644 --- a/sdk/sdk_test.go +++ b/sdk/sdk_test.go @@ -46,22 +46,22 @@ func TestNew_ShouldCreateSDK(t *testing.T) { require.NotNil(t, s) // Check platform issuer - iss, err := 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.PlatformAuthzEndpoint() + authzEndpoint, err := s.PlatformConfiguration.AuthzEndpoint() assert.Equal(t, "https://example.org/auth", authzEndpoint) require.NoError(t, err) // Check platform token endpoint - tokenEndpoint, err := s.PlatformTokenEndpoint() + 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.PlatformPublicClientID() + publicClientID, err := s.PlatformConfiguration.PublicClientID() assert.Equal(t, "myclient", publicClientID) require.NoError(t, err) @@ -74,19 +74,19 @@ func TestNew_ShouldCreateSDK(t *testing.T) { func Test_PlatformConfiguration_BadCases(t *testing.T) { assertions := func(t *testing.T, s *sdk.SDK) { - iss, err := s.PlatformIssuer() + iss, err := s.PlatformConfiguration.Issuer() assert.Equal(t, "", iss) require.ErrorIs(t, err, sdk.ErrPlatformIssuerNotFound) - authzEndpoint, err := s.PlatformAuthzEndpoint() + authzEndpoint, err := s.PlatformConfiguration.AuthzEndpoint() assert.Equal(t, "", authzEndpoint) require.ErrorIs(t, err, sdk.ErrPlatformAuthzEndpointNotFound) - tokenEndpoint, err := s.PlatformTokenEndpoint() + tokenEndpoint, err := s.PlatformConfiguration.TokenEndpoint() assert.Equal(t, "", tokenEndpoint) require.ErrorIs(t, err, sdk.ErrPlatformTokenEndpointNotFound) - publicClientID, err := s.PlatformPublicClientID() + publicClientID, err := s.PlatformConfiguration.PublicClientID() assert.Equal(t, "", publicClientID) require.ErrorIs(t, err, sdk.ErrPlatformTokenEndpointNotFound) } From 505671dd362b34109ee4a0e534bd118466542488 Mon Sep 17 00:00:00 2001 From: jakedoublev Date: Fri, 16 Aug 2024 15:47:02 -0700 Subject: [PATCH 12/19] put back token_endpoint fetching if not provided --- sdk/sdk.go | 53 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/sdk/sdk.go b/sdk/sdk.go index 09f101ef21..8c7d7f2b96 100644 --- a/sdk/sdk.go +++ b/sdk/sdk.go @@ -4,10 +4,12 @@ import ( "context" "crypto/tls" _ "embed" + "encoding/json" "errors" "fmt" "io" "net" + "net/http" "net/url" "regexp" @@ -129,6 +131,12 @@ func New(platformEndpoint string, opts ...Option) (*SDK, error) { } } cfg.PlatformConfiguration = pcfg + if cfg.tokenEndpoint == "" { + cfg.tokenEndpoint, err = getTokenEndpoint(*cfg) + if err != nil { + return nil, err + } + } } var uci []grpc.UnaryClientInterceptor @@ -369,3 +377,48 @@ func getPlatformConfiguration(conn *grpc.ClientConn) (PlatformConfiguration, err return configuration.AsMap(), nil } + +// 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) + + if !ok { + return "", errors.New("platform_issuer is not set, or is not a string") + } + + oidcConfigURL := issuerURL + "/.well-known/openid-configuration" + + req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, oidcConfigURL, nil) + if err != nil { + return "", err + } + + httpClient := &http.Client{ + Transport: &http.Transport{ + TLSClientConfig: c.tlsConfig, + }, + } + + resp, err := httpClient.Do(req) + if err != nil { + return "", err + } + defer resp.Body.Close() + + body, err := io.ReadAll(resp.Body) + if err != nil { + return "", err + } + + var config map[string]interface{} + + if err = json.Unmarshal(body, &config); err != nil { + return "", err + } + tokenEndpoint, ok := config["token_endpoint"].(string) + if !ok { + return "", errors.New("token_endpoint not found in well-known configuration") + } + + return tokenEndpoint, nil +} From 1d6ebb1a3ca23afba0b0e17eb479ec45a21d6048 Mon Sep 17 00:00:00 2001 From: jakedoublev Date: Fri, 16 Aug 2024 16:34:53 -0700 Subject: [PATCH 13/19] add comment --- sdk/options.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sdk/options.go b/sdk/options.go index f2c7e24bcf..c7856563ae 100644 --- a/sdk/options.go +++ b/sdk/options.go @@ -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 @@ -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 From 0d841be1eb3425f87e85c7c3fc6c6075e41eee4d Mon Sep 17 00:00:00 2001 From: jakedoublev Date: Mon, 19 Aug 2024 07:57:15 -0700 Subject: [PATCH 14/19] use underscores for public_client_id in config --- opentdf-dev.yaml | 2 +- opentdf-example-no-kas.yaml | 8 ++++---- opentdf-example.yaml | 16 ++++++++-------- opentdf-with-hsm.yaml | 22 +++++++++++----------- service/internal/auth/config.go | 2 +- 5 files changed, 25 insertions(+), 25 deletions(-) diff --git a/opentdf-dev.yaml b/opentdf-dev.yaml index eb4b7edccc..16ae405ae4 100644 --- a/opentdf-dev.yaml +++ b/opentdf-dev.yaml @@ -35,7 +35,7 @@ server: auth: enabled: true enforceDPoP: false - publicclientid: "opentdf-public" + public_client_id: 'opentdf-public' audience: 'http://localhost:8080' issuer: http://localhost:8888/auth/realms/opentdf policy: diff --git a/opentdf-example-no-kas.yaml b/opentdf-example-no-kas.yaml index 60530bc8fb..52d3950b9a 100644 --- a/opentdf-example-no-kas.yaml +++ b/opentdf-example-no-kas.yaml @@ -13,14 +13,14 @@ server: auth: enabled: false enforceDPoP: false - publicclientid: "opentdf-public" - 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: - - "*" + allowedorigins: + - '*' # List of methods. Examples: 'GET,POST,PUT' allowedmethods: - GET diff --git a/opentdf-example.yaml b/opentdf-example.yaml index 41b992ff9a..845dab272d 100644 --- a/opentdf-example.yaml +++ b/opentdf-example.yaml @@ -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: @@ -27,8 +27,8 @@ server: auth: enabled: true enforceDPoP: false - publicclientid: "opentdf-public" - 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 @@ -68,8 +68,8 @@ server: cors: enabled: false # '*' to allow any origin or a specific domain like 'https://yourdomain.com' - allowedorigins: - - "*" + allowedorigins: + - '*' # List of methods. Examples: 'GET,POST,PUT' allowedmethods: - GET diff --git a/opentdf-with-hsm.yaml b/opentdf-with-hsm.yaml index e47c013697..7ee5951cce 100644 --- a/opentdf-with-hsm.yaml +++ b/opentdf-with-hsm.yaml @@ -18,9 +18,9 @@ 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 @@ -28,12 +28,12 @@ server: auth: enabled: true enforceDPoP: false - publicclientid: "opentdf-public" - 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" @@ -74,8 +74,8 @@ server: cors: enabled: false # '*' to allow any origin or a specific domain like 'https://yourdomain.com' - allowedorigins: - - "*" + allowedorigins: + - '*' # List of methods. Examples: 'GET,POST,PUT' allowedmethods: - GET @@ -103,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 diff --git a/service/internal/auth/config.go b/service/internal/auth/config.go index 084ec30ce8..f50d0e3c5a 100644 --- a/service/internal/auth/config.go +++ b/service/internal/auth/config.go @@ -23,7 +23,7 @@ type AuthNConfig struct { //nolint:revive // AuthNConfig is a valid name CacheRefresh string `mapstructure:"cache_refresh_interval"` DPoPSkew time.Duration `mapstructure:"dpopskew" default:"1h"` TokenSkew time.Duration `mapstructure:"skew" default:"1m"` - PublicClientID string `yaml:"public_client_id" json:"public_client_id,omitempty" mapstructure:"publicclientid"` + PublicClientID string `yaml:"public_client_id" json:"public_client_id,omitempty" mapstructure:"public_client_id"` } type PolicyConfig struct { From a6a0c7ce976cf4440d233306702bb09abd52b38b Mon Sep 17 00:00:00 2001 From: jakedoublev Date: Mon, 19 Aug 2024 10:59:22 -0700 Subject: [PATCH 15/19] use deprecated variable name --- service/cmd/provisionKeycloak.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/service/cmd/provisionKeycloak.go b/service/cmd/provisionKeycloak.go index 194be731d1..9dc4d7d0f8 100644 --- a/service/cmd/provisionKeycloak.go +++ b/service/cmd/provisionKeycloak.go @@ -66,7 +66,7 @@ var provisionKeycloakCmd = &cobra.Command{ }, } -var provisionKeycloakFromConfigCmd = &cobra.Command{ +var deprecatedProvisionKeycloakFromConfigCmd = &cobra.Command{ Use: "keycloak-from-config", RunE: func(_ *cobra.Command, _ []string) error { slog.Info("Command keycloak-from-config has been deprecated. Please use command 'keycloak' instead.") @@ -130,5 +130,5 @@ func init() { provisionCmd.AddCommand(provisionKeycloakCmd) // Deprecated command - provisionCmd.AddCommand(provisionKeycloakFromConfigCmd) + provisionCmd.AddCommand(deprecatedProvisionKeycloakFromConfigCmd) } From e66964ffc94ebcd85c4b6ef1ea67d71c91680c1f Mon Sep 17 00:00:00 2001 From: jakedoublev Date: Mon, 19 Aug 2024 11:01:41 -0700 Subject: [PATCH 16/19] comment for redirect uri usage --- service/cmd/keycloak_data.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/service/cmd/keycloak_data.yaml b/service/cmd/keycloak_data.yaml index 849a18463f..006a326b27 100644 --- a/service/cmd/keycloak_data.yaml +++ b/service/cmd/keycloak_data.yaml @@ -78,7 +78,7 @@ realms: serviceAccountsEnabled: false publicClient: true redirectUris: - - 'http://localhost:9000/*' + - 'http://localhost:9000/*' # otdfctl CLI tool protocolMappers: - *customAudMapper users: From 3371f2324788e72d70010433253f0f55ad27d8c9 Mon Sep 17 00:00:00 2001 From: jakedoublev Date: Mon, 19 Aug 2024 11:53:51 -0700 Subject: [PATCH 17/19] cleanup --- lib/fixtures/keycloak.go | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/lib/fixtures/keycloak.go b/lib/fixtures/keycloak.go index 129760646e..8b38e1eeaf 100644 --- a/lib/fixtures/keycloak.go +++ b/lib/fixtures/keycloak.go @@ -335,7 +335,7 @@ func SetupKeycloak(ctx context.Context, kcConnectParams KeycloakConnectParams) e Username: gocloak.StringP("sampleuser"), Attributes: &map[string][]string{"superhero_name": {"thor"}, "superhero_group": {"avengers"}}, } - _, err = createUser(ctx, client, token, &kcConnectParams, user) + err = createUser(ctx, client, token, &kcConnectParams, user) if err != nil { panic("Oh no!, failed to create user :(") } @@ -439,7 +439,7 @@ func SetupCustomKeycloak(ctx context.Context, kcParams KeycloakConnectParams, ke // create the users if realmToCreate.Users != nil { for _, customUser := range realmToCreate.Users { - _, err = createUser(ctx, client, token, &kcConnectParams, customUser) + err = createUser(ctx, client, token, &kcConnectParams, customUser) if err != nil { return err } @@ -659,7 +659,7 @@ func createClient(ctx context.Context, client *gocloak.GoCloak, token *gocloak.J return longClientID, nil } -func createUser(ctx context.Context, client *gocloak.GoCloak, token *gocloak.JWT, connectParams *KeycloakConnectParams, newUser gocloak.User) (*string, error) { //nolint:unparam // return var to be used in future +func createUser(ctx context.Context, client *gocloak.GoCloak, token *gocloak.JWT, connectParams *KeycloakConnectParams, newUser gocloak.User) error { username := *newUser.Username longUserID, err := client.CreateUser(ctx, token.AccessToken, connectParams.Realm, newUser) if err != nil { @@ -668,17 +668,17 @@ func createUser(ctx context.Context, client *gocloak.GoCloak, token *gocloak.JWT slog.Warn(fmt.Sprintf("user %s already exists", username)) users, err := client.GetUsers(ctx, token.AccessToken, connectParams.Realm, gocloak.GetUsersParams{Username: newUser.Username}) if err != nil { - return nil, err + return err } if len(users) == 1 { longUserID = *users[0].ID } else { err = fmt.Errorf("error, %s user not found", username) - return nil, err + return err } default: slog.Error(fmt.Sprintf("Error creating user %s : %s", username, err)) - return nil, err + return err } } else { slog.Info(fmt.Sprintf("✅ User created: username = %s, user identifier=%s", username, longUserID)) @@ -688,12 +688,12 @@ func createUser(ctx context.Context, client *gocloak.GoCloak, token *gocloak.JWT if newUser.RealmRoles != nil { roles, err := getRealmRolesByList(ctx, connectParams.Realm, client, token, *newUser.RealmRoles) if err != nil { - return nil, err + return err } err = client.AddRealmRoleToUser(ctx, token.AccessToken, connectParams.Realm, longUserID, roles) if err != nil { slog.Error(fmt.Sprintf("Error adding realm roles to user %s : %s", *newUser.RealmRoles, connectParams.Realm)) - return nil, err + return err } } // assign client roles to user @@ -702,21 +702,21 @@ func createUser(ctx context.Context, client *gocloak.GoCloak, token *gocloak.JWT results, err := client.GetClients(ctx, token.AccessToken, connectParams.Realm, gocloak.GetClientsParams{ClientID: &clientID}) if err != nil || len(results) == 0 { slog.Error(fmt.Sprintf("Error getting %s's client: %s", clientID, err)) - return nil, err + return err } idOfClient := results[0].ID clientRoles, err := getClientRolesByList(ctx, connectParams, client, token, *idOfClient, roles) if err != nil { slog.Error(fmt.Sprintf("Error getting client roles: %s", err)) - return nil, err + return err } if err := client.AddClientRolesToUser(ctx, token.AccessToken, connectParams.Realm, *idOfClient, longUserID, clientRoles); err != nil { for _, role := range clientRoles { slog.Warn(fmt.Sprintf("Error adding role %s", *role.Name)) } - return nil, err + return err } for _, role := range clientRoles { slog.Info(fmt.Sprintf("✅ Client Role %s added to user %s", *role.Name, longUserID)) @@ -724,7 +724,7 @@ func createUser(ctx context.Context, client *gocloak.GoCloak, token *gocloak.JWT } } - return &longUserID, nil + return nil } func getRealmRolesByList(ctx context.Context, realmName string, client *gocloak.GoCloak, token *gocloak.JWT, rolesToAdd []string) ([]gocloak.Role, error) { From 45130dfe8ed14acde79ef72df45c337511a24b6a Mon Sep 17 00:00:00 2001 From: jakedoublev Date: Mon, 19 Aug 2024 12:00:57 -0700 Subject: [PATCH 18/19] put back lib/fixtures --- lib/fixtures/keycloak.go | 40 ++++++++++++---------------------------- 1 file changed, 12 insertions(+), 28 deletions(-) diff --git a/lib/fixtures/keycloak.go b/lib/fixtures/keycloak.go index 8b38e1eeaf..57e860e1c7 100644 --- a/lib/fixtures/keycloak.go +++ b/lib/fixtures/keycloak.go @@ -126,7 +126,6 @@ func SetupKeycloak(ctx context.Context, kcConnectParams KeycloakConnectParams) e testingOnlyRoleName := "opentdf-testing-role" opentdfERSClientID := "tdf-entity-resolution" opentdfAuthorizationClientID := "tdf-authorization-svc" - opentdfPublicClientID := "opentdf-public" realmMangementClientName := "realm-management" protocolMappers := []gocloak.ProtocolMapperRepresentation{ @@ -306,21 +305,6 @@ func SetupKeycloak(ctx context.Context, kcConnectParams KeycloakConnectParams) e return err } - // Create OpenTDF Public Client - _, err = createClient(ctx, client, token, &kcConnectParams, gocloak.Client{ - ClientID: gocloak.StringP(opentdfPublicClientID), - Enabled: gocloak.BoolP(true), - Name: gocloak.StringP(opentdfPublicClientID), - PublicClient: gocloak.BoolP(true), - ProtocolMappers: &protocolMappers, - // TODO: take via CLI arguments? - // Note: otdfctl CLI auth code flow runs from localhost:9000) - RedirectURIs: &[]string{"http://localhost:9000/*"}, - }, nil, nil) - if err != nil { - return err - } - // opentdfSdkClientNumericId, err := getIDOfClient(ctx, client, token, &kcConnectParams, &opentdfClientId) // if err != nil { // slog.Error(fmt.Sprintf("Error getting the SDK id: %s", err)) @@ -335,7 +319,7 @@ func SetupKeycloak(ctx context.Context, kcConnectParams KeycloakConnectParams) e Username: gocloak.StringP("sampleuser"), Attributes: &map[string][]string{"superhero_name": {"thor"}, "superhero_group": {"avengers"}}, } - err = createUser(ctx, client, token, &kcConnectParams, user) + _, err = createUser(ctx, client, token, &kcConnectParams, user) if err != nil { panic("Oh no!, failed to create user :(") } @@ -439,7 +423,7 @@ func SetupCustomKeycloak(ctx context.Context, kcParams KeycloakConnectParams, ke // create the users if realmToCreate.Users != nil { for _, customUser := range realmToCreate.Users { - err = createUser(ctx, client, token, &kcConnectParams, customUser) + _, err = createUser(ctx, client, token, &kcConnectParams, customUser) if err != nil { return err } @@ -659,7 +643,7 @@ func createClient(ctx context.Context, client *gocloak.GoCloak, token *gocloak.J return longClientID, nil } -func createUser(ctx context.Context, client *gocloak.GoCloak, token *gocloak.JWT, connectParams *KeycloakConnectParams, newUser gocloak.User) error { +func createUser(ctx context.Context, client *gocloak.GoCloak, token *gocloak.JWT, connectParams *KeycloakConnectParams, newUser gocloak.User) (*string, error) { //nolint:unparam // return var to be used in future username := *newUser.Username longUserID, err := client.CreateUser(ctx, token.AccessToken, connectParams.Realm, newUser) if err != nil { @@ -668,17 +652,17 @@ func createUser(ctx context.Context, client *gocloak.GoCloak, token *gocloak.JWT slog.Warn(fmt.Sprintf("user %s already exists", username)) users, err := client.GetUsers(ctx, token.AccessToken, connectParams.Realm, gocloak.GetUsersParams{Username: newUser.Username}) if err != nil { - return err + return nil, err } if len(users) == 1 { longUserID = *users[0].ID } else { err = fmt.Errorf("error, %s user not found", username) - return err + return nil, err } default: slog.Error(fmt.Sprintf("Error creating user %s : %s", username, err)) - return err + return nil, err } } else { slog.Info(fmt.Sprintf("✅ User created: username = %s, user identifier=%s", username, longUserID)) @@ -688,12 +672,12 @@ func createUser(ctx context.Context, client *gocloak.GoCloak, token *gocloak.JWT if newUser.RealmRoles != nil { roles, err := getRealmRolesByList(ctx, connectParams.Realm, client, token, *newUser.RealmRoles) if err != nil { - return err + return nil, err } err = client.AddRealmRoleToUser(ctx, token.AccessToken, connectParams.Realm, longUserID, roles) if err != nil { slog.Error(fmt.Sprintf("Error adding realm roles to user %s : %s", *newUser.RealmRoles, connectParams.Realm)) - return err + return nil, err } } // assign client roles to user @@ -702,21 +686,21 @@ func createUser(ctx context.Context, client *gocloak.GoCloak, token *gocloak.JWT results, err := client.GetClients(ctx, token.AccessToken, connectParams.Realm, gocloak.GetClientsParams{ClientID: &clientID}) if err != nil || len(results) == 0 { slog.Error(fmt.Sprintf("Error getting %s's client: %s", clientID, err)) - return err + return nil, err } idOfClient := results[0].ID clientRoles, err := getClientRolesByList(ctx, connectParams, client, token, *idOfClient, roles) if err != nil { slog.Error(fmt.Sprintf("Error getting client roles: %s", err)) - return err + return nil, err } if err := client.AddClientRolesToUser(ctx, token.AccessToken, connectParams.Realm, *idOfClient, longUserID, clientRoles); err != nil { for _, role := range clientRoles { slog.Warn(fmt.Sprintf("Error adding role %s", *role.Name)) } - return err + return nil, err } for _, role := range clientRoles { slog.Info(fmt.Sprintf("✅ Client Role %s added to user %s", *role.Name, longUserID)) @@ -724,7 +708,7 @@ func createUser(ctx context.Context, client *gocloak.GoCloak, token *gocloak.JWT } } - return nil + return &longUserID, nil } func getRealmRolesByList(ctx context.Context, realmName string, client *gocloak.GoCloak, token *gocloak.JWT, rolesToAdd []string) ([]gocloak.Role, error) { From b729e001df4e84c927b44676180dfa9579189ce3 Mon Sep 17 00:00:00 2001 From: jakedoublev Date: Mon, 19 Aug 2024 12:04:29 -0700 Subject: [PATCH 19/19] yaml fmt --- opentdf-dev.yaml | 4 ++-- opentdf-example-no-kas.yaml | 4 ++-- opentdf-example.yaml | 4 ++-- opentdf-with-hsm.yaml | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/opentdf-dev.yaml b/opentdf-dev.yaml index 16ae405ae4..f3a9dbfd7b 100644 --- a/opentdf-dev.yaml +++ b/opentdf-dev.yaml @@ -76,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 diff --git a/opentdf-example-no-kas.yaml b/opentdf-example-no-kas.yaml index 52d3950b9a..aee0b09250 100644 --- a/opentdf-example-no-kas.yaml +++ b/opentdf-example-no-kas.yaml @@ -18,10 +18,10 @@ server: issuer: http://localhost:8888/auth/realms/tdf 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 diff --git a/opentdf-example.yaml b/opentdf-example.yaml index 845dab272d..8050abcfaa 100644 --- a/opentdf-example.yaml +++ b/opentdf-example.yaml @@ -67,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' + # "*" 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 diff --git a/opentdf-with-hsm.yaml b/opentdf-with-hsm.yaml index 7ee5951cce..8ac69e9c71 100644 --- a/opentdf-with-hsm.yaml +++ b/opentdf-with-hsm.yaml @@ -73,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' + # "*" 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