Skip to content

Commit c2adc4f

Browse files
authored
Merge pull request #1 from openfga/initial_commit
feat: adds server.
2 parents d14b145 + 1829019 commit c2adc4f

25 files changed

+941
-0
lines changed

.github/workflows/ci.yaml

+60
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches:
6+
- "main"
7+
paths-ignore:
8+
- "**/*.md"
9+
- "LICENSE"
10+
pull_request:
11+
12+
jobs:
13+
test-and-build:
14+
runs-on: ubuntu-latest
15+
steps:
16+
- name: Checkout
17+
uses: actions/checkout@v4
18+
19+
- name: Setup Go
20+
uses: actions/setup-go@v4
21+
with:
22+
go-version: 1.22.1
23+
24+
- name: Test
25+
run: make test
26+
27+
- name: Build
28+
run: make build
29+
30+
- name: E2E Test
31+
run: make e2e
32+
33+
- name: Uploads logs if failure
34+
if: failure()
35+
uses: actions/upload-artifact@v4
36+
with:
37+
name: extauthz-e2e-logs
38+
path: extauthz/e2e/logs
39+
40+
- name: Set up QEMU
41+
uses: docker/setup-qemu-action@v3
42+
43+
- name: Set up Docker Buildx
44+
uses: docker/setup-buildx-action@v3
45+
46+
- name: Login to GHCR
47+
uses: docker/login-action@v2
48+
with:
49+
registry: ghcr.io
50+
username: ${{ github.actor }}
51+
password: ${{ secrets.GITHUB_TOKEN }}
52+
53+
- name: Build and push
54+
uses: docker/build-push-action@v5
55+
with:
56+
context: extauthz
57+
platforms: linux/amd64,linux/arm64
58+
#push: ${{ github.event_name != 'pull_request' }}
59+
push: false
60+
tags: openfga/openfga-envoy:latest

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
**/build
2+
**/logs

Makefile

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
.PHONY: run
2+
run-extauthz:
3+
@go run extauthz/cmd/extauthz/main.go
4+
5+
.PHONY: test
6+
test:
7+
@$(MAKE) -C extauthz test
8+
9+
.PHONY: build
10+
build:
11+
@$(MAKE) -C extauthz build
12+
13+
.PHONY: e2e
14+
e2e:
15+
@$(MAKE) -C extauthz e2e

extauthz/Dockerfile

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
FROM scratch
2+
3+
COPY build/envoy-extauthz-linux /envoy-extauthz
4+
5+
EXPOSE 9002
6+
7+
ENTRYPOINT ["/envoy-extauthz"]

extauthz/Makefile

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
.PHONY: test
2+
test:
3+
@go test -count=1 ./...
4+
5+
.PHONY: build
6+
build:
7+
@mkdir -p ./build
8+
@GOOS=linux CGO_ENABLED=0 go build -o ./build/envoy-extauthz-linux ./cmd/extauthz/main.go
9+
10+
.PHONY: docker
11+
docker: build
12+
@docker build -t envoy-extauthz -f Dockerfile .
13+
14+
.PHONY: e2e
15+
e2e:
16+
@./e2e/run.sh

extauthz/cmd/extauthz/main.go

+91
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
package main
2+
3+
import (
4+
"flag"
5+
"fmt"
6+
"log"
7+
"net"
8+
9+
auth_pb "github.com/envoyproxy/go-control-plane/envoy/service/auth/v3"
10+
"github.com/openfga/go-sdk/client"
11+
"github.com/openfga/openfga-envoy/extauthz/internal/extractor"
12+
"github.com/openfga/openfga-envoy/extauthz/internal/server/authz"
13+
"github.com/openfga/openfga-envoy/extauthz/internal/server/config"
14+
"google.golang.org/grpc"
15+
"google.golang.org/grpc/health"
16+
"google.golang.org/grpc/health/grpc_health_v1"
17+
)
18+
19+
const port = 9002
20+
21+
func main() {
22+
var (
23+
configPath string
24+
)
25+
26+
flag.StringVar(&configPath, "config", "./config.yaml", "path to the configuration file")
27+
flag.Parse()
28+
29+
cfg, err := config.LoadConfig(configPath)
30+
if err != nil {
31+
log.Fatalf("failed to load config: %v", err)
32+
}
33+
34+
fgaClient, err := client.NewSdkClient(&client.ClientConfiguration{
35+
// Debug: true,
36+
ApiUrl: cfg.Server.APIURL,
37+
StoreId: cfg.Server.StoreID,
38+
AuthorizationModelId: cfg.Server.AuthorizationModelID, // optional, recommended to be set for production
39+
})
40+
if err != nil {
41+
log.Fatalf("failed to initialize OpenFGA client: %v", err)
42+
}
43+
44+
extractionSet := make([]extractor.ExtractorSet, 0, len(cfg.ExtractionSet))
45+
for _, es := range cfg.ExtractionSet {
46+
var (
47+
eSet extractor.ExtractorSet
48+
err error
49+
)
50+
51+
eSet.Name = es.Name
52+
53+
eSet.User, err = extractor.MakeExtractor(es.User.Type, es.User.Config)
54+
if err != nil {
55+
log.Fatalf("failed to create user extractor: %v", err)
56+
}
57+
58+
eSet.Object, err = extractor.MakeExtractor(es.Object.Type, es.Object.Config)
59+
if err != nil {
60+
log.Fatalf("failed to create object extractor: %v", err)
61+
}
62+
63+
eSet.Relation, err = extractor.MakeExtractor(es.Relation.Type, es.Relation.Config)
64+
if err != nil {
65+
log.Fatalf("failed to create relation extractor: %v", err)
66+
}
67+
68+
extractionSet = append(extractionSet, eSet)
69+
}
70+
71+
filter := authz.NewExtAuthZFilter(fgaClient, extractionSet)
72+
73+
server := createServer(filter)
74+
75+
listener, err := net.Listen("tcp", fmt.Sprintf(":%d", port))
76+
if err != nil {
77+
panic(err)
78+
}
79+
80+
fmt.Printf("Starting server on port %d\n", port)
81+
log.Fatal(server.Serve(listener))
82+
}
83+
84+
func createServer(filter *authz.ExtAuthZFilter) *grpc.Server {
85+
grpcServer := grpc.NewServer()
86+
87+
grpc_health_v1.RegisterHealthServer(grpcServer, health.NewServer())
88+
auth_pb.RegisterAuthorizationServer(grpcServer, filter)
89+
90+
return grpcServer
91+
}

extauthz/cmd/extauthz/main_test.go

+82
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"errors"
6+
"log"
7+
"net"
8+
"testing"
9+
10+
auth_pb "github.com/envoyproxy/go-control-plane/envoy/service/auth/v3"
11+
"github.com/openfga/go-sdk/client"
12+
"github.com/openfga/openfga-envoy/extauthz/internal/extractor"
13+
"github.com/openfga/openfga-envoy/extauthz/internal/server/authz"
14+
"github.com/stretchr/testify/require"
15+
"google.golang.org/grpc"
16+
"google.golang.org/grpc/credentials/insecure"
17+
"google.golang.org/grpc/test/bufconn"
18+
)
19+
20+
func server(ctx context.Context, e extractor.ExtractorSet) (auth_pb.AuthorizationClient, func()) {
21+
buffer := 101024 * 1024
22+
lis := bufconn.Listen(buffer)
23+
24+
fgaClient, err := client.NewSdkClient(&client.ClientConfiguration{
25+
ApiUrl: "https://api.fga.example",
26+
})
27+
if err != nil {
28+
panic(err)
29+
}
30+
31+
filter := authz.NewExtAuthZFilter(fgaClient, []extractor.ExtractorSet{e})
32+
33+
baseServer := grpc.NewServer()
34+
auth_pb.RegisterAuthorizationServer(baseServer, filter)
35+
36+
go func() {
37+
if err := baseServer.Serve(lis); err != nil {
38+
log.Printf("error serving server: %v", err)
39+
}
40+
}()
41+
42+
conn, err := grpc.DialContext(ctx, "",
43+
grpc.WithContextDialer(func(context.Context, string) (net.Conn, error) {
44+
return lis.Dial()
45+
}), grpc.WithTransportCredentials(insecure.NewCredentials()))
46+
if err != nil {
47+
log.Printf("error connecting to server: %v", err)
48+
}
49+
50+
closer := func() {
51+
err := lis.Close()
52+
if err != nil {
53+
log.Printf("error closing listener: %v", err)
54+
}
55+
baseServer.Stop()
56+
}
57+
58+
return auth_pb.NewAuthorizationClient(conn), closer
59+
}
60+
61+
func TestNoUserExtractedFails(t *testing.T) {
62+
ctx := context.Background()
63+
64+
expectedErr := errors.New("no user")
65+
66+
e := extractor.ExtractorSet{
67+
Name: "extauthz",
68+
User: func(ctx context.Context, value *auth_pb.CheckRequest) (string, bool, error) {
69+
return "", false, expectedErr
70+
},
71+
}
72+
73+
client, closer := server(ctx, e)
74+
defer closer()
75+
76+
_, sErr := client.Check(ctx, &auth_pb.CheckRequest{})
77+
if sErr == nil {
78+
t.Fatal("expected error")
79+
}
80+
81+
require.ErrorContains(t, sErr, expectedErr.Error())
82+
}

extauthz/e2e/.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
config.yaml

extauthz/e2e/config.yaml.tmpl

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
server:
2+
api_url: http://openfga:8080
3+
# TODO(jcchavezs): This should be read from an env var
4+
store_id: ${STORE_ID}
5+
6+
extraction_sets:
7+
- name: test
8+
user:
9+
type: mock
10+
config:
11+
value: subject:test_subject
12+
object:
13+
type: mock
14+
config:
15+
value: resource:test_resource
16+
relation:
17+
type: mock
18+
config:
19+
value: GET

extauthz/e2e/docker-compose.yaml

+76
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
version: '3.2'
2+
3+
services:
4+
httpbin:
5+
image: mccutchen/go-httpbin:v2.9.0
6+
command: [ "/bin/go-httpbin", "-port", "8081" ]
7+
ports:
8+
- 8081:8081
9+
networks:
10+
- app_net
11+
12+
envoy:
13+
container_name: envoy
14+
depends_on:
15+
- httpbin
16+
- ext-authz
17+
image: ${ENVOY_IMAGE:-envoyproxy/envoy:v1.28-latest}
18+
# Entryoint is explicited in order to make the ENVOY_IMAGE compatible also with istio/proxyv2 images
19+
# The latter has as default entrypoint pilot-agent instead of envoy
20+
# See https://github.com/tetratelabs/proxy-wasm-go-sdk/blob/main/.github/workflows/workflow.yaml#L104
21+
entrypoint: /usr/local/bin/envoy
22+
command:
23+
- -c
24+
- /conf/envoy-config.yaml
25+
- --service-cluster # required to export metrics
26+
- envoy
27+
- --service-node # required to export metrics
28+
- envoy
29+
- --log-level
30+
- debug
31+
volumes:
32+
- .:/conf
33+
ports:
34+
- 8080:8080
35+
networks:
36+
- app_net
37+
38+
ext-authz:
39+
build:
40+
context: ..
41+
dockerfile: Dockerfile
42+
expose:
43+
- 9002
44+
ports:
45+
- 9002:9002
46+
command: ["--config", "/etc/extauthz/config.yaml"]
47+
volumes:
48+
- .:/etc/extauthz
49+
depends_on:
50+
openfga:
51+
condition: service_healthy
52+
networks:
53+
- app_net
54+
55+
openfga:
56+
image: openfga/openfga:latest
57+
container_name: openfga
58+
command: run
59+
environment:
60+
- OPENFGA_DATASTORE_ENGINE=memory
61+
ports:
62+
- "18080:8080" #http
63+
- "18081:8081" #grpc
64+
- "3000:3000" #playground
65+
- "2112:2112" #prometheus metrics
66+
healthcheck:
67+
test: ["CMD", "/usr/local/bin/grpc_health_probe", "-addr=openfga:8081"]
68+
interval: 5s
69+
timeout: 30s
70+
retries: 3
71+
networks:
72+
- app_net
73+
74+
networks:
75+
app_net:
76+
driver: bridge

0 commit comments

Comments
 (0)