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

Move authorization info from header to body #6

Merged
merged 3 commits into from
Jul 17, 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: 3 additions & 3 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# golang alpine
FROM golang:1.22.5-alpine as builder
FROM golang:1.22.5-alpine AS builder

ARG TARGETARCH
ARG TARGETOS
Expand All @@ -12,8 +12,8 @@ RUN apk update \
musl-dev \
&& update-ca-certificates

ENV GO111MODULE on
ENV GOPATH /
ENV GO111MODULE=on
ENV GOPATH=/

RUN mkdir /opt/nuts-pxp && cd /opt/nuts-pxp
COPY go.mod .
Expand Down
106 changes: 87 additions & 19 deletions api/opa/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ package opa

import (
"context"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"github.com/nuts-foundation/nuts-pxp/policy"
"strings"
)

var _ StrictServerInterface = (*Wrapper)(nil)
Expand All @@ -13,32 +15,98 @@ type Wrapper struct {
DecisionMaker policy.DecisionMaker
}

func (w Wrapper) EvaluateDocument(ctx context.Context, request EvaluateDocumentRequestObject) (EvaluateDocumentResponseObject, error) {
// parse the requestLine and extract the method and path
// the requestLine is formatted as an HTTP request line
// e.g. "GET /api/v1/resource HTTP/1.1"
// we are only interested in the method and path
method, path, err := parseRequestLine(request.Params.Request)
func (w Wrapper) EvaluateDocumentApisix(ctx context.Context, request EvaluateDocumentApisixRequestObject) (EvaluateDocumentApisixResponseObject, error) {
if request.Body == nil {
return nil, errors.New("missing body")
}
// APISIX combines the 'openid-connect' and 'opa' plugin results into the following body:
//{
// "input": {
// "var": {
// "server_port": "9080",
// "remote_addr": "172.90.10.2",
// "timestamp": 1718289289,
// "remote_port": "54228",
// "server_addr": "172.90.10.12"
// },
// "type": "http",
// "request": {
// "scheme": "http",
// "method": "POST",
// "host": "pep-right",
// "query": {},
// "path": "/web/external/transfer/notify/21189b43-04d5-4f4f-86ed-e5ae21a87f84",
// "headers": {
// "X-Userinfo": "eyJvcmdhbml6YXRpb25fbmFtZSI6IkxlZnQiLCJzY29wZSI6ImVPdmVyZHJhY2h0LXJlY2VpdmVyIiwic3ViIjoiZGlkOndlYjpub2RlLnJpZ2h0LmxvY2FsOmlhbTpyaWdodCIsImV4cCI6MTcxODI5MDE4NiwiaWF0IjoxNzE4Mjg5Mjg2LCJpc3MiOiJkaWQ6d2ViOm5vZGUucmlnaHQubG9jYWw6aWFtOnJpZ2h0IiwiYWN0aXZlIjp0cnVlLCJjbGllbnRfaWQiOiJkaWQ6d2ViOm5vZGUubGVmdC5sb2NhbDppYW06bGVmdCIsIm9yZ2FuaXphdGlvbl9jaXR5IjoiR3JvZW5sbyJ9",
// "host": "pep-right:9080",
// "authorization": "Bearer TonUNXLwVn2UgJgVfpVDNa7WaXAlE2W-mS6CfqDzeP0",
// "content-length": "0",
// "user-agent": "go-resty/2.13.1 (https://github.com/go-resty/resty)",
// "X-Access-Token": "TonUNXLwVn2UgJgVfpVDNa7WaXAlE2W-mS6CfqDzeP0",
// "accept-encoding": "gzip",
// "content-type": "text/plain; charset=utf-8",
// "connection": "close"
// },
// "port": 9080
// }
// }
//}
outcome, err := w.handleEvaluate(ctx, *request.Body)
if err != nil {
return nil, err
}
httpRequest := map[string]interface{}{}
httpRequest["method"] = method
httpRequest["path"] = path

descision, err := w.DecisionMaker.Query(ctx, httpRequest, request.Params.XUserinfo)
// Expected response by APISIX is of the form:
//{
// "result": {
// "allow": true
// }
//}
return EvaluateDocumentApisix200JSONResponse(*outcome), nil
}

func (w Wrapper) EvaluateDocument(ctx context.Context, request EvaluateDocumentRequestObject) (EvaluateDocumentResponseObject, error) {
if request.Body == nil {
return nil, errors.New("missing body")
}
//fmt.Printf("%v\n", *request.Body)

outcome, err := w.handleEvaluate(ctx, *request.Body)
if err != nil {
return nil, err
}
return EvaluateDocument200JSONResponse{Allow: descision}, nil

return EvaluateDocument200JSONResponse(*outcome), nil
}

// parseRequestLine parses the request line and extracts the method and path
// e.g. "GET /api/v1/resource HTTP/1.1" -> "GET", "/api/v1/resource"
func parseRequestLine(requestLine string) (method, path string, err error) {
parts := strings.Split(requestLine, " ")
if len(parts) != 3 {
return "", "", fmt.Errorf("invalid request line: %s", requestLine)
func (w Wrapper) handleEvaluate(ctx context.Context, input Input) (*Outcome, error) {

httpRequest, ok := input.Input["request"].(map[string]interface{})
if !ok {
return nil, errors.New("invalid request, missing 'input.request'")
}

httpHeaders, ok := httpRequest["headers"].(map[string]interface{})
if !ok {
return nil, errors.New("invalid request, missing 'input.request.headers'")
}
xUserinfoBase64, ok := httpHeaders["X-Userinfo"].(string)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does APISIX always pass the header as b64 encoded. If so, we can be OPA docker image compatible with:

token_climas := json.unmarshal(base64.decode(input.request.headers["x-Userinfo"]))

if !ok {
return nil, errors.New("invalid request, missing 'input.request.headers.X-Userinfo is not a string'")
}
xUserinfoJSON, err := base64.URLEncoding.DecodeString(xUserinfoBase64)
if err != nil {
return nil, fmt.Errorf("invalid request, failed to base64 decode X-Userinfo: %w", err)
}
xUserinfo := map[string]interface{}{}
err = json.Unmarshal(xUserinfoJSON, &xUserinfo)
if err != nil {
return nil, fmt.Errorf("invalid request, failed to unmarshal X-Userinfo: %w", err)
}

decision, err := w.DecisionMaker.Query(ctx, httpRequest, xUserinfo)
if err != nil {
return nil, err
}
return parts[0], parts[1], nil
return &Outcome{Result: map[string]interface{}{"allow": decision}}, nil
}
137 changes: 83 additions & 54 deletions api/opa/generated.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion api/pip/generated.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import (
"context"
"errors"
"fmt"
"github.com/nuts-foundation/nuts-pxp/policy"
"net/http"
"os"
"os/signal"
Expand All @@ -35,6 +34,7 @@ import (
"github.com/nuts-foundation/nuts-pxp/api/pip"
"github.com/nuts-foundation/nuts-pxp/config"
"github.com/nuts-foundation/nuts-pxp/db"
"github.com/nuts-foundation/nuts-pxp/policy"
)

func main() {
Expand Down
2 changes: 1 addition & 1 deletion makefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
run-generators: api

install-tools:
go install github.com/deepmap/oapi-codegen/v2/cmd/oapi-codegen@v2.1.0
go install github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen@v2.3.0
go install go.uber.org/mock/[email protected]
go install github.com/golangci/golangci-lint/cmd/[email protected]

Expand Down
Loading