Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: authz middleware to accept both slug and uuid for system ns #34

Merged
merged 7 commits into from
Mar 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 1 addition & 5 deletions cmd/serve.go
Original file line number Diff line number Diff line change
Expand Up @@ -189,11 +189,7 @@ func BuildAPIDependencies(

resourcePGRepository := postgres.NewResourceRepository(dbc)
resourceService := resource.NewService(
resourcePGRepository,
resourceBlobRepository,
relationService,
userService,
projectService)
resourcePGRepository, resourceBlobRepository, relationService, userService, projectService, organizationService, groupService)

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

Expand Down
58 changes: 47 additions & 11 deletions core/resource/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@ import (
"strings"

"github.com/goto/shield/core/action"
"github.com/goto/shield/core/group"
"github.com/goto/shield/core/namespace"
"github.com/goto/shield/core/organization"
"github.com/goto/shield/core/project"
"github.com/goto/shield/core/relation"
"github.com/goto/shield/core/user"
"github.com/goto/shield/internal/schema"
"github.com/goto/shield/pkg/uuid"
)

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

type OrganizationService interface {
Get(ctx context.Context, id string) (organization.Organization, error)
}

type GroupService interface {
Get(ctx context.Context, id string) (group.Group, error)
}

type Service struct {
repository Repository
configRepository ConfigRepository
relationService RelationService
userService UserService
projectService ProjectService
repository Repository
configRepository ConfigRepository
relationService RelationService
userService UserService
projectService ProjectService
organizationService OrganizationService
groupService GroupService
}

func NewService(repository Repository, configRepository ConfigRepository, relationService RelationService, userService UserService, projectService ProjectService) *Service {
func NewService(repository Repository, configRepository ConfigRepository, relationService RelationService, userService UserService, projectService ProjectService, organizationService OrganizationService, groupService GroupService) *Service {
return &Service{
repository: repository,
configRepository: configRepository,
relationService: relationService,
userService: userService,
projectService: projectService,
repository: repository,
configRepository: configRepository,
relationService: relationService,
userService: userService,
projectService: projectService,
organizationService: organizationService,
groupService: groupService,
}
}

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

if isSystemNS {
if !uuid.IsValid(res.Name) {
switch res.NamespaceID {
case namespace.DefinitionProject.ID:
project, err := s.projectService.Get(ctx, res.Name)
if err != nil {
return false, err
}
res.Name = project.ID
case namespace.DefinitionOrg.ID:
organization, err := s.organizationService.Get(ctx, res.Name)
if err != nil {
return false, err
}
res.Name = organization.ID
case namespace.DefinitionTeam.ID:
group, err := s.groupService.Get(ctx, res.Name)
if err != nil {
return false, err
}
res.Name = group.ID
}
}
fetchedResource.Idxa = res.Name
} else {
fetchedResource, err = s.repository.GetByNamespace(ctx, res.Name, res.NamespaceID)
Expand Down
1 change: 1 addition & 0 deletions internal/proxy/middleware/authz/authz.go
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,7 @@ func (c *Authz) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
c.notAllowed(rw, err)
return
}
c.log.Info("successfully checked permission", "permission", permission.Name, "result", isAuthorized)
if isAuthorized {
break
}
Expand Down
83 changes: 83 additions & 0 deletions test/e2e_test/smoke/proxy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ type EndToEndProxySmokeTestSuite struct {
suite.Suite
userID string
orgID string
orgSlug string
projID string
projSlug string
groupID string
client shieldv1beta1.ShieldServiceClient
cancelClient func()
Expand Down Expand Up @@ -50,11 +52,13 @@ func (s *EndToEndProxySmokeTestSuite) SetupTest() {
s.Require().NoError(err)
s.Require().Equal(1, len(oRes.GetOrganizations()))
s.orgID = oRes.GetOrganizations()[0].GetId()
s.orgSlug = oRes.GetOrganizations()[0].GetSlug()

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

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

s.Run("permission expression: user not having permission at proj level will not be authenticated by middleware auth", func() {
url := fmt.Sprintf("http://localhost:%d/api/create_firehose_based_on_sink", s.appConfig.Proxy.Services[0].Port)
reqBodyMap := map[string]any{
"organization": s.orgID,
"project": s.projID,
"configs": map[string]any{
"env_vars": map[string]any{
"SINK_TYPE": "bigquery",
},
},
}
reqBodyBytes, err := json.Marshal(reqBodyMap)
s.Require().NoError(err)

req, err := http.NewRequest(http.MethodPost, url, bytes.NewBuffer(reqBodyBytes))
s.Require().NoError(err)

req.Header.Set(testbench.IdentityHeader, "[email protected]")

res, err := http.DefaultClient.Do(req)
s.Require().NoError(err)

defer res.Body.Close()
s.Assert().Equal(401, res.StatusCode)
})

s.Run("permission expression: user not having permission at org level will not be authenticated by middleware auth", func() {
url := fmt.Sprintf("http://localhost:%d/api/create_firehose_based_on_sink", s.appConfig.Proxy.Services[0].Port)
reqBodyMap := map[string]any{
"organization": s.orgID,
"project": s.projID,
"configs": map[string]any{
"env_vars": map[string]any{
"SINK_TYPE": "blob",
},
},
}
reqBodyBytes, err := json.Marshal(reqBodyMap)
s.Require().NoError(err)

req, err := http.NewRequest(http.MethodPost, url, bytes.NewBuffer(reqBodyBytes))
s.Require().NoError(err)

req.Header.Set(testbench.IdentityHeader, "[email protected]")

res, err := http.DefaultClient.Do(req)
s.Require().NoError(err)

defer res.Body.Close()
s.Assert().Equal(401, res.StatusCode)
})

s.Run("permission expression: user not having permission at org level will not be authenticated by middleware auth with org passed as slug", func() {
url := fmt.Sprintf("http://localhost:%d/api/create_firehose_based_on_sink", s.appConfig.Proxy.Services[0].Port)
reqBodyMap := map[string]any{
"organization": s.orgSlug,
"project": s.projSlug,
"configs": map[string]any{
"env_vars": map[string]any{
"SINK_TYPE": "blob",
},
},
}
reqBodyBytes, err := json.Marshal(reqBodyMap)
s.Require().NoError(err)

req, err := http.NewRequest(http.MethodPost, url, bytes.NewBuffer(reqBodyBytes))
s.Require().NoError(err)

req.Header.Set(testbench.IdentityHeader, "[email protected]")

res, err := http.DefaultClient.Do(req)
s.Require().NoError(err)

defer res.Body.Close()
s.Assert().Equal(401, res.StatusCode)
})

s.Run("permission expression: user not having permission at proj level will not be authenticated by middleware auth with proj passed as slug", func() {
url := fmt.Sprintf("http://localhost:%d/api/create_firehose_based_on_sink", s.appConfig.Proxy.Services[0].Port)
reqBodyMap := map[string]any{
"organization": s.orgSlug,
"project": s.projSlug,
"configs": map[string]any{
"env_vars": map[string]any{
"SINK_TYPE": "bigquery",
Expand Down
25 changes: 24 additions & 1 deletion test/e2e_test/testbench/testdata/configs/resources/resource.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -57,4 +57,27 @@ shield/organization:
- firehose_bq_admin
- name: manage_gcs_firehose
roles:
- firehose_gcs_admin
- firehose_gcs_admin

shield/project:
type: system
roles:
- name: sink_editor
principals:
- shield/user
- shield/group
- name: firehose_project_bq_admin
principals:
- shield/user
- shield/group
- name: firehose_project_gcs_admin
principals:
- shield/user
- shield/group
permissions:
- name: manage_bq_firehose
roles:
- firehose_project_bq_admin
- name: manage_gcs_firehose
roles:
- firehose_project_bq_admin
7 changes: 5 additions & 2 deletions test/e2e_test/testbench/testdata/configs/rules/rule.yamltpl
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,9 @@ rules:
organization:
key: organization
type: json_payload
project:
key: project
type: json_payload
sink:
key: configs.env_vars.SINK_TYPE
type: json_payload
Expand All @@ -101,8 +104,8 @@ rules:
operator: ==
value: "blob"
- name: manage_bq_firehose
namespace: shield/organization
attribute: organization
namespace: shield/project
attribute: project
expression:
attribute: sink
operator: ==
Expand Down
Loading