diff --git a/.github/workflows/ko-build-branch.yaml b/.github/workflows/ko-build-branch.yaml new file mode 100644 index 0000000..e490af4 --- /dev/null +++ b/.github/workflows/ko-build-branch.yaml @@ -0,0 +1,51 @@ +# Copyright The Linux Foundation and each contributor to LFX. +# SPDX-License-Identifier: MIT +--- +name: Publish Container Branch + +"on": + pull_request: + branches: + - main + +permissions: + contents: read + +jobs: + publish: + name: Publish Container + if: ${{ github.event.pull_request.head.repo.fork == false }} + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0 + with: + go-version-file: go.mod + - uses: ko-build/setup-ko@d006021bd0c28d1ce33a07e7943d48b079944c8d # v0.9 + with: + version: v0.17.1 + - name: Prepare container tag + id: container_tag + env: + HEAD_REF: "${{ github.head_ref }}" + run: | + container_tag=$(echo "$HEAD_REF" | sed 's/[^_0-9a-zA-Z]/-/g' | cut -c -127) + echo tag="$container_tag" >> "$GITHUB_OUTPUT" + - name: Build lfx-access-check for PR + env: + VERSION: ${{ steps.container_tag.outputs.tag }} + GIT_COMMIT: ${{ github.sha }} + run: | + BUILD_TIME=$(date -u '+%Y-%m-%d_%H:%M:%S') + export BUILD_TIME + GIT_COMMIT=${GIT_COMMIT:0:7} + export GIT_COMMIT + ko build github.com/linuxfoundation/lfx-v2-access-check/cmd/lfx-access-check \ + -B \ + --platform linux/amd64,linux/arm64 \ + -t ${{ github.sha }} \ + -t ${{ steps.container_tag.outputs.tag }} \ + --sbom spdx diff --git a/.github/workflows/ko-build-main.yaml b/.github/workflows/ko-build-main.yaml index 4731cb6..3cfbbec 100644 --- a/.github/workflows/ko-build-main.yaml +++ b/.github/workflows/ko-build-main.yaml @@ -20,11 +20,11 @@ jobs: contents: read packages: write steps: - - uses: actions/checkout@v4 - - uses: actions/setup-go@v5 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0 with: go-version-file: go.mod - - uses: ko-build/setup-ko@v0.8 + - uses: ko-build/setup-ko@d006021bd0c28d1ce33a07e7943d48b079944c8d # v0.9 with: version: v0.17.1 - name: Build and publish service image diff --git a/.github/workflows/ko-build-tag.yaml b/.github/workflows/ko-build-tag.yaml index 37652ea..505e7c8 100644 --- a/.github/workflows/ko-build-tag.yaml +++ b/.github/workflows/ko-build-tag.yaml @@ -28,7 +28,7 @@ jobs: chart_version: ${{ steps.prepare.outputs.chart_version }} steps: - name: Checkout repository - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Prepare versions and chart name id: prepare @@ -44,12 +44,12 @@ jobs: } >> "$GITHUB_OUTPUT" - name: Setup Go - uses: actions/setup-go@v5 + uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0 with: go-version-file: go.mod - name: Setup Ko - uses: ko-build/setup-ko@v0.8 + uses: ko-build/setup-ko@d006021bd0c28d1ce33a07e7943d48b079944c8d # v0.9 with: version: v0.17.1 @@ -82,7 +82,7 @@ jobs: image_name: ${{ steps.publish-ghcr.outputs.image_name }} steps: - name: Checkout repository - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Publish Chart to GHCR id: publish-ghcr @@ -97,12 +97,12 @@ jobs: registry_password: ${{ secrets.GITHUB_TOKEN }} - name: Install Cosign - uses: sigstore/cosign-installer@d58896d6a1865668819e1d91763c7751a165e159 # v3.9.2 + uses: sigstore/cosign-installer@faadad0cce49287aee09b3a48701e75088a2c6ad # v4.0.0 with: cosign-release: "${{ env.COSIGN_VERSION }}" - name: Login to GitHub - uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0 + uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0 with: registry: ghcr.io username: ${{ github.actor }} diff --git a/.gitignore b/.gitignore index 5a5cc74..ff72186 100644 --- a/.gitignore +++ b/.gitignore @@ -123,3 +123,6 @@ yarn-error.log* # MegaLinter reports megalinter-reports/ + +# Local Helm chart values files +charts/lfx-v2-access-check/values.local.yaml diff --git a/CLAUDE.md b/CLAUDE.md index 617ea81..915425f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,17 +1,17 @@ -# LFX v2 Access Check Service +# LFX Access Check Service ## Quick Overview - **Purpose**: Bulk access checks for resource-action pairs -- **Framework**: Go with GOA v3 (API-first design) +- **Framework**: Go with Goa v3 (API-first design) - **Authentication**: JWT tokens from Heimdall -- **Message Queue**: NATS for async processing +- **Message Queue**: NATS for async processing; fga-sync evaluates permissions - **Deployment**: Kubernetes with Helm charts ## Architecture -``` -Client → Traefik → Heimdall → Access Check Service → NATS +```text +Client → Traefik → Heimdall → Access Check Service → NATS → fga-sync ``` ## Project Structure @@ -22,11 +22,11 @@ lfx-v2-access-check/ │ ├── main.go # Application bootstrap │ └── server.go # HTTP server setup │ -├── design/ # GOA API design definitions +├── design/ # Goa API design definitions │ ├── access-svc.go # Service design & endpoints │ └── types.go # Shared type definitions │ -├── gen/ # Generated code (GOA) +├── gen/ # Generated code (Goa) — do not edit │ ├── access_svc/ # Service interfaces │ └── http/ # HTTP transport layer │ @@ -45,12 +45,16 @@ lfx-v2-access-check/ │ ├── constants/ # Application constants │ └── log/ # Logging utilities │ +└── charts/ # Helm deployment charts +``` + ## Development Setup ### Prerequisites - Go 1.24.0+ - Docker - NATS server +- fga-sync (evaluates permissions from NATS messages) - Heimdall (JWT provider) ### Quick Start @@ -69,9 +73,9 @@ Available development and build targets: **Development:** ```bash make setup-dev # Install development tools (golangci-lint) -make setup # Setup development environment +make setup # Setup development environment make deps # Install Go dependencies -make apigen # Generate API code using GOA +make apigen # Generate API code using Goa make fmt # Format Go code make vet # Run go vet make lint # Run golangci-lint @@ -100,11 +104,12 @@ make docker-run # Run container locally **Helm/Kubernetes:** ```bash -make helm-install # Install Helm chart -make helm-upgrade # Upgrade Helm release -make helm-templates # Generate Helm templates -make helm-uninstall # Uninstall Helm release -make helm-lint # Lint Helm chart +make helm-install # Install Helm chart using values.yaml +make helm-install-local # Install Helm chart using values.local.yaml +make helm-templates # Render Helm templates +make helm-templates-local # Render Helm templates using values.local.yaml +make helm-uninstall # Uninstall Helm release +make helm-lint # Lint Helm chart ``` **Utility:** @@ -115,32 +120,45 @@ make help # Show all available targets ## API ### Access Check -``` + +Version is passed as a query parameter (`?v=1`), not in the request body. + +```http POST /access-check?v=1 Authorization: Bearer Content-Type: application/json { - "version": "1", "requests": ["project:123#read", "committee:456#write"] } ``` +Response (results correspond 1:1 with the input `requests` array): + +```json +{ + "results": ["allow", "deny"] +} +``` + ### Health Checks - `GET /livez` - Liveness probe - `GET /readyz` - Readiness probe +### OpenAPI Spec +Available at `/_access-check/openapi.json`, `openapi.yaml`, `openapi3.json`, `openapi3.yaml`. + ## Deployment ### Docker ```bash make docker-build -docker run -p 8080:8080 -e JWKS_URL=... -e NATS_URL=... lfx-access-check +docker run -p 8080:8080 ghcr.io/linuxfoundation/lfx-v2-access-check/lfx-access-check:latest ``` ### Kubernetes ```bash -helm upgrade --install lfx-v2-access-check ./charts/lfx-v2-access-check +make helm-install ``` ## Service Architecture @@ -148,7 +166,7 @@ helm upgrade --install lfx-v2-access-check ./charts/lfx-v2-access-check ### Core Components 1. **HTTP Server** (`cmd/lfx-access-check/`) - - GOA-based REST API server + - Goa-based REST API server - JWT authentication middleware - Request ID tracking - Structured logging @@ -173,15 +191,14 @@ helm upgrade --install lfx-v2-access-check ./charts/lfx-v2-access-check ### Test Structure - **Unit Tests**: Service layer, infrastructure, configuration, middleware -- **Integration Tests**: API endpoints, NATS integration, JWT authentication -- **Benchmark Tests**: Performance testing for critical paths +- **Integration Tests**: API endpoints with mock dependencies — no external services required ### Running Tests ```bash # Unit tests make test -# Integration tests (requires NATS and mock services) +# Integration tests (uses mocks — no external services needed) go test -v ./test/integration/ # Specific package tests @@ -191,54 +208,11 @@ go test ./internal/service/ make test-coverage ``` -### Integration Tests -Integration tests are located in `test/integration/` and test the complete API endpoints with real dependencies: - -**Test Files:** +### Integration Test Files - `access_check_test.go` - Tests access check endpoint with JWT validation - `health_test.go` - Tests health check endpoints (/livez, /readyz) - `plaintext_test.go` - Tests plaintext response handling -- `mocks.go` - Mock services for testing - -**Running Integration Tests:** -```bash -# Run all integration tests -go test -v ./test/integration/ - -# Run specific test -go test -v ./test/integration/ -run TestAccessCheck - -# Run with race detection -go test -v -race ./test/integration/ -``` - -**Prerequisites for Integration Tests:** -- NATS server running (for messaging tests) -- Mock JWT validation service -- Test environment variables configured - -## Deployment - -### Docker Deployment -```bash -# Build image -make docker-build - -# Run container -docker run -p 8080:8080 \ - -e JWKS_URL=http://heimdall:4457/.well-known/jwks \ - -e NATS_URL=nats://nats:4222 \ - linuxfoundation/lfx-access-check:latest -``` - -### Kubernetes Deployment -```bash -helm upgrade --install lfx-v2-access-check ./charts/lfx-v2-access-check \ - --set image.tag=v1.0.0 \ - --set config.jwksUrl=http://heimdall:4457/.well-known/jwks \ - --set config.natsUrl=nats://nats:4222 \ - --namespace lfx -``` +- `mocks.go` - Mock auth and messaging repositories ## Security diff --git a/Makefile b/Makefile index a25dd9b..2acee3c 100644 --- a/Makefile +++ b/Makefile @@ -15,6 +15,7 @@ DOCKER_TAG := $(VERSION) HELM_CHART_PATH=./charts/lfx-v2-access-check HELM_RELEASE_NAME=lfx-v2-access-check HELM_NAMESPACE=lfx +HELM_VALUES_FILE=$(HELM_CHART_PATH)/values.local.yaml # Go GO_VERSION := 1.24.2 @@ -138,11 +139,13 @@ helm-install: ## Install Helm chart helm upgrade --install $(HELM_RELEASE_NAME) $(HELM_CHART_PATH) --namespace $(HELM_NAMESPACE) --create-namespace --set image.tag=$(DOCKER_TAG) @echo "==> Helm chart installed: $(HELM_RELEASE_NAME)" -.PHONY: helm-upgrade -helm-upgrade: ## Upgrade Helm release - @echo "==> Upgrading Helm chart..." - helm upgrade $(HELM_RELEASE_NAME) $(HELM_CHART_PATH) --namespace $(HELM_NAMESPACE) --set image.tag=$(DOCKER_TAG) - @echo "==> Helm chart upgraded: $(HELM_RELEASE_NAME)" +.PHONY: helm-install-local +helm-install-local: ## Install Helm chart with local values file + @echo "==> Installing Helm chart with local values..." + helm upgrade --force --install $(HELM_RELEASE_NAME) $(HELM_CHART_PATH) \ + --namespace $(HELM_NAMESPACE) --create-namespace \ + --values $(HELM_VALUES_FILE) + @echo "==> Helm chart installed: $(HELM_RELEASE_NAME)" .PHONY: helm-templates helm-templates: ## Generate Helm templates @@ -150,6 +153,14 @@ helm-templates: ## Generate Helm templates helm template $(HELM_RELEASE_NAME) $(HELM_CHART_PATH) --namespace $(HELM_NAMESPACE) --set image.tag=$(DOCKER_TAG) @echo "==> Templates printed for Helm chart: $(HELM_RELEASE_NAME)" +.PHONY: helm-templates-local +helm-templates-local: ## Generate Helm templates with local values file + @echo "==> Rendering Helm templates with local values..." + helm template $(HELM_RELEASE_NAME) $(HELM_CHART_PATH) \ + --namespace $(HELM_NAMESPACE) \ + --values $(HELM_VALUES_FILE) + @echo "==> Templates printed for Helm chart: $(HELM_RELEASE_NAME)" + .PHONY: helm-uninstall helm-uninstall: ## Uninstall Helm release @echo "==> Uninstalling Helm chart..." diff --git a/README.md b/README.md index 1cdc6a5..1efac8f 100644 --- a/README.md +++ b/README.md @@ -1,23 +1,23 @@ -# LFX v2 Access Check Service +# LFX Access Check Service ![Build Status](https://github.com/linuxfoundation/lfx-v2-access-check/workflows/Access%20Check%20Service%20Build/badge.svg) ![License](https://img.shields.io/badge/License-MIT-blue.svg) ![Go Version](https://img.shields.io/badge/Go-1.24+-00ADD8?logo=go) -A access check service for the LFX v2 platform, providing centralized authorization and permission management across LFX services. +An access check service for the LFX Self-Service platform, providing centralized authorization and permission management across LFX services. -## ✨ Key Features +## Key Features -- **🚀 Bulk Access Checks**: Process multiple resource-action permission checks in a single HTTP request -- **🔐 JWT Authentication**: Secure authentication using Heimdall-issued JWT tokens -- **🔄 Real-time Processing**: Asynchronous message processing via NATS queue -- **🚢 Cloud Native**: Kubernetes-ready with Helm charts for easy deployment +- **Bulk Access Checks**: Process multiple resource-action permission checks in a single HTTP request +- **JWT Authentication**: Secure authentication using Heimdall-issued JWT tokens +- **Real-time Processing**: Asynchronous message processing via NATS, evaluated by fga-sync +- **Cloud Native**: Kubernetes-ready with Helm charts for easy deployment -## 🏗️ Architecture Overview +## Architecture Overview ```mermaid graph TB - subgraph "LFX v2 Platform Gateway" + subgraph "LFX Self-Service Platform Gateway" T[Traefik
API Gateway] H[Heimdall
Access Decision Service] end @@ -30,17 +30,21 @@ graph TB subgraph "Platform Infrastructure" N[NATS
Message Queue] + FGA[fga-sync
Permission Evaluator] end T --> H H --> AC AC --> AS AC --> HE - - AS <-->|bulk access checks
access-check subject| N + + AS -->|publish bulk access check| N + N -->|evaluate permissions| FGA + FGA -->|return results| N + N -->|authorization results| AS ``` -## 🔄 Access Check Flow +## Access Check Flow ```mermaid sequenceDiagram @@ -49,65 +53,79 @@ sequenceDiagram participant Heimdall as Heimdall Access Decision participant AccessCheck as Access Check Service participant NATS as NATS Queue + participant FGASync as fga-sync - Client->>Traefik: POST /access-check
Bearer: JWT + resource list + Client->>Traefik: POST /access-check?v=1
Bearer: JWT + resource list Traefik->>Heimdall: Validate JWT & authorize Heimdall-->>Traefik: Auth success Traefik->>AccessCheck: Forward authenticated request - + AccessCheck->>AccessCheck: Extract principal from JWT AccessCheck->>AccessCheck: Build resource-action pairs - AccessCheck->>NATS: Publish bulk access check
Subject: access-check - - NATS-->>AccessCheck: Return authorization results + AccessCheck->>NATS: Publish bulk access check + + NATS->>FGASync: Deliver check request + FGASync->>FGASync: Evaluate permissions in OpenFGA + FGASync-->>NATS: Return allow/deny results + + NATS-->>AccessCheck: Authorization results AccessCheck-->>Traefik: JSON response with decisions Traefik-->>Client: Access check results - Note over AccessCheck: Optimized for bulk operations
with comprehensive logging + Note over AccessCheck,FGASync: Results correspond 1:1 with
the input requests array ``` -## 🚀 Quick Start +## Quick Start ### Prerequisites -- **Go**: 1.24.0 +- **Go**: 1.24.0+ - **Docker**: For containerized deployment - **NATS**: Message queue for service communication +- **fga-sync**: Permission evaluator (processes access check messages from NATS) - **Heimdall**: JWT authentication provider ### Local Development 1. **Clone the repository** + ```bash git clone https://github.com/linuxfoundation/lfx-v2-access-check.git cd lfx-v2-access-check ``` 2. **Install dependencies** + ```bash make deps ``` 3. **Generate API code** (if needed) + ```bash make apigen ``` 4. **Build the service** + ```bash make build ``` 5. **Run tests** + ```bash make test ``` 6. **Start the service** + ```bash ./bin/lfx-access-check ``` +Run `make help` to see all available targets, including linting, coverage, Docker, and Helm commands. + ### Configuration The service is configured via environment variables: @@ -122,25 +140,55 @@ The service is configured via environment variables: | `ISSUER` | JWT issuer | `heimdall` | | `NATS_URL` | NATS server URL | `nats://nats:4222` | -### Docker Deployment +## API Reference -```bash -# Build image -make docker-build +### Check Access + +``` +POST /access-check?v=1 +Authorization: Bearer +Content-Type: application/json +``` + +**Request body:** + +```json +{ + "requests": [ + "project:123#read", + "committee:456#write" + ] +} +``` -# Run container -docker run -p 8080:8080 \ - -e JWKS_URL=http://heimdall:4457/.well-known/jwks \ - -e NATS_URL=nats://nats:4222 \ - linuxfoundation/lfx-access-check:latest +**Response:** Results are returned in the same order as the input `requests` array. + +```json +{ + "results": [ + "allow", + "deny" + ] +} ``` +Each result is either `"allow"` or `"deny"`. The resource-action pair format is `{type}:{id}#{action}`. + ### Health Endpoints -- **Liveness**: `GET /livez` - Basic service health -- **Readiness**: `GET /readyz` - Service + dependencies health +- `GET /livez` — Liveness probe (basic service health) +- `GET /readyz` — Readiness probe (service + dependencies) + +### OpenAPI Spec + +The service serves its own OpenAPI spec at: -## 🏛️ Architecture Details +- `/_access-check/openapi.json` +- `/_access-check/openapi.yaml` +- `/_access-check/openapi3.json` +- `/_access-check/openapi3.yaml` + +## Architecture Details ### Core Components @@ -171,7 +219,7 @@ docker run -p 8080:8080 \ ``` ├── cmd/lfx-access-check/ # Application entry point ├── design/ # Goa API design definitions -├── gen/ # Generated API code (Goa) +├── gen/ # Generated API code (Goa) — do not edit ├── internal/ │ ├── container/ # Dependency injection │ ├── domain/contracts/ # Domain models & interfaces @@ -186,19 +234,77 @@ docker run -p 8080:8080 \ └── charts/ # Helm deployment charts ``` -## 🚢 Deployment +## Testing + +### Unit Tests + +```bash +make test +``` + +### Integration Tests + +Integration tests are in `test/integration/` and use mock dependencies — no external services required. + +```bash +go test -v ./test/integration/ +``` + +### Coverage Report + +```bash +make test-coverage # generates coverage.html +``` + +## Deployment + +### Docker + +The production image is published to GHCR. Available tags: + +| Tag | Published when | +|-----|---------------| +| `latest` | On every tagged release | +| `vX.Y.Z` | On every tagged release (e.g. `v0.2.8`) | +| `` | On every merge to `main` and every open PR | +| `development` | On every merge to `main` | +| `` | On every open PR (special characters replaced with `-`, e.g. `my-feature-branch`) | + +Browse all published tags at: `https://github.com/linuxfoundation/lfx-v2-access-check/pkgs/container/lfx-v2-access-check%2Flfx-access-check` + +```bash +docker run -p 8080:8080 ghcr.io/linuxfoundation/lfx-v2-access-check/lfx-access-check:latest +``` + +To build and run locally from source: + +```bash +make docker-build +make docker-run +``` ### Kubernetes with Helm ```bash -# Install/upgrade with Helm -helm upgrade --install lfx-v2-access-check ./charts/lfx-v2-access-check \ - --set image.tag=latest \ - --set config.jwksUrl=http://heimdall:4457/.well-known/jwks \ - --set config.natsUrl=nats://nats:4222 +make helm-install ``` -## 📄 License +This installs using the committed `values.yaml`. For local development, you can override values without modifying `values.yaml`. Copy the example file and customize it: -This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. +```bash +cp charts/lfx-v2-access-check/values.local.example.yaml charts/lfx-v2-access-check/values.local.yaml +``` +`values.local.yaml` is gitignored. Edit it with any overrides you need (e.g. image tag, replica count), then use the local make targets: + +```bash +# Install using your local values file +make helm-install-local + +# Preview rendered templates with your local values +make helm-templates-local +``` + +## License + +This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. diff --git a/charts/lfx-v2-access-check/values.local.example.yaml b/charts/lfx-v2-access-check/values.local.example.yaml new file mode 100644 index 0000000..e89ae99 --- /dev/null +++ b/charts/lfx-v2-access-check/values.local.example.yaml @@ -0,0 +1,8 @@ +# Copyright The Linux Foundation and each contributor to LFX. +# SPDX-License-Identifier: MIT +--- +# Image configuration +image: + repository: ghcr.io/linuxfoundation/lfx-v2-access-check/lfx-access-check + tag: "latest" # Overrides appVersion from Chart.yaml + pullPolicy: Never diff --git a/charts/lfx-v2-access-check/values.yaml b/charts/lfx-v2-access-check/values.yaml index 5228703..a2f11da 100644 --- a/charts/lfx-v2-access-check/values.yaml +++ b/charts/lfx-v2-access-check/values.yaml @@ -1,7 +1,7 @@ # Copyright The Linux Foundation and each contributor to LFX. # SPDX-License-Identifier: MIT --- -replicaCount: 3 +replicaCount: 1 # podAnnotations are additional annotations applied to the pod template. # Example: