From 78457b63f30864e2e7f9d0e332304d9872767228 Mon Sep 17 00:00:00 2001 From: Safin Wasi <6601566+SafinWasi@users.noreply.github.com> Date: Thu, 23 Jan 2025 04:24:12 -0600 Subject: [PATCH] feat(jans-cedarling): add krakend plugin (#10713) * feat(jans-cedarling): add krakend plugin Signed-off-by: SafinWasi <6601566+SafinWasi@users.noreply.github.com> * docs(jans-cedarling): add krakend plugin readme Signed-off-by: SafinWasi <6601566+SafinWasi@users.noreply.github.com> * ci: add workflow to build plugin Signed-off-by: SafinWasi <6601566+SafinWasi@users.noreply.github.com> * chore(jans-cedarling): fix module name Signed-off-by: SafinWasi <6601566+SafinWasi@users.noreply.github.com> * docs(jans-cedarling): add test instructions Signed-off-by: SafinWasi <6601566+SafinWasi@users.noreply.github.com> * ci: remove TTY option Signed-off-by: moabu <47318409+moabu@users.noreply.github.com> * ci: remove builder:2.9.0-linux-generic from cedarling krakend Signed-off-by: moabu <47318409+moabu@users.noreply.github.com> * ci: add builder:2.9.0-linux-generic from cedarling krakend Signed-off-by: moabu <47318409+moabu@users.noreply.github.com> * ci: add builder:2.9.0-linux-generic from cedarling krakend Signed-off-by: moabu <47318409+moabu@users.noreply.github.com> * ci: handle CC edge case Signed-off-by: moabu <47318409+moabu@users.noreply.github.com> * ci: fix CC var Signed-off-by: moabu <47318409+moabu@users.noreply.github.com> * ci: fix CC var Signed-off-by: moabu <47318409+moabu@users.noreply.github.com> * docs(jans-cedarling): update readme Signed-off-by: SafinWasi <6601566+SafinWasi@users.noreply.github.com> * docs(jans-cedarling): add sequence diagram and steps Signed-off-by: SafinWasi <6601566+SafinWasi@users.noreply.github.com> --------- Signed-off-by: SafinWasi <6601566+SafinWasi@users.noreply.github.com> Signed-off-by: moabu <47318409+moabu@users.noreply.github.com> Co-authored-by: Mohammad Abudayyeh <47318409+moabu@users.noreply.github.com> --- .github/workflows/build-packages.yml | 58 +++++++ docs/cedarling/cedarling-krakend.md | 137 +++++++++++++++ jans-cedarling/cedarling-krakend/README.md | 137 +++++++++++++++ jans-cedarling/cedarling-krakend/go.mod | 3 + jans-cedarling/cedarling-krakend/krakend.json | 43 +++++ jans-cedarling/cedarling-krakend/main.go | 161 ++++++++++++++++++ jans-cedarling/cedarling-krakend/types.go | 30 ++++ mkdocs.yml | 4 +- 8 files changed, 572 insertions(+), 1 deletion(-) create mode 100644 docs/cedarling/cedarling-krakend.md create mode 100644 jans-cedarling/cedarling-krakend/README.md create mode 100644 jans-cedarling/cedarling-krakend/go.mod create mode 100644 jans-cedarling/cedarling-krakend/krakend.json create mode 100644 jans-cedarling/cedarling-krakend/main.go create mode 100644 jans-cedarling/cedarling-krakend/types.go diff --git a/.github/workflows/build-packages.yml b/.github/workflows/build-packages.yml index f5bc3c5af3e..575946624d8 100644 --- a/.github/workflows/build-packages.yml +++ b/.github/workflows/build-packages.yml @@ -438,3 +438,61 @@ jobs: gpg --armor --detach-sign cedarling_wasm_"${TAG}"_pkg.tar.gz || echo "Failed to sign" echo "${{ secrets.MOAUTO_WORKFLOW_TOKEN }}" | gh auth login --with-token gh release upload "${VERSION}" *.tar.gz *.sha256sum *.asc + build_cedarling_krakend: + if: github.repository == 'JanssenProject/jans' + runs-on: ubuntu-20.04 + strategy: + matrix: + krakend-builder-image: [ 'builder:2.9.0', 'builder:2.9.0-linux-generic' ] + steps: + - name: Harden Runner + uses: step-security/harden-runner@a4aa98b93cab29d9b1101a6143fb8bce00e2eac4 # v2.7.1 + with: + egress-policy: audit + + - name: Checkout + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + - name: Import GPG key + id: import_gpg + continue-on-error: true + uses: crazy-max/ghaction-import-gpg@01dd5d3ca463c7f10f7f4f7b4f177225ac661ee4 # v6.1.0 + with: + gpg_private_key: ${{ secrets.MOAUTO_GPG_PRIVATE_KEY }} + passphrase: ${{ secrets.MOAUTO_GPG_PRIVATE_KEY_PASSPHRASE }} + git_user_signingkey: true + git_commit_gpgsign: true + - name: Set environment variables + run: | + TAG=$(echo ${{ github.event.ref }} | cut -d '/' -f 3 | sed 's/^v//') + VERSION="$(echo ${{ github.event.ref }} | cut -d '/' -f 3)" + if [ "${TAG}" == "nightly" ]; then + VERSION=nightly + TAG="0.0.0" + fi + echo TAG=${TAG} >> $GITHUB_ENV + echo VERSION=${VERSION} >> $GITHUB_ENV + KRAKEND_BUILDER_IMAGE=${{ matrix.krakend-builder-image }} + KRAKEND_BUILDER_IMAGE=${KRAKEND_BUILDER_IMAGE/:/-} + echo KRAKEND_BUILDER_IMAGE=${KRAKEND_BUILDER_IMAGE} >> $GITHUB_ENV + echo CC="aarch64-linux-musl-gcc" >> $GITHUB_ENV + if [ "${{ matrix.krakend-builder-image }}" == "builder:2.9.0-linux-generic" ]; then + echo CC="aarch64-linux-gnu-gcc" >> $GITHUB_ENV + fi + - name: Build plugin for AMD64 + working-directory: ${{ github.workspace }}/jans-cedarling/cedarling-krakend + run: | + docker run -i -v "$PWD:/app" -w /app krakend/"${{ matrix.krakend-builder-image }}" go build -buildmode=plugin -o cedarling-krakend-amd64-"${{ env.KRAKEND_BUILDER_IMAGE }}"-"${{ env.TAG }}".so . + - name: Build plugin for ARM64 + working-directory: ${{ github.workspace }}/jans-cedarling/cedarling-krakend + run: | + + docker run -i -v "$PWD:/app" -w /app -e "CGO_ENABLED=1" -e "CC=${{ env.CC }}" -e "GOARCH=arm64" -e "GOHOSTARCH=amd64" krakend/"${{ matrix.krakend-builder-image }}" go build -ldflags='-extldflags=-fuse-ld=bfd -extld=${{ env.CC }}' -buildmode=plugin -o cedarling-krakend-arm64-"${{ env.KRAKEND_BUILDER_IMAGE }}"-"${{ env.TAG }}".so . + - name: Generate sha256sum and sign + working-directory: ${{ github.workspace }}/jans-cedarling/cedarling-krakend + run: | + sha256sum cedarling-krakend-amd64-"${{ env.KRAKEND_BUILDER_IMAGE }}"-"${{ env.TAG }}".so >> cedarling-krakend-amd64-"${{ env.KRAKEND_BUILDER_IMAGE }}"-"${{ env.TAG }}".so.sha256sum + sha256sum cedarling-krakend-arm64-"${{ env.KRAKEND_BUILDER_IMAGE }}"-"${{ env.TAG }}".so >> cedarling-krakend-arm64-"${{ env.KRAKEND_BUILDER_IMAGE }}"-"${{ env.TAG }}".so.sha256sum + gpg --armor --detach-sign cedarling-krakend-amd64-"${{ env.KRAKEND_BUILDER_IMAGE }}"-"${{ env.TAG }}".so || echo "Failed to sign" + gpg --armor --detach-sign cedarling-krakend-arm64-"${{ env.KRAKEND_BUILDER_IMAGE }}"-"${{ env.TAG }}".so || echo "Failed to sign" + echo "${{ secrets.MOAUTO_WORKFLOW_TOKEN }}" | gh auth login --with-token + gh release upload "${{ env.VERSION }}" *.so *.sha256sum *.asc diff --git a/docs/cedarling/cedarling-krakend.md b/docs/cedarling/cedarling-krakend.md new file mode 100644 index 00000000000..570759a7104 --- /dev/null +++ b/docs/cedarling/cedarling-krakend.md @@ -0,0 +1,137 @@ +# Cedarling KrakenD Plugin + +This is a [KrakenD HTTP server plugin](https://www.krakend.io/docs/extending/http-server-plugins/) that intercepts a call to a selected endpoint, calls the [sidecar](https://github.com/JanssenProject/jans/blob/main/jans-cedarling/flask-sidecar/README.md) and allows access only if the sidecar responds with `true`. + +## Functionality + +When an end user calls the KrakenD endpoint protected by the plugin, the following steps happen: + +1. The plugin intercepts the call, and creates an [AuthZen](https://openid.github.io/authzen/) access evaluation request. +2. The plugin sends the request to the sidecar, which is running on the same host as KrakenD +3. The sidecar calls cedarling's `authz()` interface to check if this call is allowed or not according to the policy store loaded +4. The sidecar responds back to the plugin. If the decision was `true`, the plugin allows the request to pass through to the KrakenD backend. Otherwise, it responds with 403 Forbidden. + +[Sequence Diagram](https://sequencediagram.org/index.html#initialData=C4S2BsFMAIGFICYEMBO4QDsDm0DSKkBrSDAEWgAdwBXLTAKACMB7YYZgWwtVAGMRuGYAGdo9etxR8BSIXHQlgEniH6DgeAsTLKpqmXIDKIBJF6pd09XESp02cbAVCAtAD58REqQBc0AOIAogAq0AD0FCisZsCIADoY0C4AEtAARACC1MAAFswoIABeSKDMGH4AQpCokCjQADwAUgDqwW5p9J7apO7GpuYofgDeacLUjABWMWk+I8AAnhSQM2ktwWkANAlpJiu8tmiYWJvbkcxLepDCMyNIvPvCwgD67NorTa3tAL5fWxhpKCuzGoKH2NzSCyWK0asmEPh8yWCwQACk8AEqQACO1CuwBO-12PlGnEgT12GzSZwuoCu4Jy1VMKBuv22IPAdOYwjxRIAjAAmADsADoAAyioU8zaUko5FYRKKxXixBD4ykK5i8Zjsok5NgUNI-Fn-O6lf6zNIYJAcZZEmEYOE+DJKkBleFxNJBYLug1feh9MyodzwZCHbB+JDZHKFAAUdwez1eJAAvB82n9AcJgaDIEmhXm-iaXRhc3mAJT0JDgDTAFA4+jBuxHXomAODaA1uv+gbuLreYZpfogYRFmbt2uQX29shB5zAftnRXKskYABmzFHO1X699kHAwhgK8r+-rB3sWGb-VQfkPe8gfpb3Y8Wj70BGg+HZVHN-3k+f07cTggIofgACwigAzNAABi+SMCYpgYPQJAIEAA) + +## Downloading + +The cedarling-krakend plugin builds are available via [Janssen](https://github.com/JanssenProject/jans/releases) releases. Please note that builds are architecture, platform, and KrakenD version specific. This means that builds compiled against KrakenD version `2.9.0` will not work on other versions. The build tags are formatted as follows: + +``` +cedarling-krakend---.so +``` + +Use the following table to find which build you need: + +| | amd64 | arm64 | +| - | ----- | ----- | +| Docker | `amd64-builder-2.9.0-0.0.0.so` | `arm64-builder-2.9.0-0.0.0.so` | +| On-premise | `amd64-builder-2.9.0-linux-generic-0.0.0.so` | `arm64-builder-2.9.0-linux-generic-0.0.0.so` | + +If you are running a different version of KrakenD, you can use the following steps to build the plugin yourself. + +## Building + +Krakend recommends building via their builder docker image, to produce builds that match the target architecture and Go version. To build: + +- Clone the Janssen repository + ``` + git clone --filter blob:none --no-checkout https://github.com/JanssenProject/jans + cd jans + git sparse-checkout init --cone + git checkout main + git sparse-checkout set jans-cedarling + cd cedarling-krakend + ``` +- Build the plugin, replacing `` with the KrakenD version you want to build against: + + - For Docker targets: + + ``` + docker run -it -v "$PWD:/app" -w /app krakend/builder: go build -buildmode=plugin -o cedarling-krakend.so . + ``` + + - For on-premise installations: + + ``` + docker run -it -v "$PWD:/app" -w /app krakend/builder:-linux-generic go build -buildmode=plugin -o yourplugin.so . + ``` + + - For ARM64 Docker targets: + + ```bash + docker run -it -v "$PWD:/app" -w /app \ + -e "CGO_ENABLED=1" \ + -e "CC=aarch64-linux-musl-gcc" \ + -e "GOARCH=arm64" \ + -e "GOHOSTARCH=amd64" \ + krakend/builder: \ + go build -ldflags='-extldflags=-fuse-ld=bfd -extld=aarch64-linux-musl-gcc' \ + -buildmode=plugin -o yourplugin.so . + ``` + + - For ARM64 on-premise installs: + + ```bash + docker run -it -v "$PWD:/app" -w /app \ + -e "CGO_ENABLED=1" \ + -e "CC=aarch64-linux-gnu-gcc" \ + -e "GOARCH=arm64" \ + -e "GOHOSTARCH=amd64" \ + krakend/builder:-linux-generic \ + go build -ldflags='-extldflags=-fuse-ld=bfd -extld=aarch64-linux-gnu-gcc' \ + -buildmode=plugin -o yourplugin.so . + ``` + +Check [KrakenD documentation](https://www.krakend.io/docs/extending/injecting-plugins/) on how to load plugins. + +## Prerequisites for testing + +To test the plugin, you will need: + +- A cedarling policy store with a policy for our gateway. To create this, please follow [these](https://github.com/JanssenProject/jans/wiki/Cedarling-Hello-World-%5BWIP%5D#setup-policy-store) steps. +- An instance of the cedarling sidecar, using the policy store mentioned above. Please follow [these](https://github.com/JanssenProject/jans/wiki/Cedarling-Hello-World-%5BWIP%5D#setup-sidecar) steps. +- For our demo, we will use this sample policy as outlined in the instructions: + ``` + @id("allow_one") + permit( + principal is gatewayDemo::Workload, + action == gatewayDemo::Action::"GET", + resource is gatewayDemo::HTTP_Request + ) + when { + (principal["client_id"]) == "d7f71bea-c38d-4caf-a1ba-e43c74a11a62" + }; + ``` +- A [KrakenD server installation](https://www.krakend.io/docs/overview/installing/). For development purposes, the binary install is recommended. For production setups, the Docker method is recommended. +- The plugin `.so` file for your architecture. For Mac OS hosts, ARM64 is required. +- A configuration file. Sample configuration is provided in [krakend.json](https://github.com/JanssenProject/jans/blob/main/jans-cedarling/cedarling-krakend/krakend.json). + +## Configuration + +See [krakend.json](https://github.com/JanssenProject/jans/blob/main/jans-cedarling/cedarling-krakend/krakend.json) to see an example KrakenD configuration which loads the plugin. The following table describes the plugin-specific configuration keys. These keys are mandatory and must be provided. Additional configuration is described in [KrakenD documentation](https://www.krakend.io/docs/configuration/structure/). + +The `namespace` field in the configuration needs to be the cedar namespace you used when creating the policy store. By default, Agama Lab sets the namespace to the name of the policy store. If you are following the demo, this value is `gatewayDemo`. + +| Field | Type | Example | Description | +|-------|------|---------|-------------| +| path | String | /protected | KrakenD endpoint to protect | +| sidecar_endpoint | String | http://127.0.0.1:5000/cedarling/evaluation | Sidecar evaluation URL | +| namespace | String | gatewayDemo | Cedar namespace being used by the sidecar | + +## Running + +1. Start the cedarling sidecar. The sample config expects the sidecar to be running on port 5000 +2. Place `krakend.json` and the plugin `.so` file in your current working directory +2. Run the KrakenD server: `krakend run -c krakend.json` +3. KrakenD is running on `http://127.0.0.1:8080` +4. Test with no authentication: `curl http://127.0.0.1:8080/protected`. You should get a 403 Forbidden +5. Test with authentication: + +```bash +ACCESS_TOKEN=eyJraWQiOiJjb25uZWN0X2Y5YTAwN2EyLTZkMGItNDkyYS05MGNkLWYwYzliMWMyYjVkYl9zaWdfcnMyNTYiLCJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJxenhuMVNjcmI5bFd0R3hWZWRNQ2t5LVFsX0lMc3BaYVFBNmZ5dVlrdHcwIiwiY29kZSI6IjNlMmEyMDEyLTA5OWMtNDY0Zi04OTBiLTQ0ODE2MGMyYWIyNSIsImlzcyI6Imh0dHBzOi8vYWNjb3VudC5nbHV1Lm9yZyIsInRva2VuX3R5cGUiOiJCZWFyZXIiLCJjbGllbnRfaWQiOiJkN2Y3MWJlYS1jMzhkLTRjYWYtYTFiYS1lNDNjNzRhMTFhNjIiLCJhdWQiOiJkN2Y3MWJlYS1jMzhkLTRjYWYtYTFiYS1lNDNjNzRhMTFhNjIiLCJhY3IiOiJzaW1wbGVfcGFzc3dvcmRfYXV0aCIsIng1dCNTMjU2IjoiIiwibmJmIjoxNzMxOTUzMDMwLCJzY29wZSI6WyJyb2xlIiwib3BlbmlkIiwicHJvZmlsZSIsImVtYWlsIl0sImF1dGhfdGltZSI6MTczMTk1MzAyNywiZXhwIjoxNzMyMTIxNDYwLCJpYXQiOjE3MzE5NTMwMzAsImp0aSI6InVaVWgxaERVUW82UEZrQlBud3BHemciLCJ1c2VybmFtZSI6IkRlZmF1bHQgQWRtaW4gVXNlciIsInN0YXR1cyI6eyJzdGF0dXNfbGlzdCI6eyJpZHgiOjMwNiwidXJpIjoiaHR0cHM6Ly9qYW5zLnRlc3QvamFucy1hdXRoL3Jlc3R2MS9zdGF0dXNfbGlzdCJ9fX0.Pt-Y7F-hfde_WP7ZYwyvvSS11rKYQWGZXTzjH_aJKC5VPxzOjAXqI3Igr6gJLsP1aOd9WJvOPchflZYArctopXMWClbX_TxpmADqyCMsz78r4P450TaMKj-WKEa9cL5KtgnFa0fmhZ1ZWolkDTQ_M00Xr4EIvv4zf-92Wu5fOrdjmsIGFot0jt-12WxQlJFfs5qVZ9P-cDjxvQSrO1wbyKfHQ_txkl1GDATXsw5SIpC5wct92vjAVm5CJNuv_PE8dHAY-KfPTxOuDYBuWI5uA2Yjd1WUFyicbJgcmYzUSVt03xZ0kQX9dxKExwU2YnpDorfwebaAPO7G114Bkw208g + +curl http://127.0.0.1:8080/protected -H "Authorization: Bearer $ACCESS_TOKEN" +``` + +KrakenD is configured to respond with the health check response if authentication succeeds. diff --git a/jans-cedarling/cedarling-krakend/README.md b/jans-cedarling/cedarling-krakend/README.md new file mode 100644 index 00000000000..570759a7104 --- /dev/null +++ b/jans-cedarling/cedarling-krakend/README.md @@ -0,0 +1,137 @@ +# Cedarling KrakenD Plugin + +This is a [KrakenD HTTP server plugin](https://www.krakend.io/docs/extending/http-server-plugins/) that intercepts a call to a selected endpoint, calls the [sidecar](https://github.com/JanssenProject/jans/blob/main/jans-cedarling/flask-sidecar/README.md) and allows access only if the sidecar responds with `true`. + +## Functionality + +When an end user calls the KrakenD endpoint protected by the plugin, the following steps happen: + +1. The plugin intercepts the call, and creates an [AuthZen](https://openid.github.io/authzen/) access evaluation request. +2. The plugin sends the request to the sidecar, which is running on the same host as KrakenD +3. The sidecar calls cedarling's `authz()` interface to check if this call is allowed or not according to the policy store loaded +4. The sidecar responds back to the plugin. If the decision was `true`, the plugin allows the request to pass through to the KrakenD backend. Otherwise, it responds with 403 Forbidden. + +[Sequence Diagram](https://sequencediagram.org/index.html#initialData=C4S2BsFMAIGFICYEMBO4QDsDm0DSKkBrSDAEWgAdwBXLTAKACMB7YYZgWwtVAGMRuGYAGdo9etxR8BSIXHQlgEniH6DgeAsTLKpqmXIDKIBJF6pd09XESp02cbAVCAtAD58REqQBc0AOIAogAq0AD0FCisZsCIADoY0C4AEtAARACC1MAAFswoIABeSKDMGH4AQpCokCjQADwAUgDqwW5p9J7apO7GpuYofgDeacLUjABWMWk+I8AAnhSQM2ktwWkANAlpJiu8tmiYWJvbkcxLepDCMyNIvPvCwgD67NorTa3tAL5fWxhpKCuzGoKH2NzSCyWK0asmEPh8yWCwQACk8AEqQACO1CuwBO-12PlGnEgT12GzSZwuoCu4Jy1VMKBuv22IPAdOYwjxRIAjAAmADsADoAAyioU8zaUko5FYRKKxXixBD4ykK5i8Zjsok5NgUNI-Fn-O6lf6zNIYJAcZZEmEYOE+DJKkBleFxNJBYLug1feh9MyodzwZCHbB+JDZHKFAAUdwez1eJAAvB82n9AcJgaDIEmhXm-iaXRhc3mAJT0JDgDTAFA4+jBuxHXomAODaA1uv+gbuLreYZpfogYRFmbt2uQX29shB5zAftnRXKskYABmzFHO1X699kHAwhgK8r+-rB3sWGb-VQfkPe8gfpb3Y8Wj70BGg+HZVHN-3k+f07cTggIofgACwigAzNAABi+SMCYpgYPQJAIEAA) + +## Downloading + +The cedarling-krakend plugin builds are available via [Janssen](https://github.com/JanssenProject/jans/releases) releases. Please note that builds are architecture, platform, and KrakenD version specific. This means that builds compiled against KrakenD version `2.9.0` will not work on other versions. The build tags are formatted as follows: + +``` +cedarling-krakend---.so +``` + +Use the following table to find which build you need: + +| | amd64 | arm64 | +| - | ----- | ----- | +| Docker | `amd64-builder-2.9.0-0.0.0.so` | `arm64-builder-2.9.0-0.0.0.so` | +| On-premise | `amd64-builder-2.9.0-linux-generic-0.0.0.so` | `arm64-builder-2.9.0-linux-generic-0.0.0.so` | + +If you are running a different version of KrakenD, you can use the following steps to build the plugin yourself. + +## Building + +Krakend recommends building via their builder docker image, to produce builds that match the target architecture and Go version. To build: + +- Clone the Janssen repository + ``` + git clone --filter blob:none --no-checkout https://github.com/JanssenProject/jans + cd jans + git sparse-checkout init --cone + git checkout main + git sparse-checkout set jans-cedarling + cd cedarling-krakend + ``` +- Build the plugin, replacing `` with the KrakenD version you want to build against: + + - For Docker targets: + + ``` + docker run -it -v "$PWD:/app" -w /app krakend/builder: go build -buildmode=plugin -o cedarling-krakend.so . + ``` + + - For on-premise installations: + + ``` + docker run -it -v "$PWD:/app" -w /app krakend/builder:-linux-generic go build -buildmode=plugin -o yourplugin.so . + ``` + + - For ARM64 Docker targets: + + ```bash + docker run -it -v "$PWD:/app" -w /app \ + -e "CGO_ENABLED=1" \ + -e "CC=aarch64-linux-musl-gcc" \ + -e "GOARCH=arm64" \ + -e "GOHOSTARCH=amd64" \ + krakend/builder: \ + go build -ldflags='-extldflags=-fuse-ld=bfd -extld=aarch64-linux-musl-gcc' \ + -buildmode=plugin -o yourplugin.so . + ``` + + - For ARM64 on-premise installs: + + ```bash + docker run -it -v "$PWD:/app" -w /app \ + -e "CGO_ENABLED=1" \ + -e "CC=aarch64-linux-gnu-gcc" \ + -e "GOARCH=arm64" \ + -e "GOHOSTARCH=amd64" \ + krakend/builder:-linux-generic \ + go build -ldflags='-extldflags=-fuse-ld=bfd -extld=aarch64-linux-gnu-gcc' \ + -buildmode=plugin -o yourplugin.so . + ``` + +Check [KrakenD documentation](https://www.krakend.io/docs/extending/injecting-plugins/) on how to load plugins. + +## Prerequisites for testing + +To test the plugin, you will need: + +- A cedarling policy store with a policy for our gateway. To create this, please follow [these](https://github.com/JanssenProject/jans/wiki/Cedarling-Hello-World-%5BWIP%5D#setup-policy-store) steps. +- An instance of the cedarling sidecar, using the policy store mentioned above. Please follow [these](https://github.com/JanssenProject/jans/wiki/Cedarling-Hello-World-%5BWIP%5D#setup-sidecar) steps. +- For our demo, we will use this sample policy as outlined in the instructions: + ``` + @id("allow_one") + permit( + principal is gatewayDemo::Workload, + action == gatewayDemo::Action::"GET", + resource is gatewayDemo::HTTP_Request + ) + when { + (principal["client_id"]) == "d7f71bea-c38d-4caf-a1ba-e43c74a11a62" + }; + ``` +- A [KrakenD server installation](https://www.krakend.io/docs/overview/installing/). For development purposes, the binary install is recommended. For production setups, the Docker method is recommended. +- The plugin `.so` file for your architecture. For Mac OS hosts, ARM64 is required. +- A configuration file. Sample configuration is provided in [krakend.json](https://github.com/JanssenProject/jans/blob/main/jans-cedarling/cedarling-krakend/krakend.json). + +## Configuration + +See [krakend.json](https://github.com/JanssenProject/jans/blob/main/jans-cedarling/cedarling-krakend/krakend.json) to see an example KrakenD configuration which loads the plugin. The following table describes the plugin-specific configuration keys. These keys are mandatory and must be provided. Additional configuration is described in [KrakenD documentation](https://www.krakend.io/docs/configuration/structure/). + +The `namespace` field in the configuration needs to be the cedar namespace you used when creating the policy store. By default, Agama Lab sets the namespace to the name of the policy store. If you are following the demo, this value is `gatewayDemo`. + +| Field | Type | Example | Description | +|-------|------|---------|-------------| +| path | String | /protected | KrakenD endpoint to protect | +| sidecar_endpoint | String | http://127.0.0.1:5000/cedarling/evaluation | Sidecar evaluation URL | +| namespace | String | gatewayDemo | Cedar namespace being used by the sidecar | + +## Running + +1. Start the cedarling sidecar. The sample config expects the sidecar to be running on port 5000 +2. Place `krakend.json` and the plugin `.so` file in your current working directory +2. Run the KrakenD server: `krakend run -c krakend.json` +3. KrakenD is running on `http://127.0.0.1:8080` +4. Test with no authentication: `curl http://127.0.0.1:8080/protected`. You should get a 403 Forbidden +5. Test with authentication: + +```bash +ACCESS_TOKEN=eyJraWQiOiJjb25uZWN0X2Y5YTAwN2EyLTZkMGItNDkyYS05MGNkLWYwYzliMWMyYjVkYl9zaWdfcnMyNTYiLCJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJxenhuMVNjcmI5bFd0R3hWZWRNQ2t5LVFsX0lMc3BaYVFBNmZ5dVlrdHcwIiwiY29kZSI6IjNlMmEyMDEyLTA5OWMtNDY0Zi04OTBiLTQ0ODE2MGMyYWIyNSIsImlzcyI6Imh0dHBzOi8vYWNjb3VudC5nbHV1Lm9yZyIsInRva2VuX3R5cGUiOiJCZWFyZXIiLCJjbGllbnRfaWQiOiJkN2Y3MWJlYS1jMzhkLTRjYWYtYTFiYS1lNDNjNzRhMTFhNjIiLCJhdWQiOiJkN2Y3MWJlYS1jMzhkLTRjYWYtYTFiYS1lNDNjNzRhMTFhNjIiLCJhY3IiOiJzaW1wbGVfcGFzc3dvcmRfYXV0aCIsIng1dCNTMjU2IjoiIiwibmJmIjoxNzMxOTUzMDMwLCJzY29wZSI6WyJyb2xlIiwib3BlbmlkIiwicHJvZmlsZSIsImVtYWlsIl0sImF1dGhfdGltZSI6MTczMTk1MzAyNywiZXhwIjoxNzMyMTIxNDYwLCJpYXQiOjE3MzE5NTMwMzAsImp0aSI6InVaVWgxaERVUW82UEZrQlBud3BHemciLCJ1c2VybmFtZSI6IkRlZmF1bHQgQWRtaW4gVXNlciIsInN0YXR1cyI6eyJzdGF0dXNfbGlzdCI6eyJpZHgiOjMwNiwidXJpIjoiaHR0cHM6Ly9qYW5zLnRlc3QvamFucy1hdXRoL3Jlc3R2MS9zdGF0dXNfbGlzdCJ9fX0.Pt-Y7F-hfde_WP7ZYwyvvSS11rKYQWGZXTzjH_aJKC5VPxzOjAXqI3Igr6gJLsP1aOd9WJvOPchflZYArctopXMWClbX_TxpmADqyCMsz78r4P450TaMKj-WKEa9cL5KtgnFa0fmhZ1ZWolkDTQ_M00Xr4EIvv4zf-92Wu5fOrdjmsIGFot0jt-12WxQlJFfs5qVZ9P-cDjxvQSrO1wbyKfHQ_txkl1GDATXsw5SIpC5wct92vjAVm5CJNuv_PE8dHAY-KfPTxOuDYBuWI5uA2Yjd1WUFyicbJgcmYzUSVt03xZ0kQX9dxKExwU2YnpDorfwebaAPO7G114Bkw208g + +curl http://127.0.0.1:8080/protected -H "Authorization: Bearer $ACCESS_TOKEN" +``` + +KrakenD is configured to respond with the health check response if authentication succeeds. diff --git a/jans-cedarling/cedarling-krakend/go.mod b/jans-cedarling/cedarling-krakend/go.mod new file mode 100644 index 00000000000..68799e0302a --- /dev/null +++ b/jans-cedarling/cedarling-krakend/go.mod @@ -0,0 +1,3 @@ +module cedarling-krakend + +go 1.22.9 diff --git a/jans-cedarling/cedarling-krakend/krakend.json b/jans-cedarling/cedarling-krakend/krakend.json new file mode 100644 index 00000000000..421eb86ff93 --- /dev/null +++ b/jans-cedarling/cedarling-krakend/krakend.json @@ -0,0 +1,43 @@ +{ + "version": 3, + "plugin": { + "pattern": ".so", + "folder": "./" + }, + "endpoints": [ + { + "endpoint": "/test/{id}", + "backend": [ + { + "host": [ + "http://localhost:8080" + ], + "url_pattern": "/__health" + } + ] + }, + { + "endpoint": "/protected", + "backend": [ + { + "host": [ + "http://localhost:8080" + ], + "url_pattern": "/__health" + } + ] + } + ], + "extra_config": { + "plugin/http-server": { + "name": [ + "cedarling-krakend" + ], + "cedarling-krakend": { + "path": "/protected", + "sidecar_endpoint": "http://127.0.0.1:5000/cedarling/evaluation", + "namespace": "gatewayDemo" + } + } + } +} diff --git a/jans-cedarling/cedarling-krakend/main.go b/jans-cedarling/cedarling-krakend/main.go new file mode 100644 index 00000000000..a1f0b54261a --- /dev/null +++ b/jans-cedarling/cedarling-krakend/main.go @@ -0,0 +1,161 @@ +// SPDX-License-Identifier: Apache-2.0 + +package main + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "html" + "io" + "net/http" + "strings" +) + +// pluginName is the plugin name +var pluginName = "cedarling-krakend" + +// HandlerRegisterer is the symbol the plugin loader will try to load. It must implement the Registerer interface +var HandlerRegisterer = registerer(pluginName) + +func (r registerer) RegisterHandlers(f func(name string, handler func(context.Context, map[string]interface{}, http.Handler) (http.Handler, error))) { + f(string(r), r.registerHandlers) +} + +func createPayload(req *http.Request, namespace string) AuthZenPayload { + bearer := req.Header.Get("Authorization") + split_bearer := strings.Split(bearer, " ") + token := split_bearer[1] + var subject_properties = map[string]string{} + subject_properties["access_token"] = token + resource_properties := map[string]map[string]string{} + resource_properties["header"] = map[string]string{} + resource_properties["url"] = map[string]string{} + resource_properties["url"]["host"] = req.Host + resource_properties["url"]["path"] = req.URL.Path + resource_properties["url"]["protocol"] = "http" + payload := AuthZenPayload{ + Subject: Subject{ + Type: "JWT", + Id: "cedarling", + Properties: subject_properties, + }, + Resource: Resource{ + Type: fmt.Sprintf("%s::HTTP_Request", namespace), + Id: "some_id", + Properties: resource_properties, + }, + Action: Action{ + Name: fmt.Sprintf("%s::Action::\"%s\"", namespace, req.Method), + }, + } + return payload +} + +func (r registerer) registerHandlers(_ context.Context, extra map[string]interface{}, h http.Handler) (http.Handler, error) { + // If the plugin requires some configuration, it should be under the name of the plugin. E.g.: + /* + "extra_config":{ + "plugin/http-server":{ + "name":["krakend-server-example"], + "krakend-server-example":{ + "path": "/some-path" + } + } + } + */ + // The config variable contains all the keys you have defined in the configuration + // if the key doesn't exists or is not a map the plugin returns an error and the default handler + config, ok := extra[pluginName].(map[string]interface{}) + if !ok { + return h, errors.New("configuration not found") + } + + // The plugin will look for this path: + path, ok := config["path"].(string) + if !ok { + return h, errors.New("No path provided") + } + sidecar_endpoint, ok := config["sidecar_endpoint"].(string) + if !ok { + return h, errors.New("No sidecar endpoint provided") + } + namespace, ok := config["namespace"].(string) + if !ok { + return h, errors.New("No cedar namespace provided") + } + logger.Debug(fmt.Sprintf("The plugin is now protecting %s\n", path)) + + customHandler := func(w http.ResponseWriter, req *http.Request) { + + // If the requested path is not what we defined, continue. + if req.URL.Path != path { + h.ServeHTTP(w, req) + } + + // The path has to be hijacked: + if req.Header.Get("Authorization") == "" { + http.Error(w, "Authorization not found", http.StatusForbidden) + } else { + payload := createPayload(req, namespace) + body_bytes, _ := json.Marshal(payload) + response, err := http.Post(sidecar_endpoint, "application/json", bytes.NewReader(body_bytes)) + if err != nil { + logger.Warning(err) + http.Error(w, "Forbidden", http.StatusForbidden) + return + } + defer response.Body.Close() + response_bytes, err := io.ReadAll(response.Body) + if err != nil { + logger.Warning(err) + http.Error(w, "Forbidden", http.StatusForbidden) + return + } + var response_json AuthzenResponse + json.Unmarshal(response_bytes, &response_json) + if response_json.Decision == true { + h.ServeHTTP(w, req) + } else { + http.Error(w, "Forbidden", http.StatusForbidden) + } + } + logger.Debug("request:", html.EscapeString(req.URL.Path)) + } + return http.HandlerFunc(customHandler), nil +} + +func main() {} + +// This logger is replaced by the RegisterLogger method to load the one from KrakenD +var logger Logger = noopLogger{} + +func (registerer) RegisterLogger(v interface{}) { + l, ok := v.(Logger) + if !ok { + return + } + logger = l + logger.Debug(fmt.Sprintf("[PLUGIN: %s] Logger loaded", HandlerRegisterer)) +} + +type Logger interface { + Debug(v ...interface{}) + Info(v ...interface{}) + Warning(v ...interface{}) + Error(v ...interface{}) + Critical(v ...interface{}) + Fatal(v ...interface{}) +} + +// Empty logger implementation +type noopLogger struct{} + +func (n noopLogger) Debug(_ ...interface{}) {} +func (n noopLogger) Info(_ ...interface{}) {} +func (n noopLogger) Warning(_ ...interface{}) {} +func (n noopLogger) Error(_ ...interface{}) {} +func (n noopLogger) Critical(_ ...interface{}) {} +func (n noopLogger) Fatal(_ ...interface{}) {} diff --git a/jans-cedarling/cedarling-krakend/types.go b/jans-cedarling/cedarling-krakend/types.go new file mode 100644 index 00000000000..d7953889241 --- /dev/null +++ b/jans-cedarling/cedarling-krakend/types.go @@ -0,0 +1,30 @@ +package main + +type registerer string + +type Subject struct { + Type string `json:"type"` + Id string `json:"id"` + Properties map[string]string `json:"properties,omitempty"` +} +type Resource struct { + Type string `json:"type"` + Id string `json:"id"` + Properties map[string]map[string]string `json:"properties,omitempty"` +} + +type Action struct { + Name string `json:"name"` + Properties map[string]string `json:"properties,omitempty"` +} + +type AuthZenPayload struct { + Subject Subject `json:"subject"` + Resource Resource `json:"resource"` + Action Action `json:"action"` + Context map[string]string `json:"context,omitempty"` +} + +type AuthzenResponse struct { + Decision bool `json:"decision"` +} diff --git a/mkdocs.yml b/mkdocs.yml index 1f91f74db38..d42a5345311 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -412,7 +412,9 @@ nav: - JWT: 'cedarling/cedarling-jwt.md' - Logs: 'cedarling/cedarling-logs.md' - Properties: 'cedarling/cedarling-properties.md' - - Sidecar: 'cedarling/cedarling-sidecar.md' + - Sidecar: + - cedarling/cedarling-sidecar.md + - KrakenD Plugin: cedarling/cedarling-krakend.md - Python: - cedarling/python/README.md - How to use: cedarling/python/usage.md