Skip to content

Commit 82e8895

Browse files
pflynn-virtrudmihalcik-virtrustrantalisjrschumacherjakedoublev
authored
feat(kas): authorization decisions (#431)
resolves #392 Authorization service integrate Access PDP into GetDecisions resolves #312 KAS using Authorization Service - Removes unused code from KAS - Adds `client_id` to GetEntitlements endpoint ``` { "entities": [ { "id": "0", "client_id": "opentdf" } ], "scope": { "attribute_value_fqns": [ "https://brightonhealthclinic.org/attr/healthrecordtype/value/basicpatientinfo" ]} } ``` --------- Co-authored-by: Dave Mihalcik <[email protected]> Co-authored-by: Sean Trantalis <[email protected]> Co-authored-by: Ryan Schumacher <[email protected]> Co-authored-by: Jake Van Vorhis <[email protected]> Co-authored-by: Elizabeth Healy <[email protected]> Co-authored-by: Morgan Kleene <[email protected]> Co-authored-by: Tim Tschampel <[email protected]> Co-authored-by: Krish Suchak <[email protected]> Co-authored-by: sujankota <[email protected]>
1 parent 1bb9379 commit 82e8895

File tree

26 files changed

+498
-1342
lines changed

26 files changed

+498
-1342
lines changed

docs/grpc/index.html

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/openapi/authorization/authorization.swagger.json

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

examples/cmd/encrypt.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ func encrypt(cmd *cobra.Command, args []string) error {
4646
defer tdfFile.Close()
4747

4848
tdf, err := client.CreateTDF(tdfFile, strReader,
49-
//sdk.WithDataAttributes("https://example.com/attributes/1", "https://example.com/attributes/2"),
49+
//sdk.WithDataAttributes("https://example.com/attr/attr1/value/value1"),
5050
sdk.WithKasInformation(
5151
sdk.KASInfo{
5252
URL: "http://localhost:8080",

go.mod

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ require (
1616
github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1
1717
github.com/jackc/pgerrcode v0.0.0-20220416144525-469b46aa5efa
1818
github.com/jackc/pgx/v5 v5.5.5
19-
github.com/jarcoal/httpmock v1.3.1
2019
github.com/lestrrat-go/jwx/v2 v2.0.21
2120
github.com/miekg/pkcs11 v1.1.1
2221
github.com/open-policy-agent/opa v0.62.1
@@ -29,7 +28,6 @@ require (
2928
github.com/stretchr/testify v1.9.0
3029
github.com/testcontainers/testcontainers-go v0.28.0
3130
github.com/valyala/fasthttp v1.52.0
32-
github.com/virtru/access-pdp v1.11.0
3331
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225
3432
golang.org/x/oauth2 v0.18.0
3533
google.golang.org/grpc v1.62.1
@@ -141,7 +139,6 @@ require (
141139
go.opentelemetry.io/otel/sdk v1.24.0 // indirect
142140
go.opentelemetry.io/otel/trace v1.24.0 // indirect
143141
go.uber.org/multierr v1.11.0 // indirect
144-
go.uber.org/zap v1.27.0 // indirect
145142
golang.org/x/mod v0.15.0 // indirect
146143
golang.org/x/time v0.5.0 // indirect
147144
golang.org/x/tools v0.18.0 // indirect

go.sum

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -190,8 +190,6 @@ github.com/jackc/pgx/v5 v5.5.5 h1:amBjrZVmksIdNjxGW/IiIMzxMKZFelXbUoPNb+8sjQw=
190190
github.com/jackc/pgx/v5 v5.5.5/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A=
191191
github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk=
192192
github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
193-
github.com/jarcoal/httpmock v1.3.1 h1:iUx3whfZWVf3jT01hQTO/Eo5sAYtB2/rqaUuOtpInww=
194-
github.com/jarcoal/httpmock v1.3.1/go.mod h1:3yb8rc4BI7TCBhFY8ng0gjuLKJNquuDNiPaZjnENuYg=
195193
github.com/joeshaw/multierror v0.0.0-20140124173710-69b34d4ec901 h1:rp+c0RAYOWj8l6qbCUTSiRLG/iKnW3K3/QfPPuSsBt4=
196194
github.com/joeshaw/multierror v0.0.0-20140124173710-69b34d4ec901/go.mod h1:Z86h9688Y0wesXCyonoVr47MasHilkuLMqGhRZ4Hpak=
197195
github.com/jonboulle/clockwork v0.4.0 h1:p4Cf1aMWXnXAUh8lVfewRBx1zaTSYKrKMF2g3ST4RZ4=
@@ -232,8 +230,6 @@ github.com/marcboeker/go-duckdb v1.5.6 h1:5+hLUXRuKlqARcnW4jSsyhCwBRlu4FGjM0UTf2
232230
github.com/marcboeker/go-duckdb v1.5.6/go.mod h1:wm91jO2GNKa6iO9NTcjXIRsW+/ykPoJbQcHSXhdAl28=
233231
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
234232
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
235-
github.com/maxatome/go-testdeep v1.12.0 h1:Ql7Go8Tg0C1D/uMMX59LAoYK7LffeJQ6X2T04nTH68g=
236-
github.com/maxatome/go-testdeep v1.12.0/go.mod h1:lPZc/HAcJMP92l7yI6TRz1aZN5URwUBUAfUNvrclaNM=
237233
github.com/mfridman/interpolate v0.0.2 h1:pnuTK7MQIxxFz1Gr+rjSIx9u7qVjf5VOoM/u6BbAxPY=
238234
github.com/mfridman/interpolate v0.0.2/go.mod h1:p+7uk6oE07mpE/Ik1b8EckO0O4ZXiGAfshKBWLUM9Xg=
239235
github.com/miekg/dns v1.1.57 h1:Jzi7ApEIzwEPLHWRcafCN9LZSBbqQpxjt/wpgvg7wcM=
@@ -363,8 +359,6 @@ github.com/valyala/fasthttp v1.52.0 h1:wqBQpxH71XW0e2g+Og4dzQM8pk34aFYlA1Ga8db7g
363359
github.com/valyala/fasthttp v1.52.0/go.mod h1:hf5C4QnVMkNXMspnsUlfM3WitlgYflyhHYoKol/szxQ=
364360
github.com/vertica/vertica-sql-go v1.3.3 h1:fL+FKEAEy5ONmsvya2WH5T8bhkvY27y/Ik3ReR2T+Qw=
365361
github.com/vertica/vertica-sql-go v1.3.3/go.mod h1:jnn2GFuv+O2Jcjktb7zyc4Utlbu9YVqpHH/lx63+1M4=
366-
github.com/virtru/access-pdp v1.11.0 h1:JmMUo5EExOg99TKNQ6AdwUDLdBwVvSpjxUVVJxJ0RR4=
367-
github.com/virtru/access-pdp v1.11.0/go.mod h1:7OkDvrJX9qtzZ8KYFv7uvbp3IuhJZBqjVaPcH+Irnc0=
368362
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo=
369363
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
370364
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=
@@ -402,12 +396,8 @@ go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y
402396
go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU=
403397
go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I=
404398
go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM=
405-
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
406-
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
407399
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
408400
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
409-
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
410-
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
411401
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
412402
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
413403
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=

internal/entitlements/pdp.go

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,15 @@ func OpaInput(entity *authorization.Entity, sms map[string]*attributes.GetAttrib
1313
inputUnstructured["attribute_mappings"] = sms
1414
// Entity
1515
ea := make(map[string]interface{})
16-
ea["id"] = entity.Id
17-
ea["email_address"] = entity.GetEmailAddress()
16+
ea["id"] = entity.GetId()
17+
switch entity.GetEntityType().(type) {
18+
case *authorization.Entity_ClientId:
19+
ea["client_id"] = entity.GetClientId()
20+
case *authorization.Entity_EmailAddress:
21+
ea["email_address"] = entity.GetEmailAddress()
22+
case *authorization.Entity_Jwt:
23+
ea["jwt"] = entity.GetJwt()
24+
}
1825
inputUnstructured["entity"] = ea
1926

2027
inputUnstructured["idp"] = config

internal/idpplugin/keycloak_plugin.go

Lines changed: 48 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,9 @@ type KeyCloakConnector struct {
3131

3232
func EntityResolution(ctx context.Context,
3333
req *authorization.IdpPluginRequest, config *authorization.IdpConfig) (*authorization.IdpPluginResponse, error) {
34-
slog.Info(fmt.Sprintf("req: %+v", req))
35-
slog.Info(fmt.Sprintf("config: %+v", config))
36-
jsonString, err := json.Marshal(config.Config.AsMap())
34+
// note this only logs when run in test not when running in the OPE engine.
35+
slog.DebugContext(ctx, "EntityResolution", "req", fmt.Sprintf("%+v", req), "config", fmt.Sprintf("%+v", config))
36+
jsonString, err := json.Marshal(config.GetConfig().AsMap())
3737
if err != nil {
3838
slog.Error("Error marshalling keycloak config!", "error", err)
3939
return nil, err
@@ -52,26 +52,55 @@ func EntityResolution(ctx context.Context,
5252
payload := req.GetEntities()
5353

5454
var resolvedEntities []*authorization.IdpEntityRepresentation
55-
slog.Debug("EntityResolution invoked with", "payload", payload)
55+
slog.DebugContext(ctx, "EntityResolution invoked", "payload", payload)
5656

57-
for i, ident := range payload {
58-
slog.Debug("Lookup", "entity", ident.GetEntityType())
57+
for _, ident := range payload {
58+
slog.DebugContext(ctx, "Lookup", "entity", ident.GetEntityType())
5959
var keycloakEntities []*gocloak.User
6060
var getUserParams gocloak.GetUsersParams
61-
6261
exactMatch := true
63-
switch ident.EntityType.(type) {
62+
switch ident.GetEntityType().(type) {
63+
case *authorization.Entity_ClientId:
64+
slog.DebugContext(ctx, "GetClient", "client_id", ident.GetClientId())
65+
clientID := ident.GetClientId()
66+
clients, err := connector.client.GetClients(ctx, connector.token.AccessToken, kcConfig.Realm, gocloak.GetClientsParams{
67+
ClientID: &clientID,
68+
})
69+
if err != nil {
70+
slog.Error(err.Error())
71+
return &authorization.IdpPluginResponse{},
72+
status.Error(codes.Internal, services.ErrGetRetrievalFailed)
73+
}
74+
var jsonEntities []*structpb.Struct
75+
for _, client := range clients {
76+
json, err := typeToGenericJSONMap(client)
77+
if err != nil {
78+
slog.Error("Error serializing entity representation!", "error", err)
79+
return &authorization.IdpPluginResponse{},
80+
status.Error(codes.Internal, services.ErrCreationFailed)
81+
}
82+
var mystruct, struct_err = structpb.NewStruct(json)
83+
if struct_err != nil {
84+
slog.Error("Error making struct!", "error", err)
85+
return &authorization.IdpPluginResponse{},
86+
status.Error(codes.Internal, services.ErrCreationFailed)
87+
}
88+
jsonEntities = append(jsonEntities, mystruct)
89+
}
90+
resolvedEntities = append(
91+
resolvedEntities,
92+
&authorization.IdpEntityRepresentation{
93+
OriginalId: ident.GetId(),
94+
AdditionalProps: jsonEntities,
95+
},
96+
)
97+
return &authorization.IdpPluginResponse{
98+
EntityRepresentations: resolvedEntities,
99+
}, nil
64100
case *authorization.Entity_EmailAddress:
65-
getUserParams = gocloak.GetUsersParams{Email: func() *string { t := payload[i].GetEmailAddress(); return &t }(), Exact: &exactMatch}
101+
getUserParams = gocloak.GetUsersParams{Email: func() *string { t := ident.GetEmailAddress(); return &t }(), Exact: &exactMatch}
66102
case *authorization.Entity_UserName:
67-
getUserParams = gocloak.GetUsersParams{Username: func() *string { t := payload[i].GetUserName(); return &t }(), Exact: &exactMatch}
68-
// case "":
69-
// return &authorization.IdpPluginResponse{},
70-
// status.Error(codes.InvalidArgument, services.ErrNotFound)
71-
default:
72-
typeErr := fmt.Errorf("Unsupported/unknown type for entity %s", ident.String())
73-
return &authorization.IdpPluginResponse{},
74-
status.Error(codes.InvalidArgument, typeErr.Error())
103+
getUserParams = gocloak.GetUsersParams{Username: func() *string { t := ident.GetUserName(); return &t }(), Exact: &exactMatch}
75104
}
76105

77106
users, err := connector.client.GetUsers(ctx, connector.token.AccessToken, kcConfig.Realm, getUserParams)
@@ -93,7 +122,7 @@ func EntityResolution(ctx context.Context,
93122
ctx,
94123
connector.token.AccessToken,
95124
kcConfig.Realm,
96-
gocloak.GetGroupsParams{Search: func() *string { t := payload[i].GetEmailAddress(); return &t }()},
125+
gocloak.GetGroupsParams{Search: func() *string { t := ident.GetEmailAddress(); return &t }()},
97126
)
98127
if groupErr != nil {
99128
slog.Error("Error getting group", "group", groupErr)
@@ -153,7 +182,7 @@ func EntityResolution(ctx context.Context,
153182
OriginalId: ident.GetId(),
154183
AdditionalProps: jsonEntities},
155184
)
156-
slog.Debug("Entities", "resolved", fmt.Sprintf("%+v", resolvedEntities))
185+
slog.DebugContext(ctx, "Entities", "resolved", fmt.Sprintf("%+v", resolvedEntities))
157186
}
158187

159188
return &authorization.IdpPluginResponse{

internal/idpplugin/keycloak_plugin_test.go

Lines changed: 41 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"io"
88
"net/http"
99
"net/http/httptest"
10+
"os"
1011
"strings"
1112
"testing"
1213

@@ -76,13 +77,15 @@ func test_server_resp(t *testing.T, w http.ResponseWriter, r *http.Request, k st
7677
}
7778
}
7879
func test_server(t *testing.T, userSearchQueryAndResp map[string]string, groupSearchQueryAndResp map[string]string,
79-
groupByIdAndResponse map[string]string, groupMemberQueryAndResponse map[string]string) *httptest.Server {
80+
groupByIdAndResponse map[string]string, groupMemberQueryAndResponse map[string]string, clientsSearchQueryAndResp map[string]string) *httptest.Server {
8081
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
8182
if r.URL.Path == "/realms/tdf/protocol/openid-connect/token" {
8283
_, err := io.WriteString(w, token_resp)
8384
if err != nil {
8485
t.Error(err)
8586
}
87+
} else if r.URL.Path == "/admin/realms/tdf/clients" {
88+
test_server_resp(t, w, r, r.URL.RawQuery, clientsSearchQueryAndResp)
8689
} else if r.URL.Path == "/admin/realms/tdf/users" {
8790
test_server_resp(t, w, r, r.URL.RawQuery, userSearchQueryAndResp)
8891
} else if r.URL.Path == "/admin/realms/tdf/groups" && groupSearchQueryAndResp != nil {
@@ -101,12 +104,44 @@ func test_server(t *testing.T, userSearchQueryAndResp map[string]string, groupSe
101104
return server
102105
}
103106

107+
func Test_KCEntityResolutionByClientId(t *testing.T) {
108+
109+
var validBody []*authorization.Entity
110+
validBody = append(validBody, &authorization.Entity{Id: "1234", EntityType: &authorization.Entity_ClientId{ClientId: "opentdf"}})
111+
112+
var ctxb = context.Background()
113+
114+
var req = authorization.IdpPluginRequest{}
115+
req.Entities = validBody
116+
csqr := map[string]string{
117+
"clientId=opentdf": by_email_bob_resp,
118+
}
119+
server := test_server(t, nil, nil, nil, nil, csqr)
120+
defer server.Close()
121+
var kcconfig = test_keycloakConfig(server)
122+
var kcConfigInterface map[string]interface{}
123+
inrec, err := json.Marshal(kcconfig)
124+
assert.Nil(t, err)
125+
126+
require.NoError(t, json.Unmarshal(inrec, &kcConfigInterface))
127+
kcConfigStruct, err := structpb.NewStruct(kcConfigInterface)
128+
var resp, reserr = idpplugin.EntityResolution(ctxb, &req, &authorization.IdpConfig{
129+
Config: kcConfigStruct,
130+
})
131+
132+
assert.Nil(t, reserr)
133+
_ = json.NewEncoder(os.Stdout).Encode(resp)
134+
var entity_representations = resp.GetEntityRepresentations()
135+
assert.NotNil(t, entity_representations)
136+
assert.Equal(t, 1, len(entity_representations))
137+
}
138+
104139
func Test_KCEntityResolutionByEmail(t *testing.T) {
105140

106141
server := test_server(t, map[string]string{
107142
"email=bob%40sample.org&exact=true": by_email_bob_resp,
108143
"email=alice%40sample.org&exact=true": by_email_alice_resp,
109-
}, nil, nil, nil)
144+
}, nil, nil, nil, nil)
110145
defer server.Close()
111146

112147
var validBody []*authorization.Entity
@@ -150,7 +185,7 @@ func Test_KCEntityResolutionByUsername(t *testing.T) {
150185
server := test_server(t, map[string]string{
151186
"exact=true&username=bob.smith": by_username_bob_resp,
152187
"exact=true&username=alice.smith": by_username_alice_resp,
153-
}, nil, nil, nil)
188+
}, nil, nil, nil, nil)
154189
defer server.Close()
155190

156191
// validBody := `{"entity_identifiers": [{"type": "username","identifier": "bob.smith"}]}`
@@ -200,7 +235,8 @@ func Test_KCEntityResolutionByGroupEmail(t *testing.T) {
200235
"group1-uuid": group_resp,
201236
}, map[string]string{
202237
"group1-uuid": group_submember_resp,
203-
})
238+
},
239+
nil)
204240
defer server.Close()
205241

206242
var validBody []*authorization.Entity
@@ -246,7 +282,7 @@ func Test_KCEntityResolutionNotFoundError(t *testing.T) {
246282
"group1-uuid": group_resp,
247283
}, map[string]string{
248284
"group1-uuid": group_submember_resp,
249-
})
285+
}, nil)
250286
defer server.Close()
251287

252288
var validBody []*authorization.Entity

internal/server/server.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ type Config struct {
4848
HSM security.HSMConfig `yaml:"hsm"`
4949
TLS TLSConfig `yaml:"tls"`
5050
WellKnownConfigRegister func(namespace string, config any) error
51-
Port int `yaml:"port" default:"9000"`
51+
Port int `yaml:"port" default:"8080"`
5252
Host string `yaml:"host,omitempty"`
5353
}
5454

opentdf-example-no-kas.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ server:
4343
auth:
4444
enabled: false
4545
audience: "http://localhost:8080"
46-
issuer: http://localhost:8888/auth/realms/opentdf
46+
issuer: http://localhost:8888/auth/realms/tdf
4747
clients:
4848
- "opentdf"
4949
grpc:

0 commit comments

Comments
 (0)