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(api): authorization-and-roles-management-service #580

Open
wants to merge 36 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
9abf43c
.env.example
akiyatomohiro Oct 18, 2024
7bc7ae4
Add policy file generation and CI pipeline setup
akiyatomohiro Oct 30, 2024
b77600e
fix .env.example
akiyatomohiro Oct 30, 2024
1fa29a4
fix policy file generation
akiyatomohiro Oct 30, 2024
c69e017
fix go mod and definition.go
akiyatomohiro Oct 30, 2024
3bb4e0a
fix policy file generation
akiyatomohiro Oct 30, 2024
dde19b4
fix check permittion
akiyatomohiro Oct 31, 2024
d53e66d
fix check permittion
akiyatomohiro Oct 31, 2024
e737f61
fix CheckPermission
akiyatomohiro Nov 5, 2024
6dea602
go mod tidy
akiyatomohiro Nov 5, 2024
115724b
go mod tidy
akiyatomohiro Nov 6, 2024
2fcdf75
fix SearchUser
akiyatomohiro Nov 6, 2024
1c4567c
fix ci
akiyatomohiro Nov 6, 2024
076a30e
fix ci
akiyatomohiro Nov 6, 2024
cb43988
fix ci
akiyatomohiro Nov 6, 2024
84c557b
fix ci
akiyatomohiro Nov 6, 2024
1a6345b
fix ci
akiyatomohiro Nov 6, 2024
f0cf2cb
fix ci
akiyatomohiro Nov 6, 2024
2149cb8
fix TestProject_Create
akiyatomohiro Nov 6, 2024
7ba3324
move check permission logic to reearthx
akiyatomohiro Nov 6, 2024
62710e5
fix definition.go
akiyatomohiro Nov 6, 2024
58501af
fix CheckPermission
akiyatomohiro Nov 6, 2024
aa4c1de
fix CheckPermission
akiyatomohiro Nov 6, 2024
9f17fd3
fix workflow
akiyatomohiro Nov 8, 2024
3358c1a
fix TestCreateProject e2e
akiyatomohiro Nov 28, 2024
f09801d
fix Generate policies of ci
akiyatomohiro Dec 12, 2024
408e13b
add Authorization System document to readme
akiyatomohiro Dec 12, 2024
d754df7
fix Validate Cerbos policies of ci
akiyatomohiro Dec 12, 2024
3f3c213
fix List generated policy files of ci
akiyatomohiro Dec 12, 2024
340fef4
fi update_policies
akiyatomohiro Dec 12, 2024
6006589
fix PermissionChecker generate implementation
akiyatomohiro Dec 12, 2024
fb0e535
fix Generate policies of ci
akiyatomohiro Dec 12, 2024
06545d7
fix conflict
akiyatomohiro Dec 23, 2024
ef82859
Refactoring interactor
akiyatomohiro Dec 23, 2024
378e277
fix definitions.go
akiyatomohiro Dec 23, 2024
518c722
fix difinitions.go
akiyatomohiro Dec 23, 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
47 changes: 36 additions & 11 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ jobs:
outputs:
ui: ${{ steps.ui.outputs.any_changed }}
api: ${{ steps.api.outputs.any_changed }}
policies: ${{ steps.policies.outputs.any_changed }}
engine: ${{ steps.engine.outputs.any_changed }}
websocket: ${{ steps.websocket.outputs.any_changed }}
steps:
Expand All @@ -20,13 +21,24 @@ jobs:
id: api
uses: tj-actions/changed-files@v41
with:
files: |
api/**
.github/workflows/ci.yml
.github/workflows/ci_api.yml
.github/workflows/build_api.yml
.github/workflows/deploy_api_nightly.yml
CHANGELOG.md
files: |
api/**
.github/workflows/ci.yml
.github/workflows/ci_api.yml
.github/workflows/build_api.yml
.github/workflows/deploy_api_nightly.yml
CHANGELOG.md

- name: changed files for policies
id: policies
uses: tj-actions/changed-files@v41
with:
files: |
api/internal/rbac/**
.github/workflows/ci.yml
.github/workflows/check_cerbos_policies.yml
.github/workflows/update_cerbos_policies.yml
CHANGELOG.md

- name: changed files for ui
id: ui
Expand Down Expand Up @@ -63,14 +75,18 @@ jobs:
needs: prepare
if: needs.prepare.outputs.api == 'true'
uses: ./.github/workflows/ci_api.yml
ci-policies:
needs: prepare
if: needs.prepare.outputs.policies == 'true'
uses: ./.github/workflows/ci_policies.yml
ci-ui:
needs: prepare
if: needs.prepare.outputs.ui == 'true'
uses: ./.github/workflows/ci_ui.yml
ci-websocket:
needs: prepare
if: needs.prepare.outputs.websocket == 'true'
uses: ./.github/workflows/ci_websocket.yml
needs: prepare
if: needs.prepare.outputs.websocket == 'true'
uses: ./.github/workflows/ci_websocket.yml
ci-engine:
needs: prepare
if: needs.prepare.outputs.engine == 'true'
Expand All @@ -79,6 +95,7 @@ jobs:
runs-on: ubuntu-latest
needs:
- ci-api
- ci-policies
- ci-ui
- ci-websocket
- ci-engine
Expand Down Expand Up @@ -138,7 +155,15 @@ jobs:
new_tag_short: ${{ needs.ci-collect-info.outputs.new_tag_short }}
name: ${{ needs.ci-collect-info.outputs.name }}
sha: ${{ github.sha }}
secrets: inherit
secrets: inherit
update-policies:
needs:
- ci
- ci-policies
- ci-collect-info
uses: ./.github/workflows/update_policies.yml
if: ${{ !failure() && needs.ci-policies.result == 'success' && github.event_name == 'push' && (github.ref_name == 'main' || github.ref_name == 'release' || startsWith(github.ref_name, 'release/')) }}
secrets: inherit
akiyatomohiro marked this conversation as resolved.
Show resolved Hide resolved
build-and-deploy-ui:
needs:
- ci
Expand Down
51 changes: 51 additions & 0 deletions .github/workflows/ci_policies.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
name: ci-policies
on:
workflow_call:
env:
GO_VERSION: '1.22'

jobs:
cerbosCheck:
runs-on: ubuntu-latest
steps:
- name: checkout
uses: actions/checkout@v3
- name: set up
uses: actions/setup-go@v4
with:
go-version: ${{ env.GO_VERSION }}
cache: false
akiyatomohiro marked this conversation as resolved.
Show resolved Hide resolved
- name: Generate policies
run: |
set -e
if ! make gen-policies; then
echo "Policy generation failed"
exit 1
fi
working-directory: api
- name: List generated policy files
run: |
set -eo pipefail
echo "Generated policy files in api/policies/:"
ls -la policies/ || echo "No files found"
if [ -d "policies" ] && [ "$(ls -A policies/)" ]; then
echo "Content of policy files:"
for file in policies/*.yaml; do
echo "--- Content of $file ---"
cat "$file"
done
else
echo "No policy files were generated"
exit 1
fi
working-directory: api
- name: Setup Cerbos
uses: cerbos/cerbos-setup-action@v1
with:
version: '0.36.0'
- name: Validate Cerbos policies
run: |
set -eo pipefail
cerbos compile ./policies
echo "Policy validation successful"
working-directory: api
80 changes: 80 additions & 0 deletions .github/workflows/update_policies.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
name: update-policies
on:
workflow_call:
env:
GO_VERSION: '1.22'
GCS_BUCKET_PATH: gs://cerbos-oss-policyfile-bucket

jobs:
update-policies:
runs-on: ubuntu-latest
steps:
- name: checkout
uses: actions/checkout@v3
- name: set up
uses: actions/setup-go@v4
with:
go-version: ${{ env.GO_VERSION }}
cache: false
akiyatomohiro marked this conversation as resolved.
Show resolved Hide resolved
- name: Generate policies
run: |
set -e
if ! make gen-policies; then
echo "Policy generation failed"
exit 1
fi
working-directory: api
- name: Authenticate to Google Cloud
uses: google-github-actions/auth@v1
with:
credentials_json: ${{ secrets.GCP_SA_KEY }}
- name: Set up Cloud SDK
uses: google-github-actions/setup-gcloud@v1
akiyatomohiro marked this conversation as resolved.
Show resolved Hide resolved
- name: Sync policies with Cloud Storage
run: |
set -euo pipefail
echo "All files in bucket (before sync):"
gsutil ls "${GCS_BUCKET_PATH}/" || true

echo "Current flow files in bucket:"
bucket_files=$(gsutil ls "${GCS_BUCKET_PATH}/flow_*.yaml" || true)
echo "$bucket_files"

echo "Local policy files:"
local_files=$(ls policies/flow_*.yaml 2>/dev/null || true)
echo "$local_files"

for file in policies/flow_*.yaml; do
if [ -f "$file" ]; then
if ! yamllint "$file"; then
echo "Error: Invalid YAML in $file"
exit 1
fi
fi
done

for file in policies/flow_*.yaml; do
if [ -f "$file" ]; then
file_name=$(basename "$file")
echo "Uploading/Updating: $file_name"
if ! gsutil cp "$file" "${GCS_BUCKET_PATH}/$file_name"; then
echo "Error: Failed to upload $file_name"
exit 1
fi
fi
done

for bucket_file in "${GCS_BUCKET_PATH}"/flow_*.yaml; do
file_name=$(basename "$bucket_file")
if [ ! -f "policies/$file_name" ] && [[ "$file_name" == flow_* ]]; then
echo "Deleting: $file_name"
if ! gsutil rm "$bucket_file"; then
echo "Error: Failed to delete $bucket_file"
exit 1
fi
fi
done

echo "Sync completed. All files in bucket:"
gsutil ls "${GCS_BUCKET_PATH}/" || true
working-directory: api
4 changes: 3 additions & 1 deletion api/.env.example
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
# General
PORT=8080
REEARTH_FLOW_DB=mongodb://localhost
REEARTH_FLOW_HOST=https://localhost:8080
REEARTH_FLOW_ASSETBASEURL=https://localhost:8080/assets
REEARTH_FLOW_DEV=false

# Host configurations
REEARTH_FLOW_HOST=https://localhost:8080
REEARTH_DASHBOARD_HOST=http://localhost:8090

# Local Auth serv
REEARTH_FLOW_AUTH0_DOMAIN=https://example.auth0.com
Expand Down
5 changes: 4 additions & 1 deletion api/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,7 @@ run-db:
gql:
go generate ./internal/adapter/gql

.PHONY: lint test e2e build run-app run-db gql
gen-policies:
go run ./cmd/policy-generator

akiyatomohiro marked this conversation as resolved.
Show resolved Hide resolved
.PHONY: lint test e2e build run-app run-db gql gen-policies
25 changes: 25 additions & 0 deletions api/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,28 @@ $ make run-db
```console
$ go run ./cmd/reearth-flow
```

## Authorization System

Re:Earth Flow uses Role-Based Access Control (RBAC) to manage permissions. The system is built using Cerbos for policy enforcement.

### Authorization Configuration
All authorization-related definitions are managed in `api/internal/rbac/definitions.go`. This file contains:

- Resource definitions (e.g., project, workflow)
- Action definitions (e.g., read, edit)
- Role definitions (owner, maintainer, writer, reader)
- Permission mappings between resources, actions, and roles

To add or modify permissions:
1. Open `api/internal/rbac/definitions.go`
2. Add/modify resources in the `ResourceXXX` constants
3. Add/modify actions in the `ActionXXX` constants
4. Add/modify roles in the `roleXXX` constants
5. Update the permission mappings in the `DefineResources` function

### Deployment
The permission definitions are automatically synchronized with the Cerbos server's storage bucket when changes are merged into the main branch via CI. This ensures that the latest permission settings are always available for authorization checks.

### Implementation in Use Cases
Permission checks should be implemented in use case interactors.
18 changes: 18 additions & 0 deletions api/cmd/policy-generator/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package main

import (
"log"

"github.com/reearth/reearth-flow/api/internal/rbac"
"github.com/reearth/reearthx/cerbos/generator"
)

func main() {
if err := generator.GeneratePolicies(
rbac.ServiceName,
rbac.DefineResources,
rbac.PolicyFileDir,
); err != nil {
log.Fatalf("Failed to generate policies: %v", err)
}
}
42 changes: 26 additions & 16 deletions api/e2e/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ func init() {
mongotest.Env = "REEARTH_FLOW_DB"
}

func StartServer(t *testing.T, cfg *config.Config, useMongo bool, seeder Seeder) *httpexpect.Expect {
e, _, _ := StartServerAndRepos(t, cfg, useMongo, seeder)
func StartServer(t *testing.T, cfg *config.Config, useMongo bool, seeder Seeder, allowPermission bool) *httpexpect.Expect {
e, _, _ := StartServerAndRepos(t, cfg, useMongo, seeder, allowPermission)
return e
}

Expand Down Expand Up @@ -60,13 +60,13 @@ func initGateway() *gateway.Container {
}
}

func StartServerAndRepos(t *testing.T, cfg *config.Config, useMongo bool, seeder Seeder) (*httpexpect.Expect, *repo.Container, *gateway.Container) {
func StartServerAndRepos(t *testing.T, cfg *config.Config, useMongo bool, seeder Seeder, allowPermission bool) (*httpexpect.Expect, *repo.Container, *gateway.Container) {
repos := initRepos(t, useMongo, seeder)
gateways := initGateway()
return StartServerWithRepos(t, cfg, repos, gateways), repos, gateways
return StartServerWithRepos(t, cfg, repos, gateways, allowPermission), repos, gateways
}

func StartServerWithRepos(t *testing.T, cfg *config.Config, repos *repo.Container, gateways *gateway.Container) *httpexpect.Expect {
func StartServerWithRepos(t *testing.T, cfg *config.Config, repos *repo.Container, gateways *gateway.Container, allowPermission bool) *httpexpect.Expect {
t.Helper()

if testing.Short() {
Expand All @@ -80,12 +80,17 @@ func StartServerWithRepos(t *testing.T, cfg *config.Config, repos *repo.Containe
t.Fatalf("server failed to listen: %v", err)
}

// mockPermissionChecker
mockPermissionChecker := gateway.NewMockPermissionChecker()
mockPermissionChecker.Allow = allowPermission

srv := app.NewServer(ctx, &app.ServerConfig{
Config: cfg,
Repos: repos,
Gateways: gateways,
Debug: true,
AccountRepos: repos.AccountRepos(),
Config: cfg,
Repos: repos,
Gateways: gateways,
Debug: true,
AccountRepos: repos.AccountRepos(),
PermissionChecker: mockPermissionChecker,
})

ch := make(chan error)
Expand Down Expand Up @@ -115,18 +120,18 @@ type GraphQLRequest struct {
Variables map[string]any `json:"variables"`
}

func StartGQLServer(t *testing.T, cfg *config.Config, useMongo bool, seeder Seeder) (*httpexpect.Expect, *accountrepo.Container) {
e, r := StartGQLServerAndRepos(t, cfg, useMongo, seeder)
func StartGQLServer(t *testing.T, cfg *config.Config, useMongo bool, seeder Seeder, allowPermission bool) (*httpexpect.Expect, *accountrepo.Container) {
e, r := StartGQLServerAndRepos(t, cfg, useMongo, seeder, allowPermission)
return e, r
}

func StartGQLServerAndRepos(t *testing.T, cfg *config.Config, useMongo bool, seeder Seeder) (*httpexpect.Expect, *accountrepo.Container) {
func StartGQLServerAndRepos(t *testing.T, cfg *config.Config, useMongo bool, seeder Seeder, allowPermission bool) (*httpexpect.Expect, *accountrepo.Container) {
repos := initRepos(t, useMongo, seeder)
acRepos := repos.AccountRepos()
return StartGQLServerWithRepos(t, cfg, repos, acRepos), acRepos
return StartGQLServerWithRepos(t, cfg, repos, acRepos, allowPermission), acRepos
}

func StartGQLServerWithRepos(t *testing.T, cfg *config.Config, repos *repo.Container, accountrepos *accountrepo.Container) *httpexpect.Expect {
func StartGQLServerWithRepos(t *testing.T, cfg *config.Config, repos *repo.Container, accountrepos *accountrepo.Container, allowPermission bool) *httpexpect.Expect {
t.Helper()

if testing.Short() {
Expand All @@ -140,6 +145,10 @@ func StartGQLServerWithRepos(t *testing.T, cfg *config.Config, repos *repo.Conta
t.Fatalf("server failed to listen: %v", err)
}

// mockPermissionChecker
mockPermissionChecker := gateway.NewMockPermissionChecker()
mockPermissionChecker.Allow = allowPermission

srv := app.NewServer(ctx, &app.ServerConfig{
Config: cfg,
Repos: repos,
Expand All @@ -150,7 +159,8 @@ func StartGQLServerWithRepos(t *testing.T, cfg *config.Config, repos *repo.Conta
AccountGateways: &accountgateway.Container{
Mailer: mailer.New(ctx, &mailer.Config{}),
},
Debug: true,
Debug: true,
PermissionChecker: mockPermissionChecker,
})

ch := make(chan error)
Expand Down
2 changes: 1 addition & 1 deletion api/e2e/gql_me_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ func TestMe(t *testing.T) {
Disabled: true,
},
},
true, baseSeeder)
true, baseSeeder, true)
akiyatomohiro marked this conversation as resolved.
Show resolved Hide resolved

requestBody := GraphQLRequest{
OperationName: "GetMe",
Expand Down
Loading
Loading