Skip to content

Commit

Permalink
feat: enable resource identifier composing from multiple variables (#84)
Browse files Browse the repository at this point in the history
  • Loading branch information
FemiNoviaLina authored Jul 9, 2024
1 parent ea97a84 commit 1ba6c1e
Show file tree
Hide file tree
Showing 15 changed files with 300 additions and 80 deletions.
30 changes: 30 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -58,4 +58,34 @@ jobs:
env:
POSTGRES_PASSWORD: postgres
run: make e2e-smoke-test
e2e-regression-test:
runs-on: ubuntu-latest
services:
echo-server:
image: ealen/echo-server
ports:
- "4000:80"
spicedb:
image: quay.io/authzed/spicedb:v1.0.0
ports:
- "8080:8080"
- "50051:50051"
- "50053:50053"
steps:
- name: Checkout
uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@v2
with:
go-version: '1.21'
- name: install dependencies
run: go mod tidy
- name: install spicedb binary
uses: authzed/action-spicedb@v1
- name: run regression tests
env:
POSTGRES_PASSWORD: postgres
run: make e2e-regression-test

2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,8 @@ require (
github.com/tidwall/pretty v1.2.1 // indirect
github.com/tklauser/go-sysconf v0.3.12 // indirect
github.com/tklauser/numcpus v0.6.1 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasttemplate v1.2.2
github.com/yuin/goldmark v1.5.3 // indirect
github.com/yuin/goldmark-emoji v1.0.1 // indirect
github.com/yusufpapurcu/wmi v1.2.3 // indirect
Expand Down
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -1956,6 +1956,10 @@ github.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5/go.mod h1:70zkFmudgCuE/
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
github.com/vishvananda/netlink v0.0.0-20181108222139-023a6dafdcdf/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk=
github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE=
github.com/vishvananda/netlink v1.1.1-0.20201029203352-d40f9887b852/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho=
Expand Down
40 changes: 40 additions & 0 deletions internal/proxy/attribute/attribute.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package attribute

import (
"strings"

"github.com/valyala/fasttemplate"
)

const (
TypeJSONPayload AttributeType = "json_payload"
TypeGRPCPayload AttributeType = "grpc_payload"
TypeQuery AttributeType = "query"
TypeHeader AttributeType = "header"
TypePathParam AttributeType = "path_param"
TypeConstant AttributeType = "constant"
TypeComposite AttributeType = "composite"

SourceRequest AttributeType = "request"
SourceResponse AttributeType = "response"
)

type AttributeType string

type Attribute struct {
Key string `yaml:"key" mapstructure:"key"`
Type AttributeType `yaml:"type" mapstructure:"type"`
Index string `yaml:"index" mapstructure:"index"` // proto index
Path string `yaml:"path" mapstructure:"path"`
Params []string `yaml:"params" mapstructure:"params"`
Source string `yaml:"source" mapstructure:"source"`
Value string `yaml:"value" mapstructure:"value"`
}

func Compose(attribute string, attrs map[string]interface{}) string {
if strings.Contains(attribute, "${") {
template := fasttemplate.New(attribute, "${", "}")
return template.ExecuteString(attrs)
}
return attribute
}
37 changes: 24 additions & 13 deletions internal/proxy/hook/authz/authz.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"github.com/goto/shield/core/relation"
"github.com/goto/shield/core/resource"
"github.com/goto/shield/core/user"
proxyattr "github.com/goto/shield/internal/proxy/attribute"
"github.com/goto/shield/internal/proxy/hook"
"github.com/goto/shield/internal/proxy/middleware"
"github.com/goto/shield/pkg/body_extractor"
Expand Down Expand Up @@ -92,9 +93,9 @@ type Relation struct {
}

type Config struct {
Action string `yaml:"action" mapstructure:"action"`
Attributes map[string]hook.Attribute `yaml:"attributes" mapstructure:"attributes"`
Relations []Relation `yaml:"relations" mapstructure:"relations"`
Action string `yaml:"action" mapstructure:"action"`
Attributes map[string]proxyattr.Attribute `yaml:"attributes" mapstructure:"attributes"`
Relations []Relation `yaml:"relations" mapstructure:"relations"`
}

func (a Authz) Info() hook.Info {
Expand Down Expand Up @@ -151,17 +152,17 @@ func (a Authz) ServeHook(res *http.Response, err error) (*http.Response, error)
for id, attr := range config.Attributes {
bdy, _ := middleware.ExtractRequestBody(res.Request)
bodySource := &res.Body
if attr.Source == string(hook.SourceRequest) {
if attr.Source == string(proxyattr.SourceRequest) {
bodySource = &bdy
}

headerSource := &res.Header
if attr.Source == string(hook.SourceRequest) {
if attr.Source == string(proxyattr.SourceRequest) {
headerSource = &res.Request.Header
}

switch attr.Type {
case hook.AttributeTypeGRPCPayload:
case proxyattr.TypeGRPCPayload:
if !strings.HasPrefix(res.Header.Get("Content-Type"), "application/grpc") {
a.log.Error("middleware: not a grpc request", "attr", attr)
return a.escape.ServeHook(res, fmt.Errorf("invalid header for http request: %s", res.Header.Get("Content-Type")))
Expand All @@ -175,7 +176,7 @@ func (a Authz) ServeHook(res *http.Response, err error) (*http.Response, error)
attributes[id] = payloadField

a.log.Info("middleware: extracted", "field", payloadField, "attr", attr)
case hook.AttributeTypeJSONPayload:
case proxyattr.TypeJSONPayload:
if attr.Key == "" {
a.log.Error("middleware: payload key field empty")
return a.escape.ServeHook(res, fmt.Errorf("payload key field empty"))
Expand All @@ -189,7 +190,7 @@ func (a Authz) ServeHook(res *http.Response, err error) (*http.Response, error)
attributes[id] = payloadField

a.log.Info("middleware: extracted", "field", payloadField, "attr", attr)
case hook.AttributeTypeHeader:
case proxyattr.TypeHeader:
if attr.Key == "" {
a.log.Error("middleware: header key field empty")
return a.escape.ServeHook(res, fmt.Errorf("failed to parse json payload"))
Expand All @@ -203,7 +204,7 @@ func (a Authz) ServeHook(res *http.Response, err error) (*http.Response, error)
attributes[id] = headerAttr
a.log.Info("middleware: extracted", "field", headerAttr, "attr", attr)

case hook.AttributeTypeQuery:
case proxyattr.TypeQuery:
if attr.Key == "" {
a.log.Error("middleware: query key field empty")
return a.escape.ServeHook(res, fmt.Errorf("failed to parse json payload"))
Expand All @@ -217,14 +218,14 @@ func (a Authz) ServeHook(res *http.Response, err error) (*http.Response, error)
attributes[id] = queryAttr
a.log.Info("middleware: extracted", "field", queryAttr, "attr", attr)

case hook.AttributeTypeConstant:
case proxyattr.TypeConstant, proxyattr.TypeComposite:
if attr.Value == "" {
a.log.Error("middleware: constant value empty")
a.log.Error("middleware:", string(attr.Type), "value empty")
return a.escape.ServeHook(res, fmt.Errorf("failed to parse json payload"))
}

attributes[id] = attr.Value
a.log.Info("middleware: extracted", "constant_key", res, "attr", attributes[id])
a.log.Info("middleware: extracted", "key", res, "attr", attributes[id])

default:
a.log.Error("middleware: unknown attribute type", "attr", attr)
Expand Down Expand Up @@ -347,9 +348,11 @@ func (a Authz) createResources(permissionAttributes map[string]interface{}) ([]r
return nil, fmt.Errorf("namespace, resource type, projects, resource, and team are required")
}

resourcesName := composeResourcesName(resourceList, permissionAttributes)

// TODO(krtkvrm): needs revision
for _, project := range projects {
for _, res := range resourceList {
for _, res := range resourcesName {
resources = append(resources, resource.Resource{
Name: res,
ProjectID: project,
Expand Down Expand Up @@ -389,3 +392,11 @@ func getAttributesValues(attributes interface{}) ([]string, error) {
}
return values, nil
}

func composeResourcesName(resourceList []string, permissionAttributes map[string]interface{}) []string {
var resourcesName []string
for _, res := range resourceList {
resourcesName = append(resourcesName, proxyattr.Compose(res, permissionAttributes))
}
return resourcesName
}
11 changes: 6 additions & 5 deletions internal/proxy/hook/authz/authz_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
"github.com/goto/shield/core/relation"
"github.com/goto/shield/core/resource"
"github.com/goto/shield/core/rule"
"github.com/goto/shield/internal/proxy/attribute"
"github.com/goto/shield/internal/proxy/hook"
"github.com/goto/shield/internal/proxy/hook/authz/mocks"
shieldlogger "github.com/goto/shield/pkg/logger"
Expand Down Expand Up @@ -275,7 +276,7 @@ func TestServeHook(t *testing.T) {
rule.HookSpec{
Name: "authz",
Config: map[string]interface{}{
"attributes": map[string]hook.Attribute{
"attributes": map[string]attribute.Attribute{
"project": {
Type: "constant",
Value: testPermissionAttributesMap["project"].(string),
Expand Down Expand Up @@ -346,7 +347,7 @@ func TestServeHook(t *testing.T) {
rule.HookSpec{
Name: "authz",
Config: map[string]interface{}{
"attributes": map[string]hook.Attribute{
"attributes": map[string]attribute.Attribute{
"project": {
Type: "constant",
Value: testPermissionAttributesMap["project"].(string),
Expand Down Expand Up @@ -442,7 +443,7 @@ func TestServeHook(t *testing.T) {
rule.HookSpec{
Name: "authz",
Config: map[string]interface{}{
"attributes": map[string]hook.Attribute{
"attributes": map[string]attribute.Attribute{
"project": {
Type: "constant",
Value: testPermissionAttributesMap["project"].(string),
Expand Down Expand Up @@ -541,7 +542,7 @@ func TestServeHook(t *testing.T) {
rule.HookSpec{
Name: "authz",
Config: map[string]interface{}{
"attributes": map[string]hook.Attribute{
"attributes": map[string]attribute.Attribute{
"project": {
Type: "constant",
Value: testPermissionAttributesMap["project"].(string),
Expand Down Expand Up @@ -641,7 +642,7 @@ func TestServeHook(t *testing.T) {
rule.HookSpec{
Name: "authz",
Config: map[string]interface{}{
"attributes": map[string]hook.Attribute{
"attributes": map[string]attribute.Attribute{
"project": {
Type: "constant",
Value: testPermissionAttributesMap["project"].(string),
Expand Down
21 changes: 0 additions & 21 deletions internal/proxy/hook/hook.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,27 +17,6 @@ type Info struct {
Description string
}

const (
AttributeTypeJSONPayload AttributeType = "json_payload"
AttributeTypeGRPCPayload AttributeType = "grpc_payload"
AttributeTypeQuery AttributeType = "query"
AttributeTypeHeader AttributeType = "header"
AttributeTypeConstant AttributeType = "constant"

SourceRequest AttributeType = "request"
SourceResponse AttributeType = "response"
)

type AttributeType string

type Attribute struct {
Key string `yaml:"key" mapstructure:"key"`
Type AttributeType `yaml:"type" mapstructure:"type"`
Index string `yaml:"index" mapstructure:"index"` // proto index
Source string `yaml:"source" mapstructure:"source"`
Value string `yaml:"value" mapstructure:"value"`
}

func ExtractHook(r *http.Request, name string) (rule.HookSpec, bool) {
rl, ok := ExtractRule(r)
if !ok {
Expand Down
21 changes: 0 additions & 21 deletions internal/proxy/middleware/attribute.go

This file was deleted.

13 changes: 7 additions & 6 deletions internal/proxy/middleware/attributes/attributes.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (

"github.com/goto/salt/log"
"github.com/goto/shield/core/project"
"github.com/goto/shield/internal/proxy/attribute"
"github.com/goto/shield/internal/proxy/middleware"
"github.com/goto/shield/pkg/body_extractor"
)
Expand All @@ -24,7 +25,7 @@ type Attributes struct {
}

type Config struct {
Attributes map[string]middleware.Attribute `yaml:"attributes" mapstructure:"attributes"`
Attributes map[string]attribute.Attribute `yaml:"attributes" mapstructure:"attributes"`
}

type ProjectService interface {
Expand Down Expand Up @@ -86,7 +87,7 @@ func (a *Attributes) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
_ = res

switch attr.Type {
case middleware.AttributeTypeGRPCPayload:
case attribute.TypeGRPCPayload:
// check if grpc request
if !strings.HasPrefix(req.Header.Get("Content-Type"), "application/grpc") {
a.log.Error("middleware: not a grpc request", "attr", attr)
Expand All @@ -105,7 +106,7 @@ func (a *Attributes) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
requestAttributes[res] = payloadField
a.log.Info("middleware: extracted", "field", payloadField, "attr", attr)

case middleware.AttributeTypeJSONPayload:
case attribute.TypeJSONPayload:
if attr.Key == "" {
a.log.Error("middleware: payload key field empty")
a.notAllowed(rw)
Expand All @@ -121,7 +122,7 @@ func (a *Attributes) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
requestAttributes[res] = payloadField
a.log.Info("middleware: extracted", "field", payloadField, "attr", attr)

case middleware.AttributeTypeHeader:
case attribute.TypeHeader:
if attr.Key == "" {
a.log.Error("middleware: header key field empty")
a.notAllowed(rw)
Expand All @@ -137,7 +138,7 @@ func (a *Attributes) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
requestAttributes[res] = headerAttr
a.log.Info("middleware: extracted", "field", headerAttr, "attr", attr)

case middleware.AttributeTypeQuery:
case attribute.TypeQuery:
if attr.Key == "" {
a.log.Error("middleware: query key field empty")
a.notAllowed(rw)
Expand All @@ -153,7 +154,7 @@ func (a *Attributes) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
requestAttributes[res] = queryAttr
a.log.Info("middleware: extracted", "field", queryAttr, "attr", attr)

case middleware.AttributeTypeConstant:
case attribute.TypeConstant:
if attr.Value == "" {
a.log.Error("middleware: constant value empty")
a.notAllowed(rw)
Expand Down
Loading

0 comments on commit 1ba6c1e

Please sign in to comment.