Skip to content

Commit 713e2e8

Browse files
authored
feat: authz middleware to accept both slug and uuid for system ns (#34)
* feat: AuthCheck middleware to accept both slug and uuid for system ns * fix: add services * test: add test with org as slug * test: remove tests * test: add tests * test: add tests * chore: coverage
1 parent 1c41dd2 commit 713e2e8

File tree

6 files changed

+161
-19
lines changed

6 files changed

+161
-19
lines changed

cmd/serve.go

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -189,11 +189,7 @@ func BuildAPIDependencies(
189189

190190
resourcePGRepository := postgres.NewResourceRepository(dbc)
191191
resourceService := resource.NewService(
192-
resourcePGRepository,
193-
resourceBlobRepository,
194-
relationService,
195-
userService,
196-
projectService)
192+
resourcePGRepository, resourceBlobRepository, relationService, userService, projectService, organizationService, groupService)
197193

198194
relationAdapter := adapter.NewRelation(groupService, userService, relationService)
199195

core/resource/service.go

Lines changed: 47 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,14 @@ import (
55
"strings"
66

77
"github.com/goto/shield/core/action"
8+
"github.com/goto/shield/core/group"
89
"github.com/goto/shield/core/namespace"
910
"github.com/goto/shield/core/organization"
1011
"github.com/goto/shield/core/project"
1112
"github.com/goto/shield/core/relation"
1213
"github.com/goto/shield/core/user"
1314
"github.com/goto/shield/internal/schema"
15+
"github.com/goto/shield/pkg/uuid"
1416
)
1517

1618
type RelationService interface {
@@ -28,21 +30,33 @@ type ProjectService interface {
2830
Get(ctx context.Context, id string) (project.Project, error)
2931
}
3032

33+
type OrganizationService interface {
34+
Get(ctx context.Context, id string) (organization.Organization, error)
35+
}
36+
37+
type GroupService interface {
38+
Get(ctx context.Context, id string) (group.Group, error)
39+
}
40+
3141
type Service struct {
32-
repository Repository
33-
configRepository ConfigRepository
34-
relationService RelationService
35-
userService UserService
36-
projectService ProjectService
42+
repository Repository
43+
configRepository ConfigRepository
44+
relationService RelationService
45+
userService UserService
46+
projectService ProjectService
47+
organizationService OrganizationService
48+
groupService GroupService
3749
}
3850

39-
func NewService(repository Repository, configRepository ConfigRepository, relationService RelationService, userService UserService, projectService ProjectService) *Service {
51+
func NewService(repository Repository, configRepository ConfigRepository, relationService RelationService, userService UserService, projectService ProjectService, organizationService OrganizationService, groupService GroupService) *Service {
4052
return &Service{
41-
repository: repository,
42-
configRepository: configRepository,
43-
relationService: relationService,
44-
userService: userService,
45-
projectService: projectService,
53+
repository: repository,
54+
configRepository: configRepository,
55+
relationService: relationService,
56+
userService: userService,
57+
projectService: projectService,
58+
organizationService: organizationService,
59+
groupService: groupService,
4660
}
4761
}
4862

@@ -158,6 +172,28 @@ func (s Service) CheckAuthz(ctx context.Context, res Resource, act action.Action
158172
fetchedResource := res
159173

160174
if isSystemNS {
175+
if !uuid.IsValid(res.Name) {
176+
switch res.NamespaceID {
177+
case namespace.DefinitionProject.ID:
178+
project, err := s.projectService.Get(ctx, res.Name)
179+
if err != nil {
180+
return false, err
181+
}
182+
res.Name = project.ID
183+
case namespace.DefinitionOrg.ID:
184+
organization, err := s.organizationService.Get(ctx, res.Name)
185+
if err != nil {
186+
return false, err
187+
}
188+
res.Name = organization.ID
189+
case namespace.DefinitionTeam.ID:
190+
group, err := s.groupService.Get(ctx, res.Name)
191+
if err != nil {
192+
return false, err
193+
}
194+
res.Name = group.ID
195+
}
196+
}
161197
fetchedResource.Idxa = res.Name
162198
} else {
163199
fetchedResource, err = s.repository.GetByNamespace(ctx, res.Name, res.NamespaceID)

internal/proxy/middleware/authz/authz.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,7 @@ func (c *Authz) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
257257
c.notAllowed(rw, err)
258258
return
259259
}
260+
c.log.Info("successfully checked permission", "permission", permission.Name, "result", isAuthorized)
260261
if isAuthorized {
261262
break
262263
}

test/e2e_test/smoke/proxy_test.go

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@ type EndToEndProxySmokeTestSuite struct {
2020
suite.Suite
2121
userID string
2222
orgID string
23+
orgSlug string
2324
projID string
25+
projSlug string
2426
groupID string
2527
client shieldv1beta1.ShieldServiceClient
2628
cancelClient func()
@@ -50,11 +52,13 @@ func (s *EndToEndProxySmokeTestSuite) SetupTest() {
5052
s.Require().NoError(err)
5153
s.Require().Equal(1, len(oRes.GetOrganizations()))
5254
s.orgID = oRes.GetOrganizations()[0].GetId()
55+
s.orgSlug = oRes.GetOrganizations()[0].GetSlug()
5356

5457
pRes, err := s.client.ListProjects(ctx, &shieldv1beta1.ListProjectsRequest{})
5558
s.Require().NoError(err)
5659
s.Require().Equal(1, len(pRes.GetProjects()))
5760
s.projID = pRes.GetProjects()[0].GetId()
61+
s.projSlug = pRes.GetProjects()[0].GetSlug()
5862

5963
gRes, err := s.client.ListGroups(ctx, &shieldv1beta1.ListGroupsRequest{})
6064
s.Require().NoError(err)
@@ -159,10 +163,89 @@ func (s *EndToEndProxySmokeTestSuite) TestProxyToEchoServer() {
159163
s.Assert().Equal(401, res.StatusCode)
160164
})
161165

166+
s.Run("permission expression: user not having permission at proj level will not be authenticated by middleware auth", func() {
167+
url := fmt.Sprintf("http://localhost:%d/api/create_firehose_based_on_sink", s.appConfig.Proxy.Services[0].Port)
168+
reqBodyMap := map[string]any{
169+
"organization": s.orgID,
170+
"project": s.projID,
171+
"configs": map[string]any{
172+
"env_vars": map[string]any{
173+
"SINK_TYPE": "bigquery",
174+
},
175+
},
176+
}
177+
reqBodyBytes, err := json.Marshal(reqBodyMap)
178+
s.Require().NoError(err)
179+
180+
req, err := http.NewRequest(http.MethodPost, url, bytes.NewBuffer(reqBodyBytes))
181+
s.Require().NoError(err)
182+
183+
req.Header.Set(testbench.IdentityHeader, "[email protected]")
184+
185+
res, err := http.DefaultClient.Do(req)
186+
s.Require().NoError(err)
187+
188+
defer res.Body.Close()
189+
s.Assert().Equal(401, res.StatusCode)
190+
})
191+
162192
s.Run("permission expression: user not having permission at org level will not be authenticated by middleware auth", func() {
163193
url := fmt.Sprintf("http://localhost:%d/api/create_firehose_based_on_sink", s.appConfig.Proxy.Services[0].Port)
164194
reqBodyMap := map[string]any{
165195
"organization": s.orgID,
196+
"project": s.projID,
197+
"configs": map[string]any{
198+
"env_vars": map[string]any{
199+
"SINK_TYPE": "blob",
200+
},
201+
},
202+
}
203+
reqBodyBytes, err := json.Marshal(reqBodyMap)
204+
s.Require().NoError(err)
205+
206+
req, err := http.NewRequest(http.MethodPost, url, bytes.NewBuffer(reqBodyBytes))
207+
s.Require().NoError(err)
208+
209+
req.Header.Set(testbench.IdentityHeader, "[email protected]")
210+
211+
res, err := http.DefaultClient.Do(req)
212+
s.Require().NoError(err)
213+
214+
defer res.Body.Close()
215+
s.Assert().Equal(401, res.StatusCode)
216+
})
217+
218+
s.Run("permission expression: user not having permission at org level will not be authenticated by middleware auth with org passed as slug", func() {
219+
url := fmt.Sprintf("http://localhost:%d/api/create_firehose_based_on_sink", s.appConfig.Proxy.Services[0].Port)
220+
reqBodyMap := map[string]any{
221+
"organization": s.orgSlug,
222+
"project": s.projSlug,
223+
"configs": map[string]any{
224+
"env_vars": map[string]any{
225+
"SINK_TYPE": "blob",
226+
},
227+
},
228+
}
229+
reqBodyBytes, err := json.Marshal(reqBodyMap)
230+
s.Require().NoError(err)
231+
232+
req, err := http.NewRequest(http.MethodPost, url, bytes.NewBuffer(reqBodyBytes))
233+
s.Require().NoError(err)
234+
235+
req.Header.Set(testbench.IdentityHeader, "[email protected]")
236+
237+
res, err := http.DefaultClient.Do(req)
238+
s.Require().NoError(err)
239+
240+
defer res.Body.Close()
241+
s.Assert().Equal(401, res.StatusCode)
242+
})
243+
244+
s.Run("permission expression: user not having permission at proj level will not be authenticated by middleware auth with proj passed as slug", func() {
245+
url := fmt.Sprintf("http://localhost:%d/api/create_firehose_based_on_sink", s.appConfig.Proxy.Services[0].Port)
246+
reqBodyMap := map[string]any{
247+
"organization": s.orgSlug,
248+
"project": s.projSlug,
166249
"configs": map[string]any{
167250
"env_vars": map[string]any{
168251
"SINK_TYPE": "bigquery",

test/e2e_test/testbench/testdata/configs/resources/resource.yaml

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,4 +57,27 @@ shield/organization:
5757
- firehose_bq_admin
5858
- name: manage_gcs_firehose
5959
roles:
60-
- firehose_gcs_admin
60+
- firehose_gcs_admin
61+
62+
shield/project:
63+
type: system
64+
roles:
65+
- name: sink_editor
66+
principals:
67+
- shield/user
68+
- shield/group
69+
- name: firehose_project_bq_admin
70+
principals:
71+
- shield/user
72+
- shield/group
73+
- name: firehose_project_gcs_admin
74+
principals:
75+
- shield/user
76+
- shield/group
77+
permissions:
78+
- name: manage_bq_firehose
79+
roles:
80+
- firehose_project_bq_admin
81+
- name: manage_gcs_firehose
82+
roles:
83+
- firehose_project_bq_admin

test/e2e_test/testbench/testdata/configs/rules/rule.yamltpl

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,9 @@ rules:
8989
organization:
9090
key: organization
9191
type: json_payload
92+
project:
93+
key: project
94+
type: json_payload
9295
sink:
9396
key: configs.env_vars.SINK_TYPE
9497
type: json_payload
@@ -101,8 +104,8 @@ rules:
101104
operator: ==
102105
value: "blob"
103106
- name: manage_bq_firehose
104-
namespace: shield/organization
105-
attribute: organization
107+
namespace: shield/project
108+
attribute: project
106109
expression:
107110
attribute: sink
108111
operator: ==

0 commit comments

Comments
 (0)