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

Add echo middleware #15

Closed
wants to merge 34 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
399c0bd
Add license
Joffref Aug 24, 2022
e9c371c
Create dependency-review.yml
Joffref Aug 24, 2022
2313715
Create codeql.yml
Joffref Aug 24, 2022
a659379
Create go-test.yml
Joffref Aug 24, 2022
b2b28d4
Adding push trigger
Joffref Aug 24, 2022
6eed725
Remove unused test, add documentation
Joffref Aug 24, 2022
c805938
Fix test
Joffref Aug 24, 2022
0930f1d
Refactoring package
Joffref Aug 25, 2022
07c9242
fix go mod issue
Joffref Aug 25, 2022
3184a7f
fix inputmethod neeeded by default
Joffref Aug 25, 2022
8b6d1ca
remove unnecessary test
Joffref Aug 25, 2022
f33b061
Resolve flaky test
Joffref Aug 25, 2022
6d6245a
Fix gin handler
Joffref Aug 25, 2022
58e3f57
Enhance gin middleware
Joffref Aug 25, 2022
03be059
Issues fix
Joffref Aug 25, 2022
a907cf1
Add dependabot action (#1)
Joffref Aug 25, 2022
1ba853e
V1.0.1 (#2)
Joffref Aug 26, 2022
7cc0872
Bump github.com/open-policy-agent/opa from 0.43.0 to 0.43.1 (#3)
dependabot[bot] Sep 16, 2022
9a4d376
simplify conditions (#6)
marat-42 Feb 23, 2023
031d93e
Bump golang.org/x/text from 0.3.7 to 0.3.8 (#5)
dependabot[bot] Feb 23, 2023
e5f3f07
Bump golang.org/x/net from 0.0.0-20220225172249-27dd8689420f to 0.7.0…
dependabot[bot] Feb 23, 2023
8aebef2
Bump github.com/gin-gonic/gin from 1.8.1 to 1.9.1 (#9)
dependabot[bot] Jun 3, 2023
fb2e70d
Bump github.com/gofiber/fiber/v2 from 2.36.0 to 2.43.0 (#10)
dependabot[bot] Aug 16, 2023
3e442f4
Bump golang.org/x/net from 0.10.0 to 0.17.0 (#12)
dependabot[bot] Oct 14, 2023
f9809d5
Bump github.com/gofiber/fiber/v2 from 2.43.0 to 2.49.2 (#11)
dependabot[bot] Oct 14, 2023
62f9d33
Added echo middleware
Feb 16, 2024
caac7a6
Added echo middleware
nerdyslacker Feb 16, 2024
be675d2
Updated documentation
Feb 16, 2024
b532346
Updated documentation
nerdyslacker Feb 16, 2024
94aa104
update go mod
Feb 16, 2024
41fbc0c
update go mod
nerdyslacker Feb 16, 2024
db04927
Added tests for echo middleware
nerdyslacker Feb 18, 2024
44f4910
Added tests for echo middleware
nerdyslacker Feb 18, 2024
fdb4c1b
Merge branch 'master' of https://github.com/nerdyslacker/opa-middleware
nerdyslacker Feb 18, 2024
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
44 changes: 43 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Open Policy Agent Middleware

This middleware integrates Open Policy Agent (OPA) to your http/gin/fiber app.
This middleware integrates Open Policy Agent (OPA) to your http/gin/fiber/echo app.
You can use it to enforce policies on endpoints.
You can use OPA as local policy engine, or as a remote policy engine.

Expand Down Expand Up @@ -200,4 +200,46 @@ func main() {
})
app.Listen(":8080")
}
```

## Usage with Echo
```go
package main

import (
"github.com/Joffref/opa-middleware"
"github.com/Joffref/opa-middleware/config"
"github.com/labstack/echo/v4"
)

func main() {
e := echo.New()
middleware, err := opamiddleware.NewEchoMiddleware(
&config.Config{
URL: "http://localhost:8181/",
Query: "data.policy.allow",
ExceptedResult: true,
DeniedStatusCode: 403,
DeniedMessage: "Forbidden",
},
func(c echo.Context) (map[string]interface{}, error) {
return map[string]interface{}{
"path": c.Request().URL.Path,
"method": c.Request().Method,
}, nil
},
)
if err != nil {
return
}
e.Use(middleware.Use())
e.GET("/ping", func(c echo.Context) error {
err := c.JSON(200, "pong")
if err != nil {
return err
}
return nil
})
e.Start(":8080")
}
```
76 changes: 76 additions & 0 deletions echo_middleware.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package opamiddleware

import (
"errors"
"github.com/Joffref/opa-middleware/config"
"github.com/Joffref/opa-middleware/internal"
"github.com/labstack/echo/v4"
"net/http"
)

type EchoInputCreationMethod func(c echo.Context) (map[string]interface{}, error)

type EchoMiddleware struct {
Config *config.Config
InputCreationMethod EchoInputCreationMethod `json:"binding_method,omitempty"`
}

func NewEchoMiddleware(cfg *config.Config, input EchoInputCreationMethod) (*EchoMiddleware, error) {
err := cfg.Validate()
if err != nil {
return nil, err
}
if input == nil {
if cfg.InputCreationMethod == nil {
return nil, errors.New("[opa-middleware-echo] InputCreationMethod must be provided")
}
input = func(c echo.Context) (map[string]interface{}, error) {
bind, err := cfg.InputCreationMethod(c.Request())
if err != nil {
return nil, err
}
return bind, nil
}
}
return &EchoMiddleware{
Config: cfg,
InputCreationMethod: input,
}, nil
}

func (e *EchoMiddleware) Use() echo.MiddlewareFunc {
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
if e.Config.Debug {
e.Config.Logger.Printf("[opa-middleware-echo] Request received")
}
result, err := e.query(c)
if err != nil {
if e.Config.Debug {
e.Config.Logger.Printf("[opa-middleware-echo] Error: %s", err.Error())
}
return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": err.Error()})
}
if e.Config.Debug {
e.Config.Logger.Printf("[opa-middleware-echo] Result: %t", result)
}
if result != e.Config.ExceptedResult {
return c.JSON(e.Config.DeniedStatusCode, map[string]interface{}{"error": e.Config.DeniedMessage})
}
return next(c)
}
}
}

func (e *EchoMiddleware) query(c echo.Context) (bool, error) {
bind, err := e.InputCreationMethod(c)
if err != nil {
return !e.Config.ExceptedResult, err
}
if e.Config.URL != "" {
input := make(map[string]interface{})
input["input"] = bind
return internal.QueryURL(c.Request(), e.Config, input)
}
return internal.QueryPolicy(c.Request(), e.Config, bind)
}
180 changes: 180 additions & 0 deletions echo_middleware_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
package opamiddleware

import (
"github.com/Joffref/opa-middleware/config"
"github.com/labstack/echo/v4"
"net/http"
"net/http/httptest"
"net/url"
"testing"
)

var Test_Policy = `
package policy

default allow = false

allow {
input.path = "/api/v1/users"
input.method = "GET"
}`

func TestEchoMiddleware_Query(t *testing.T) {
type fields struct {
Config *config.Config
InputCreationMethod EchoInputCreationMethod
}
type args struct {
req *http.Request
}
tests := []struct {
name string
fields fields
args args
want bool
wantErr bool
}{
{
name: "Test EchoMiddleware_Query",
fields: fields{
Config: &config.Config{
Policy: Test_Policy,
Query: "data.policy.allow",
ExceptedResult: true,
DeniedStatusCode: 403,
DeniedMessage: "Forbidden",
},
InputCreationMethod: func(c echo.Context) (map[string]interface{}, error) {
return map[string]interface{}{
"path": c.Request().URL.Path,
"method": c.Request().Method,
}, nil
},
},
args: args{
req: &http.Request{
URL: &url.URL{
Path: "/api/v1/users",
},
Method: "GET",
},
},
want: true,
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
e := echo.New()
h := &EchoMiddleware{
Config: tt.fields.Config,
InputCreationMethod: tt.fields.InputCreationMethod,
}
c := e.NewContext(tt.args.req, httptest.NewRecorder())
got, err := h.query(c)
if (err != nil) != tt.wantErr {
t.Errorf("Query() error = %v, wantErr %v", err, tt.wantErr)
return
}
if got != tt.want {
t.Errorf("Query() got = %v, want %v", got, tt.want)
}
})
}
}

func TestEchoMiddleware_Use(t *testing.T) {
type fields struct {
Config *config.Config
InputCreationMethod EchoInputCreationMethod
}
tests := []struct {
name string
fields fields
}{
{
name: "Test EchoMiddleware_Use",
fields: fields{
Config: &config.Config{
Policy: Test_Policy,
Query: "data.policy.allow",
ExceptedResult: true,
DeniedStatusCode: 403,
DeniedMessage: "Forbidden",
},
InputCreationMethod: func(c echo.Context) (map[string]interface{}, error) {
return map[string]interface{}{
"path": c.Request().URL.Path,
"method": c.Request().Method,
}, nil
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
h := &EchoMiddleware{
Config: tt.fields.Config,
InputCreationMethod: tt.fields.InputCreationMethod,
}
h.Use()
})
}
}

func TestNewEchoMiddleware(t *testing.T) {
type args struct {
cfg *config.Config
inputCreationMethod EchoInputCreationMethod
}
tests := []struct {
name string
args args
want *EchoMiddleware
wantErr bool
}{
{
name: "Test NewEchoMiddleware",
args: args{
cfg: &config.Config{
Policy: "policy",
Query: "data.query",
ExceptedResult: true,
DeniedStatusCode: 403,
DeniedMessage: "Forbidden",
},
inputCreationMethod: func(c echo.Context) (map[string]interface{}, error) {
return map[string]interface{}{
"path": c.Request().URL.Path,
"method": c.Request().Method,
}, nil
},
},
want: &EchoMiddleware{
Config: &config.Config{
Policy: "policy",
Query: "data.query",
ExceptedResult: true,
DeniedStatusCode: 403,
DeniedMessage: "Forbidden",
},
InputCreationMethod: func(c echo.Context) (map[string]interface{}, error) {
return map[string]interface{}{
"path": c.Request().URL.Path,
"method": c.Request().Method,
}, nil
},
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_, err := NewEchoMiddleware(tt.args.cfg, tt.args.inputCreationMethod)
if (err != nil) != tt.wantErr {
t.Errorf("NewEchoMiddleware() error = %v, wantErr %v", err, tt.wantErr)
return
}
})
}
}
13 changes: 8 additions & 5 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ go 1.18
require (
github.com/gin-gonic/gin v1.9.1
github.com/gofiber/fiber/v2 v2.49.2
github.com/labstack/echo/v4 v4.11.4
github.com/open-policy-agent/opa v0.43.1
github.com/valyala/fasthttp v1.49.0
)
Expand All @@ -27,9 +28,10 @@ require (
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.16.7 // indirect
github.com/klauspost/cpuid/v2 v2.2.4 // indirect
github.com/labstack/gommon v0.4.2 // indirect
github.com/leodido/go-urn v1.2.4 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.19 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.15 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
Expand All @@ -40,16 +42,17 @@ require (
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.11 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasttemplate v1.2.2 // indirect
github.com/valyala/tcplisten v1.0.0 // indirect
github.com/vektah/gqlparser/v2 v2.4.6 // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/yashtewari/glob-intersection v0.1.0 // indirect
golang.org/x/arch v0.3.0 // indirect
golang.org/x/crypto v0.14.0 // indirect
golang.org/x/net v0.17.0 // indirect
golang.org/x/sys v0.13.0 // indirect
golang.org/x/text v0.13.0 // indirect
golang.org/x/crypto v0.17.0 // indirect
golang.org/x/net v0.19.0 // indirect
golang.org/x/sys v0.15.0 // indirect
golang.org/x/text v0.14.0 // indirect
google.golang.org/protobuf v1.30.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
Expand Down
Loading