├── .deepsource.toml ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── feature_request.md │ └── help-and-questions.md ├── label-commenter-config.yml └── workflows │ ├── default.yml │ ├── dependency_review.yml │ ├── go.yml │ ├── labels.yml │ ├── manual_release.yml │ ├── release.yml │ ├── security.yml │ └── upload.yml ├── .gitignore ├── Dockerfile ├── Dockerfile-builder ├── Dockerfile-builder-linux ├── LICENSE ├── Makefile ├── README.md ├── SECURITY.md ├── backend_factory.go ├── builder ├── files │ ├── krakend-logrotate │ ├── krakend.conf-rsyslog │ ├── krakend.init │ ├── krakend.service │ └── rpmmacros ├── scripts │ ├── postrm.deb │ ├── postrm.rpm │ ├── preinst.deb │ ├── preinst.rpm │ ├── prerm.deb │ └── prerm.rpm └── skel │ └── .gitignore ├── cmd ├── krakend-ce │ ├── main.go │ └── schema │ │ ├── .gitignore │ │ └── empty.json └── krakend-integration │ └── main.go ├── deps.sh ├── encoding.go ├── executor.go ├── find_glibc.sh ├── go.mod ├── go.sum ├── handler_factory.go ├── krakend.json ├── plugin.go ├── proxy_factory.go ├── router_engine.go ├── sd.go └── tests ├── fixtures ├── bench.json ├── krakend.json ├── lua │ ├── base64.lua │ ├── collection.lua │ ├── decorator.lua │ └── json.lua └── specs │ ├── backend_301.json │ ├── backend_302.json │ ├── backend_303.json │ ├── backend_307.json │ ├── backend_404.json │ ├── botdetector_1.json │ ├── botdetector_2.json │ ├── botdetector_3.json │ ├── botdetector_4.json │ ├── cel-1.json │ ├── cel-2.json │ ├── cel-3.json │ ├── cel-4.json │ ├── cel-5.json │ ├── cel-6.json │ ├── cel-7.json │ ├── collection.json │ ├── cors_1.json │ ├── cors_2.json │ ├── cors_3.json │ ├── cors_4.json │ ├── cors_5.json │ ├── detail_error.json │ ├── flatmap_1.json │ ├── integration_jsonschema.json │ ├── integration_schema.json │ ├── jsonschema_1.json │ ├── jwt_1.json │ ├── jwt_2.json │ ├── jwt_3.json │ ├── jwt_4.json │ ├── jwt_5.json │ ├── lua_1.json │ ├── lua_2.json │ ├── negotitate_json.json │ ├── negotitate_plain.json │ ├── no-op_1.json │ ├── no-op_2.json │ ├── param_forwarding_1.json │ ├── param_forwarding_2.json │ ├── param_forwarding_3.json │ ├── param_forwarding_4.json │ ├── query_forwarding_1.json │ ├── query_forwarding_2.json │ ├── query_forwarding_3.json │ ├── router_401_1.json │ ├── router_404.json │ ├── router_405.json │ ├── router_redirect.json │ ├── rss.json │ ├── sequential_1.json │ ├── sequential_2.json │ ├── sequential_3.json │ ├── sequential_4.json │ ├── static.json │ ├── timeout.json │ ├── xml_1.json │ └── xml_2.json ├── integration.go ├── integration_example_test.go └── integration_test.go /.deepsource.toml: -------------------------------------------------------------------------------- 1 | version = 1 2 | 3 | [[analyzers]] 4 | name = "shell" 5 | enabled = true 6 | 7 | [[analyzers]] 8 | name = "go" 9 | enabled = true 10 | 11 | [analyzers.meta] 12 | import_root = "github.com/krakendio/krakend-ce" -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 14 | 15 | **Environment info:** 16 | 17 | * KrakenD version: Run `krakend help | grep Version` and copy the output here 18 | * System info: Run `uname -srm` or write `docker` when using containers 19 | * Hardware specs: Number of CPUs, RAM, etc 20 | * Backend technology: Node, PHP, Java, Go, etc. 21 | * Additional environment information: 22 | 23 | **Describe the bug** 24 | A clear and concise description of what the bug is. 25 | 26 | 27 | **Your configuration file**: 28 | 29 | 30 | ```json 31 | { 32 | "version": 3, 33 | ... 34 | } 35 | ``` 36 | 37 | **Commands used** 38 | How did you start the software? 39 | ``` 40 | #Example: 41 | docker run --rm -it -v $PWD:/etc/krakend \ 42 | -e FC_ENABLE=1 \ 43 | -e FC_SETTINGS="/etc/krakend/config/settings" \ 44 | -e FC_PARTIALS="/etc/krakend/config/partials" \ 45 | -e FC_OUT=out.json \ 46 | devopsfaith/krakend \ 47 | run -c /etc/krakend/config/krakend.json -d 48 | ``` 49 | **Expected behavior** 50 | A clear and concise description of what you expected to happen. 51 | 52 | **Logs** 53 | If applicable, any logs you saw in the console and debugging information 54 | 55 | **Additional context** 56 | Add any other context about the problem here. 57 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | **Version of KrakenD you are using** 10 | Get it with: `krakend help | grep Version` 11 | 12 | **Is your feature request related to a problem? Please describe.** 13 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 14 | 15 | **Describe the solution you'd like** 16 | A clear and concise description of what you want to happen. 17 | 18 | **Describe alternatives you've considered** 19 | A clear and concise description of any alternative solutions or features you've considered. 20 | 21 | **Additional context** 22 | Add any other context or screenshots about the feature request here. 23 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/help-and-questions.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Help and questions 3 | about: You are stuck trying to do something, you get unexpected results, or you have a question or suggestion 4 | title: '' 5 | labels: 'question' 6 | assignees: '' 7 | 8 | --- 9 | 14 | 15 | **Environment info:** 16 | 17 | * KrakenD version: Run `krakend help | grep Version` and copy the output here 18 | * System info: Run `uname -srm` or write `docker` when using containers 19 | * Hardware specs: Number of CPUs, RAM, etc 20 | * Backend technology: Node, PHP, Java, Go, etc. 21 | * Additional environment information: 22 | 23 | **Describe what are you trying to do**: 24 | A clear and concise description of what you want to do and what is the expected result. 25 | 26 | **Your configuration file**: 27 | 28 | 29 | ```json 30 | { 31 | "version": 3, 32 | ... 33 | } 34 | ``` 35 | 36 | **Configuration check output**: 37 | Result of `krakend check -dtc krakend.json --lint` command 38 | 39 | ``` 40 | Output of the linter here. 41 | ``` 42 | 43 | **Commands used:** 44 | How did you start the software? 45 | ``` 46 | #Example: 47 | krakend run -d -c krakend.json 48 | 49 | # Or maybe... 50 | docker run --rm -it -v $PWD:/etc/krakend \ 51 | -e FC_ENABLE=1 \ 52 | -e FC_SETTINGS="/etc/krakend/config/settings" \ 53 | -e FC_PARTIALS="/etc/krakend/config/partials" \ 54 | -e FC_OUT=out.json \ 55 | devopsfaith/krakend \ 56 | run -c /etc/krakend/config/krakend.json -d 57 | ``` 58 | 59 | **Logs:** 60 | Logs you saw in the console and debugging information 61 | 62 | **Additional comments:** 63 | -------------------------------------------------------------------------------- /.github/label-commenter-config.yml: -------------------------------------------------------------------------------- 1 | comment: 2 | footer: | 3 | --- 4 | > This is an automated comment. Responding to the bot or mentioning it won't have any effect 5 | 6 | labels: 7 | - name: invalid 8 | labeled: 9 | issue: 10 | body: | 11 | Hi, thanks for bringing this issue to our attention. 12 | 13 | Unfortunately, this issue has been marked invalid and will be closed. 14 | The most common reasons for marking an issue or pull request as invalid is because: 15 | 16 | - It's vague or not clearly actionable 17 | - It contains insufficient details to reproduce 18 | - It's plugin review or custom code review 19 | - It does not use the issue template 20 | - It's unrelated to the project (e.g., related to one of its libraries) 21 | - It does not follow the technical philosophy of the project 22 | - Violates our [Code of Conduct](https://github.com/krakend/.github/blob/main/CODE_OF_CONDUCT.md) 23 | - It's about KrakenD Enterprise functionalities (contact [Enterprise support](https://www.krakend.io/support/) instead) 24 | 25 | You can still make an edit or leave additional comments that lead to reopening this issue. 26 | action: close 27 | pr: 28 | body: | 29 | Hi @{{ pull_request.user.login }}, thanks for having spent the time to code and send an improvement to KrakenD. 30 | 31 | Unfortunately, this pull request has been marked as invalid and will be closed without merging. 32 | The most common reasons for marking an issue or pull request as invalid is because: 33 | 34 | - Contains insufficient details, it's unclear for the reviewer, or it's impossible to move forward without a lot of interaction 35 | - It's unrelated to the project (e.g., related to one of its libraries) 36 | - It does not follow the philosophy of the project 37 | - Violates our [Code of Conduct](https://github.com/krakend/.github/blob/main/CODE_OF_CONDUCT.md) 38 | 39 | You can still make an edit or leave additional comments that lead to reopening this issue. 40 | action: close 41 | - name: wontfix 42 | labeled: 43 | issue: 44 | body: | 45 | Hi, thank you for bringing this issue to our attention. 46 | 47 | Many factors influence our product roadmaps and determine the features, fixes, and suggestions we implement. 48 | When deciding what to prioritize and work on, we combine your feedback and suggestions with insights from our development team, product analytics, research findings, and more. 49 | 50 | This information, combined with our product vision, determines what we implement and its priority order. Unfortunately, we don't foresee this issue progressing any further in the short-medium term, and we are closing it. 51 | 52 | While this issue is now closed, we continue monitoring requests for our future roadmap, **including this one**. 53 | 54 | If you have additional information you would like to provide, please share. 55 | action: close 56 | pr: 57 | body: | 58 | Hi @{{ pull_request.user.login }}, thanks for having spent the time to code and send an improvement to KrakenD. 59 | 60 | When deciding what to accept and include in our product, we are cautious about what we add, and the time our team needs to spend to have it done exemplary, considering all edge cases. As a result, we rarely add features, make changes that a tiny number of users need, or are out-of-scope of the project. For example, we might choose safety over having a specific additional feature that adds complexity we don't see crystal clear. Sometimes "less is more" because we can focus better on crucial functionality. 61 | 62 | Although it's never nice to reject someone's work, after evaluating your code, we think it's better not to merge it or continue putting effort into it on both sides. If this PR is to solve what you considered a bug, our understanding of the functionality does not need to match your thinking. So while this pull request is now closed, **this is not a definitive decision**. You are free to provide additional information that would help us change our minds. 63 | 64 | KrakenD is used in millions of servers every year, and the slightest change has an impact. We are doing it for all community users' benefit and to keep the code's simplicity, philosophy, and maintainability for the long run. 65 | action: close 66 | - name: duplicate 67 | labeled: 68 | issue: 69 | body: An issue like this already exists, please follow it in the other thread 70 | action: close 71 | - name: good first issue 72 | labeled: 73 | issue: 74 | body: This issue is easy for contributing. Everyone can work on this. 75 | - name: spam 76 | labeled: 77 | issue: 78 | body: | 79 | This issue has been **LOCKED** because of spam! 80 | 81 | Please do not spam messages and/or issues on the issue tracker. You may get blocked from this repository for doing so. 82 | action: close 83 | locking: lock 84 | lock_reason: spam 85 | pr: 86 | body: | 87 | This pull-request has been **LOCKED** because of spam! 88 | 89 | Please do not spam messages and/or pull-requests on this project. You may get blocked from this repository for doing so. 90 | action: close 91 | locking: lock 92 | lock_reason: spam -------------------------------------------------------------------------------- /.github/workflows/default.yml: -------------------------------------------------------------------------------- 1 | on: 2 | schedule: 3 | - cron: '0 10 * * *' 4 | name: Issue and PR hygiene 5 | jobs: 6 | stale: 7 | uses: krakend/.github/.github/workflows/stale.yml@main 8 | lock-threads: 9 | uses: krakend/.github/.github/workflows/lock-threads.yml@main 10 | -------------------------------------------------------------------------------- /.github/workflows/dependency_review.yml: -------------------------------------------------------------------------------- 1 | name: 'Dependency Review' 2 | on: [pull_request] 3 | permissions: 4 | contents: read 5 | pull-requests: write 6 | jobs: 7 | dependency-review: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: 'Checkout Repository' 11 | uses: actions/checkout@v4 12 | - name: Dependency Review 13 | uses: actions/dependency-review-action@v4 14 | with: 15 | comment-summary-in-pr: on-failure 16 | -------------------------------------------------------------------------------- /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Go 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | 11 | build: 12 | runs-on: ubuntu-24.04 13 | steps: 14 | - uses: actions/checkout@v3 15 | 16 | - name: Set up Go 17 | uses: actions/setup-go@v3 18 | with: 19 | go-version: "1.24" 20 | 21 | - name: Test 22 | run: make test 23 | -------------------------------------------------------------------------------- /.github/workflows/labels.yml: -------------------------------------------------------------------------------- 1 | name: Label commenter 2 | on: 3 | issues: 4 | types: [labeled, unlabeled] 5 | pull_request_target: 6 | types: [labeled, unlabeled] 7 | jobs: 8 | stale: 9 | uses: krakend/.github/.github/workflows/label-commenter.yml@main 10 | permissions: 11 | issues: write 12 | pull-requests: write 13 | contents: read 14 | -------------------------------------------------------------------------------- /.github/workflows/manual_release.yml: -------------------------------------------------------------------------------- 1 | on: 2 | workflow_dispatch: 3 | inputs: 4 | release-id: 5 | type: string 6 | description: release id destination 7 | required: true 8 | name: Handle Release 9 | jobs: 10 | generate: 11 | name: Create release-artifacts 12 | runs-on: ubuntu-24.04 13 | steps: 14 | - name: Checkout the repository 15 | uses: actions/checkout@master 16 | - name: Import GPG key 17 | id: import_gpg 18 | uses: crazy-max/ghaction-import-gpg@v5 19 | with: 20 | gpg_private_key: ${{ secrets.PGP_SIGNING_KEY }} 21 | fingerprint: "5B270F2E01E375FD9D5635E25DE6FD698AD6FDD2" 22 | - name: List keys 23 | run: gpg -K 24 | - name: Generate the artifacts for Debian/Ubuntu/Redhat/Centos (AMD64/ARM64) 25 | uses: docker://krakend/builder:latest-linux-generic 26 | with: 27 | args: sh -c "git config --global --add safe.directory /github/workspace; 28 | export CGO_ENABLED=1 CC=aarch64-linux-gnu-gcc ARCH=arm64 OS_TAG=_generic-linux GOARCH=arm64 GOHOSTARCH=amd64 EXTRA_LDFLAGS='-extld=aarch64-linux-gnu-gcc'; 29 | make -e build && make -e tgz; 30 | make clean; 31 | export CC= GOARCH=amd64 ARCH=amd64 OS_TAG=_generic-linux EXTRA_LDFLAGS=; 32 | make -e build && make -e tgz;" 33 | - name: Build DEB package (AMD64) 34 | uses: docker://krakend/fpm:deb 35 | with: 36 | entrypoint: /bin/bash 37 | args: -c "make -e deb-release" 38 | - name: Build RPM package (AMD64) 39 | uses: docker://krakend/fpm:rpm 40 | with: 41 | entrypoint: /bin/bash 42 | args: -c "echo '${{ secrets.PGP_SIGNING_KEY }}' > pgp.key; 43 | gpg --import pgp.key; 44 | cp builder/files/rpmmacros /etc/rpm/macros; 45 | make -e rpm-release && 46 | rpmsign --addsign *rpm" 47 | - name: Generate the artifacts for Alpine (AMD64/ARM64) 48 | uses: docker://krakend/builder:latest 49 | with: 50 | args: sh -c "export GOARCH=amd64 ARCH=amd64 OS_TAG=_alpine; 51 | make -e build && make -e tgz; 52 | make clean; 53 | export CGO_ENABLED=1 ARCH=arm64 OS_TAG=_alpine GOARCH=arm64 GOHOSTARCH=amd64; 54 | export CC=aarch64-linux-musl-gcc EXTRA_LDFLAGS='-extldflags=-fuse-ld=bfd -extld=aarch64-linux-musl-gcc'; 55 | make -e build && make -e tgz" 56 | - name: ASC files 57 | run: for i in $(ls *.tar.gz *.deb *.rpm); 58 | do gpg --armor --detach $i; 59 | sha512sum $i >> checksums.txt; 60 | done 61 | - name: Cache artifacts 62 | id: cache 63 | uses: actions/cache/save@v4 64 | with: 65 | path: | 66 | *.tar.gz 67 | *.asc 68 | *.deb 69 | *.rpm 70 | checksums.txt 71 | key: ${{github.ref}}-artifacts 72 | upload: 73 | name: Upload release-artifacts 74 | runs-on: ubuntu-24.04 75 | needs: generate 76 | permissions: 77 | actions: read 78 | packages: write 79 | checks: write 80 | contents: write 81 | statuses: read 82 | steps: 83 | - name: Restore cache 84 | id: cache-restore 85 | uses: actions/cache/restore@v4 86 | with: 87 | path: | 88 | *.tar.gz 89 | *.asc 90 | *.deb 91 | *.rpm 92 | checksums.txt 93 | key: ${{github.ref}}-artifacts 94 | - name: Upload the artifacts 95 | uses: skx/github-action-publish-binaries@master 96 | env: 97 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 98 | with: 99 | releaseId: ${{ github.event.inputs.release-id }} 100 | args: '*.tar.gz *.asc *.deb *.rpm checksums.txt' 101 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | on: 2 | release: 3 | types: [created] 4 | name: Handle Release 5 | jobs: 6 | builder: 7 | name: Generate builder containers 8 | runs-on: ubuntu-24.04 9 | steps: 10 | - name: Checkout the repository 11 | uses: actions/checkout@master 12 | - name: Set the environment variables 13 | run: | 14 | echo "GOLANG_VERSION=$(grep -m 1 GOLANG_VERSION Makefile | sed 's/^.*= //g')" >> $GITHUB_ENV 15 | echo "ALPINE_VERSION=$(grep -m 1 ALPINE_VERSION Makefile | sed 's/^.*= //g')" >> $GITHUB_ENV 16 | - name: Log in to Docker Hub 17 | uses: docker/login-action@v2 18 | with: 19 | username: ${{ secrets.DOCKER_USERNAME }} 20 | password: ${{ secrets.DOCKER_PASSWORD }} 21 | - name: Docker meta 22 | id: meta 23 | uses: docker/metadata-action@v4 24 | with: 25 | images: | 26 | krakend/builder 27 | tags: | 28 | type=semver,pattern={{version}} 29 | type=semver,pattern={{major}}.{{minor}} 30 | type=semver,pattern={{major}} 31 | - name: Docker meta 32 | id: meta-linux-generic 33 | uses: docker/metadata-action@v4 34 | with: 35 | images: | 36 | krakend/builder 37 | flavor: | 38 | suffix=-linux-generic,onlatest=true 39 | tags: | 40 | type=semver,pattern={{version}} 41 | type=semver,pattern={{major}}.{{minor}} 42 | type=semver,pattern={{major}} 43 | - name: Build and push KrakenD plugin builder (Alpine) 44 | id: container-build 45 | uses: docker/build-push-action@v2 46 | with: 47 | context: . 48 | file: Dockerfile-builder 49 | build-args: | 50 | GOLANG_VERSION=${{ env.GOLANG_VERSION }} 51 | ALPINE_VERSION=${{ env.ALPINE_VERSION }} 52 | push: true 53 | tags: ${{ steps.meta.outputs.tags }} 54 | labels: ${{ steps.meta.outputs.labels }} 55 | - name: Build and push KrakenD plugin builder (Linux generic) 56 | id: container-build-linux-generic 57 | uses: docker/build-push-action@v2 58 | with: 59 | context: . 60 | file: Dockerfile-builder-linux 61 | build-args: | 62 | GOLANG_VERSION=${{ env.GOLANG_VERSION }} 63 | push: true 64 | tags: ${{ steps.meta-linux-generic.outputs.tags }} 65 | labels: ${{ steps.meta-linux-generic.outputs.labels }} 66 | generate: 67 | name: Create release-artifacts 68 | runs-on: ubuntu-24.04 69 | needs: builder 70 | steps: 71 | - name: Checkout the repository 72 | uses: actions/checkout@master 73 | - name: Import GPG key 74 | id: import_gpg 75 | uses: crazy-max/ghaction-import-gpg@v5 76 | with: 77 | gpg_private_key: ${{ secrets.PGP_SIGNING_KEY }} 78 | fingerprint: "5B270F2E01E375FD9D5635E25DE6FD698AD6FDD2" 79 | - name: List keys 80 | run: gpg -K 81 | - name: Generate the artifacts for Debian/Ubuntu/Redhat/Centos (AMD64/ARM64) 82 | uses: docker://krakend/builder:latest-linux-generic 83 | with: 84 | args: sh -c "git config --global --add safe.directory /github/workspace; 85 | export CGO_ENABLED=1 CC=aarch64-linux-gnu-gcc ARCH=arm64 OS_TAG=_generic-linux GOARCH=arm64 GOHOSTARCH=amd64 EXTRA_LDFLAGS='-extld=aarch64-linux-gnu-gcc'; 86 | make -e build && make -e tgz; 87 | make clean; 88 | export CC= GOARCH=amd64 ARCH=amd64 OS_TAG=_generic-linux EXTRA_LDFLAGS=; 89 | make -e build && make -e tgz;" 90 | - name: Build DEB package (AMD64) 91 | uses: docker://krakend/fpm:deb 92 | with: 93 | entrypoint: /bin/bash 94 | args: -c "make -e deb-release" 95 | - name: Build RPM package (AMD64) 96 | uses: docker://krakend/fpm:rpm 97 | with: 98 | entrypoint: /bin/bash 99 | args: -c "echo '${{ secrets.PGP_SIGNING_KEY }}' > pgp.key; 100 | gpg --import pgp.key; 101 | cp builder/files/rpmmacros /etc/rpm/macros; 102 | make -e rpm-release && 103 | rpmsign --addsign *rpm" 104 | - name: Generate the artifacts for Alpine (AMD64/ARM64) 105 | uses: docker://krakend/builder:latest 106 | with: 107 | args: sh -c "export GOARCH=amd64 ARCH=amd64 OS_TAG=_alpine; 108 | make -e build && make -e tgz; 109 | make clean; 110 | export CGO_ENABLED=1 ARCH=arm64 OS_TAG=_alpine GOARCH=arm64 GOHOSTARCH=amd64; 111 | export CC=aarch64-linux-musl-gcc EXTRA_LDFLAGS='-extldflags=-fuse-ld=bfd -extld=aarch64-linux-musl-gcc'; 112 | make -e build && make -e tgz" 113 | - name: ASC files 114 | run: for i in $(ls *.tar.gz *.deb *.rpm); 115 | do gpg --armor --detach $i; 116 | sha512sum $i >> checksums.txt; 117 | done 118 | - name: Cache artifacts 119 | id: cache 120 | uses: actions/cache/save@v4 121 | with: 122 | path: | 123 | *.tar.gz 124 | *.asc 125 | *.deb 126 | *.rpm 127 | checksums.txt 128 | key: ${{github.ref}}-artifacts 129 | upload: 130 | name: Upload release-artifacts 131 | runs-on: ubuntu-24.04 132 | needs: generate 133 | permissions: 134 | actions: read 135 | packages: write 136 | checks: write 137 | contents: write 138 | statuses: read 139 | steps: 140 | - name: Restore cache 141 | id: cache-restore 142 | uses: actions/cache/restore@v4 143 | with: 144 | path: | 145 | *.tar.gz 146 | *.asc 147 | *.deb 148 | *.rpm 149 | checksums.txt 150 | key: ${{github.ref}}-artifacts 151 | - name: Upload the artifacts 152 | uses: skx/github-action-publish-binaries@master 153 | env: 154 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 155 | with: 156 | args: '*.tar.gz *.asc *.deb *.rpm checksums.txt' 157 | -------------------------------------------------------------------------------- /.github/workflows/security.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: security 3 | 4 | # Run for all pushes to master and pull requests when Go or YAML files change 5 | on: 6 | push: 7 | branches: 8 | - master 9 | pull_request: 10 | # The branches below must be a subset of the branches above 11 | branches: [ master ] 12 | schedule: 13 | - cron: '23 20 * * 2' 14 | 15 | jobs: 16 | security-repo-scan: 17 | name: security-repo-scan 18 | runs-on: ubuntu-24.04 19 | permissions: 20 | security-events: write 21 | actions: read 22 | contents: read 23 | 24 | steps: 25 | - name: Checkout 26 | uses: actions/checkout@v3 27 | 28 | - name: Run Trivy vulnerability scanner in repo mode 29 | uses: aquasecurity/trivy-action@master 30 | with: 31 | scan-type: 'fs' 32 | security-checks: 'vuln,secret' 33 | ignore-unfixed: true 34 | format: 'sarif' 35 | output: 'trivy-results.sarif' 36 | severity: 'HIGH,CRITICAL' 37 | skip-dirs: 'tests' 38 | 39 | - name: Upload Trivy scan results to GitHub Security tab 40 | uses: github/codeql-action/upload-sarif@v2 41 | with: 42 | sarif_file: 'trivy-results.sarif' 43 | 44 | image-scan: 45 | permissions: 46 | security-events: write 47 | actions: read 48 | contents: read 49 | strategy: 50 | matrix: 51 | config: 52 | - image: krakend/krakend-ce 53 | dockerfile: Dockerfile 54 | runs-on: ubuntu-24.04 55 | steps: 56 | - name: Checkout 57 | uses: actions/checkout@v3 58 | 59 | - name: Set the environment variables 60 | run: | 61 | echo "GOLANG_VERSION=$(grep -m 1 GOLANG_VERSION Makefile | sed 's/^.*= //g')" >> $GITHUB_ENV 62 | echo "ALPINE_VERSION=$(grep -m 1 ALPINE_VERSION Makefile | sed 's/^.*= //g')" >> $GITHUB_ENV 63 | 64 | - name: Docker meta 65 | id: meta 66 | uses: docker/metadata-action@v4 67 | with: 68 | images: | 69 | ${{ matrix.config.image }} 70 | tags: | 71 | type=sha 72 | 73 | - name: "Build image ${{ matrix.config.image }}" 74 | uses: docker/build-push-action@v2 75 | with: 76 | context: . 77 | file: ${{ matrix.config.dockerfile }} 78 | build-args: | 79 | GOLANG_VERSION=${{ env.GOLANG_VERSION }} 80 | ALPINE_VERSION=${{ env.ALPINE_VERSION }} 81 | push: false 82 | load: true 83 | tags: ${{ steps.meta.outputs.tags }} 84 | labels: ${{ steps.meta.outputs.labels }} 85 | 86 | - name: Run Trivy vulnerability scanner in image mode 87 | uses: aquasecurity/trivy-action@master 88 | with: 89 | scan-type: 'image' 90 | image-ref: ${{ steps.meta.outputs.tags }} 91 | security-checks: 'vuln,secret' 92 | ignore-unfixed: true 93 | format: 'sarif' 94 | output: 'trivy-results.sarif' 95 | severity: 'HIGH,CRITICAL' 96 | skip-dirs: 'tests' 97 | 98 | - name: Upload Trivy scan results to GitHub Security tab 99 | uses: github/codeql-action/upload-sarif@v2 100 | with: 101 | sarif_file: 'trivy-results.sarif' -------------------------------------------------------------------------------- /.github/workflows/upload.yml: -------------------------------------------------------------------------------- 1 | on: 2 | workflow_dispatch: 3 | inputs: 4 | cache-ref-key: 5 | type: string 6 | description: cache-ref-key to upload to release page 7 | required: true 8 | release-id: 9 | type: string 10 | description: release id destination 11 | required: true 12 | 13 | name: Upload cache to release page 14 | jobs: 15 | upload: 16 | name: Upload release-artifacts 17 | runs-on: ubuntu-24.04 18 | permissions: 19 | actions: read 20 | packages: write 21 | checks: write 22 | contents: write 23 | statuses: read 24 | steps: 25 | - name: Restore cache 26 | id: cache-restore 27 | uses: actions/cache/restore@v4 28 | with: 29 | path: | 30 | *.tar.gz 31 | *.asc 32 | *.deb 33 | *.rpm 34 | checksums.txt 35 | key: ${{ github.event.inputs.cache-ref-key }} 36 | - name: Upload the artifacts 37 | uses: skx/github-action-publish-binaries@master 38 | env: 39 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 40 | with: 41 | releaseId: ${{ github.event.inputs.release-id }} 42 | args: '*.tar.gz *.asc *.deb *.rpm checksums.txt' 43 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor 2 | bench_res 3 | sec_scan 4 | krakend 5 | krakend-alpine 6 | cmd/krakend-integration/krakend-integration 7 | tests/fixtures/krakend.json.final 8 | .vscode -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | ARG GOLANG_VERSION 2 | ARG ALPINE_VERSION 3 | FROM golang:${GOLANG_VERSION}-alpine${ALPINE_VERSION} as builder 4 | 5 | RUN apk --no-cache --virtual .build-deps add make gcc musl-dev binutils-gold 6 | 7 | COPY . /app 8 | WORKDIR /app 9 | 10 | RUN make build 11 | 12 | 13 | FROM alpine:${ALPINE_VERSION} 14 | 15 | LABEL maintainer="community@krakend.io" 16 | 17 | RUN apk upgrade --no-cache --no-interactive && apk add --no-cache ca-certificates tzdata && \ 18 | adduser -u 1000 -S -D -H krakend && \ 19 | mkdir /etc/krakend && \ 20 | echo '{ "version": 3 }' > /etc/krakend/krakend.json 21 | 22 | COPY --from=builder /app/krakend /usr/bin/krakend 23 | 24 | USER 1000 25 | 26 | WORKDIR /etc/krakend 27 | 28 | ENTRYPOINT [ "/usr/bin/krakend" ] 29 | CMD [ "run", "-c", "/etc/krakend/krakend.json" ] 30 | 31 | EXPOSE 8000 8090 32 | -------------------------------------------------------------------------------- /Dockerfile-builder: -------------------------------------------------------------------------------- 1 | ARG GOLANG_VERSION 2 | ARG ALPINE_VERSION 3 | FROM golang:${GOLANG_VERSION}-alpine${ALPINE_VERSION} 4 | 5 | RUN apk --no-cache --virtual .build-deps add tar make gcc musl-dev binutils-gold 6 | 7 | RUN cd / && wget http://musl.cc/aarch64-linux-musl-cross.tgz && \ 8 | tar zxf aarch64-linux-musl-cross.tgz && rm -f aarch64-linux-musl-cross.tgz 9 | 10 | ENV PATH="$PATH:/aarch64-linux-musl-cross/bin:/aarch64-linux-musl-cross/aarch64-linux-musl/bin" 11 | -------------------------------------------------------------------------------- /Dockerfile-builder-linux: -------------------------------------------------------------------------------- 1 | ARG GOLANG_VERSION 2 | FROM golang:${GOLANG_VERSION}-bullseye 3 | 4 | RUN apt-get update && \ 5 | apt-get install -y gcc make gcc-aarch64-linux-gnu binutils-aarch64-linux-gnu \ 6 | && rm -rf /var/lib/apt/lists/* 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: all build test 2 | 3 | # This Makefile is a simple example that demonstrates usual steps to build a binary that can be run in the same 4 | # architecture that was compiled in. The "ldflags" in the build assure that any needed dependency is included in the 5 | # binary and no external dependencies are needed to run the service. 6 | 7 | BIN_NAME :=krakend 8 | OS := $(shell uname | tr '[:upper:]' '[:lower:]') 9 | MODULE := github.com/krakendio/krakend-ce/v2 10 | VERSION := 2.10.0 11 | SCHEMA_VERSION := $(shell echo "${VERSION}" | cut -d '.' -f 1,2) 12 | GIT_COMMIT := $(shell git rev-parse --short=7 HEAD) 13 | PKGNAME := krakend 14 | LICENSE := Apache 2.0 15 | VENDOR= 16 | URL := http://krakend.io 17 | RELEASE := 0 18 | USER := krakend 19 | ARCH := amd64 20 | DESC := High performance API gateway. Aggregate, filter, manipulate and add middlewares 21 | MAINTAINER := Daniel Ortiz 22 | DOCKER_WDIR := /tmp/fpm 23 | DOCKER_FPM := devopsfaith/fpm 24 | GOLANG_VERSION := 1.24.2 25 | GLIBC_VERSION := $(shell sh find_glibc.sh) 26 | ALPINE_VERSION := 3.21 27 | OS_TAG := 28 | EXTRA_LDFLAGS := 29 | 30 | FPM_OPTS=-s dir -v $(VERSION) -n $(PKGNAME) \ 31 | --license "$(LICENSE)" \ 32 | --vendor "$(VENDOR)" \ 33 | --maintainer "$(MAINTAINER)" \ 34 | --architecture $(ARCH) \ 35 | --url "$(URL)" \ 36 | --description "$(DESC)" \ 37 | --config-files etc/ \ 38 | --verbose 39 | 40 | DEB_OPTS= -t deb --deb-user $(USER) \ 41 | --depends ca-certificates \ 42 | --depends rsyslog \ 43 | --depends logrotate \ 44 | --before-remove builder/scripts/prerm.deb \ 45 | --after-remove builder/scripts/postrm.deb \ 46 | --before-install builder/scripts/preinst.deb 47 | 48 | RPM_OPTS =--rpm-user $(USER) \ 49 | --depends rsyslog \ 50 | --depends logrotate \ 51 | --before-install builder/scripts/preinst.rpm \ 52 | --before-remove builder/scripts/prerm.rpm \ 53 | --after-remove builder/scripts/postrm.rpm 54 | 55 | all: test 56 | 57 | build: cmd/krakend-ce/schema/schema.json 58 | @echo "Building the binary..." 59 | @go get . 60 | @go build -ldflags="-X ${MODULE}/pkg.Version=${VERSION} -X github.com/luraproject/lura/v2/core.KrakendVersion=${VERSION} \ 61 | -X github.com/luraproject/lura/v2/core.GlibcVersion=${GLIBC_VERSION} ${EXTRA_LDFLAGS}" \ 62 | -o ${BIN_NAME} ./cmd/krakend-ce 63 | @echo "You can now use ./${BIN_NAME}" 64 | 65 | test: build 66 | go test -v ./tests 67 | 68 | cmd/krakend-ce/schema/schema.json: 69 | @echo "Fetching v${SCHEMA_VERSION} schema" 70 | @wget -qO $@ https://raw.githubusercontent.com/krakend/krakend-schema/refs/heads/main/v${SCHEMA_VERSION}/krakend.json || wget -qO $@ https://krakend.io/schema/krakend.json 71 | 72 | # Build KrakenD using docker (defaults to whatever the golang container uses) 73 | build_on_docker: docker-builder-linux 74 | docker run --rm -it -v "${PWD}:/app" -w /app krakend/builder:${VERSION}-linux-generic sh -c "git config --global --add safe.directory /app && make -e build" 75 | 76 | # Build the container using the Dockerfile (alpine) 77 | docker: 78 | docker build --no-cache --pull --build-arg GOLANG_VERSION=${GOLANG_VERSION} --build-arg ALPINE_VERSION=${ALPINE_VERSION} -t devopsfaith/krakend:${VERSION} . 79 | 80 | docker-builder: 81 | docker build --no-cache --pull --build-arg GOLANG_VERSION=${GOLANG_VERSION} --build-arg ALPINE_VERSION=${ALPINE_VERSION} -t krakend/builder:${VERSION} -f Dockerfile-builder . 82 | 83 | docker-builder-linux: 84 | docker build --no-cache --pull --build-arg GOLANG_VERSION=${GOLANG_VERSION} -t krakend/builder:${VERSION}-linux-generic -f Dockerfile-builder-linux . 85 | 86 | benchmark: 87 | @mkdir -p bench_res 88 | @touch bench_res/${GIT_COMMIT}.out 89 | @docker run --rm -d --name krakend -v "${PWD}/tests/fixtures:/etc/krakend" -p 8080:8080 devopsfaith/krakend:${VERSION} run -dc /etc/krakend/bench.json 90 | @sleep 2 91 | @docker run --rm -it --link krakend peterevans/vegeta sh -c \ 92 | "echo 'GET http://krakend:8080/test' | vegeta attack -rate=0 -duration=30s -max-workers=300 | tee results.bin | vegeta report" > bench_res/${GIT_COMMIT}.out 93 | @docker stop krakend 94 | @cat bench_res/${GIT_COMMIT}.out 95 | 96 | security_scan: 97 | @mkdir -p sec_scan 98 | @touch sec_scan/${GIT_COMMIT}.out 99 | @docker run --rm -d --name krakend -v "${PWD}/tests/fixtures:/etc/krakend" -p 8080:8080 devopsfaith/krakend:${VERSION} run -dc /etc/krakend/bench.json 100 | @docker run --rm -it --link krakend instrumentisto/nmap --script vuln krakend > sec_scan/${GIT_COMMIT}.out 101 | @docker stop krakend 102 | @cat sec_scan/${GIT_COMMIT}.out 103 | 104 | builder/skel/%/etc/init.d/krakend: builder/files/krakend.init 105 | mkdir -p "$(dir $@)" 106 | cp builder/files/krakend.init "$@" 107 | 108 | builder/skel/%/usr/bin/krakend: krakend 109 | mkdir -p "$(dir $@)" 110 | cp krakend "$@" 111 | 112 | builder/skel/%/etc/krakend/krakend.json: krakend.json 113 | mkdir -p "$(dir $@)" 114 | cp krakend.json "$@" 115 | 116 | builder/skel/%/lib/systemd/system/krakend.service: builder/files/krakend.service 117 | mkdir -p "$(dir $@)" 118 | cp builder/files/krakend.service "$@" 119 | 120 | builder/skel/%/usr/lib/systemd/system/krakend.service: builder/files/krakend.service 121 | mkdir -p "$(dir $@)" 122 | cp builder/files/krakend.service "$@" 123 | 124 | builder/skel/%/etc/rsyslog.d/krakend.conf: builder/files/krakend.conf-rsyslog 125 | mkdir -p "$(dir $@)" 126 | cp builder/files/krakend.conf-rsyslog "$@" 127 | 128 | builder/skel/%/etc/logrotate.d/krakend: builder/files/krakend-logrotate 129 | mkdir -p "$(dir $@)" 130 | cp builder/files/krakend-logrotate "$@" 131 | 132 | .PHONY: tgz 133 | tgz: builder/skel/tgz/usr/bin/krakend 134 | tgz: builder/skel/tgz/etc/krakend/krakend.json 135 | tgz: builder/skel/tgz/etc/init.d/krakend 136 | tar zcvf krakend_${VERSION}_${ARCH}${OS_TAG}.tar.gz -C builder/skel/tgz/ . 137 | 138 | .PHONY: deb 139 | deb: builder/skel/deb/usr/bin/krakend 140 | deb: builder/skel/deb/etc/krakend/krakend.json 141 | deb: builder/skel/deb/etc/rsyslog.d/krakend.conf 142 | deb: builder/skel/deb/etc/logrotate.d/krakend 143 | docker run --rm -it -v "${PWD}:${DOCKER_WDIR}" -w ${DOCKER_WDIR} ${DOCKER_FPM}:deb -t deb ${DEB_OPTS} \ 144 | --iteration ${RELEASE} \ 145 | --deb-systemd builder/files/krakend.service \ 146 | -C builder/skel/deb \ 147 | ${FPM_OPTS} 148 | 149 | .PHONY: rpm 150 | rpm: builder/skel/rpm/usr/lib/systemd/system/krakend.service 151 | rpm: builder/skel/rpm/usr/bin/krakend 152 | rpm: builder/skel/rpm/etc/krakend/krakend.json 153 | rpm: builder/skel/rpm/etc/rsyslog.d/krakend.conf 154 | rpm: builder/skel/rpm/etc/logrotate.d/krakend 155 | docker run --rm -it -v "${PWD}:${DOCKER_WDIR}" -w ${DOCKER_WDIR} ${DOCKER_FPM}:rpm -t rpm ${RPM_OPTS} \ 156 | --iteration ${RELEASE} \ 157 | -C builder/skel/rpm \ 158 | ${FPM_OPTS} 159 | 160 | .PHONY: deb-release 161 | deb-release: builder/skel/deb-release/usr/bin/krakend 162 | deb-release: builder/skel/deb-release/etc/krakend/krakend.json 163 | /usr/local/bin/fpm -t deb ${DEB_OPTS} \ 164 | --iteration ${RELEASE} \ 165 | --deb-systemd builder/files/krakend.service \ 166 | -C builder/skel/deb-release \ 167 | ${FPM_OPTS} 168 | 169 | .PHONY: rpm-release 170 | rpm-release: builder/skel/rpm-release/usr/lib/systemd/system/krakend.service 171 | rpm-release: builder/skel/rpm-release/usr/bin/krakend 172 | rpm-release: builder/skel/rpm-release/etc/krakend/krakend.json 173 | /usr/local/bin/fpm -t rpm ${RPM_OPTS} \ 174 | --iteration ${RELEASE} \ 175 | -C builder/skel/rpm-release \ 176 | ${FPM_OPTS} 177 | 178 | .PHONY: clean 179 | clean: 180 | rm -rf builder/skel/* 181 | rm -f ${BIN_NAME} 182 | rm -rf vendor/ 183 | rm -f cmd/krakend-ce/schema/schema.json 184 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Krakend logo](https://raw.githubusercontent.com/devopsfaith/krakend.io/master/images/logo.png) 2 | 3 | # KrakenD 4 | [![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fkrakendio%2Fkrakend-ce.svg?type=shield)](https://app.fossa.com/projects/git%2Bgithub.com%2Fkrakendio%2Fkrakend-ce?ref=badge_shield) 5 | 6 | KrakenD is an extensible, ultra-high performance API Gateway that helps you effortlessly adopt microservices and secure communications. KrakenD is easy to operate and run and scales out without a single point of failure. 7 | 8 | **KrakenD Community Edition** (or *KrakenD-CE*) is the open-source distribution of [KrakenD](https://www.krakend.io). 9 | 10 | [KrakenD Site](https://www.krakend.io/) | [Documentation](https://www.krakend.io/docs/overview/) | [Blog](https://www.krakend.io/blog/) | [Twitter](https://twitter.com/krakend_io) | [Downloads](https://www.krakend.io/download/) 11 | 12 | ## Benefits 13 | 14 | - **Easy integration** of an ultra-high performance gateway. 15 | - **Effortlessly transition to microservices** and Backend For Frontend implementations. 16 | - **True linear scalability**: Thanks to its **stateless design**, every KrakenD node can operate independently in the cluster without any coordination or centralized persistence. 17 | - **Low operational cost**: +70K reqs/s on a single instance of regular size. Super low memory consumption with high traffic (usually under 50MB w/ +1000 concurrent). Fewer machines. Smaller machines. Lower budget. 18 | - **Platform-agnostic**. Whether you work in a Cloud-native environment (e.g., Kubernetes) or self-hosted on-premises. 19 | - **No vendor lock-in**: Reuse the best existing open-source and proprietary tools rather than having everything in the gateway (telemetry, identity providers, etc.) 20 | - **API Lifecycle**: Using **GitOps** and **declarative configuration**. 21 | - **Decouple clients** from existing services. Create new APIs without changing your existing API contracts. 22 | 23 | ## Technical features 24 | 25 | - **Content aggregation**, composition, and filtering: Create views and mashups of aggregated content from your APIs. 26 | - **Content Manipulation and format transformation**: Change responses, convert transparently from XML to JSON, and vice-versa. 27 | - **Security**: Zero-trust policy, CORS, OAuth, JWT, HSTS, clickjacking protection, HPKP, MIME-Sniffing prevention, XSS protection... 28 | - **Concurrent calls**: Serve content faster than consuming backends directly. 29 | - **SSL** and **HTTP2** ready 30 | - **Throttling**: Limits of usage in the router and proxy layers 31 | - **Multi-layer rate-limiting** for the end-user and between KrakenD and your services, including bursting, load balancing, and circuit breaker. 32 | - **Telemetry** and dashboards of all sorts: Datadog, Zipkin, Jaeger, Prometheus, Grafana... 33 | - **Extensible** with Go plugins, Lua scripts, Martian, or Google CEL spec. 34 | 35 | See the [website](https://www.krakend.io) for more information. 36 | 37 | ## Download 38 | KrakenD is [packaged and distributed in several formats](https://www.krakend.io/download/). You don't need to clone this repo to use KrakenD unless you want to tweak and build the binary yourself. 39 | 40 | ## Run 41 | In its simplest form with the [offical Docker image](https://hub.docker.com/_/krakend): 42 | 43 | docker run -it -p "8080:8080" krakend 44 | 45 | Now see [http://localhost:8080/__health](http://localhost:8080/__health). The gateway is listening. Now *CTRL-C* and replace `/etc/krakend/krakend.json` with your [first configuration](https://designer.krakend.io). 46 | 47 | ## Build 48 | See the required Go version in the `Makefile`, and then: 49 | ``` 50 | make build 51 | ``` 52 | 53 | Or, if you don't have or don't want to install `go`, you can build it using the golang docker container: 54 | 55 | ``` 56 | make build_on_docker 57 | ``` 58 | 59 | 60 | ## License 61 | [![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fkrakendio%2Fkrakend-ce.svg?type=large)](https://app.fossa.com/projects/git%2Bgithub.com%2Fkrakendio%2Fkrakend-ce?ref=badge_large) 62 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | KrakenD Community Edition only fixes the latest version of the software, and does not patch prior versions. 3 | 4 | ## Reporting a Vulnerability 5 | 6 | If you are an existing KrakenD customer or partner, please submit a support ticket or contact KrakenD through any Enterprise channels explaining your findings. 7 | 8 | If you are not a customer, please email security@krakend.io with your discovery. 9 | 10 | As soon as we read and understand your finding we will provide an answer with next steps and possible timelines. 11 | 12 | We want to thank you in advance for the time you have spent to follow this issue, as it helps all open source users. We develop our software in the open with the help of a global community of developers and contributors with whom we share a common understanding and trust in the free exchange of knowledge. 13 | 14 | KrakenD’s policy is to credit and reward all researchers provided they follow responsible disclosure practices: 15 | 16 | They do not publish the vulnerability prior to KrakenD releasing a fix for it. 17 | They do not divulge exact details of the issue, for example, through exploits or proof-of-concept code. 18 | KrakenD does not credit employees of KrakenD for vulnerabilities they have found. 19 | 20 | Current rewards could include (but are not limited to): 21 | 22 | Public acknowledgement in release notes when a fix for reported security bug is issued 23 | Addition to the KrakenD Contributors Github organization 24 | Opportunity to meet with our technical staff 25 | KrakenD swag 26 | 27 | KrakenD DOES NOT provide cash awards for discovered vulnerabilities at this time. 28 | 29 | Thank you 30 | 31 | 32 | -------------------------------------------------------------------------------- /backend_factory.go: -------------------------------------------------------------------------------- 1 | package krakend 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | otellura "github.com/krakend/krakend-otel/lura" 8 | amqp "github.com/krakendio/krakend-amqp/v2" 9 | cel "github.com/krakendio/krakend-cel/v2" 10 | cb "github.com/krakendio/krakend-circuitbreaker/v2/gobreaker/proxy" 11 | httpcache "github.com/krakendio/krakend-httpcache/v2" 12 | lambda "github.com/krakendio/krakend-lambda/v2" 13 | lua "github.com/krakendio/krakend-lua/v2/proxy" 14 | martian "github.com/krakendio/krakend-martian/v2" 15 | metrics "github.com/krakendio/krakend-metrics/v2/gin" 16 | oauth2client "github.com/krakendio/krakend-oauth2-clientcredentials/v2" 17 | opencensus "github.com/krakendio/krakend-opencensus/v2" 18 | pubsub "github.com/krakendio/krakend-pubsub/v2" 19 | ratelimit "github.com/krakendio/krakend-ratelimit/v3/proxy" 20 | "github.com/luraproject/lura/v2/config" 21 | "github.com/luraproject/lura/v2/logging" 22 | "github.com/luraproject/lura/v2/proxy" 23 | "github.com/luraproject/lura/v2/transport/http/client" 24 | httprequestexecutor "github.com/luraproject/lura/v2/transport/http/client/plugin" 25 | ) 26 | 27 | // NewBackendFactory creates a BackendFactory by stacking all the available middlewares: 28 | // - oauth2 client credentials 29 | // - http cache 30 | // - martian 31 | // - pubsub 32 | // - amqp 33 | // - cel 34 | // - lua 35 | // - rate-limit 36 | // - circuit breaker 37 | // - metrics collector 38 | // - opencensus collector 39 | func NewBackendFactory(logger logging.Logger, metricCollector *metrics.Metrics) proxy.BackendFactory { 40 | return NewBackendFactoryWithContext(context.Background(), logger, metricCollector) 41 | } 42 | 43 | func newRequestExecutorFactory(logger logging.Logger) func(*config.Backend) client.HTTPRequestExecutor { 44 | requestExecutorFactory := func(cfg *config.Backend) client.HTTPRequestExecutor { 45 | clientFactory := client.NewHTTPClient 46 | if _, ok := cfg.ExtraConfig[oauth2client.Namespace]; ok { 47 | clientFactory = oauth2client.NewHTTPClient(cfg) 48 | } 49 | 50 | clientFactory = httpcache.NewHTTPClient(cfg, clientFactory) 51 | clientFactory = otellura.InstrumentedHTTPClientFactory(clientFactory, cfg) 52 | // TODO: check what happens if we have both, opencensus and otel enabled ? 53 | return opencensus.HTTPRequestExecutorFromConfig(clientFactory, cfg) 54 | } 55 | return httprequestexecutor.HTTPRequestExecutor(logger, requestExecutorFactory) 56 | } 57 | 58 | func internalNewBackendFactory(ctx context.Context, requestExecutorFactory func(*config.Backend) client.HTTPRequestExecutor, 59 | logger logging.Logger, metricCollector *metrics.Metrics) proxy.BackendFactory { 60 | 61 | backendFactory := martian.NewConfiguredBackendFactory(logger, requestExecutorFactory) 62 | bf := pubsub.NewBackendFactory(ctx, logger, backendFactory) 63 | backendFactory = bf.New 64 | backendFactory = amqp.NewBackendFactory(ctx, logger, backendFactory) 65 | backendFactory = lambda.BackendFactory(logger, backendFactory) 66 | backendFactory = cel.BackendFactory(logger, backendFactory) 67 | backendFactory = lua.BackendFactory(logger, backendFactory) 68 | backendFactory = ratelimit.BackendFactory(logger, backendFactory) 69 | backendFactory = cb.BackendFactory(backendFactory, logger) 70 | backendFactory = metricCollector.BackendFactory("backend", backendFactory) 71 | backendFactory = opencensus.BackendFactory(backendFactory) 72 | backendFactory = otellura.BackendFactory(backendFactory) 73 | return func(remote *config.Backend) proxy.Proxy { 74 | logger.Debug(fmt.Sprintf("[BACKEND: %s] Building the backend pipe", remote.URLPattern)) 75 | return backendFactory(remote) 76 | } 77 | } 78 | 79 | // NewBackendFactoryWithContext creates a BackendFactory by stacking all the available middlewares and injecting the received context 80 | func NewBackendFactoryWithContext(ctx context.Context, logger logging.Logger, metricCollector *metrics.Metrics) proxy.BackendFactory { 81 | requestExecutorFactory := newRequestExecutorFactory(logger) 82 | return internalNewBackendFactory(ctx, requestExecutorFactory, logger, metricCollector) 83 | } 84 | 85 | type backendFactory struct{} 86 | 87 | func (backendFactory) NewBackendFactory(ctx context.Context, l logging.Logger, m *metrics.Metrics) proxy.BackendFactory { 88 | return NewBackendFactoryWithContext(ctx, l, m) 89 | } 90 | -------------------------------------------------------------------------------- /builder/files/krakend-logrotate: -------------------------------------------------------------------------------- 1 | /var/log/krakend.log 2 | { 3 | rotate 7 4 | daily 5 | missingok 6 | delaycompress 7 | compress 8 | postrotate 9 | /usr/lib/rsyslog/rsyslog-rotate 10 | endscript 11 | } 12 | -------------------------------------------------------------------------------- /builder/files/krakend.conf-rsyslog: -------------------------------------------------------------------------------- 1 | local3.* -/var/log/krakend.log -------------------------------------------------------------------------------- /builder/files/krakend.init: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | ### BEGIN INIT INFO 3 | # Provides: krakend 4 | # Required-Start: $local_fs $remote_fs $network $syslog 5 | # Required-Stop: $local_fs $remote_fs $network $syslog 6 | # Default-Start: 2 3 4 5 7 | # Default-Stop: 0 1 6 8 | # Short-Description: Init for KrakenD 9 | # Description: Init and control system for KrakenD 10 | ### END INIT INFO 11 | set -ue 12 | 13 | NAME="krakend" 14 | DESC="krakend" 15 | 16 | . /lib/lsb/init-functions 17 | 18 | PID=/var/run/krakend.pid 19 | 20 | start() { 21 | start-stop-daemon --start --background --quiet \ 22 | --pidfile "$PID" --make-pidfile \ 23 | --exec /usr/bin/krakend run -c /etc/krakend/krakend.json 24 | } 25 | 26 | stop() { 27 | start-stop-daemon --stop --quiet --pidfile "$PID" 28 | } 29 | 30 | case "${1-}" in 31 | start) 32 | echo -n "Starting $DESC: " 33 | start 34 | echo "$NAME." 35 | ;; 36 | stop) 37 | echo -n "Stopping $DESC: " 38 | stop 39 | echo "$NAME." 40 | ;; 41 | restart) 42 | echo -n "Restarting $DESC: " 43 | stop 44 | sleep 1 45 | start 46 | echo "$NAME." 47 | ;; 48 | status) 49 | status_of_proc -p "$PID" "$NAME" "$NAME" 50 | ;; 51 | *) 52 | echo "Usage: $0 {start|stop|restart|status}" >&2 53 | exit 1 54 | ;; 55 | esac 56 | -------------------------------------------------------------------------------- /builder/files/krakend.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Krakend API Gateway 3 | Documentation=http://krakend.io 4 | After=network.target 5 | 6 | [Service] 7 | User=krakend 8 | ExecStart=/usr/bin/krakend run -c /etc/krakend/krakend.json 9 | ExecStop=/bin/kill -s TERM $MAINPID 10 | # Having non-zero Limit*s causes performance problems due to accounting overhead 11 | # in the kernel. We recommend using cgroups to do container-local accounting. 12 | LimitNOFILE=infinity 13 | LimitNPROC=infinity 14 | LimitCORE=infinity 15 | # Uncomment TasksMax if your systemd version supports it. 16 | # Only systemd 226 and above support this version. 17 | #TasksMax=infinity 18 | TimeoutStartSec=0 19 | KillMode=process 20 | 21 | [Install] 22 | WantedBy=multi-user.target 23 | -------------------------------------------------------------------------------- /builder/files/rpmmacros: -------------------------------------------------------------------------------- 1 | %_gpg_name 5DE6FD698AD6FDD2 -------------------------------------------------------------------------------- /builder/scripts/postrm.deb: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | USERNAME=krakend 4 | 5 | if [ -x "$(command -v deluser)" ]; then 6 | deluser --quiet --system $USERNAME > /dev/null || true 7 | else 8 | echo >&2 "not removing $USERNAME system account because deluser command was not found" 9 | fi 10 | -------------------------------------------------------------------------------- /builder/scripts/postrm.rpm: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | USERNAME=krakend 4 | 5 | if [ "$1" = "0" ]; then 6 | getent passwd $USERNAME && userdel $USERNAME > /dev/null 7 | fi 8 | exit 0 9 | -------------------------------------------------------------------------------- /builder/scripts/preinst.deb: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | USER=krakend 6 | 7 | if ! getent passwd $USER >/dev/null ; then 8 | adduser --system --disabled-login --gecos "KrakenD" --shell /bin/false --no-create-home $USER > /dev/null 9 | fi 10 | 11 | if [ -x "/etc/init.d/krakend" ] || [ -e "/etc/init/krakend.conf" ]; then 12 | service krakend stop || exit $? 13 | fi 14 | -------------------------------------------------------------------------------- /builder/scripts/preinst.rpm: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if [ "$1" = "1" ]; then 4 | getent passwd krakend > /dev/null || \ 5 | useradd -r -M -s /sbin/nologin -c "krakend service account" krakend 6 | fi 7 | -------------------------------------------------------------------------------- /builder/scripts/prerm.deb: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | if [ -x "/etc/init.d/krakend" ] || [ -e "/etc/init/krakend.conf" ]; then 6 | invoke-rc.d krakend stop || exit $? 7 | fi 8 | -------------------------------------------------------------------------------- /builder/scripts/prerm.rpm: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | if [ -e "/etc/init/krakend.conf" ]; then 6 | initctl stop krakend > /dev/null 7 | fi 8 | -------------------------------------------------------------------------------- /builder/skel/.gitignore: -------------------------------------------------------------------------------- 1 | ubuntu-* 2 | debian-* 3 | bin/ 4 | el*/ 5 | -------------------------------------------------------------------------------- /cmd/krakend-ce/main.go: -------------------------------------------------------------------------------- 1 | // Krakend-ce sets up a complete KrakenD API Gateway ready to serve 2 | 3 | package main 4 | 5 | import ( 6 | "context" 7 | "embed" 8 | "log" 9 | "os" 10 | "os/signal" 11 | "syscall" 12 | 13 | krakend "github.com/krakendio/krakend-ce/v2" 14 | cmd "github.com/krakendio/krakend-cobra/v2" 15 | flexibleconfig "github.com/krakendio/krakend-flexibleconfig/v2" 16 | viper "github.com/krakendio/krakend-viper/v2" 17 | "github.com/luraproject/lura/v2/config" 18 | ) 19 | 20 | const ( 21 | fcPartials = "FC_PARTIALS" 22 | fcTemplates = "FC_TEMPLATES" 23 | fcSettings = "FC_SETTINGS" 24 | fcPath = "FC_OUT" 25 | fcEnable = "FC_ENABLE" 26 | ) 27 | 28 | //go:embed schema 29 | var embedSchema embed.FS 30 | 31 | func main() { 32 | sigs := make(chan os.Signal, 1) 33 | signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) 34 | ctx, cancel := context.WithCancel(context.Background()) 35 | defer cancel() 36 | 37 | go func() { 38 | select { 39 | case sig := <-sigs: 40 | log.Println("Signal intercepted:", sig) 41 | cancel() 42 | case <-ctx.Done(): 43 | } 44 | }() 45 | 46 | krakend.RegisterEncoders() 47 | 48 | for key, alias := range aliases { 49 | config.ExtraConfigAlias[alias] = key 50 | } 51 | 52 | var cfg config.Parser 53 | cfg = viper.New() 54 | if os.Getenv(fcEnable) != "" { 55 | cfg = flexibleconfig.NewTemplateParser(flexibleconfig.Config{ 56 | Parser: cfg, 57 | Partials: os.Getenv(fcPartials), 58 | Settings: os.Getenv(fcSettings), 59 | Path: os.Getenv(fcPath), 60 | Templates: os.Getenv(fcTemplates), 61 | }) 62 | } 63 | 64 | var rawSchema string 65 | schema, err := embedSchema.ReadFile("schema/schema.json") 66 | if err == nil { 67 | rawSchema = string(schema) 68 | } 69 | 70 | commandsToLoad := []cmd.Command{ 71 | cmd.RunCommand, 72 | cmd.NewCheckCmd(rawSchema), 73 | cmd.PluginCommand, 74 | cmd.VersionCommand, 75 | cmd.AuditCommand, 76 | krakend.NewTestPluginCmd(), 77 | } 78 | 79 | cmd.DefaultRoot = cmd.NewRoot(cmd.RootCommand, commandsToLoad...) 80 | cmd.DefaultRoot.Cmd.CompletionOptions.DisableDefaultCmd = true 81 | 82 | cmd.Execute(cfg, krakend.NewExecutor(ctx)) 83 | } 84 | 85 | var aliases = map[string]string{ 86 | "github_com/devopsfaith/krakend/transport/http/server/handler": "plugin/http-server", 87 | "github.com/devopsfaith/krakend/transport/http/client/executor": "plugin/http-client", 88 | "github.com/devopsfaith/krakend/proxy/plugin": "plugin/req-resp-modifier", 89 | "github.com/devopsfaith/krakend/proxy": "proxy", 90 | "github_com/luraproject/lura/router/gin": "router", 91 | 92 | "github.com/devopsfaith/krakend-httpcache": "qos/http-cache", 93 | "github.com/devopsfaith/krakend-circuitbreaker/gobreaker": "qos/circuit-breaker", 94 | 95 | "github.com/devopsfaith/krakend-oauth2-clientcredentials": "auth/client-credentials", 96 | "github.com/devopsfaith/krakend-jose/validator": "auth/validator", 97 | "github.com/devopsfaith/krakend-jose/signer": "auth/signer", 98 | "github_com/devopsfaith/bloomfilter": "auth/revoker", 99 | 100 | "github_com/devopsfaith/krakend-botdetector": "security/bot-detector", 101 | "github_com/devopsfaith/krakend-httpsecure": "security/http", 102 | "github_com/devopsfaith/krakend-cors": "security/cors", 103 | 104 | "github.com/devopsfaith/krakend-cel": "validation/cel", 105 | "github.com/devopsfaith/krakend-jsonschema": "validation/json-schema", 106 | 107 | "github.com/devopsfaith/krakend-amqp/agent": "async/amqp", 108 | 109 | "github.com/devopsfaith/krakend-amqp/consume": "backend/amqp/consumer", 110 | "github.com/devopsfaith/krakend-amqp/produce": "backend/amqp/producer", 111 | "github.com/devopsfaith/krakend-lambda": "backend/lambda", 112 | "github.com/devopsfaith/krakend-pubsub/publisher": "backend/pubsub/publisher", 113 | "github.com/devopsfaith/krakend-pubsub/subscriber": "backend/pubsub/subscriber", 114 | "github.com/devopsfaith/krakend/transport/http/client/graphql": "backend/graphql", 115 | "github.com/devopsfaith/krakend/http": "backend/http", 116 | 117 | "github_com/devopsfaith/krakend-gelf": "telemetry/gelf", 118 | "github_com/devopsfaith/krakend-gologging": "telemetry/logging", 119 | "github_com/devopsfaith/krakend-logstash": "telemetry/logstash", 120 | "github_com/devopsfaith/krakend-metrics": "telemetry/metrics", 121 | "github_com/letgoapp/krakend-influx": "telemetry/influx", 122 | "github_com/devopsfaith/krakend-opencensus": "telemetry/opencensus", 123 | 124 | "github.com/devopsfaith/krakend-lua/router": "modifier/lua-endpoint", 125 | "github.com/devopsfaith/krakend-lua/proxy": "modifier/lua-proxy", 126 | "github.com/devopsfaith/krakend-lua/proxy/backend": "modifier/lua-backend", 127 | "github.com/devopsfaith/krakend-martian": "modifier/martian", 128 | } 129 | -------------------------------------------------------------------------------- /cmd/krakend-ce/schema/.gitignore: -------------------------------------------------------------------------------- 1 | *.json 2 | !empty.json -------------------------------------------------------------------------------- /cmd/krakend-ce/schema/empty.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /cmd/krakend-integration/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "os" 7 | 8 | "github.com/krakendio/krakend-ce/v2/tests" 9 | ) 10 | 11 | func main() { 12 | flag.Parse() 13 | 14 | runner, tcs, err := tests.NewIntegration(nil, nil, nil) 15 | if err != nil { 16 | fmt.Println(err) 17 | os.Exit(1) 18 | return 19 | } 20 | 21 | errors := 0 22 | 23 | for _, tc := range tcs { 24 | if err := runner.Check(tc); err != nil { 25 | errors++ 26 | fmt.Printf("%s: %s\n", tc.Name, err.Error()) 27 | continue 28 | } 29 | fmt.Printf("%s: ok\n", tc.Name) 30 | } 31 | fmt.Printf("%d test completed\n", len(tcs)) 32 | runner.Close() 33 | 34 | if errors == 0 { 35 | return 36 | } 37 | 38 | fmt.Printf("%d test failed\n", errors) 39 | os.Exit(1) 40 | } 41 | -------------------------------------------------------------------------------- /deps.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | FILE=/tmp/krakend_ce_deps.txt 4 | 5 | go list -m -u all > "$FILE" 6 | 7 | OUTPUT=$(grep -r "\[" "$FILE" | grep krakend | sed 's/\[//g' | sed 's/\]//g' | awk '{print "go get", $1"@"$3 }') 8 | 9 | if [ "$OUTPUT" != "" ]; then 10 | echo "$OUTPUT" 11 | echo "go mod tidy" 12 | exit 1 13 | fi 14 | 15 | echo "all deps up to date." 16 | -------------------------------------------------------------------------------- /encoding.go: -------------------------------------------------------------------------------- 1 | package krakend 2 | 3 | import ( 4 | rss "github.com/krakendio/krakend-rss/v2" 5 | xml "github.com/krakendio/krakend-xml/v2" 6 | ginxml "github.com/krakendio/krakend-xml/v2/gin" 7 | "github.com/luraproject/lura/v2/router/gin" 8 | ) 9 | 10 | // RegisterEncoders registers all the available encoders 11 | func RegisterEncoders() { 12 | xml.Register() 13 | rss.Register() 14 | 15 | gin.RegisterRender(xml.Name, ginxml.Render) 16 | } 17 | -------------------------------------------------------------------------------- /executor.go: -------------------------------------------------------------------------------- 1 | package krakend 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "io" 7 | "net/http" 8 | "os" 9 | "time" 10 | 11 | "github.com/gin-gonic/gin" 12 | "github.com/go-contrib/uuid" 13 | "golang.org/x/sync/errgroup" 14 | 15 | kotel "github.com/krakend/krakend-otel" 16 | otellura "github.com/krakend/krakend-otel/lura" 17 | otelgin "github.com/krakend/krakend-otel/router/gin" 18 | krakendbf "github.com/krakendio/bloomfilter/v2/krakend" 19 | asyncamqp "github.com/krakendio/krakend-amqp/v2/async" 20 | audit "github.com/krakendio/krakend-audit" 21 | cel "github.com/krakendio/krakend-cel/v2" 22 | cmd "github.com/krakendio/krakend-cobra/v2" 23 | cors "github.com/krakendio/krakend-cors/v2/gin" 24 | gelf "github.com/krakendio/krakend-gelf/v2" 25 | gologging "github.com/krakendio/krakend-gologging/v2" 26 | influxdb "github.com/krakendio/krakend-influx/v2" 27 | jose "github.com/krakendio/krakend-jose/v2" 28 | logstash "github.com/krakendio/krakend-logstash/v2" 29 | metrics "github.com/krakendio/krakend-metrics/v2/gin" 30 | opencensus "github.com/krakendio/krakend-opencensus/v2" 31 | _ "github.com/krakendio/krakend-opencensus/v2/exporter/datadog" 32 | _ "github.com/krakendio/krakend-opencensus/v2/exporter/influxdb" 33 | _ "github.com/krakendio/krakend-opencensus/v2/exporter/jaeger" 34 | _ "github.com/krakendio/krakend-opencensus/v2/exporter/ocagent" 35 | _ "github.com/krakendio/krakend-opencensus/v2/exporter/prometheus" 36 | _ "github.com/krakendio/krakend-opencensus/v2/exporter/stackdriver" 37 | _ "github.com/krakendio/krakend-opencensus/v2/exporter/xray" 38 | _ "github.com/krakendio/krakend-opencensus/v2/exporter/zipkin" 39 | pubsub "github.com/krakendio/krakend-pubsub/v2" 40 | usage "github.com/krakendio/krakend-usage/v2" 41 | "github.com/luraproject/lura/v2/async" 42 | "github.com/luraproject/lura/v2/config" 43 | "github.com/luraproject/lura/v2/core" 44 | "github.com/luraproject/lura/v2/logging" 45 | "github.com/luraproject/lura/v2/proxy" 46 | router "github.com/luraproject/lura/v2/router/gin" 47 | "github.com/luraproject/lura/v2/sd/dnssrv" 48 | serverhttp "github.com/luraproject/lura/v2/transport/http/server" 49 | server "github.com/luraproject/lura/v2/transport/http/server/plugin" 50 | ) 51 | 52 | // NewExecutor returns an executor for the cmd package. The executor initalizes the entire gateway by 53 | // registering the components and composing a RouterFactory wrapping all the middlewares. 54 | func NewExecutor(ctx context.Context) cmd.Executor { 55 | eb := new(ExecutorBuilder) 56 | return eb.NewCmdExecutor(ctx) 57 | } 58 | 59 | // PluginLoader defines the interface for the collaborator responsible of starting the plugin loaders 60 | // Deprecated: Use PluginLoaderWithContext 61 | type PluginLoader interface { 62 | Load(folder, pattern string, logger logging.Logger) 63 | } 64 | 65 | // PluginLoaderWithContext defines the interface for the collaborator responsible of starting the plugin loaders 66 | type PluginLoaderWithContext interface { 67 | LoadWithContext(ctx context.Context, folder, pattern string, logger logging.Logger) 68 | } 69 | 70 | // SubscriberFactoriesRegister registers all the required subscriber factories from the available service 71 | // discover components and adapters and returns a service register function. 72 | // The service register function will register the service by the given name and port to all the available 73 | // service discover clients 74 | type SubscriberFactoriesRegister interface { 75 | Register(context.Context, config.ServiceConfig, logging.Logger) func(string, int) 76 | } 77 | 78 | // TokenRejecterFactory returns a jose.ChainedRejecterFactory containing all the required jose.RejecterFactory. 79 | // It also should setup and manage any service related to the management of the revocation process, if required. 80 | type TokenRejecterFactory interface { 81 | NewTokenRejecter(context.Context, config.ServiceConfig, logging.Logger, func(string, int)) (jose.ChainedRejecterFactory, error) 82 | } 83 | 84 | // MetricsAndTracesRegister registers the defined observability components and returns a metrics collector, 85 | // if required. 86 | type MetricsAndTracesRegister interface { 87 | Register(context.Context, config.ServiceConfig, logging.Logger) *metrics.Metrics 88 | } 89 | 90 | // EngineFactory returns a gin engine, ready to be passed to the KrakenD RouterFactory 91 | type EngineFactory interface { 92 | NewEngine(config.ServiceConfig, router.EngineOptions) *gin.Engine 93 | } 94 | 95 | // ProxyFactory returns a KrakenD proxy factory, ready to be passed to the KrakenD RouterFactory 96 | type ProxyFactory interface { 97 | NewProxyFactory(logging.Logger, proxy.BackendFactory, *metrics.Metrics) proxy.Factory 98 | } 99 | 100 | // BackendFactory returns a KrakenD backend factory, ready to be passed to the KrakenD proxy factory 101 | type BackendFactory interface { 102 | NewBackendFactory(context.Context, logging.Logger, *metrics.Metrics) proxy.BackendFactory 103 | } 104 | 105 | // HandlerFactory returns a KrakenD router handler factory, ready to be passed to the KrakenD RouterFactory 106 | type HandlerFactory interface { 107 | NewHandlerFactory(logging.Logger, *metrics.Metrics, jose.RejecterFactory) router.HandlerFactory 108 | } 109 | 110 | // LoggerFactory returns a KrakenD Logger factory, ready to be passed to the KrakenD RouterFactory 111 | type LoggerFactory interface { 112 | NewLogger(config.ServiceConfig) (logging.Logger, io.Writer, error) 113 | } 114 | 115 | // RunServer defines the interface of a function used by the KrakenD router to start the service 116 | type RunServer func(context.Context, config.ServiceConfig, http.Handler) error 117 | 118 | // RunServerFactory returns a RunServer with several wraps around the injected one 119 | type RunServerFactory interface { 120 | NewRunServer(logging.Logger, router.RunServerFunc) RunServer 121 | } 122 | 123 | // AgentStarter defines a type that starts a set of agents 124 | type AgentStarter interface { 125 | Start( 126 | context.Context, 127 | []*config.AsyncAgent, 128 | logging.Logger, 129 | chan<- string, 130 | proxy.Factory, 131 | ) func() error 132 | } 133 | 134 | // ExecutorBuilder is a composable builder. Every injected property is used by the NewCmdExecutor method. 135 | type ExecutorBuilder struct { 136 | // PluginLoader is deprecated: Use PluginLoaderWithContext 137 | PluginLoader PluginLoader 138 | PluginLoaderWithContext PluginLoaderWithContext 139 | LoggerFactory LoggerFactory 140 | SubscriberFactoriesRegister SubscriberFactoriesRegister 141 | TokenRejecterFactory TokenRejecterFactory 142 | MetricsAndTracesRegister MetricsAndTracesRegister 143 | EngineFactory EngineFactory 144 | 145 | ProxyFactory ProxyFactory 146 | BackendFactory BackendFactory 147 | HandlerFactory HandlerFactory 148 | RunServerFactory RunServerFactory 149 | AgentStarterFactory AgentStarter 150 | 151 | Middlewares []gin.HandlerFunc 152 | } 153 | 154 | // NewCmdExecutor returns an executor for the cmd package. The executor initializes the entire gateway by 155 | // delegating most of the tasks to the injected collaborators. They register the components and 156 | // compose a RouterFactory wrapping all the middlewares. 157 | // Every nil collaborator is replaced by the default one offered by this package. 158 | func (e *ExecutorBuilder) NewCmdExecutor(ctx context.Context) cmd.Executor { 159 | e.checkCollaborators() 160 | 161 | return func(cfg config.ServiceConfig) { 162 | cfg.Normalize() 163 | 164 | logger, gelfWriter, gelfErr := e.LoggerFactory.NewLogger(cfg) 165 | if gelfErr != nil { 166 | return 167 | } 168 | 169 | logger.Info(fmt.Sprintf("Starting KrakenD v%s", core.KrakendVersion)) 170 | startReporter(ctx, logger, cfg) 171 | 172 | if wd, err := os.Getwd(); err == nil { 173 | logger.Info("Working directory is", wd) 174 | } 175 | 176 | dnssrv.SetTTL(cfg.DNSCacheTTL) 177 | 178 | if cfg.Plugin != nil { 179 | e.PluginLoaderWithContext.LoadWithContext(ctx, cfg.Plugin.Folder, cfg.Plugin.Pattern, logger) 180 | } 181 | 182 | metricCollector := e.MetricsAndTracesRegister.Register(ctx, cfg, logger) 183 | if metricsAndTracesCloser, ok := e.MetricsAndTracesRegister.(io.Closer); ok { 184 | defer metricsAndTracesCloser.Close() 185 | } 186 | 187 | // Initializes the global cache for the JWK clients if enabled in the config 188 | if err := jose.SetGlobalCacher(logger, cfg.ExtraConfig); err != nil && err != jose.ErrNoValidatorCfg { 189 | logger.Error("[SERVICE: JOSE]", err.Error()) 190 | } 191 | tokenRejecterFactory, err := e.TokenRejecterFactory.NewTokenRejecter( 192 | ctx, 193 | cfg, 194 | logger, 195 | e.SubscriberFactoriesRegister.Register(ctx, cfg, logger), 196 | ) 197 | if err != nil && err != krakendbf.ErrNoConfig { 198 | logger.Warning("[SERVICE: Bloomfilter]", err.Error()) 199 | } 200 | 201 | bpf := e.BackendFactory.NewBackendFactory(ctx, logger, metricCollector) 202 | pf := e.ProxyFactory.NewProxyFactory(logger, bpf, metricCollector) 203 | // we move the proxy factory out of the default proxy factory to make 204 | // sure that is always the outer middleware and that wraps any internal 205 | // proxy layer middleware: 206 | pf = otellura.ProxyFactory(pf) 207 | 208 | agentPing := make(chan string, len(cfg.AsyncAgents)) 209 | 210 | handlerF := e.HandlerFactory.NewHandlerFactory(logger, metricCollector, tokenRejecterFactory) 211 | handlerF = otelgin.New(handlerF) 212 | 213 | runServerChain := serverhttp.RunServerWithLoggerFactory(logger) 214 | runServerChain = otellura.GlobalRunServer(logger, runServerChain) 215 | runServerChain = router.RunServerFunc(e.RunServerFactory.NewRunServer(logger, runServerChain)) 216 | 217 | // setup the krakend router 218 | routerFactory := router.NewFactory(router.Config{ 219 | Engine: e.EngineFactory.NewEngine(cfg, router.EngineOptions{ 220 | Logger: logger, 221 | Writer: gelfWriter, 222 | Health: (<-chan string)(agentPing), 223 | }), 224 | ProxyFactory: pf, 225 | Middlewares: e.Middlewares, 226 | Logger: logger, 227 | HandlerFactory: handlerF, 228 | RunServer: runServerChain, 229 | }) 230 | 231 | // start the engines 232 | logger.Info("Starting the KrakenD instance") 233 | 234 | if len(cfg.AsyncAgents) == 0 { 235 | routerFactory.NewWithContext(ctx).Run(cfg) 236 | return 237 | } 238 | 239 | // start the async agents in the same error group as the router 240 | g, gctx := errgroup.WithContext(ctx) 241 | gctx, closeGroupCtx := context.WithCancel(gctx) 242 | 243 | if cfg.SequentialStart { 244 | waitAgents := e.AgentStarterFactory.Start(gctx, cfg.AsyncAgents, logger, (chan<- string)(agentPing), pf) 245 | g.Go(waitAgents) 246 | } else { 247 | g.Go(func() error { 248 | return e.AgentStarterFactory.Start(gctx, cfg.AsyncAgents, logger, (chan<- string)(agentPing), pf)() 249 | }) 250 | } 251 | 252 | g.Go(func() error { 253 | logger.Info("[SERVICE: Gin] Building the router") 254 | routerFactory.NewWithContext(ctx).Run(cfg) 255 | closeGroupCtx() 256 | return nil 257 | }) 258 | 259 | g.Wait() 260 | } 261 | } 262 | 263 | func (e *ExecutorBuilder) checkCollaborators() { 264 | if e.PluginLoader == nil { 265 | e.PluginLoader = new(pluginLoader) 266 | } 267 | if e.PluginLoaderWithContext == nil { 268 | e.PluginLoaderWithContext = new(pluginLoader) 269 | } 270 | if e.SubscriberFactoriesRegister == nil { 271 | e.SubscriberFactoriesRegister = new(registerSubscriberFactories) 272 | } 273 | if e.TokenRejecterFactory == nil { 274 | e.TokenRejecterFactory = new(BloomFilterJWT) 275 | } 276 | if e.MetricsAndTracesRegister == nil { 277 | e.MetricsAndTracesRegister = new(MetricsAndTraces) 278 | } 279 | if e.EngineFactory == nil { 280 | e.EngineFactory = new(engineFactory) 281 | } 282 | if e.ProxyFactory == nil { 283 | e.ProxyFactory = new(proxyFactory) 284 | } 285 | if e.BackendFactory == nil { 286 | e.BackendFactory = new(backendFactory) 287 | } 288 | if e.HandlerFactory == nil { 289 | e.HandlerFactory = new(handlerFactory) 290 | } 291 | if e.LoggerFactory == nil { 292 | e.LoggerFactory = new(LoggerBuilder) 293 | } 294 | if e.RunServerFactory == nil { 295 | e.RunServerFactory = new(DefaultRunServerFactory) 296 | } 297 | if e.AgentStarterFactory == nil { 298 | e.AgentStarterFactory = async.AgentStarter([]async.Factory{asyncamqp.StartAgent}) 299 | } 300 | } 301 | 302 | // DefaultRunServerFactory creates the default RunServer by wrapping the injected RunServer 303 | // with the plugin loader and the CORS module 304 | type DefaultRunServerFactory struct{} 305 | 306 | func (*DefaultRunServerFactory) NewRunServer(l logging.Logger, next router.RunServerFunc) RunServer { 307 | return RunServer(server.New( 308 | l, 309 | server.RunServer(cors.NewRunServerWithLogger(cors.RunServer(next), l)), 310 | )) 311 | } 312 | 313 | // LoggerBuilder is the default BuilderFactory implementation. 314 | type LoggerBuilder struct{} 315 | 316 | // NewLogger sets up the logging components as defined at the configuration. 317 | func (LoggerBuilder) NewLogger(cfg config.ServiceConfig) (logging.Logger, io.Writer, error) { 318 | var writers []io.Writer 319 | gelfWriter, gelfErr := gelf.NewWriter(cfg.ExtraConfig) 320 | if gelfErr == nil { 321 | writers = append(writers, gelfWriterWrapper{gelfWriter}) 322 | gologging.SetFormatterSelector(func(w io.Writer) string { 323 | switch w.(type) { 324 | case gelfWriterWrapper: 325 | return "%{message}" 326 | default: 327 | return gologging.DefaultPattern 328 | } 329 | }) 330 | } else { 331 | gelfWriter = nil 332 | } 333 | 334 | logger, gologgingErr := logstash.NewLogger(cfg.ExtraConfig) 335 | 336 | if gologgingErr != nil { 337 | logger, gologgingErr = gologging.NewLogger(cfg.ExtraConfig, writers...) 338 | 339 | if gologgingErr != nil { 340 | var err error 341 | logger, err = logging.NewLogger("DEBUG", os.Stdout, "KRAKEND") 342 | if err != nil { 343 | return logger, gelfWriter, err 344 | } 345 | if gologgingErr != gologging.ErrWrongConfig { 346 | logger.Error("[SERVICE: Logging] Unable to create the logger:", gologgingErr.Error()) 347 | } 348 | } 349 | } 350 | 351 | if gelfErr != nil && gelfErr != gelf.ErrWrongConfig { 352 | logger.Error("[SERVICE: Logging][GELF] Unable to create the writer:", gelfErr.Error()) 353 | } 354 | 355 | if gologgingErr == nil { 356 | logger.Debug("[SERVICE: telemetry/logging] Improved logging started.") 357 | } 358 | 359 | return logger, gelfWriter, nil 360 | } 361 | 362 | // BloomFilterJWT is the default TokenRejecterFactory implementation. 363 | type BloomFilterJWT struct{} 364 | 365 | // NewTokenRejecter registers the bloomfilter component and links it to a token rejecter. Then it returns a chained 366 | // rejecter factory with the created token rejecter and other based on the CEL component. 367 | func (BloomFilterJWT) NewTokenRejecter(ctx context.Context, cfg config.ServiceConfig, l logging.Logger, reg func(n string, p int)) (jose.ChainedRejecterFactory, error) { 368 | rejecter, err := krakendbf.Register(ctx, "krakend-bf", cfg, l, reg) 369 | 370 | return jose.ChainedRejecterFactory([]jose.RejecterFactory{ 371 | jose.RejecterFactoryFunc(func(_ logging.Logger, _ *config.EndpointConfig) jose.Rejecter { 372 | return jose.RejecterFunc(rejecter.RejectToken) 373 | }), 374 | jose.RejecterFactoryFunc(func(l logging.Logger, cfg *config.EndpointConfig) jose.Rejecter { 375 | if r := cel.NewRejecter(l, cfg); r != nil { 376 | return r 377 | } 378 | return jose.FixedRejecter(false) 379 | }), 380 | }), err 381 | } 382 | 383 | // MetricsAndTraces is the default implementation of the MetricsAndTracesRegister interface. 384 | type MetricsAndTraces struct { 385 | shutdownFn func() 386 | } 387 | 388 | // Register registers the metrics, influx and opencensus packages as required by the given configuration. 389 | func (m *MetricsAndTraces) Register(ctx context.Context, cfg config.ServiceConfig, l logging.Logger) *metrics.Metrics { 390 | metricCollector := metrics.New(ctx, cfg.ExtraConfig, l) 391 | 392 | if err := influxdb.New(ctx, cfg.ExtraConfig, metricCollector, l); err != nil { 393 | if err != influxdb.ErrNoConfig { 394 | l.Warning("[SERVICE: InfluxDB]", err.Error()) 395 | } 396 | } else { 397 | l.Debug("[SERVICE: InfluxDB] Service correctly registered") 398 | } 399 | 400 | if err := opencensus.Register(ctx, cfg, append(opencensus.DefaultViews, pubsub.OpenCensusViews...)...); err != nil { 401 | if err != opencensus.ErrNoConfig { 402 | l.Warning("[SERVICE: OpenCensus]", err.Error()) 403 | } 404 | } else { 405 | l.Debug("[SERVICE: OpenCensus] Service correctly registered") 406 | } 407 | 408 | if shutdownFn, err := kotel.Register(ctx, l, cfg); err == nil { 409 | m.shutdownFn = shutdownFn 410 | } else { 411 | l.Error(fmt.Sprintf("[SERVICE: OpenTelemetry] cannot register exporters: %s", err.Error())) 412 | } 413 | 414 | return metricCollector 415 | } 416 | 417 | func (m *MetricsAndTraces) Close() { 418 | if m.shutdownFn != nil { 419 | m.shutdownFn() 420 | } 421 | } 422 | 423 | const ( 424 | usageDisable = "USAGE_DISABLE" 425 | usageDelay = 5 * time.Second 426 | ) 427 | 428 | func startReporter(ctx context.Context, logger logging.Logger, cfg config.ServiceConfig) { 429 | logPrefix := "[SERVICE: Telemetry]" 430 | if os.Getenv(usageDisable) == "1" { 431 | return 432 | } 433 | 434 | clusterID, err := cfg.Hash() 435 | if err != nil { 436 | logger.Debug(logPrefix, "Unable to create the Cluster ID hash:", err.Error()) 437 | return 438 | } 439 | 440 | go func() { 441 | time.Sleep(usageDelay) 442 | 443 | serverID := uuid.NewV4().String() 444 | logger.Debug(logPrefix, "Registering usage stats for Cluster ID", clusterID) 445 | 446 | s := audit.Parse(&cfg) 447 | a, _ := audit.Marshal(&s) 448 | if err := usage.Report( 449 | ctx, 450 | usage.Options{ 451 | ClusterID: clusterID, 452 | ServerID: serverID, 453 | Version: core.KrakendVersion, 454 | UserAgent: core.KrakendUserAgent, 455 | ExtraPayload: a, 456 | Client: &http.Client{Transport: serverhttp.NewTransport(cfg, logger)}, 457 | }, 458 | nil, 459 | ); err != nil { 460 | logger.Debug(logPrefix, "Unable to create the usage report client:", err.Error()) 461 | } 462 | }() 463 | } 464 | 465 | type gelfWriterWrapper struct { 466 | io.Writer 467 | } 468 | -------------------------------------------------------------------------------- /find_glibc.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | OS=$(uname) 4 | GLIBC=UNKNOWN-0.0.0 5 | 6 | get_os_version() { 7 | . /etc/os-release 8 | os_release="${ID}-${VERSION_ID}" 9 | } 10 | 11 | case $OS in 12 | Linux*) 13 | if ldd --version 2>&1 | grep -i musl > /dev/null; then 14 | get_os_version 15 | GLIBC="MUSL-$(ldd --version 2>&1 | grep Version | cut -d\ -f2)_($os_release)" 16 | else 17 | get_os_version 18 | GLIBC="GLIBC-$(ldd --version 2>&1 | grep ^ldd | awk '{print $(NF)}')_($os_release)" 19 | fi 20 | ;; 21 | Darwin*) 22 | GLIBC=DARWIN-$(sw_vers | grep ProductVersion: | tr -s '\t' | cut -d$'\t' -f2) 23 | ;; 24 | *) 25 | ;; 26 | esac 27 | 28 | echo "$GLIBC" -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/krakendio/krakend-ce/v2 2 | 3 | go 1.24 4 | 5 | toolchain go1.24.2 6 | 7 | require ( 8 | github.com/gin-gonic/gin v1.9.1 9 | github.com/go-contrib/uuid v1.2.0 10 | github.com/krakend/krakend-otel v0.10.2 11 | github.com/krakendio/bloomfilter/v2 v2.0.4 12 | github.com/krakendio/krakend-amqp/v2 v2.1.0 13 | github.com/krakendio/krakend-audit v0.2.2 14 | github.com/krakendio/krakend-botdetector/v2 v2.2.1 15 | github.com/krakendio/krakend-cel/v2 v2.1.1 16 | github.com/krakendio/krakend-circuitbreaker/v2 v2.0.1 17 | github.com/krakendio/krakend-cobra/v2 v2.5.2 18 | github.com/krakendio/krakend-cors/v2 v2.1.5 19 | github.com/krakendio/krakend-flexibleconfig/v2 v2.2.2 20 | github.com/krakendio/krakend-gelf/v2 v2.0.1 21 | github.com/krakendio/krakend-gologging/v2 v2.0.3 22 | github.com/krakendio/krakend-httpcache/v2 v2.1.0 23 | github.com/krakendio/krakend-httpsecure/v2 v2.1.2 24 | github.com/krakendio/krakend-influx/v2 v2.1.1 25 | github.com/krakendio/krakend-jose/v2 v2.8.1 26 | github.com/krakendio/krakend-jsonschema/v2 v2.0.3 27 | github.com/krakendio/krakend-lambda/v2 v2.0.3 28 | github.com/krakendio/krakend-logstash/v2 v2.0.2 29 | github.com/krakendio/krakend-lua/v2 v2.7.1 30 | github.com/krakendio/krakend-martian/v2 v2.1.1 31 | github.com/krakendio/krakend-metrics/v2 v2.0.3 32 | github.com/krakendio/krakend-oauth2-clientcredentials/v2 v2.1.1 33 | github.com/krakendio/krakend-opencensus/v2 v2.1.2 34 | github.com/krakendio/krakend-pubsub/v2 v2.1.1 35 | github.com/krakendio/krakend-ratelimit/v3 v3.2.3 36 | github.com/krakendio/krakend-rss/v2 v2.0.3 37 | github.com/krakendio/krakend-usage/v2 v2.0.0 38 | github.com/krakendio/krakend-viper/v2 v2.0.1 39 | github.com/krakendio/krakend-xml/v2 v2.1.2 40 | github.com/luraproject/lura/v2 v2.10.0 41 | github.com/spf13/cobra v1.8.1 42 | github.com/xeipuuv/gojsonschema v1.2.1-0.20200424115421-065759f9c3d7 43 | golang.org/x/sync v0.12.0 44 | ) 45 | 46 | require ( 47 | cloud.google.com/go v0.115.0 // indirect 48 | cloud.google.com/go/auth v0.8.1 // indirect 49 | cloud.google.com/go/auth/oauth2adapt v0.2.4 // indirect 50 | cloud.google.com/go/compute/metadata v0.5.0 // indirect 51 | cloud.google.com/go/iam v1.1.13 // indirect 52 | cloud.google.com/go/kms v1.18.5 // indirect 53 | cloud.google.com/go/longrunning v0.5.12 // indirect 54 | cloud.google.com/go/monitoring v1.20.4 // indirect 55 | cloud.google.com/go/pubsub v1.41.0 // indirect 56 | cloud.google.com/go/trace v1.10.12 // indirect 57 | contrib.go.opencensus.io/exporter/aws v0.0.0-20230502192102-15967c811cec // indirect 58 | contrib.go.opencensus.io/exporter/jaeger v0.2.1 // indirect 59 | contrib.go.opencensus.io/exporter/ocagent v0.7.0 // indirect 60 | contrib.go.opencensus.io/exporter/prometheus v0.4.2 // indirect 61 | contrib.go.opencensus.io/exporter/stackdriver v0.13.14 // indirect 62 | contrib.go.opencensus.io/exporter/zipkin v0.1.2 // indirect 63 | github.com/Azure/azure-amqp-common-go/v3 v3.2.3 // indirect 64 | github.com/Azure/azure-sdk-for-go/sdk/azcore v1.14.0 // indirect 65 | github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0 // indirect 66 | github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect 67 | github.com/Azure/azure-sdk-for-go/sdk/keyvault/azkeys v0.10.0 // indirect 68 | github.com/Azure/azure-sdk-for-go/sdk/keyvault/internal v0.7.1 // indirect 69 | github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus v1.7.1 // indirect 70 | github.com/Azure/go-amqp v1.0.5 // indirect 71 | github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 // indirect 72 | github.com/DataDog/datadog-go v4.8.3+incompatible // indirect 73 | github.com/DataDog/opencensus-go-exporter-datadog v0.0.0-20220622145613-731d59e8b567 // indirect 74 | github.com/IBM/sarama v1.43.1 // indirect 75 | github.com/Masterminds/goutils v1.1.1 // indirect 76 | github.com/Masterminds/semver/v3 v3.2.1 // indirect 77 | github.com/Masterminds/sprig/v3 v3.2.3 // indirect 78 | github.com/Microsoft/go-winio v0.6.1 // indirect 79 | github.com/PuerkitoBio/goquery v1.8.1 // indirect 80 | github.com/alecthomas/chroma v0.10.0 // indirect 81 | github.com/andybalholm/cascadia v1.3.2 // indirect 82 | github.com/antlr4-go/antlr/v4 v4.13.0 // indirect 83 | github.com/aws/aws-sdk-go v1.55.5 // indirect 84 | github.com/aws/aws-sdk-go-v2 v1.30.3 // indirect 85 | github.com/aws/aws-sdk-go-v2/config v1.27.27 // indirect 86 | github.com/aws/aws-sdk-go-v2/credentials v1.17.27 // indirect 87 | github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.11 // indirect 88 | github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.15 // indirect 89 | github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.15 // indirect 90 | github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 // indirect 91 | github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3 // indirect 92 | github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.17 // indirect 93 | github.com/aws/aws-sdk-go-v2/service/kms v1.35.3 // indirect 94 | github.com/aws/aws-sdk-go-v2/service/sns v1.31.3 // indirect 95 | github.com/aws/aws-sdk-go-v2/service/sqs v1.34.3 // indirect 96 | github.com/aws/aws-sdk-go-v2/service/sso v1.22.4 // indirect 97 | github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.4 // indirect 98 | github.com/aws/aws-sdk-go-v2/service/sts v1.30.3 // indirect 99 | github.com/aws/smithy-go v1.20.3 // indirect 100 | github.com/beorn7/perks v1.0.1 // indirect 101 | github.com/bytedance/sonic v1.12.5 // indirect 102 | github.com/bytedance/sonic/loader v0.2.0 // indirect 103 | github.com/catalinc/hashcash v0.0.0-20161205220751-e6bc29ff4de9 // indirect 104 | github.com/cenkalti/backoff/v3 v3.2.2 // indirect 105 | github.com/cenkalti/backoff/v4 v4.3.0 // indirect 106 | github.com/census-instrumentation/opencensus-proto v0.4.1 // indirect 107 | github.com/cespare/xxhash/v2 v2.3.0 // indirect 108 | github.com/clbanning/mxj v1.8.4 // indirect 109 | github.com/cloudwego/base64x v0.1.4 // indirect 110 | github.com/cloudwego/iasm v0.2.0 // indirect 111 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect 112 | github.com/dlclark/regexp2 v1.11.4 // indirect 113 | github.com/eapache/go-resiliency v1.6.0 // indirect 114 | github.com/eapache/go-xerial-snappy v0.0.0-20230731223053-c322873962e3 // indirect 115 | github.com/eapache/queue v1.1.0 // indirect 116 | github.com/fatih/color v1.17.0 // indirect 117 | github.com/felixge/httpsnoop v1.0.4 // indirect 118 | github.com/fsnotify/fsnotify v1.7.0 // indirect 119 | github.com/gabriel-vasile/mimetype v1.4.7 // indirect 120 | github.com/gin-contrib/sse v0.1.0 // indirect 121 | github.com/go-jose/go-jose/v3 v3.0.4 // indirect 122 | github.com/go-jose/go-jose/v4 v4.0.5 // indirect 123 | github.com/go-kit/log v0.2.1 // indirect 124 | github.com/go-logfmt/logfmt v0.6.0 // indirect 125 | github.com/go-logr/logr v1.4.2 // indirect 126 | github.com/go-logr/stdr v1.2.2 // indirect 127 | github.com/go-playground/locales v0.14.1 // indirect 128 | github.com/go-playground/universal-translator v0.18.1 // indirect 129 | github.com/go-playground/validator/v10 v10.23.0 // indirect 130 | github.com/goccy/go-json v0.10.4 // indirect 131 | github.com/golang-jwt/jwt/v5 v5.2.2 // indirect 132 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect 133 | github.com/golang/protobuf v1.5.4 // indirect 134 | github.com/golang/snappy v0.0.4 // indirect 135 | github.com/google/cel-go v0.20.0 // indirect 136 | github.com/google/martian v2.1.1-0.20190517191504-25dcb96d9e51+incompatible // indirect 137 | github.com/google/s2a-go v0.1.8 // indirect 138 | github.com/google/uuid v1.6.0 // indirect 139 | github.com/google/wire v0.6.0 // indirect 140 | github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect 141 | github.com/googleapis/gax-go/v2 v2.13.0 // indirect 142 | github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect 143 | github.com/hashicorp/errwrap v1.1.0 // indirect 144 | github.com/hashicorp/go-cleanhttp v0.5.2 // indirect 145 | github.com/hashicorp/go-multierror v1.1.1 // indirect 146 | github.com/hashicorp/go-retryablehttp v0.7.7 // indirect 147 | github.com/hashicorp/go-rootcerts v1.0.2 // indirect 148 | github.com/hashicorp/go-secure-stdlib/parseutil v0.1.8 // indirect 149 | github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect 150 | github.com/hashicorp/go-sockaddr v1.0.6 // indirect 151 | github.com/hashicorp/go-uuid v1.0.3 // indirect 152 | github.com/hashicorp/golang-lru v1.0.2 // indirect 153 | github.com/hashicorp/hcl v1.0.1-vault-5 // indirect 154 | github.com/hashicorp/vault/api v1.14.0 // indirect 155 | github.com/huandu/xstrings v1.4.0 // indirect 156 | github.com/imdario/mergo v0.3.16 // indirect 157 | github.com/inconshreveable/mousetrap v1.1.0 // indirect 158 | github.com/influxdata/influxdb v1.11.6 // indirect 159 | github.com/jcmturner/aescts/v2 v2.0.0 // indirect 160 | github.com/jcmturner/dnsutils/v2 v2.0.0 // indirect 161 | github.com/jcmturner/gofork v1.7.6 // indirect 162 | github.com/jcmturner/gokrb5/v8 v8.4.4 // indirect 163 | github.com/jcmturner/rpc/v2 v2.0.3 // indirect 164 | github.com/jmespath/go-jmespath v0.4.0 // indirect 165 | github.com/json-iterator/go v1.1.12 // indirect 166 | github.com/klauspost/compress v1.18.0 // indirect 167 | github.com/klauspost/cpuid/v2 v2.2.9 // indirect 168 | github.com/kpacha/opencensus-influxdb v0.0.0-20180520162117-1b490a38de4c // indirect 169 | github.com/krakend/go-auth0/v2 v2.0.3 // indirect 170 | github.com/krakend/lru v0.0.0-20250121172718-0e3a6eab620d // indirect 171 | github.com/krakendio/binder v0.0.0-20241115150014-29ceb1a9414b // indirect 172 | github.com/krakendio/flatmap v1.1.1 // indirect 173 | github.com/krakendio/httpcache v0.0.0-20221129153752-65a87a5c2bc5 // indirect 174 | github.com/kylelemons/godebug v1.1.0 // indirect 175 | github.com/leodido/go-urn v1.4.0 // indirect 176 | github.com/magiconair/properties v1.8.7 // indirect 177 | github.com/mattn/go-colorable v0.1.13 // indirect 178 | github.com/mattn/go-isatty v0.0.20 // indirect 179 | github.com/mitchellh/copystructure v1.2.0 // indirect 180 | github.com/mitchellh/go-homedir v1.1.0 // indirect 181 | github.com/mitchellh/mapstructure v1.5.0 // indirect 182 | github.com/mitchellh/reflectwalk v1.0.2 // indirect 183 | github.com/mmcdole/gofeed v1.2.1 // indirect 184 | github.com/mmcdole/goxpp v1.1.0 // indirect 185 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 186 | github.com/modern-go/reflect2 v1.0.2 // indirect 187 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 188 | github.com/nats-io/nats-server/v2 v2.10.27 // indirect 189 | github.com/nats-io/nats.go v1.39.1 // indirect 190 | github.com/nats-io/nkeys v0.4.10 // indirect 191 | github.com/nats-io/nuid v1.0.1 // indirect 192 | github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 // indirect 193 | github.com/openzipkin/zipkin-go v0.2.5 // indirect 194 | github.com/pelletier/go-toml v1.9.5 // indirect 195 | github.com/pelletier/go-toml/v2 v2.2.3 // indirect 196 | github.com/philhofer/fwd v1.1.2 // indirect 197 | github.com/pierrec/lz4/v4 v4.1.21 // indirect 198 | github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect 199 | github.com/prometheus/client_golang v1.20.2 // indirect 200 | github.com/prometheus/client_model v0.6.1 // indirect 201 | github.com/prometheus/common v0.55.0 // indirect 202 | github.com/prometheus/procfs v0.15.1 // indirect 203 | github.com/prometheus/prometheus v0.54.0 // indirect 204 | github.com/prometheus/statsd_exporter v0.26.1 // indirect 205 | github.com/rabbitmq/amqp091-go v1.9.0 // indirect 206 | github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect 207 | github.com/rs/cors v1.11.1 // indirect 208 | github.com/rs/cors/wrapper/gin v0.0.0-20240830163046-1084d89a1692 // indirect 209 | github.com/ryanuber/go-glob v1.0.0 // indirect 210 | github.com/santhosh-tekuri/jsonschema/v6 v6.0.1 // indirect 211 | github.com/shopspring/decimal v1.3.1 // indirect 212 | github.com/sony/gobreaker v0.5.0 // indirect 213 | github.com/spf13/afero v1.11.0 // indirect 214 | github.com/spf13/cast v1.6.0 // indirect 215 | github.com/spf13/jwalterweatherman v1.1.0 // indirect 216 | github.com/spf13/pflag v1.0.5 // indirect 217 | github.com/spf13/viper v1.12.0 // indirect 218 | github.com/stoewer/go-strcase v1.2.0 // indirect 219 | github.com/streadway/amqp v1.1.0 // indirect 220 | github.com/subosito/gotenv v1.6.0 // indirect 221 | github.com/tinylib/msgp v1.1.8 // indirect 222 | github.com/tmthrgd/atomics v0.0.0-20190904060638-dc7a5fcc7e0d // indirect 223 | github.com/tmthrgd/go-bitset v0.0.0-20190904054048-394d9a556c05 // indirect 224 | github.com/tmthrgd/go-bitwise v0.0.0-20190904053232-1430ee983fca // indirect 225 | github.com/tmthrgd/go-byte-test v0.0.0-20190904060354-2794345b9929 // indirect 226 | github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect 227 | github.com/tmthrgd/go-memset v0.0.0-20190904060434-6fb7a21f88f1 // indirect 228 | github.com/tmthrgd/go-popcount v0.0.0-20190904054823-afb1ace8b04f // indirect 229 | github.com/twitchyliquid64/golang-asm v0.15.1 // indirect 230 | github.com/uber/jaeger-client-go v2.28.0+incompatible // indirect 231 | github.com/ugorji/go/codec v1.2.12 // indirect 232 | github.com/unrolled/secure v1.15.0 // indirect 233 | github.com/valyala/fastrand v1.1.0 // indirect 234 | github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect 235 | github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect 236 | github.com/yuin/gopher-lua v1.1.1 // indirect 237 | go.opencensus.io v0.24.0 // indirect 238 | go.opentelemetry.io/auto/sdk v1.1.0 // indirect 239 | go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.53.0 // indirect 240 | go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 // indirect 241 | go.opentelemetry.io/otel v1.33.0 // indirect 242 | go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.28.0 // indirect 243 | go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.28.0 // indirect 244 | go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0 // indirect 245 | go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.28.0 // indirect 246 | go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.28.0 // indirect 247 | go.opentelemetry.io/otel/exporters/prometheus v0.47.0 // indirect 248 | go.opentelemetry.io/otel/metric v1.33.0 // indirect 249 | go.opentelemetry.io/otel/sdk v1.28.0 // indirect 250 | go.opentelemetry.io/otel/sdk/metric v1.28.0 // indirect 251 | go.opentelemetry.io/otel/trace v1.33.0 // indirect 252 | go.opentelemetry.io/proto/otlp v1.3.1 // indirect 253 | gocloud.dev v0.39.0 // indirect 254 | gocloud.dev/pubsub/kafkapubsub v0.37.0 // indirect 255 | gocloud.dev/pubsub/natspubsub v0.37.0 // indirect 256 | gocloud.dev/pubsub/rabbitpubsub v0.37.0 // indirect 257 | gocloud.dev/secrets/hashivault v0.39.0 // indirect 258 | golang.org/x/arch v0.12.0 // indirect 259 | golang.org/x/crypto v0.36.0 // indirect 260 | golang.org/x/exp v0.0.0-20240409090435-93d18d7e34b8 // indirect 261 | golang.org/x/mod v0.19.0 // indirect 262 | golang.org/x/net v0.38.0 // indirect 263 | golang.org/x/oauth2 v0.22.0 // indirect 264 | golang.org/x/sys v0.31.0 // indirect 265 | golang.org/x/text v0.23.0 // indirect 266 | golang.org/x/time v0.10.0 // indirect 267 | golang.org/x/tools v0.23.0 // indirect 268 | golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9 // indirect 269 | google.golang.org/api v0.191.0 // indirect 270 | google.golang.org/genproto v0.0.0-20240812133136-8ffd90a71988 // indirect 271 | google.golang.org/genproto/googleapis/api v0.0.0-20240812133136-8ffd90a71988 // indirect 272 | google.golang.org/genproto/googleapis/rpc v0.0.0-20240812133136-8ffd90a71988 // indirect 273 | google.golang.org/grpc v1.66.0 // indirect 274 | google.golang.org/protobuf v1.36.3 // indirect 275 | gopkg.in/DataDog/dd-trace-go.v1 v1.62.0 // indirect 276 | gopkg.in/Graylog2/go-gelf.v2 v2.0.0-20191017102106-1550ee647df0 // indirect 277 | gopkg.in/ini.v1 v1.67.0 // indirect 278 | gopkg.in/yaml.v2 v2.4.0 // indirect 279 | gopkg.in/yaml.v3 v3.0.1 // indirect 280 | ) 281 | -------------------------------------------------------------------------------- /handler_factory.go: -------------------------------------------------------------------------------- 1 | package krakend 2 | 3 | import ( 4 | "fmt" 5 | 6 | botdetector "github.com/krakendio/krakend-botdetector/v2/gin" 7 | jose "github.com/krakendio/krakend-jose/v2" 8 | ginjose "github.com/krakendio/krakend-jose/v2/gin" 9 | lua "github.com/krakendio/krakend-lua/v2/router/gin" 10 | metrics "github.com/krakendio/krakend-metrics/v2/gin" 11 | opencensus "github.com/krakendio/krakend-opencensus/v2/router/gin" 12 | ratelimit "github.com/krakendio/krakend-ratelimit/v3/router/gin" 13 | "github.com/luraproject/lura/v2/config" 14 | "github.com/luraproject/lura/v2/logging" 15 | "github.com/luraproject/lura/v2/proxy" 16 | router "github.com/luraproject/lura/v2/router/gin" 17 | "github.com/luraproject/lura/v2/transport/http/server" 18 | 19 | "github.com/gin-gonic/gin" 20 | ) 21 | 22 | // NewHandlerFactory returns a HandlerFactory with a rate-limit and a metrics collector middleware injected 23 | func NewHandlerFactory(logger logging.Logger, metricCollector *metrics.Metrics, rejecter jose.RejecterFactory) router.HandlerFactory { 24 | handlerFactory := router.CustomErrorEndpointHandler(logger, server.DefaultToHTTPError) 25 | handlerFactory = ratelimit.NewRateLimiterMw(logger, handlerFactory) 26 | handlerFactory = lua.HandlerFactory(logger, handlerFactory) 27 | handlerFactory = ginjose.HandlerFactory(handlerFactory, logger, rejecter) 28 | handlerFactory = metricCollector.NewHTTPHandlerFactory(handlerFactory) 29 | handlerFactory = opencensus.New(handlerFactory) 30 | handlerFactory = botdetector.New(handlerFactory, logger) 31 | 32 | return func(cfg *config.EndpointConfig, p proxy.Proxy) gin.HandlerFunc { 33 | logger.Debug(fmt.Sprintf("[ENDPOINT: %s] Building the http handler", cfg.Endpoint)) 34 | return handlerFactory(cfg, p) 35 | } 36 | } 37 | 38 | type handlerFactory struct{} 39 | 40 | func (handlerFactory) NewHandlerFactory(l logging.Logger, m *metrics.Metrics, r jose.RejecterFactory) router.HandlerFactory { 41 | return NewHandlerFactory(l, m, r) 42 | } 43 | -------------------------------------------------------------------------------- /krakend.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://www.krakend.io/schema/v2.5/krakend.json", 3 | "version": 3, 4 | "name": "My lovely gateway", 5 | "port": 8080, 6 | "cache_ttl": "3600s", 7 | "timeout": "3s", 8 | "extra_config": { 9 | "telemetry/logging": { 10 | "level": "DEBUG", 11 | "prefix": "[KRAKEND]", 12 | "syslog": false, 13 | "stdout": true 14 | }, 15 | "telemetry/metrics": { 16 | "collection_time": "60s", 17 | "proxy_disabled": false, 18 | "router_disabled": false, 19 | "backend_disabled": false, 20 | "endpoint_disabled": false, 21 | "listen_address": ":8090" 22 | }, 23 | "security/cors": { 24 | "allow_origins": [ "http://192.168.99.100:3000", "http://localhost:3000" ], 25 | "allow_methods": [ "POST", "GET" ], 26 | "allow_headers": [ "Origin", "Authorization", "Content-Type" ], 27 | "expose_headers": [ "Content-Length" ], 28 | "max_age": "12h" 29 | } 30 | }, 31 | "endpoints": [ 32 | { 33 | "endpoint": "/supu", 34 | "method": "GET", 35 | "input_headers": ["Authorization", "Content-Type"], 36 | "backend": [ 37 | { 38 | "host": [ 39 | "http://127.0.0.1:8000" 40 | ], 41 | "url_pattern": "/__debug/supu", 42 | "extra_config": { 43 | "modifier/martian": { 44 | "fifo.Group": { 45 | "scope": ["request", "response"], 46 | "aggregateErrors": true, 47 | "modifiers": [ 48 | { 49 | "header.Modifier": { 50 | "scope": ["request", "response"], 51 | "name" : "X-Martian", 52 | "value" : "ouh yeah!" 53 | } 54 | }, 55 | { 56 | "header.RegexFilter": { 57 | "scope": ["request"], 58 | "header" : "X-Neptunian", 59 | "regex" : "no!", 60 | "modifier": { 61 | "header.Modifier": { 62 | "scope": ["request"], 63 | "name" : "X-Martian-New", 64 | "value" : "some value" 65 | } 66 | } 67 | } 68 | } 69 | ] 70 | } 71 | }, 72 | "qos/circuit-breaker": { 73 | "interval": 60, 74 | "timeout": 10, 75 | "max_errors": 1 76 | } 77 | } 78 | } 79 | ] 80 | }, 81 | { 82 | "endpoint": "/github/{user}", 83 | "method": "GET", 84 | "backend": [ 85 | { 86 | "host": [ 87 | "https://api.github.com" 88 | ], 89 | "url_pattern": "/", 90 | "allow": [ 91 | "authorizations_url", 92 | "code_search_url" 93 | ], 94 | "disable_host_sanitize": true, 95 | "extra_config": { 96 | "modifier/martian": { 97 | "fifo.Group": { 98 | "scope": ["request", "response"], 99 | "aggregateErrors": true, 100 | "modifiers": [ 101 | { 102 | "header.Modifier": { 103 | "scope": ["request", "response"], 104 | "name" : "X-Martian", 105 | "value" : "ouh yeah!" 106 | } 107 | }, 108 | { 109 | "body.Modifier": { 110 | "scope": ["request"], 111 | "contentType" : "application/json", 112 | "body" : "eyJtc2ciOiJ5b3Ugcm9jayEifQ==" 113 | } 114 | }, 115 | { 116 | "header.RegexFilter": { 117 | "scope": ["request"], 118 | "header" : "X-Neptunian", 119 | "regex" : "no!", 120 | "modifier": { 121 | "header.Modifier": { 122 | "scope": ["request"], 123 | "name" : "X-Martian-New", 124 | "value" : "some value" 125 | } 126 | } 127 | } 128 | } 129 | ] 130 | } 131 | }, 132 | "qos/ratelimit/proxy": { 133 | "max_rate": 2, 134 | "capacity": 2 135 | }, 136 | "qos/circuit-breaker": { 137 | "interval": 60, 138 | "timeout": 10, 139 | "max_errors": 1 140 | } 141 | } 142 | } 143 | ] 144 | }, 145 | { 146 | "endpoint": "/private/{user}", 147 | "backend": [ 148 | { 149 | "host": [ "http://api.github.com" ], 150 | "url_pattern": "/", 151 | "allow": [ 152 | "authorizations_url", 153 | "code_search_url" 154 | ] 155 | } 156 | ], 157 | "extra_config": { 158 | "auth/validator": { 159 | "alg": "RS256", 160 | "audience": ["http://api.example.com"], 161 | "roles_key": "http://api.example.com/custom/roles", 162 | "roles": [ "user", "admin" ], 163 | "jwk_url": "https://albert-test.auth0.com/.well-known/jwks.json" 164 | } 165 | } 166 | }, 167 | { 168 | "endpoint": "/show/{id}", 169 | "backend": [ 170 | { 171 | "host": [ 172 | "http://showrss.info/" 173 | ], 174 | "url_pattern": "/user/schedule/{id}.rss", 175 | "encoding": "rss", 176 | "group": "schedule", 177 | "allow": ["items", "title"], 178 | "extra_config": { 179 | "qos/ratelimit/proxy": { 180 | "max_rate": 1, 181 | "capacity": 1 182 | }, 183 | "qos/circuit-breaker": { 184 | "interval": 60, 185 | "timeout": 10, 186 | "max_errors": 1 187 | } 188 | } 189 | }, 190 | { 191 | "host": [ 192 | "http://showrss.info/" 193 | ], 194 | "url_pattern": "/user/{id}.rss", 195 | "encoding": "rss", 196 | "group": "available", 197 | "allow": ["items", "title"], 198 | "extra_config": { 199 | "qos/ratelimit/proxy": { 200 | "max_rate": 2, 201 | "capacity": 2 202 | }, 203 | "qos/circuit-breaker": { 204 | "interval": 60, 205 | "timeout": 10, 206 | "max_errors": 1 207 | } 208 | } 209 | } 210 | ], 211 | "extra_config": { 212 | "qos/ratelimit/router": { 213 | "max_rate": 50, 214 | "client_max_rate": 5, 215 | "strategy": "ip" 216 | } 217 | } 218 | } 219 | ] 220 | } 221 | -------------------------------------------------------------------------------- /plugin.go: -------------------------------------------------------------------------------- 1 | package krakend 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os" 7 | "path/filepath" 8 | "regexp" 9 | "strings" 10 | "time" 11 | 12 | cmd "github.com/krakendio/krakend-cobra/v2" 13 | "github.com/luraproject/lura/v2/logging" 14 | proxy "github.com/luraproject/lura/v2/proxy/plugin" 15 | client "github.com/luraproject/lura/v2/transport/http/client/plugin" 16 | server "github.com/luraproject/lura/v2/transport/http/server/plugin" 17 | "github.com/spf13/cobra" 18 | ) 19 | 20 | // LoadPlugins loads and registers the plugins so they can be used if enabled at the configuration 21 | func LoadPlugins(folder, pattern string, logger logging.Logger) { 22 | LoadPluginsWithContext(context.Background(), folder, pattern, logger) 23 | } 24 | 25 | func LoadPluginsWithContext(ctx context.Context, folder, pattern string, logger logging.Logger) { 26 | logger.Debug("[SERVICE: Plugin Loader] Starting loading process") 27 | 28 | n, err := client.LoadWithLogger( 29 | folder, 30 | pattern, 31 | client.RegisterClient, 32 | logger, 33 | ) 34 | logPluginLoaderErrors(logger, "[SERVICE: Executor Plugin]", n, err) 35 | 36 | n, err = server.LoadWithLogger( 37 | folder, 38 | pattern, 39 | server.RegisterHandler, 40 | logger, 41 | ) 42 | logPluginLoaderErrors(logger, "[SERVICE: Handler Plugin]", n, err) 43 | 44 | n, err = proxy.LoadWithLoggerAndContext( 45 | ctx, 46 | folder, 47 | pattern, 48 | proxy.RegisterModifier, 49 | logger, 50 | ) 51 | logPluginLoaderErrors(logger, "[SERVICE: Modifier Plugin]", n, err) 52 | 53 | logger.Debug("[SERVICE: Plugin Loader] Loading process completed") 54 | } 55 | 56 | func logPluginLoaderErrors(logger logging.Logger, tag string, n int, err error) { 57 | if err != nil { 58 | if mErrs, ok := err.(pluginLoaderErr); ok { 59 | for _, err := range mErrs.Errs() { 60 | logger.Debug(tag, err.Error()) 61 | } 62 | } else { 63 | logger.Debug(tag, err.Error()) 64 | } 65 | } 66 | if n > 0 { 67 | logger.Info(tag, "Total plugins loaded:", n) 68 | } 69 | } 70 | 71 | type pluginLoader struct{} 72 | 73 | func (pluginLoader) Load(folder, pattern string, logger logging.Logger) { 74 | LoadPlugins(folder, pattern, logger) 75 | } 76 | 77 | func (pluginLoader) LoadWithContext(ctx context.Context, folder, pattern string, logger logging.Logger) { 78 | LoadPluginsWithContext(ctx, folder, pattern, logger) 79 | } 80 | 81 | type pluginLoaderErr interface { 82 | Errs() []error 83 | } 84 | 85 | var ( 86 | serverExpected bool 87 | clientExpected bool 88 | modifierExpected bool 89 | 90 | testPluginCmd = &cobra.Command{ 91 | Use: "test-plugin [flags] [artifacts]", 92 | Short: "Tests that one or more plugins are loadable into KrakenD.", 93 | Run: testPluginFunc, 94 | Example: "krakend test-plugin -scm ./plugins/my_plugin.so ./plugins/my_other_plugin.so", 95 | } 96 | 97 | serverExpectedFlag cmd.FlagBuilder 98 | clientExpectedFlag cmd.FlagBuilder 99 | modifierExpectedFlag cmd.FlagBuilder 100 | 101 | reLogErrorPlugins = regexp.MustCompile(`(?m)plugin \#\d+ \(.*\): (.*)`) 102 | ) 103 | 104 | func init() { 105 | serverExpectedFlag = cmd.BoolFlagBuilder(&serverExpected, "server", "s", false, "The artifact should contain a Server Plugin.") 106 | clientExpectedFlag = cmd.BoolFlagBuilder(&clientExpected, "client", "c", false, "The artifact should contain a Client Plugin.") 107 | modifierExpectedFlag = cmd.BoolFlagBuilder(&modifierExpected, "modifier", "m", false, "The artifact should contain a Req/Resp Modifier Plugin.") 108 | } 109 | 110 | func NewTestPluginCmd() cmd.Command { 111 | return cmd.NewCommand(testPluginCmd, serverExpectedFlag, clientExpectedFlag, modifierExpectedFlag) 112 | } 113 | 114 | func testPluginFunc(ccmd *cobra.Command, args []string) { 115 | if len(args) == 0 { 116 | ccmd.Println("At least one plugin is required.") 117 | os.Exit(1) 118 | } 119 | if !serverExpected && !clientExpected && !modifierExpected { 120 | ccmd.Println("You must declare the expected type of the plugin.") 121 | os.Exit(1) 122 | } 123 | 124 | start := time.Now() 125 | 126 | ctx, cancel := context.WithCancel(context.Background()) 127 | 128 | var failed int 129 | globalOK := true 130 | for _, pluginPath := range args { 131 | f, err := os.Open(pluginPath) 132 | if os.IsNotExist(err) { 133 | ccmd.Println(fmt.Sprintf("[KO] Unable to open the plugin %s.", pluginPath)) 134 | failed++ 135 | globalOK = false 136 | continue 137 | } 138 | f.Close() 139 | 140 | name := filepath.Base(pluginPath) 141 | folder := filepath.Dir(pluginPath) 142 | ok := true 143 | 144 | if serverExpected { 145 | ok = checkHandlerPlugin(ccmd, folder, name) && ok 146 | } 147 | 148 | if modifierExpected { 149 | ok = checkModifierPlugin(ctx, ccmd, folder, name) && ok 150 | } 151 | 152 | if clientExpected { 153 | ok = checkClientPlugin(ccmd, folder, name) && ok 154 | } 155 | 156 | if !ok { 157 | failed++ 158 | } 159 | 160 | globalOK = globalOK && ok 161 | } 162 | 163 | cancel() 164 | 165 | if !globalOK { 166 | ccmd.Println(fmt.Sprintf("[KO] %d tested plugin(s) in %s.\n%d plugin(s) failed.", len(args), time.Since(start), failed)) 167 | os.Exit(1) 168 | } 169 | 170 | ccmd.Println(fmt.Sprintf("[OK] %d tested plugin(s) in %s", len(args), time.Since(start))) 171 | } 172 | 173 | func checkClientPlugin(ccmd *cobra.Command, folder, name string) bool { 174 | _, err := client.LoadWithLogger( 175 | folder, 176 | name, 177 | client.RegisterClient, 178 | logging.NoOp, 179 | ) 180 | if err == nil { 181 | ccmd.Println(fmt.Sprintf("[OK] CLIENT\t%s", name)) 182 | return true 183 | } 184 | 185 | var msg string 186 | if mErrs, ok := err.(pluginLoaderErr); ok { 187 | for _, err := range mErrs.Errs() { 188 | msg += err.Error() 189 | } 190 | } else { 191 | msg = err.Error() 192 | } 193 | 194 | if strings.Contains(msg, "symbol ClientRegisterer not found") { 195 | ccmd.Println(fmt.Sprintf("[KO] CLIENT\t%s: The plugin does not contain a ClientRegisterer.", name)) 196 | return false 197 | } 198 | 199 | for _, match := range reLogErrorPlugins.FindAllStringSubmatch(msg, -1) { 200 | msg = match[1] 201 | } 202 | 203 | ccmd.Println(fmt.Sprintf("[KO] CLIENT\t%s: %s", name, msg)) 204 | return false 205 | } 206 | 207 | func checkHandlerPlugin(ccmd *cobra.Command, folder, name string) bool { 208 | _, err := server.LoadWithLogger( 209 | folder, 210 | name, 211 | server.RegisterHandler, 212 | logging.NoOp, 213 | ) 214 | if err == nil { 215 | ccmd.Println(fmt.Sprintf("[OK] SERVER\t%s", name)) 216 | return true 217 | } 218 | 219 | var msg string 220 | if mErrs, ok := err.(pluginLoaderErr); ok { 221 | for _, err := range mErrs.Errs() { 222 | msg += err.Error() 223 | } 224 | } else { 225 | msg = err.Error() 226 | } 227 | 228 | if strings.Contains(msg, "symbol HandlerRegisterer not found") { 229 | ccmd.Println(fmt.Sprintf("[KO] SERVER\t%s: The plugin does not contain a HandlerRegisterer.", name)) 230 | return false 231 | } 232 | 233 | for _, match := range reLogErrorPlugins.FindAllStringSubmatch(msg, -1) { 234 | msg = match[1] 235 | } 236 | 237 | ccmd.Println(fmt.Sprintf("[KO] SERVER\t%s: %s", name, msg)) 238 | return false 239 | } 240 | 241 | func checkModifierPlugin(ctx context.Context, ccmd *cobra.Command, folder, name string) bool { 242 | _, err := proxy.LoadWithLoggerAndContext( 243 | ctx, 244 | folder, 245 | name, 246 | proxy.RegisterModifier, 247 | logging.NoOp, 248 | ) 249 | if err == nil { 250 | ccmd.Println(fmt.Sprintf("[OK] MODIFIER\t%s", name)) 251 | return true 252 | } 253 | 254 | var msg string 255 | if mErrs, ok := err.(pluginLoaderErr); ok { 256 | for _, err := range mErrs.Errs() { 257 | msg += err.Error() 258 | } 259 | } else { 260 | msg = err.Error() 261 | } 262 | 263 | if strings.Contains(msg, "symbol ModifierRegisterer not found") { 264 | ccmd.Println(fmt.Sprintf("[KO] MODIFIER\t%s: The plugin does not contain a ModifierRegisterer.", name)) 265 | return false 266 | } 267 | 268 | for _, match := range reLogErrorPlugins.FindAllStringSubmatch(msg, -1) { 269 | msg = match[1] 270 | } 271 | 272 | ccmd.Println(fmt.Sprintf("[KO] MODIFIER\t%s: %s", name, msg)) 273 | return false 274 | } 275 | -------------------------------------------------------------------------------- /proxy_factory.go: -------------------------------------------------------------------------------- 1 | package krakend 2 | 3 | import ( 4 | "fmt" 5 | 6 | cel "github.com/krakendio/krakend-cel/v2" 7 | jsonschema "github.com/krakendio/krakend-jsonschema/v2" 8 | lua "github.com/krakendio/krakend-lua/v2/proxy" 9 | metrics "github.com/krakendio/krakend-metrics/v2/gin" 10 | opencensus "github.com/krakendio/krakend-opencensus/v2" 11 | "github.com/luraproject/lura/v2/config" 12 | "github.com/luraproject/lura/v2/logging" 13 | "github.com/luraproject/lura/v2/proxy" 14 | ) 15 | 16 | func internalNewProxyFactory(logger logging.Logger, backendFactory proxy.BackendFactory, 17 | metricCollector *metrics.Metrics) proxy.Factory { 18 | 19 | proxyFactory := proxy.NewDefaultFactory(backendFactory, logger) 20 | proxyFactory = proxy.NewShadowFactory(proxyFactory) 21 | proxyFactory = jsonschema.ProxyFactory(logger, proxyFactory) 22 | proxyFactory = cel.ProxyFactory(logger, proxyFactory) 23 | proxyFactory = lua.ProxyFactory(logger, proxyFactory) 24 | proxyFactory = metricCollector.ProxyFactory("pipe", proxyFactory) 25 | proxyFactory = opencensus.ProxyFactory(proxyFactory) 26 | return proxyFactory 27 | } 28 | 29 | // NewProxyFactory returns a new ProxyFactory wrapping the injected BackendFactory with the default proxy stack and a metrics collector 30 | func NewProxyFactory(logger logging.Logger, backendFactory proxy.BackendFactory, metricCollector *metrics.Metrics) proxy.Factory { 31 | proxyFactory := internalNewProxyFactory(logger, backendFactory, metricCollector) 32 | 33 | return proxy.FactoryFunc(func(cfg *config.EndpointConfig) (proxy.Proxy, error) { 34 | logger.Debug(fmt.Sprintf("[ENDPOINT: %s] Building the proxy pipe", cfg.Endpoint)) 35 | return proxyFactory.New(cfg) 36 | }) 37 | } 38 | 39 | type proxyFactory struct{} 40 | 41 | func (proxyFactory) NewProxyFactory(logger logging.Logger, backendFactory proxy.BackendFactory, metricCollector *metrics.Metrics) proxy.Factory { 42 | return NewProxyFactory(logger, backendFactory, metricCollector) 43 | } 44 | -------------------------------------------------------------------------------- /router_engine.go: -------------------------------------------------------------------------------- 1 | package krakend 2 | 3 | import ( 4 | "encoding/json" 5 | 6 | "github.com/gin-gonic/gin" 7 | 8 | botdetector "github.com/krakendio/krakend-botdetector/v2/gin" 9 | httpsecure "github.com/krakendio/krakend-httpsecure/v2/gin" 10 | lua "github.com/krakendio/krakend-lua/v2/router/gin" 11 | opencensus "github.com/krakendio/krakend-opencensus/v2/router/gin" 12 | "github.com/luraproject/lura/v2/config" 13 | "github.com/luraproject/lura/v2/core" 14 | luragin "github.com/luraproject/lura/v2/router/gin" 15 | "github.com/luraproject/lura/v2/transport/http/server" 16 | ) 17 | 18 | // NewEngine creates a new gin engine with some default values and a secure middleware 19 | func NewEngine(cfg config.ServiceConfig, opt luragin.EngineOptions) *gin.Engine { 20 | engine := luragin.NewEngine(cfg, opt) 21 | 22 | engine.NoRoute(opencensus.HandlerFunc(&config.EndpointConfig{Endpoint: "NoRoute"}, defaultHandler, nil)) 23 | engine.NoMethod(opencensus.HandlerFunc(&config.EndpointConfig{Endpoint: "NoMethod"}, defaultHandler, nil)) 24 | if v, ok := cfg.ExtraConfig[luragin.Namespace]; ok && v != nil { 25 | var ginOpts ginOptions 26 | if b, err := json.Marshal(v); err == nil { 27 | json.Unmarshal(b, &ginOpts) 28 | } 29 | if ginOpts.ErrorBody.Err404 != nil { 30 | engine.NoRoute(opencensus.HandlerFunc(&config.EndpointConfig{Endpoint: "NoRoute"}, jsonHandler(404, ginOpts.ErrorBody.Err404), nil)) 31 | } 32 | if ginOpts.ErrorBody.Err405 != nil { 33 | engine.NoMethod(opencensus.HandlerFunc(&config.EndpointConfig{Endpoint: "NoMethod"}, jsonHandler(405, ginOpts.ErrorBody.Err405), nil)) 34 | } 35 | } 36 | 37 | logPrefix := "[SERVICE: Gin]" 38 | if err := httpsecure.Register(cfg.ExtraConfig, engine); err != nil && err != httpsecure.ErrNoConfig { 39 | opt.Logger.Warning(logPrefix+"[HTTPsecure]", err) 40 | } else if err == nil { 41 | opt.Logger.Debug(logPrefix + "[HTTPsecure] Successfully loaded module") 42 | } 43 | 44 | lua.Register(opt.Logger, cfg.ExtraConfig, engine) 45 | 46 | botdetector.Register(cfg, opt.Logger, engine) 47 | 48 | return engine 49 | } 50 | 51 | func defaultHandler(c *gin.Context) { 52 | c.Header(core.KrakendHeaderName, core.KrakendHeaderValue) 53 | c.Header(server.CompleteResponseHeaderName, server.HeaderIncompleteResponseValue) 54 | } 55 | 56 | func jsonHandler(status int, v interface{}) gin.HandlerFunc { 57 | return func(c *gin.Context) { 58 | defaultHandler(c) 59 | c.JSON(status, v) 60 | } 61 | } 62 | 63 | type engineFactory struct{} 64 | 65 | func (engineFactory) NewEngine(cfg config.ServiceConfig, opt luragin.EngineOptions) *gin.Engine { 66 | return NewEngine(cfg, opt) 67 | } 68 | 69 | type ginOptions struct { 70 | // ErrorBody sets the json body to return to handlers like NoRoute (404) and NoMethod (405) 71 | // Example: "404": { "error": "Not Found", "status": 404 } 72 | ErrorBody struct { 73 | Err404 interface{} `json:"404"` 74 | Err405 interface{} `json:"405"` 75 | } `json:"error_body"` 76 | } 77 | -------------------------------------------------------------------------------- /sd.go: -------------------------------------------------------------------------------- 1 | package krakend 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/luraproject/lura/v2/config" 7 | "github.com/luraproject/lura/v2/logging" 8 | "github.com/luraproject/lura/v2/sd/dnssrv" 9 | ) 10 | 11 | // RegisterSubscriberFactories registers all the available sd adaptors 12 | func RegisterSubscriberFactories(_ context.Context, _ config.ServiceConfig, _ logging.Logger) func(n string, p int) { 13 | // register the dns service discovery 14 | dnssrv.Register() 15 | 16 | return func(name string, port int) {} 17 | } 18 | 19 | type registerSubscriberFactories struct{} 20 | 21 | func (registerSubscriberFactories) Register(ctx context.Context, cfg config.ServiceConfig, logger logging.Logger) func(n string, p int) { 22 | return RegisterSubscriberFactories(ctx, cfg, logger) 23 | } 24 | -------------------------------------------------------------------------------- /tests/fixtures/bench.json: -------------------------------------------------------------------------------- 1 | { 2 | "version":3, 3 | "host":["http://localhost:8080"], 4 | "read_header_timeout":"200ms", 5 | "extra_config": { 6 | "github_com/devopsfaith/krakend-gologging": { 7 | "level": "CRITICAL", 8 | "prefix": "[KRAKEND]" 9 | } 10 | }, 11 | "endpoints": [{ 12 | "endpoint": "/test", 13 | "backend": [{ 14 | "url_pattern": "/__health" 15 | }] 16 | }] 17 | } -------------------------------------------------------------------------------- /tests/fixtures/lua/base64.lua: -------------------------------------------------------------------------------- 1 | -- source: https://github.com/toastdriven/lua-base64/blob/master/base64.lua 2 | 3 | -- Base64-encoding 4 | -- Sourced from http://en.wikipedia.org/wiki/Base64 5 | 6 | local __author__ = 'Daniel Lindsley' 7 | local __version__ = 'scm-1' 8 | local __license__ = 'BSD' 9 | 10 | 11 | local index_table = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/' 12 | 13 | 14 | function to_binary(integer) 15 | local remaining = tonumber(integer) 16 | local bin_bits = '' 17 | 18 | for i = 7, 0, -1 do 19 | local current_power = 2 ^ i 20 | 21 | if remaining >= current_power then 22 | bin_bits = bin_bits .. '1' 23 | remaining = remaining - current_power 24 | else 25 | bin_bits = bin_bits .. '0' 26 | end 27 | end 28 | 29 | return bin_bits 30 | end 31 | 32 | function from_binary(bin_bits) 33 | return tonumber(bin_bits, 2) 34 | end 35 | 36 | 37 | function to_base64(to_encode) 38 | local bit_pattern = '' 39 | local encoded = '' 40 | local trailing = '' 41 | 42 | for i = 1, string.len(to_encode) do 43 | bit_pattern = bit_pattern .. to_binary(string.byte(string.sub(to_encode, i, i))) 44 | end 45 | 46 | -- Check the number of bytes. If it's not evenly divisible by three, 47 | -- zero-pad the ending & append on the correct number of ``=``s. 48 | if string.len(bit_pattern) % 3 == 2 then 49 | trailing = '==' 50 | bit_pattern = bit_pattern .. '0000000000000000' 51 | elseif string.len(bit_pattern) % 3 == 1 then 52 | trailing = '=' 53 | bit_pattern = bit_pattern .. '00000000' 54 | end 55 | 56 | for i = 1, string.len(bit_pattern), 6 do 57 | local byte = string.sub(bit_pattern, i, i+5) 58 | local offset = tonumber(from_binary(byte)) 59 | encoded = encoded .. string.sub(index_table, offset+1, offset+1) 60 | end 61 | 62 | return string.sub(encoded, 1, -1 - string.len(trailing)) .. trailing 63 | end 64 | 65 | 66 | function from_base64(to_decode) 67 | local padded = to_decode:gsub("%s", "") 68 | local unpadded = padded:gsub("=", "") 69 | local bit_pattern = '' 70 | local decoded = '' 71 | 72 | for i = 1, string.len(unpadded) do 73 | local char = string.sub(to_decode, i, i) 74 | local offset, _ = string.find(index_table, char) 75 | if offset == nil then 76 | error("Invalid character '" .. char .. "' found.") 77 | end 78 | 79 | bit_pattern = bit_pattern .. string.sub(to_binary(offset-1), 3) 80 | end 81 | 82 | for i = 1, string.len(bit_pattern), 8 do 83 | local byte = string.sub(bit_pattern, i, i+7) 84 | decoded = decoded .. string.char(from_binary(byte)) 85 | end 86 | 87 | local padding_length = padded:len()-unpadded:len() 88 | 89 | if (padding_length == 1 or padding_length == 2) then 90 | decoded = decoded:sub(1,-2) 91 | end 92 | return decoded 93 | end -------------------------------------------------------------------------------- /tests/fixtures/lua/collection.lua: -------------------------------------------------------------------------------- 1 | function pre_backend( req ) 2 | local authHeader = req:headers("Authorization") 3 | local first = string.find(authHeader, "%.") 4 | local last = string.find(authHeader, "%.", first+1) 5 | local rawData = string.sub(authHeader, first+1, last-1) 6 | local decoded = from_base64(rawData) 7 | local jwtData = json_parse(decoded) 8 | 9 | req:url(req:url() .. jwtData["id"]) 10 | end 11 | 12 | function post_proxy( resp ) 13 | local responseData = resp:data() 14 | local data = {} 15 | local col = responseData:get("collection") 16 | 17 | local size = col:len() 18 | responseData:set("total", size) 19 | 20 | local paths = {} 21 | for i=0,size-1 do 22 | local element = col:get(i) 23 | local t = element:get("path") 24 | table.insert(paths, t) 25 | end 26 | responseData:set("paths", paths) 27 | responseData:del("collection") 28 | end 29 | 30 | function json_error() 31 | custom_error('{"msg":"I refuse to make any coffee, I am a teapot!"}', 418) 32 | end 33 | -------------------------------------------------------------------------------- /tests/fixtures/lua/decorator.lua: -------------------------------------------------------------------------------- 1 | function post_proxy_decorator( resp ) 2 | local responseData = resp:data() 3 | local responseContent = responseData:get("source_result") 4 | local message = responseContent:get("message") 5 | 6 | local c = string.match(message, "Successfully") 7 | 8 | if not not c 9 | then 10 | responseData:set("result", "success") 11 | else 12 | responseData:set("result", "failed") 13 | end 14 | end -------------------------------------------------------------------------------- /tests/fixtures/lua/json.lua: -------------------------------------------------------------------------------- 1 | -- source: https://gist.github.com/tylerneylon/59f4bcf316be525b30ab 2 | 3 | --[[ json.lua 4 | A compact pure-Lua JSON library. 5 | The main functions are: json_stringify, json_parse. 6 | ## json_stringify: 7 | This expects the following to be true of any tables being encoded: 8 | * They only have string or number keys. Number keys must be represented as 9 | strings in json; this is part of the json spec. 10 | * They are not recursive. Such a structure cannot be specified in json. 11 | A Lua table is considered to be an array if and only if its set of keys is a 12 | consecutive sequence of positive integers starting at 1. Arrays are encoded like 13 | so: `[2, 3, false, "hi"]`. Any other type of Lua table is encoded as a json 14 | object, encoded like so: `{"key1": 2, "key2": false}`. 15 | Because the Lua nil value cannot be a key, and as a table value is considerd 16 | equivalent to a missing key, there is no way to express the json "null" value in 17 | a Lua table. The only way this will output "null" is if your entire input obj is 18 | nil itself. 19 | An empty Lua table, {}, could be considered either a json object or array - 20 | it's an ambiguous edge case. We choose to treat this as an object as it is the 21 | more general type. 22 | To be clear, none of the above considerations is a limitation of this code. 23 | Rather, it is what we get when we completely observe the json specification for 24 | as arbitrary a Lua object as json is capable of expressing. 25 | ## json_parse: 26 | This function parses json, with the exception that it does not pay attention to 27 | \u-escaped unicode code points in strings. 28 | It is difficult for Lua to return null as a value. In order to prevent the loss 29 | of keys with a null value in a json string, this function uses the one-off 30 | table value json.null (which is just an empty table) to indicate null values. 31 | This way you can check if a value is null with the conditional 32 | `val == json.null`. 33 | If you have control over the data and are using Lua, I would recommend just 34 | avoiding null values in your data to begin with. 35 | --]] 36 | 37 | 38 | local json = {} 39 | 40 | 41 | -- Internal functions. 42 | 43 | local function kind_of(obj) 44 | if type(obj) ~= 'table' then return type(obj) end 45 | local i = 1 46 | for _ in pairs(obj) do 47 | if obj[i] ~= nil then i = i + 1 else return 'table' end 48 | end 49 | if i == 1 then return 'table' else return 'array' end 50 | end 51 | 52 | local function escape_str(s) 53 | local in_char = {'\\', '"', '/', '\b', '\f', '\n', '\r', '\t'} 54 | local out_char = {'\\', '"', '/', 'b', 'f', 'n', 'r', 't'} 55 | for i, c in ipairs(in_char) do 56 | s = s:gsub(c, '\\' .. out_char[i]) 57 | end 58 | return s 59 | end 60 | 61 | -- Returns pos, did_find; there are two cases: 62 | -- 1. Delimiter found: pos = pos after leading space + delim; did_find = true. 63 | -- 2. Delimiter not found: pos = pos after leading space; did_find = false. 64 | -- This throws an error if err_if_missing is true and the delim is not found. 65 | local function skip_delim(str, pos, delim, err_if_missing) 66 | pos = pos + #str:match('^%s*', pos) 67 | if str:sub(pos, pos) ~= delim then 68 | if err_if_missing then 69 | error('Expected ' .. delim .. ' near position ' .. pos) 70 | end 71 | return pos, false 72 | end 73 | return pos + 1, true 74 | end 75 | 76 | -- Expects the given pos to be the first character after the opening quote. 77 | -- Returns val, pos; the returned pos is after the closing quote character. 78 | local function parse_str_val(str, pos, val) 79 | val = val or '' 80 | local early_end_error = 'End of input found while parsing string.' 81 | if pos > #str then error(early_end_error) end 82 | local c = str:sub(pos, pos) 83 | if c == '"' then return val, pos + 1 end 84 | if c ~= '\\' then return parse_str_val(str, pos + 1, val .. c) end 85 | -- We must have a \ character. 86 | local esc_map = {b = '\b', f = '\f', n = '\n', r = '\r', t = '\t'} 87 | local nextc = str:sub(pos + 1, pos + 1) 88 | if not nextc then error(early_end_error) end 89 | return parse_str_val(str, pos + 2, val .. (esc_map[nextc] or nextc)) 90 | end 91 | 92 | -- Returns val, pos; the returned pos is after the number's final character. 93 | local function parse_num_val(str, pos) 94 | local num_str = str:match('^-?%d+%.?%d*[eE]?[+-]?%d*', pos) 95 | local val = tonumber(num_str) 96 | if not val then error('Error parsing number at position ' .. pos .. '.') end 97 | return val, pos + #num_str 98 | end 99 | 100 | 101 | -- Public values and functions. 102 | 103 | function json_stringify(obj, as_key) 104 | local s = {} -- We'll build the string as an array of strings to be concatenated. 105 | local kind = kind_of(obj) -- This is 'array' if it's an array or type(obj) otherwise. 106 | if kind == 'array' then 107 | if as_key then error('Can\'t encode array as key.') end 108 | s[#s + 1] = '[' 109 | for i, val in ipairs(obj) do 110 | if i > 1 then s[#s + 1] = ', ' end 111 | s[#s + 1] = json_stringify(val) 112 | end 113 | s[#s + 1] = ']' 114 | elseif kind == 'table' then 115 | if as_key then error('Can\'t encode table as key.') end 116 | s[#s + 1] = '{' 117 | for k, v in pairs(obj) do 118 | if #s > 1 then s[#s + 1] = ', ' end 119 | s[#s + 1] = json_stringify(k, true) 120 | s[#s + 1] = ':' 121 | s[#s + 1] = json_stringify(v) 122 | end 123 | s[#s + 1] = '}' 124 | elseif kind == 'string' then 125 | return '"' .. escape_str(obj) .. '"' 126 | elseif kind == 'number' then 127 | if as_key then return '"' .. tostring(obj) .. '"' end 128 | return tostring(obj) 129 | elseif kind == 'boolean' then 130 | return tostring(obj) 131 | elseif kind == 'nil' then 132 | return 'null' 133 | else 134 | error('Unjsonifiable type: ' .. kind .. '.') 135 | end 136 | return table.concat(s) 137 | end 138 | 139 | json.null = {} -- This is a one-off table to represent the null value. 140 | 141 | function json_parse(str, pos, end_delim) 142 | pos = pos or 1 143 | if pos > #str then error('Reached unexpected end of input.') end 144 | local pos = pos + #str:match('^%s*', pos) -- Skip whitespace. 145 | local first = str:sub(pos, pos) 146 | if first == '{' then -- Parse an object. 147 | local obj, key, delim_found = {}, true, true 148 | pos = pos + 1 149 | while true do 150 | key, pos = json_parse(str, pos, '}') 151 | if key == nil then return obj, pos end 152 | if not delim_found then error('Comma missing between object items.') end 153 | pos = skip_delim(str, pos, ':', true) -- true -> error if missing. 154 | obj[key], pos = json_parse(str, pos) 155 | pos, delim_found = skip_delim(str, pos, ',') 156 | end 157 | elseif first == '[' then -- Parse an array. 158 | local arr, val, delim_found = {}, true, true 159 | pos = pos + 1 160 | while true do 161 | val, pos = json_parse(str, pos, ']') 162 | if val == nil then return arr, pos end 163 | if not delim_found then error('Comma missing between array items.') end 164 | arr[#arr + 1] = val 165 | pos, delim_found = skip_delim(str, pos, ',') 166 | end 167 | elseif first == '"' then -- Parse a string. 168 | return parse_str_val(str, pos + 1) 169 | elseif first == '-' or first:match('%d') then -- Parse a number. 170 | return parse_num_val(str, pos) 171 | elseif first == end_delim then -- End of an object or array. 172 | return nil, pos + 1 173 | else -- Parse true, false, or null. 174 | local literals = {['true'] = true, ['false'] = false, ['null'] = json.null} 175 | for lit_str, lit_val in pairs(literals) do 176 | local lit_end = pos + #lit_str - 1 177 | if str:sub(pos, lit_end) == lit_str then return lit_val, lit_end + 1 end 178 | end 179 | local pos_info_str = 'position ' .. pos .. ': ' .. str:sub(pos, pos + 10) 180 | error('Invalid json syntax starting at ' .. pos_info_str) 181 | end 182 | end 183 | -------------------------------------------------------------------------------- /tests/fixtures/specs/backend_301.json: -------------------------------------------------------------------------------- 1 | { 2 | "in": { 3 | "method": "GET", 4 | "url": "http://localhost:8080/redirect/301" 5 | }, 6 | "out": { 7 | "status_code": 200, 8 | "body": "{\"foo\":42,\"headers\":{\"Accept-Encoding\":[\"gzip\"],\"Referer\":[\"http://127.0.0.1:8081/redirect/?status=301\"],\"User-Agent\":[\"KrakenD Version 2.10.0\"],\"X-Forwarded-Host\":[\"localhost:8080\"]},\"path\":\"/param_forwarding/\",\"query\":{\"status\":[\"301\"]}}", 9 | "header": { 10 | "content-type": ["application/json; charset=utf-8"], 11 | "Cache-Control": ["public, max-age=3600"], 12 | "X-Krakend-Completed": ["true"] 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /tests/fixtures/specs/backend_302.json: -------------------------------------------------------------------------------- 1 | { 2 | "in": { 3 | "method": "GET", 4 | "url": "http://localhost:8080/redirect/302" 5 | }, 6 | "out": { 7 | "status_code": 200, 8 | "body": "{\"foo\":42,\"headers\":{\"Accept-Encoding\":[\"gzip\"],\"Referer\":[\"http://127.0.0.1:8081/redirect/?status=302\"],\"User-Agent\":[\"KrakenD Version 2.10.0\"],\"X-Forwarded-Host\":[\"localhost:8080\"]},\"path\":\"/param_forwarding/\",\"query\":{\"status\":[\"302\"]}}", 9 | "header": { 10 | "content-type": ["application/json; charset=utf-8"], 11 | "Cache-Control": ["public, max-age=3600"], 12 | "X-Krakend-Completed": ["true"] 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /tests/fixtures/specs/backend_303.json: -------------------------------------------------------------------------------- 1 | { 2 | "in": { 3 | "method": "GET", 4 | "url": "http://localhost:8080/redirect/303" 5 | }, 6 | "out": { 7 | "status_code": 200, 8 | "body": "{\"foo\":42,\"headers\":{\"Accept-Encoding\":[\"gzip\"],\"Referer\":[\"http://127.0.0.1:8081/redirect/?status=303\"],\"User-Agent\":[\"KrakenD Version 2.10.0\"],\"X-Forwarded-Host\":[\"localhost:8080\"]},\"path\":\"/param_forwarding/\",\"query\":{\"status\":[\"303\"]}}", 9 | "header": { 10 | "content-type": ["application/json; charset=utf-8"], 11 | "Cache-Control": ["public, max-age=3600"], 12 | "X-Krakend-Completed": ["true"] 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /tests/fixtures/specs/backend_307.json: -------------------------------------------------------------------------------- 1 | { 2 | "in": { 3 | "method": "GET", 4 | "url": "http://localhost:8080/redirect/307" 5 | }, 6 | "out": { 7 | "status_code": 200, 8 | "body": "{\"foo\":42,\"headers\":{\"Accept-Encoding\":[\"gzip\"],\"Referer\":[\"http://127.0.0.1:8081/redirect/?status=307\"],\"User-Agent\":[\"KrakenD Version 2.10.0\"],\"X-Forwarded-Host\":[\"localhost:8080\"]},\"path\":\"/param_forwarding/\",\"query\":{\"status\":[\"307\"]}}", 9 | "header": { 10 | "content-type": ["application/json; charset=utf-8"], 11 | "Cache-Control": ["public, max-age=3600"], 12 | "X-Krakend-Completed": ["true"] 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /tests/fixtures/specs/backend_404.json: -------------------------------------------------------------------------------- 1 | { 2 | "in": { 3 | "method": "GET", 4 | "url": "http://localhost:8080/crash" 5 | }, 6 | "out": { 7 | "status_code": 500, 8 | "body": "invalid status code 404 [GET /unknown]: http://127.0.0.1:8081/unknown", 9 | "header": { 10 | "content-type": ["text/plain; charset=utf-8"], 11 | "Cache-Control": [""], 12 | "X-Krakend-Completed": ["false"] 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /tests/fixtures/specs/botdetector_1.json: -------------------------------------------------------------------------------- 1 | { 2 | "in": { 3 | "method": "GET", 4 | "url": "http://localhost:8080/param_forwarding/some/foo/bar", 5 | "header": { 6 | "User-Agent": "a" 7 | } 8 | }, 9 | "out": { 10 | "status_code": 403, 11 | "body": "", 12 | "header": { 13 | "content-type": [""], 14 | "X-Krakend-Completed": [""] 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /tests/fixtures/specs/botdetector_2.json: -------------------------------------------------------------------------------- 1 | { 2 | "in": { 3 | "method": "GET", 4 | "url": "http://localhost:8080/param_forwarding/some/foo/bar", 5 | "header": { 6 | "User-Agent": "b" 7 | } 8 | }, 9 | "out": { 10 | "status_code": 403, 11 | "body": "", 12 | "header": { 13 | "content-type": [""], 14 | "X-Krakend-Completed": [""] 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /tests/fixtures/specs/botdetector_3.json: -------------------------------------------------------------------------------- 1 | { 2 | "in": { 3 | "method": "GET", 4 | "url": "http://localhost:8080/param_forwarding/some/foo/bar", 5 | "header": { 6 | "User-Agent": "Pingdom.com_bot_version_1.14" 7 | } 8 | }, 9 | "out": { 10 | "status_code": 403, 11 | "body": "", 12 | "header": { 13 | "content-type": [""], 14 | "X-Krakend-Completed": [""] 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /tests/fixtures/specs/botdetector_4.json: -------------------------------------------------------------------------------- 1 | { 2 | "in": { 3 | "method": "GET", 4 | "url": "http://localhost:8080/param_forwarding/some/foo/bar", 5 | "header": { 6 | "User-Agent": "facebookexternalhit/1.2.3" 7 | } 8 | }, 9 | "out": { 10 | "status_code": 403, 11 | "body": "", 12 | "header": { 13 | "content-type": [""], 14 | "X-Krakend-Completed": [""] 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /tests/fixtures/specs/cel-1.json: -------------------------------------------------------------------------------- 1 | { 2 | "in": { 3 | "method": "GET", 4 | "url": "http://localhost:8080/cel/req-resp/1", 5 | "header": { 6 | "X-Header": "something" 7 | } 8 | }, 9 | "out": { 10 | "status_code": 500, 11 | "body": "request aborted by evaluator #1", 12 | "header": { 13 | "content-type": ["text/plain; charset=utf-8"], 14 | "Cache-Control": [""], 15 | "X-Krakend-Completed": ["false"] 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /tests/fixtures/specs/cel-2.json: -------------------------------------------------------------------------------- 1 | { 2 | "in": { 3 | "method": "GET", 4 | "url": "http://localhost:8080/cel/req-resp/30" 5 | }, 6 | "out": { 7 | "status_code": 500, 8 | "body": "request aborted by evaluator #0", 9 | "header": { 10 | "content-type": ["text/plain; charset=utf-8"], 11 | "Cache-Control": [""], 12 | "X-Krakend-Completed": ["false"] 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /tests/fixtures/specs/cel-3.json: -------------------------------------------------------------------------------- 1 | { 2 | "in": { 3 | "method": "GET", 4 | "url": "http://localhost:8080/cel/req-resp/10", 5 | "header": { 6 | "X-Header": "something" 7 | } 8 | }, 9 | "out": { 10 | "status_code": 200, 11 | "body": "{\"backend2\":{\"message\":\"pong\"}}", 12 | "header": { 13 | "content-type": ["application/json; charset=utf-8"], 14 | "X-Krakend-Completed": ["false"], 15 | "Cache-Control": [""] 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /tests/fixtures/specs/cel-4.json: -------------------------------------------------------------------------------- 1 | { 2 | "in": { 3 | "method": "GET", 4 | "url": "http://localhost:8080/cel/req-resp/6", 5 | "header": { 6 | "X-Header": "something" 7 | } 8 | }, 9 | "out": { 10 | "status_code": 200, 11 | "body": "{\"backend1\":{\"message\":\"pong\"}}", 12 | "header": { 13 | "content-type": ["application/json; charset=utf-8"], 14 | "X-Krakend-Completed": ["false"], 15 | "Cache-Control": [""] 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /tests/fixtures/specs/cel-5.json: -------------------------------------------------------------------------------- 1 | { 2 | "in": { 3 | "method": "GET", 4 | "url": "http://localhost:8080/cel/req-resp/30", 5 | "header": { 6 | "X-Header": "something" 7 | } 8 | }, 9 | "out": { 10 | "status_code": 200, 11 | "body": "{\"backend1\":{\"message\":\"pong\"},\"backend2\":{\"message\":\"pong\"}}", 12 | "header": { 13 | "content-type": ["application/json; charset=utf-8"], 14 | "Cache-Control": ["public, max-age=3600"], 15 | "X-Krakend-Completed": ["true"] 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /tests/fixtures/specs/cel-6.json: -------------------------------------------------------------------------------- 1 | { 2 | "in": { 3 | "method": "GET", 4 | "url": "http://localhost:8080/cel/jwt/ko", 5 | "header": { 6 | "authorization": "bearer yJhbGciOiJIUzI1NiIsImtpZCI6InNpbTIifQ.eyJhdWQiOiJodHRwOi8vYXBpLmV4YW1wbGUuY29tIiwiZXhwIjoxNzM1Njg5NjAwLCJpc3MiOiJodHRwczovL2tyYWtlbmQuaW8iLCJqdGkiOiJtbmIyM3Zjc3J0NzU2eXVpb21uYnZjeDk4ZXJ0eXVpb3AiLCJyb2xlcyI6WyJyb2xlX3kiXSwic3ViIjoiMTIzNDU2Nzg5MHF3ZXJ0eXVpbyJ9.LRUfHzFra-9kmG_VTqA1FWFEbe3yZon7Y8yWN6CGPzk" 7 | } 8 | }, 9 | "out": { 10 | "status_code": 401, 11 | "body": "", 12 | "header": { 13 | "content-type": [""], 14 | "Cache-Control": [""] 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /tests/fixtures/specs/cel-7.json: -------------------------------------------------------------------------------- 1 | { 2 | "in": { 3 | "method": "GET", 4 | "url": "http://localhost:8080/cel/jwt/ok", 5 | "header": { 6 | "authorization": "bearer eyJhbGciOiJIUzI1NiIsImtpZCI6InNpbTIifQ.eyJhdWQiOiJodHRwOi8vYXBpLmV4YW1wbGUuY29tIiwiZXhwIjoyMDUxODgyNzU1LCJodHRwOi8vZXhhbXBsZS5jb20vY3VzdG9tIjoiZm9vYmFyIiwiaXNzIjoiaHR0cHM6Ly9rcmFrZW5kLmlvIiwianRpIjoibW5iMjN2Y3NydDc1Nnl1aW9tbmJ2Y3g5OGVydHl1aW9wIiwicm9sZXMiOlsicm9sZV9hIiwicm9sZV9iIl0sInNjb3BlIjoicHJvZHVjdDpsaXN0OnJlYWQgcHJvZHVjdDppdGVtOnJlYWQiLCJzdWIiOiIxMjM0NTY3ODkwcXdlcnR5dWlvIn0.TRacL5nwlMhTCZfL32zcXtiWub8gP-3Snv1iTyCrpj4" 7 | } 8 | }, 9 | "out": { 10 | "status_code": 200, 11 | "body": { 12 | "foo":42, 13 | "headers":{ 14 | "Accept-Encoding":["gzip"], 15 | "User-Agent":["KrakenD Version 2.10.0"], 16 | "X-Forwarded-Host":["localhost:8080"] 17 | }, 18 | "path":"/param_forwarding/ok/1234567890qwertyuio/foobar", 19 | "query":{} 20 | }, 21 | "header": { 22 | "content-type": ["application/json; charset=utf-8"], 23 | "Cache-Control": ["public, max-age=3600"], 24 | "X-Krakend-Completed": ["true"] 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /tests/fixtures/specs/collection.json: -------------------------------------------------------------------------------- 1 | { 2 | "in": { 3 | "method": "GET", 4 | "url": "http://localhost:8080/combination/2" 5 | }, 6 | "out": { 7 | "status_code": 200, 8 | "body": "{\"foo\":42,\"posts\":[{\"i\":0,\"path\":\"/collection/2\"},{\"i\":1,\"path\":\"/collection/2\"},{\"i\":2,\"path\":\"/collection/2\"},{\"i\":3,\"path\":\"/collection/2\"},{\"i\":4,\"path\":\"/collection/2\"},{\"i\":5,\"path\":\"/collection/2\"},{\"i\":6,\"path\":\"/collection/2\"},{\"i\":7,\"path\":\"/collection/2\"},{\"i\":8,\"path\":\"/collection/2\"},{\"i\":9,\"path\":\"/collection/2\"}]}", 9 | "header": { 10 | "content-type": ["application/json; charset=utf-8"], 11 | "Cache-Control": ["public, max-age=3600"], 12 | "X-Krakend-Completed": ["true"] 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /tests/fixtures/specs/cors_1.json: -------------------------------------------------------------------------------- 1 | { 2 | "in": { 3 | "method": "OPTIONS", 4 | "url": "http://localhost:8080/param_forwarding/some/foo/bar?a=1&b=2", 5 | "header": { 6 | "User-Agent": "some", 7 | "Origin": "http://foo.example", 8 | "Access-Control-Request-Method": "GET", 9 | "Access-Control-Request-Headers": "content-type,origin" 10 | } 11 | }, 12 | "out": { 13 | "status_code": 204, 14 | "body": "", 15 | "header": { 16 | "content-type": [""], 17 | "Cache-Control": [""], 18 | "X-Krakend-Completed": [""], 19 | "Access-Control-Allow-Origin": ["*"], 20 | "Access-Control-Allow-Methods": ["GET"], 21 | "Access-Control-Allow-Headers": ["content-type,origin"] 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /tests/fixtures/specs/cors_2.json: -------------------------------------------------------------------------------- 1 | { 2 | "in": { 3 | "method": "OPTIONS", 4 | "url": "http://localhost:8080/param_forwarding/some/foo/bar?a=1&b=2", 5 | "header": { 6 | "User-Agent": "some", 7 | "Origin": "http://foo.example.tld", 8 | "Access-Control-Request-Method": "PUT" 9 | } 10 | }, 11 | "out": { 12 | "status_code": 204, 13 | "body": "", 14 | "header": { 15 | "content-type": [""], 16 | "Cache-Control": [""], 17 | "X-Krakend-Completed": [""], 18 | "Access-Control-Allow-Origin": [""], 19 | "Access-Control-Allow-Methods": [""], 20 | "Access-Control-Allow-Headers": [""] 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /tests/fixtures/specs/cors_3.json: -------------------------------------------------------------------------------- 1 | { 2 | "in": { 3 | "method": "OPTIONS", 4 | "url": "http://localhost:8080/param_forwarding/some/foo/bar?a=1&b=2", 5 | "header": { 6 | "User-Agent": "some", 7 | "Origin": "http://foo.example", 8 | "Access-Control-Request-Method": "POST", 9 | "Access-Control-Request-Headers": "X-PINGOTHER" 10 | } 11 | }, 12 | "out": { 13 | "status_code": 204, 14 | "body": "", 15 | "header": { 16 | "content-type": [""], 17 | "Cache-Control": [""], 18 | "X-Krakend-Completed": [""], 19 | "Access-Control-Allow-Origin": [""], 20 | "Access-Control-Allow-Methods": [""], 21 | "Access-Control-Allow-Headers": [""] 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /tests/fixtures/specs/cors_4.json: -------------------------------------------------------------------------------- 1 | { 2 | "in": { 3 | "method": "OPTIONS", 4 | "url": "http://localhost:8080/param_forwarding/some/foo/bar?a=1&b=2", 5 | "header": { 6 | "User-Agent": "some", 7 | "Origin": "http://foo.example.tld", 8 | "Access-Control-Request-Method": "GET", 9 | "Access-Control-Request-Headers": "content-type,origin" 10 | } 11 | }, 12 | "out": { 13 | "status_code": 204, 14 | "body": "", 15 | "header": { 16 | "content-type": [""], 17 | "Cache-Control": [""], 18 | "X-Krakend-Completed": [""], 19 | "Access-Control-Allow-Origin": ["*"], 20 | "Access-Control-Allow-Methods": ["GET"], 21 | "Access-Control-Allow-Headers": ["content-type,origin"] 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /tests/fixtures/specs/cors_5.json: -------------------------------------------------------------------------------- 1 | { 2 | "in": { 3 | "method": "GET", 4 | "url": "http://localhost:8080/param_forwarding/some/foo/bar", 5 | "header": { 6 | "User-Agent": "some", 7 | "Origin": "http://foo.example.tld" 8 | } 9 | }, 10 | "out": { 11 | "status_code": 200, 12 | "body": "{\"foo\":42,\"headers\":{\"Accept-Encoding\":[\"gzip\"],\"User-Agent\":[\"KrakenD Version 2.10.0\"],\"X-Forwarded-Host\":[\"localhost:8080\"]},\"path\":\"/param_forwarding/bar\",\"query\":{\"foo\":[\"foo\"]}}", 13 | "header": { 14 | "content-type": ["application/json; charset=utf-8"], 15 | "Cache-Control": ["public, max-age=3600"], 16 | "X-Krakend-Completed": ["true"], 17 | "X-Krakend": ["Version 2.10.0"], 18 | "Vary": ["Origin"], 19 | "Access-Control-Allow-Origin": ["*"], 20 | "Access-Control-Expose-Headers": ["Content-Length"] 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /tests/fixtures/specs/detail_error.json: -------------------------------------------------------------------------------- 1 | { 2 | "in": { 3 | "method": "GET", 4 | "url": "http://localhost:8080/detail_error" 5 | }, 6 | "out": { 7 | "status_code": 200, 8 | "body": { 9 | "error_backend_b": { 10 | "http_status_code": 404, 11 | "http_body_encoding": "text/plain; charset=utf-8", 12 | "http_body": "404 page not found\n" 13 | }, 14 | "foo": 42, 15 | "headers": { 16 | "Accept-Encoding": ["gzip"], 17 | "User-Agent": ["KrakenD Version 2.10.0"], 18 | "X-Forwarded-Host": ["localhost:8080"] 19 | }, 20 | "path": "/param_forwarding/", 21 | "query": {} 22 | }, 23 | "header": { 24 | "content-type": ["application/json; charset=utf-8"], 25 | "Cache-Control": [""], 26 | "X-Krakend-Completed": ["false"] 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /tests/fixtures/specs/flatmap_1.json: -------------------------------------------------------------------------------- 1 | { 2 | "in": { 3 | "method": "GET", 4 | "url": "http://localhost:8080/flatmap/2" 5 | }, 6 | "out": { 7 | "status_code": 200, 8 | "body": "{\"paths\":[{\"url\":\"/collection/2\"},{\"url\":\"/collection/2\"},{\"url\":\"/collection/2\"},{\"url\":\"/collection/2\"},{\"url\":\"/collection/2\"},{\"url\":\"/collection/2\"},{\"url\":\"/collection/2\"},{\"url\":\"/collection/2\"},{\"url\":\"/collection/2\"},{\"url\":\"/collection/2\"}]}", 9 | "header": { 10 | "content-type": ["application/json; charset=utf-8"], 11 | "Cache-Control": ["public, max-age=3600"], 12 | "X-Krakend-Completed": ["true"] 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /tests/fixtures/specs/integration_jsonschema.json: -------------------------------------------------------------------------------- 1 | { 2 | "@comment": "Makes sure that the health endpoint contains three fields with the right types", 3 | "in": { 4 | "url": "http://localhost:8080/__health" 5 | }, 6 | "out": { 7 | "status_code": 200, 8 | "schema": { 9 | "$schema": "http://json-schema.org/draft-07/schema#", 10 | "required": ["agents","now","status"], 11 | "properties": { 12 | "agents": { 13 | "type": "object" 14 | }, 15 | "now": { 16 | "type": "string" 17 | }, 18 | "status": { 19 | "type": "string", 20 | "enum": ["ok"] 21 | } 22 | } 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /tests/fixtures/specs/integration_schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "in": { 3 | "method": "GET", 4 | "url": "http://localhost:8080/static" 5 | }, 6 | "out": { 7 | "status_code": 200, 8 | "@comment": "schema is used over the body", 9 | "body": "{\"foo\":42,\"bar\":\"foobar\"}", 10 | "schema": { 11 | "$schema": "http://json-schema.org/draft-04/schema#", 12 | "type": "object", 13 | "properties": { 14 | "foo": { 15 | "type": "integer" 16 | }, 17 | "bar": { 18 | "type": "string" 19 | } 20 | }, 21 | "required": [ 22 | "foo", 23 | "bar" 24 | ] 25 | }, 26 | "header": { 27 | "content-type": [ 28 | "application/json; charset=utf-8" 29 | ], 30 | "Cache-Control": [ 31 | "public, max-age=3600" 32 | ], 33 | "X-Krakend-Completed": [ 34 | "true" 35 | ] 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /tests/fixtures/specs/jsonschema_1.json: -------------------------------------------------------------------------------- 1 | { 2 | "in": { 3 | "method": "POST", 4 | "url": "http://localhost:8080/jsonschema", 5 | "body": { 6 | "foo": 42 7 | } 8 | }, 9 | "out": { 10 | "status_code": 400 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /tests/fixtures/specs/jwt_1.json: -------------------------------------------------------------------------------- 1 | { 2 | "in": { 3 | "method": "GET", 4 | "url": "http://localhost:8080/private/custom", 5 | "header": { 6 | "authorization": "bearer eyJhbGciOiJIUzI1NiIsImtpZCI6InNpbTIifQ.eyJhdWQiOiJodHRwOi8vYXBpLmV4YW1wbGUuY29tIiwiZXhwIjoyMDUxODgyNzU1LCJpc3MiOiJodHRwczovL2tyYWtlbmQuaW8iLCJqdGkiOiJtbmIyM3Zjc3J0NzU2eXVpb21uYnZjeDk4ZXJ0eXVpb3AiLCJyb2xlcyI6WyJyb2xlX3kiXSwic3ViIjoiMTIzNDU2Nzg5MHF3ZXJ0eXVpbyJ9.J4YMo4FzMxhVgRS5nZrTVgncqBu3pfF-CkT1J1lHPb8" 7 | } 8 | }, 9 | "out": { 10 | "status_code": 403, 11 | "body": "", 12 | "header": { 13 | "content-type": [""], 14 | "Cache-Control": [""], 15 | "X-Krakend-Completed": [""] 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /tests/fixtures/specs/jwt_2.json: -------------------------------------------------------------------------------- 1 | { 2 | "in": { 3 | "method": "GET", 4 | "url": "http://localhost:8080/private/custom", 5 | "header": { 6 | "authorization": "bearer a.bad.token" 7 | } 8 | }, 9 | "out": { 10 | "status_code": 401, 11 | "body": "", 12 | "header": { 13 | "content-type": [""], 14 | "Cache-Control": [""], 15 | "X-Krakend-Completed": [""] 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /tests/fixtures/specs/jwt_3.json: -------------------------------------------------------------------------------- 1 | { 2 | "in": { 3 | "method": "GET", 4 | "url": "http://localhost:8080/private/custom", 5 | "header": { 6 | "authorization": "bearer eyJhbGciOiJIUzI1NiIsImtpZCI6InNpbTIifQ.eyJhdWQiOiJodHRwOi8vYXBpLmV4YW1wbGUuY29tIiwiZXhwIjoyMDUxODgyNzU1LCJpc3MiOiJodHRwczovL2tyYWtlbmQuaW8iLCJqdGkiOiJtbmIyM3Zjc3J0NzU2eXVpb21uYnZjeDk4ZXJ0eXVpb3AiLCJyb2xlcyI6WyJyb2xlX2EiLCJyb2xlX2IiXSwic3ViIjoiMTIzNDU2Nzg5MHF3ZXJ0eXVpbyJ9.BSHRvJ9_QdIT3F2pVhIhyzFv0vQ43Ltz32ehrtLhlIw" 7 | } 8 | }, 9 | "out": { 10 | "status_code": 200, 11 | "body": "{\"age\":25,\"firstName\":\"John\",\"id_user\":1,\"lastName\":\"Smith\"}", 12 | "header": { 13 | "content-type": ["application/json; charset=utf-8"], 14 | "Cache-Control": ["public, max-age=3600"], 15 | "X-Krakend-Completed": ["true"] 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /tests/fixtures/specs/jwt_4.json: -------------------------------------------------------------------------------- 1 | { 2 | "in": { 3 | "method": "GET", 4 | "url": "http://localhost:8080/token" 5 | }, 6 | "out": { 7 | "status_code": 200, 8 | "body": "{\"access_token\":\"eyJhbGciOiJIUzI1NiIsImtpZCI6InNpbTIiLCJ0eXAiOiJKV1QifQ.eyJhdWQiOiJodHRwOi8vYXBpLmV4YW1wbGUuY29tIiwiZXhwIjoxNzM1Njg5NjAwLCJpc3MiOiJodHRwczovL2tyYWtlbmQuaW8iLCJqdGkiOiJtbmIyM3Zjc3J0NzU2eXVpb21uYnZjeDk4ZXJ0eXVpb3AiLCJyb2xlcyI6WyJyb2xlX3kiXSwic3ViIjoiMTIzNDU2Nzg5MHF3ZXJ0eXVpbyJ9.zkfOW6Fsd0DzhYTXjc_SA3oOv2mG76JZTAvRaVibPNA\"}", 9 | "header": { 10 | "content-type": ["application/json; charset=utf-8"], 11 | "Cache-Control": [""], 12 | "X-Krakend-Completed": [""] 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /tests/fixtures/specs/jwt_5.json: -------------------------------------------------------------------------------- 1 | { 2 | "in": { 3 | "method": "GET", 4 | "url": "http://localhost:8080/private/extract_jwt_claim", 5 | "header": { 6 | "authorization": "bearer eyJhbGciOiJIUzI1NiIsImtpZCI6InNpbTIifQ.eyJhdWQiOiJodHRwOi8vYXBpLmV4YW1wbGUuY29tIiwiZXhwIjoyMDUxODgyNzU1LCJpc3MiOiJodHRwczovL2tyYWtlbmQuaW8iLCJqdGkiOiJtbmIyM3Zjc3J0NzU2eXVpb21uYnZjeDk4ZXJ0eXVpb3AiLCJyb2xlcyI6WyJyb2xlX2EiLCJyb2xlX2IiXSwic3ViIjoiMTIzNDU2Nzg5MHF3ZXJ0eXVpbyJ9.BSHRvJ9_QdIT3F2pVhIhyzFv0vQ43Ltz32ehrtLhlIw" 7 | } 8 | }, 9 | "out": { 10 | "status_code": 200, 11 | "body": "{\"age\":25,\"firstName\":\"John\",\"id_user\":1,\"lastName\":\"Smith\",\"path\":\"/param_forwarding/1234567890qwertyuio\"}", 12 | "header": { 13 | "content-type": ["application/json; charset=utf-8"], 14 | "Cache-Control": ["public, max-age=3600"], 15 | "X-Krakend-Completed": ["true"] 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /tests/fixtures/specs/lua_1.json: -------------------------------------------------------------------------------- 1 | { 2 | "in": { 3 | "method": "GET", 4 | "url": "http://localhost:8080/lua/collection", 5 | "header": { 6 | "Authorization": "bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWQiOjEsImlhdCI6MTUxNjIzOTAyMn0.X_M6O0tdAGnnqqYoV_Q4xQZeG58gth-PG7KSW96tsic" 7 | } 8 | }, 9 | "out": { 10 | "status_code": 200, 11 | "body": "{\"paths\":{\"1\":\"/collection/1\",\"10\":\"/collection/1\",\"2\":\"/collection/1\",\"3\":\"/collection/1\",\"4\":\"/collection/1\",\"5\":\"/collection/1\",\"6\":\"/collection/1\",\"7\":\"/collection/1\",\"8\":\"/collection/1\",\"9\":\"/collection/1\"},\"total\":10}", 12 | "header": { 13 | "content-type": ["application/json; charset=utf-8"], 14 | "Cache-Control": ["public, max-age=3600"], 15 | "X-Krakend-Completed": ["true"] 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /tests/fixtures/specs/lua_2.json: -------------------------------------------------------------------------------- 1 | { 2 | "in": { 3 | "method": "GET", 4 | "url": "http://localhost:8080/lua/custom_error" 5 | }, 6 | "out": { 7 | "status_code": 418, 8 | "body": {"msg":"I refuse to make any coffee, I am a teapot!"} 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /tests/fixtures/specs/negotitate_json.json: -------------------------------------------------------------------------------- 1 | { 2 | "in": { 3 | "method": "GET", 4 | "url": "http://localhost:8080/negotiate", 5 | "header": { 6 | "accept": "application/json; charset=utf-8" 7 | } 8 | }, 9 | "out": { 10 | "status_code": 200, 11 | "body": "{\"user\":{\"-type\":\"admin\",\"name\":\"Elliot\",\"social\":{\"facebook\":\"https://facebook.com\",\"twitter\":\"https://twitter.com\",\"youtube\":\"https://youtube.com\"}}}", 12 | "header": { 13 | "content-type": ["application/json; charset=utf-8"], 14 | "Cache-Control": ["public, max-age=3600"], 15 | "X-Krakend-Completed": ["true"] 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /tests/fixtures/specs/negotitate_plain.json: -------------------------------------------------------------------------------- 1 | { 2 | "in": { 3 | "method": "GET", 4 | "url": "http://localhost:8080/negotiate", 5 | "header": { 6 | "accept": "text/plain" 7 | } 8 | }, 9 | "out": { 10 | "status_code": 200, 11 | "body": "user:\n -type: admin\n name: Elliot\n social:\n facebook: https://facebook.com\n twitter: https://twitter.com\n youtube: https://youtube.com\n", 12 | "header": { 13 | "content-type": ["application/x-yaml; charset=utf-8"], 14 | "Cache-Control": ["public, max-age=3600"], 15 | "X-Krakend-Completed": ["true"] 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /tests/fixtures/specs/no-op_1.json: -------------------------------------------------------------------------------- 1 | { 2 | "in": { 3 | "method": "POST", 4 | "url": "http://localhost:8080/no-op/ok" 5 | }, 6 | "out": { 7 | "status_code": 200, 8 | "body": "{\"foo\":42,\"headers\":{\"Accept-Encoding\":[\"gzip\"],\"Content-Length\":[\"0\"],\"User-Agent\":[\"KrakenD Version 2.10.0\"],\"X-Forwarded-Host\":[\"localhost:8080\"]},\"path\":\"/param_forwarding/\",\"query\":{}}\n", 9 | "header": { 10 | "content-type": ["application/json"], 11 | "Cache-Control": [""], 12 | "Set-Cookie": ["test1=test1","test2=test2"], 13 | "X-Krakend-Completed": ["false"] 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /tests/fixtures/specs/no-op_2.json: -------------------------------------------------------------------------------- 1 | { 2 | "in": { 3 | "method": "POST", 4 | "url": "http://localhost:8080/no-op/ko" 5 | }, 6 | "out": { 7 | "status_code": 404, 8 | "body": "404 page not found", 9 | "header": { 10 | "content-type": ["text/plain"], 11 | "Cache-Control": [""] 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /tests/fixtures/specs/param_forwarding_1.json: -------------------------------------------------------------------------------- 1 | { 2 | "in": { 3 | "method": "GET", 4 | "url": "http://localhost:8080/param_forwarding/some/foo/bar?a=1&b=2", 5 | "header": { 6 | "Authorization": "bearer 123456", 7 | "X-Y-Z": "true", 8 | "A-B-C": "ignore" 9 | } 10 | }, 11 | "out": { 12 | "status_code": 200, 13 | "body": "{\"foo\":42,\"headers\":{\"Accept-Encoding\":[\"gzip\"],\"Authorization\":[\"bearer 123456\"],\"User-Agent\":[\"KrakenD Version 2.10.0\"],\"X-Forwarded-Host\":[\"localhost:8080\"],\"X-Y-Z\":[\"true\"]},\"path\":\"/param_forwarding/bar\",\"query\":{\"foo\":[\"foo\"]}}", 14 | "header": { 15 | "content-type": ["application/json; charset=utf-8"], 16 | "Cache-Control": ["public, max-age=3600"], 17 | "X-Krakend-Completed": ["true"] 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /tests/fixtures/specs/param_forwarding_2.json: -------------------------------------------------------------------------------- 1 | { 2 | "in": { 3 | "method": "GET", 4 | "url": "http://localhost:8080/param_forwarding/all/foo/bar?a=1&b=2", 5 | "header": { 6 | "Authorization": "bearer 123456", 7 | "X-Y-Z": "true", 8 | "A-B-C": "ignore" 9 | } 10 | }, 11 | "out": { 12 | "status_code": 200, 13 | "body": "{\"foo\":42,\"headers\":{\"A-B-C\":[\"ignore\"],\"Accept-Encoding\":[\"gzip\"],\"Authorization\":[\"bearer 123456\"],\"User-Agent\":[\"Go-http-client/1.1\"],\"X-Forwarded-Host\":[\"localhost:8080\"],\"X-Forwarded-Via\":[\"KrakenD Version 2.10.0\"],\"X-Y-Z\":[\"true\"]},\"path\":\"/param_forwarding/bar\",\"query\":{\"foo\":[\"foo\"]}}", 14 | "header": { 15 | "content-type": ["application/json; charset=utf-8"], 16 | "Cache-Control": ["public, max-age=3600"], 17 | "X-Krakend-Completed": ["true"] 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /tests/fixtures/specs/param_forwarding_3.json: -------------------------------------------------------------------------------- 1 | { 2 | "in": { 3 | "method": "GET", 4 | "url": "http://localhost:8080/param_forwarding/some/foo/bar?a=1&b=2", 5 | "header": { 6 | "autHoriZatiOn": "bearer 123456", 7 | "x-y-Z": "true", 8 | "a-b-c": "ignore" 9 | } 10 | }, 11 | "out": { 12 | "status_code": 200, 13 | "body": "{\"foo\":42,\"headers\":{\"Accept-Encoding\":[\"gzip\"],\"Authorization\":[\"bearer 123456\"],\"User-Agent\":[\"KrakenD Version 2.10.0\"],\"X-Forwarded-Host\":[\"localhost:8080\"],\"X-Y-Z\":[\"true\"]},\"path\":\"/param_forwarding/bar\",\"query\":{\"foo\":[\"foo\"]}}", 14 | "header": { 15 | "content-type": ["application/json; charset=utf-8"], 16 | "Cache-Control": ["public, max-age=3600"], 17 | "X-Krakend-Completed": ["true"] 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /tests/fixtures/specs/param_forwarding_4.json: -------------------------------------------------------------------------------- 1 | { 2 | "in": { 3 | "method": "POST", 4 | "url": "http://localhost:8080/param_forwarding/some/foo/bar?a=1&b=2", 5 | "header": { 6 | "autHoriZatiOn": "bearer 123456", 7 | "x-y-Z": "true", 8 | "a-b-c": "ignore" 9 | }, 10 | "body": {"foo":"bar"} 11 | }, 12 | "out": { 13 | "status_code": 200, 14 | "body": { 15 | "body": "{\"foo\":\"bar\"}", 16 | "foo":42, 17 | "headers":{ 18 | "Accept-Encoding":["gzip"], 19 | "Authorization":["bearer 123456"], 20 | "User-Agent":["KrakenD Version 2.10.0"], 21 | "X-Forwarded-Host":["localhost:8080"], 22 | "X-Y-Z":["true"] 23 | }, 24 | "path":"/param_forwarding/bar", 25 | "query":{ 26 | "dump_body":["1"], 27 | "foo":["foo"] 28 | } 29 | }, 30 | "header": { 31 | "content-type": ["application/json; charset=utf-8"], 32 | "Cache-Control": ["public, max-age=3600"], 33 | "X-Krakend-Completed": ["true"] 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /tests/fixtures/specs/query_forwarding_1.json: -------------------------------------------------------------------------------- 1 | { 2 | "in": { 3 | "method": "GET", 4 | "url": "http://localhost:8080/query_forwarding/all/foo?a=1&b=2", 5 | "header": { 6 | "X-Y-Z": "true" 7 | } 8 | }, 9 | "out": { 10 | "status_code": 200, 11 | "body": "{\"foo\":42,\"headers\":{\"Accept-Encoding\":[\"gzip\"],\"User-Agent\":[\"KrakenD Version 2.10.0\"],\"X-Forwarded-Host\":[\"localhost:8080\"]},\"path\":\"/param_forwarding/foo\",\"query\":{\"a\":[\"1\"],\"b\":[\"2\"]}}", 12 | "header": { 13 | "content-type": ["application/json; charset=utf-8"], 14 | "Cache-Control": ["public, max-age=3600"], 15 | "X-Krakend-Completed": ["true"] 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /tests/fixtures/specs/query_forwarding_2.json: -------------------------------------------------------------------------------- 1 | { 2 | "in": { 3 | "method": "GET", 4 | "url": "http://localhost:8080/query_forwarding/all/foo?a=1&b=2&b=3" 5 | }, 6 | "out": { 7 | "status_code": 200, 8 | "body": "{\"foo\":42,\"headers\":{\"Accept-Encoding\":[\"gzip\"],\"User-Agent\":[\"KrakenD Version 2.10.0\"],\"X-Forwarded-Host\":[\"localhost:8080\"]},\"path\":\"/param_forwarding/foo\",\"query\":{\"a\":[\"1\"],\"b\":[\"2\",\"3\"]}}", 9 | "header": { 10 | "content-type": ["application/json; charset=utf-8"], 11 | "Cache-Control": ["public, max-age=3600"], 12 | "X-Krakend-Completed": ["true"] 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /tests/fixtures/specs/query_forwarding_3.json: -------------------------------------------------------------------------------- 1 | { 2 | "in": { 3 | "method": "GET", 4 | "url": "http://localhost:8080/query_forwarding/some/foo?a=1&b=2&b=3" 5 | }, 6 | "out": { 7 | "status_code": 200, 8 | "body": "{\"foo\":42,\"headers\":{\"Accept-Encoding\":[\"gzip\"],\"User-Agent\":[\"KrakenD Version 2.10.0\"],\"X-Forwarded-Host\":[\"localhost:8080\"]},\"path\":\"/param_forwarding/foo\",\"query\":{\"a\":[\"1\"]}}", 9 | "header": { 10 | "content-type": ["application/json; charset=utf-8"], 11 | "Cache-Control": ["public, max-age=3600"], 12 | "X-Krakend-Completed": ["true"] 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /tests/fixtures/specs/router_401_1.json: -------------------------------------------------------------------------------- 1 | { 2 | "in": { 3 | "method": "GET", 4 | "url": "http://localhost:8080/private/custom" 5 | }, 6 | "out": { 7 | "status_code": 401, 8 | "body": "", 9 | "header": { 10 | "Cache-Control": [""], 11 | "content-type": [""], 12 | "X-Krakend-Completed": [""] 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /tests/fixtures/specs/router_404.json: -------------------------------------------------------------------------------- 1 | { 2 | "in": { 3 | "method": "GET", 4 | "url": "http://localhost:8080/unknown" 5 | }, 6 | "out": { 7 | "status_code": 404, 8 | "body": "404 page not found", 9 | "header": { 10 | "content-type": ["text/plain"], 11 | "Cache-Control": [""] 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /tests/fixtures/specs/router_405.json: -------------------------------------------------------------------------------- 1 | { 2 | "in": { 3 | "method": "POST", 4 | "url": "http://localhost:8080/crash" 5 | }, 6 | "out": { 7 | "status_code": 405, 8 | "body": "405 method not allowed", 9 | "header": { 10 | "content-type": ["text/plain"], 11 | "Cache-Control": [""], 12 | "X-Krakend-Completed": ["false"] 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /tests/fixtures/specs/router_redirect.json: -------------------------------------------------------------------------------- 1 | { 2 | "in": { 3 | "method": "GET", 4 | "url": "http://localhost:8080/StAtic" 5 | }, 6 | "out": { 7 | "status_code": 200, 8 | "body": "{\"bar\":\"foobar\",\"foo\":42}", 9 | "header": { 10 | "content-type": ["application/json; charset=utf-8"], 11 | "Cache-Control": ["public, max-age=3600"], 12 | "X-Krakend-Completed": ["true"] 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /tests/fixtures/specs/rss.json: -------------------------------------------------------------------------------- 1 | { 2 | "in": { 3 | "method": "GET", 4 | "url": "http://localhost:8080/show/1482" 5 | }, 6 | "out": { 7 | "status_code": 200, 8 | "body": "{\"available\":{\"title\":\"showRSS personal feed\"},\"schedule\":{\"title\":\"showRSS schedule feed\"}}", 9 | "header": { 10 | "content-type": ["application/json; charset=utf-8"], 11 | "Cache-Control": ["public, max-age=3600"], 12 | "X-Krakend-Completed": ["true"] 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /tests/fixtures/specs/sequential_1.json: -------------------------------------------------------------------------------- 1 | { 2 | "in": { 3 | "method": "GET", 4 | "url": "http://localhost:8080/sequential/ok/foobar" 5 | }, 6 | "out": { 7 | "status_code": 200, 8 | "body": "{\"foo\":42,\"headers\":{\"Accept-Encoding\":[\"gzip\"],\"User-Agent\":[\"KrakenD Version 2.10.0\"],\"X-Forwarded-Host\":[\"localhost:8080\"]},\"path\":\"/param_forwarding/42\",\"query\":{}}", 9 | "header": { 10 | "content-type": ["application/json; charset=utf-8"], 11 | "Cache-Control": ["public, max-age=3600"], 12 | "X-Krakend-Completed": ["true"] 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /tests/fixtures/specs/sequential_2.json: -------------------------------------------------------------------------------- 1 | { 2 | "in": { 3 | "method": "GET", 4 | "url": "http://localhost:8080/sequential/incomplete/foobar" 5 | }, 6 | "out": { 7 | "status_code": 200, 8 | "body": "{\"foo\":42}", 9 | "header": { 10 | "content-type": ["application/json; charset=utf-8"], 11 | "Cache-Control": [""], 12 | "X-Krakend-Completed": ["false"] 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /tests/fixtures/specs/sequential_3.json: -------------------------------------------------------------------------------- 1 | { 2 | "in": { 3 | "method": "GET", 4 | "url": "http://localhost:8080/sequential/propagate" 5 | }, 6 | "out": { 7 | "status_code": 200, 8 | "body": "{\"first\":{\"foo\":42},\"second\":{\"path\":\"/param_forwarding/42\"}}", 9 | "header": { 10 | "content-type": ["application/json; charset=utf-8"], 11 | "Cache-Control": ["public, max-age=3600"], 12 | "X-Krakend-Completed": ["true"] 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /tests/fixtures/specs/sequential_4.json: -------------------------------------------------------------------------------- 1 | { 2 | "in": { 3 | "method": "GET", 4 | "url": "http://localhost:8080/sequential/propagate-full" 5 | }, 6 | "out": { 7 | "status_code": 200, 8 | "body": "{\"foo\":42,\"headers\":{\"Accept-Encoding\":[\"gzip\"],\"User-Agent\":[\"KrakenD Version 2.10.0\"],\"X-Forwarded-Host\":[\"localhost:8080\"]},\"path\":\"/param_forwarding/eyJtZXNzYWdlIjoicG9uZyJ9\",\"query\":{}}", 9 | "header": { 10 | "content-type": ["application/json; charset=utf-8"], 11 | "Cache-Control": ["public, max-age=3600"], 12 | "X-Krakend-Completed": ["true"] 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /tests/fixtures/specs/static.json: -------------------------------------------------------------------------------- 1 | { 2 | "in": { 3 | "method": "GET", 4 | "url": "http://localhost:8080/static" 5 | }, 6 | "out": { 7 | "status_code": 200, 8 | "body": "{\"bar\":\"foobar\",\"foo\":42}", 9 | "header": { 10 | "content-type": ["application/json; charset=utf-8"], 11 | "Cache-Control": ["public, max-age=3600"], 12 | "X-Krakend-Completed": ["true"] 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /tests/fixtures/specs/timeout.json: -------------------------------------------------------------------------------- 1 | { 2 | "in": { 3 | "method": "GET", 4 | "url": "http://localhost:8080/timeout" 5 | }, 6 | "out": { 7 | "status_code": 200, 8 | "body": "{\"first\":{\"foo\":42,\"headers\":{\"Accept-Encoding\":[\"gzip\"],\"User-Agent\":[\"KrakenD Version 2.10.0\"],\"X-Forwarded-Host\":[\"localhost:8080\"]},\"path\":\"/param_forwarding/123\",\"query\":{}}}", 9 | "header": { 10 | "content-type": ["application/json; charset=utf-8"], 11 | "Cache-Control": [""], 12 | "X-Krakend-Completed": ["false"] 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /tests/fixtures/specs/xml_1.json: -------------------------------------------------------------------------------- 1 | { 2 | "in": { 3 | "method": "GET", 4 | "url": "http://localhost:8080/xml/xml" 5 | }, 6 | "out": { 7 | "status_code": 200, 8 | "body": "Elliothttps://facebook.comhttps://twitter.comhttps://youtube.com", 9 | "header": { 10 | "content-type": ["application/xml"], 11 | "Cache-Control": ["public, max-age=3600"], 12 | "X-Krakend-Completed": ["true"] 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /tests/fixtures/specs/xml_2.json: -------------------------------------------------------------------------------- 1 | { 2 | "in": { 3 | "method": "GET", 4 | "url": "http://localhost:8080/xml/json" 5 | }, 6 | "out": { 7 | "status_code": 200, 8 | "body": "42gziphttp://127.0.0.1:8081/param_forwardingKrakenD Version 2.10.0localhost:8080/param_forwarding/", 9 | "header": { 10 | "content-type": ["application/xml"], 11 | "Cache-Control": ["public, max-age=3600"], 12 | "X-Krakend-Completed": ["true"] 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /tests/integration.go: -------------------------------------------------------------------------------- 1 | // Package tests implements utility functions to help with API Gateway testing. 2 | package tests 3 | 4 | import ( 5 | "bytes" 6 | "encoding/json" 7 | "flag" 8 | "fmt" 9 | "io" 10 | "log" 11 | "net" 12 | "net/http" 13 | "net/textproto" 14 | "os" 15 | "os/exec" 16 | "path" 17 | "reflect" 18 | "regexp" 19 | "strconv" 20 | "strings" 21 | "sync" 22 | "time" 23 | 24 | "github.com/xeipuuv/gojsonschema" 25 | ) 26 | 27 | var ( 28 | defaultBinPath *string = flag.String("krakend_bin_path", ".././krakend", "The default path to the krakend bin") 29 | defaultSpecsPath *string = flag.String("krakend_specs_path", "./fixtures/specs", "The default path to the specs folder") 30 | defaultBackendPort *int = flag.Int("krakend_backend_port", 8081, "The port for the mocked backend api") 31 | defaultCfgPath *string = flag.String( 32 | "krakend_config_path", 33 | "fixtures/krakend.json", 34 | "The default path to the krakend config", 35 | ) 36 | defaultDelay *time.Duration = flag.Duration( 37 | "krakend_delay", 38 | 200*time.Millisecond, 39 | "The delay for the delayed backend endpoint", 40 | ) 41 | defaultEnvironPatterns *string = flag.String( 42 | "krakend_envar_pattern", 43 | "", 44 | "Comma separated list of patterns to use to filter the envars to pass (set to \".*\" to pass everything)", 45 | ) 46 | notFollowRedirects = flag.Bool("client_not_follow_redirects", false, "The test http client should not follow http redirects") 47 | defaultStartupWait *time.Duration = flag.Duration( 48 | "krakend_startup_wait", 49 | 1500*time.Millisecond, 50 | "The time to wait to let servers startup before start testing", 51 | ) 52 | defaultReadyURL *string = flag.String( 53 | "krakend_ready_url", 54 | "", 55 | "The url to check for system under test readiness.", 56 | ) 57 | defaultReadyURLWait *time.Duration = flag.Duration( 58 | "krakend_ready_url_wait", 59 | 1500*time.Millisecond, 60 | "The maximum time to wait for the ready url to return a 200 Ok response.", 61 | ) 62 | ) 63 | 64 | // TestCase defines a single case to be tested 65 | type TestCase struct { 66 | Name string `json:"name"` 67 | Err string `json:"error"` 68 | In Input `json:"in"` 69 | Out Output `json:"out"` 70 | Next []TestCase `json:"next"` 71 | } 72 | 73 | // Input is the definition of the request to send in a given TestCase 74 | type Input struct { 75 | URL string `json:"url"` 76 | Method string `json:"method"` 77 | Header map[string]string `json:"header"` 78 | Body interface{} `json:"body"` 79 | } 80 | 81 | // Output contains the data required to verify the response received in a given TestCase 82 | type Output struct { 83 | StatusCode int `json:"status_code"` 84 | Body interface{} `json:"body"` 85 | Header map[string][]string `json:"header"` 86 | Schema map[string]interface{} `json:"schema"` 87 | } 88 | 89 | // CmdBuilder defines an interface for building the cmd to be managed by the Runner 90 | type CmdBuilder interface { 91 | New(*Config) *exec.Cmd 92 | } 93 | 94 | // BackendBuilder defines an interface for building a server as a backend for the tests 95 | type BackendBuilder interface { 96 | New(*Config) http.Server 97 | } 98 | 99 | // GenericServer defines an interface to launch a server that 100 | // could be an http.Server, a different type, or a wrapper 101 | // around multiple servers. 102 | type GenericServer interface { 103 | Close() error 104 | ListenAndServe() error 105 | } 106 | 107 | // ComposableBackendBuilder allows us to return 108 | // a more generic interface for any kind of server. 109 | type ComposableBackendBuilder interface { 110 | NewGenericServer(*Config) GenericServer 111 | } 112 | 113 | // Config contains options for running a test. 114 | type Config struct { 115 | BinPath string 116 | CfgPath string 117 | SpecsPath string 118 | EnvironPatterns string 119 | BackendPort int 120 | Delay time.Duration 121 | HttpClient *http.Client 122 | StartupWait time.Duration 123 | ReadyURL string 124 | ReadyURLWait time.Duration 125 | } 126 | 127 | func (c *Config) getBinPath() string { 128 | if c.BinPath != "" { 129 | return c.BinPath 130 | } 131 | return *defaultBinPath 132 | } 133 | 134 | func (c *Config) getCfgPath() string { 135 | if c.CfgPath != "" { 136 | return c.CfgPath 137 | } 138 | return *defaultCfgPath 139 | } 140 | 141 | func (c *Config) getSpecsPath() string { 142 | if c.SpecsPath != "" { 143 | return c.SpecsPath 144 | } 145 | return *defaultSpecsPath 146 | } 147 | 148 | func (c *Config) getBackendPort() int { 149 | if c.BackendPort != 0 { 150 | return c.BackendPort 151 | } 152 | return *defaultBackendPort 153 | } 154 | 155 | func (c *Config) getDelay() time.Duration { 156 | if c.Delay != 0 { 157 | return c.Delay 158 | } 159 | return *defaultDelay 160 | } 161 | 162 | func (c *Config) getReadyURL() string { 163 | if c.ReadyURL != "" { 164 | return c.ReadyURL 165 | } 166 | return *defaultReadyURL 167 | } 168 | 169 | func (c *Config) getReadyURLWait() time.Duration { 170 | if c.ReadyURLWait != 0 { 171 | return c.ReadyURLWait 172 | } 173 | return *defaultReadyURLWait 174 | } 175 | 176 | func (c *Config) getStartupWait() time.Duration { 177 | if c.StartupWait != 0 { 178 | return c.StartupWait 179 | } 180 | return *defaultStartupWait 181 | } 182 | 183 | func (c *Config) getEnvironPatterns() string { 184 | if c.EnvironPatterns != "" { 185 | return c.EnvironPatterns 186 | } 187 | return *defaultEnvironPatterns 188 | } 189 | 190 | func (c *Config) getHttpClient() *http.Client { 191 | if c.HttpClient != nil { 192 | return c.HttpClient 193 | } 194 | return defaultHttpClient() 195 | 196 | } 197 | 198 | func defaultHttpClient() *http.Client { 199 | if *notFollowRedirects { 200 | return &http.Client{ 201 | CheckRedirect: func(_ *http.Request, _ []*http.Request) error { 202 | return http.ErrUseLastResponse 203 | }, 204 | } 205 | } 206 | return http.DefaultClient 207 | } 208 | 209 | var defaultConfig Config 210 | 211 | // NewIntegration sets up a runner for the integration test and returns it with the parsed specs from the specs folder 212 | // and an error signaling if something went wrong. It uses the default values for any nil argument 213 | func NewIntegration(cfg *Config, cb CmdBuilder, bb BackendBuilder) (*Runner, []TestCase, error) { 214 | if cfg == nil { 215 | cfg = &defaultConfig 216 | } 217 | 218 | if cb == nil { 219 | cb = defaultCmdBuilder 220 | } 221 | cmd := cb.New(cfg) 222 | 223 | var tcs []TestCase 224 | if err := cmd.Start(); err != nil { 225 | return nil, tcs, err 226 | } 227 | closeFuncs := []func(){ 228 | func() { 229 | cmd.Process.Kill() 230 | }, 231 | } 232 | 233 | go func() { fmt.Println(cmd.Wait()) }() 234 | 235 | var err error 236 | tcs, err = testCases(*cfg) 237 | if err != nil { 238 | cmd.Process.Kill() 239 | return nil, tcs, err 240 | } 241 | 242 | if bb == nil { 243 | bb = DefaultBackendBuilder 244 | } 245 | 246 | var backend GenericServer 247 | cbb, ok := bb.(ComposableBackendBuilder) 248 | if ok { 249 | backend = cbb.NewGenericServer(cfg) 250 | } else { 251 | httpServer := bb.New(cfg) 252 | backend = &httpServer 253 | } 254 | 255 | closeFuncs = append(closeFuncs, func() { backend.Close() }) 256 | 257 | go func() { 258 | if err := backend.ListenAndServe(); err != nil { 259 | log.Printf("backend closed: %v\n", err) 260 | } 261 | }() 262 | 263 | // wait for system under test and backend server to be ready 264 | if err := waitForStartup(cfg.getReadyURL(), cfg.getReadyURLWait(), cfg.getStartupWait()); err != nil { 265 | return nil, tcs, err 266 | } 267 | 268 | return &Runner{ 269 | closeFuncs: closeFuncs, 270 | once: new(sync.Once), 271 | httpClient: cfg.getHttpClient(), 272 | }, tcs, nil 273 | } 274 | 275 | // waitForStartup checks the ready endpoint if provided for the provided time 276 | // and then and additional startupWait time for the backend services to 277 | // be ready. 278 | func waitForStartup(readyURL string, readyURLWait, startupWait time.Duration) error { 279 | if readyURL != "" { 280 | return waitForReady(readyURL, readyURLWait) 281 | } 282 | 283 | if startupWait <= 0 { 284 | startupWait = 1500 * time.Millisecond 285 | } 286 | <-time.After(startupWait) 287 | return nil 288 | } 289 | 290 | func waitForReady(readyURL string, readyURLWait time.Duration) error { 291 | if readyURLWait < time.Second { 292 | readyURLWait = time.Second 293 | } 294 | deadline := time.Now().Add(readyURLWait) 295 | resp, err := http.Get(readyURL) 296 | 297 | for (resp == nil || resp.StatusCode != 200) && time.Now().Before(deadline) { 298 | <-time.After(time.Second) 299 | resp, err = http.Get(readyURL) 300 | } 301 | 302 | if resp != nil && resp.StatusCode == 200 { 303 | fmt.Printf("system under test %s is ready\n", readyURL) 304 | return nil 305 | } 306 | 307 | if err != nil { 308 | return fmt.Errorf("cannot check %s is ready: %s", readyURL, err.Error()) 309 | } 310 | 311 | if resp != nil { 312 | return fmt.Errorf("cannot check %s is ready: expecting 200 status code, got %d", 313 | readyURL, resp.StatusCode) 314 | } 315 | 316 | return fmt.Errorf("cannot check %s is ready", readyURL) 317 | } 318 | 319 | // Runner handles the integration test execution, by dealing with the request generation, response verification 320 | // and the final shutdown 321 | type Runner struct { 322 | closeFuncs []func() 323 | once *sync.Once 324 | httpClient *http.Client 325 | } 326 | 327 | // Close shuts down the mocked backend server and the process of the instance 328 | // under test 329 | func (i *Runner) Close() { 330 | i.once.Do(func() { 331 | for _, closeF := range i.closeFuncs { 332 | closeF() 333 | } 334 | }) 335 | 336 | } 337 | 338 | // Check runs a test case, returning an error if something goes wrong 339 | func (i *Runner) Check(tc TestCase) error { 340 | req, err := newRequest(tc.In) 341 | if err != nil { 342 | return err 343 | } 344 | resp, err := i.httpClient.Do(req) 345 | if err != nil && err.Error() != tc.Err { 346 | return err 347 | } 348 | 349 | if err != nil { 350 | return nil 351 | } 352 | 353 | return assertResponse(resp, tc.Out) 354 | } 355 | 356 | type responseError struct { 357 | errMessage []string 358 | } 359 | 360 | func (m responseError) Error() string { 361 | return "wrong response:\n\t" + strings.Join(m.errMessage, "\n\t") 362 | } 363 | 364 | func assertResponse(actual *http.Response, expected Output) error { 365 | var errMsgs []string 366 | if actual.StatusCode != expected.StatusCode { 367 | errMsgs = append(errMsgs, fmt.Sprintf("unexpected status code. have: %d, want: %d", actual.StatusCode, expected.StatusCode)) 368 | } 369 | 370 | for k, vs := range expected.Header { 371 | k = textproto.CanonicalMIMEHeaderKey(k) 372 | hs, ok := actual.Header[k] 373 | isEqual := reflect.DeepEqual(vs, hs) 374 | if ok && isEqual { 375 | continue 376 | } 377 | 378 | if ok { 379 | errMsgs = append(errMsgs, fmt.Sprintf("unexpected value for header %s. have: %s, want: %s", k, hs, vs)) 380 | continue 381 | } 382 | 383 | if vs[0] != "" { 384 | errMsgs = append(errMsgs, fmt.Sprintf("header %s not present: %+v", k, actual.Header)) 385 | } 386 | } 387 | 388 | var body interface{} 389 | var bodyBytes []byte 390 | if actual.Body != nil { 391 | b, err := io.ReadAll(actual.Body) 392 | if err != nil { 393 | return err 394 | } 395 | _ = actual.Body.Close() 396 | 397 | switch expected.Body.(type) { 398 | case string: 399 | body = string(b) 400 | default: 401 | _ = json.Unmarshal(b, &body) 402 | } 403 | bodyBytes = b 404 | } 405 | 406 | if len(expected.Schema) != 0 { 407 | if len(bodyBytes) == 0 { 408 | return responseError{ 409 | errMessage: append(errMsgs, "cannot validate empty body"), 410 | } 411 | } 412 | s, err := json.Marshal(expected.Schema) 413 | if err != nil { 414 | return responseError{ 415 | errMessage: append(errMsgs, fmt.Sprintf("problem marshaling the user provided json-schema: %s", err)), 416 | } 417 | } 418 | schema, err := gojsonschema.NewSchema(gojsonschema.NewBytesLoader(s)) 419 | if err != nil { 420 | return responseError{ 421 | errMessage: append(errMsgs, fmt.Sprintf("problem generating json-schema schema: %s", err)), 422 | } 423 | } 424 | result, err := schema.Validate(gojsonschema.NewBytesLoader(bodyBytes)) 425 | if err != nil { 426 | return responseError{ 427 | errMessage: append(errMsgs, fmt.Sprintf("problem validating the body: %s", err)), 428 | } 429 | } 430 | if !result.Valid() { 431 | return responseError{ 432 | errMessage: append(errMsgs, fmt.Sprintf("the result is not valid: %s", result.Errors())), 433 | } 434 | } 435 | } else if expected.Body != "" { 436 | if !reflect.DeepEqual(body, expected.Body) { 437 | errMsgs = append(errMsgs, fmt.Sprintf("unexpected body.\n\t\thave: %v\n\t\twant: %v", body, expected.Body)) 438 | } 439 | } 440 | if len(errMsgs) == 0 { 441 | return nil 442 | } 443 | 444 | return responseError{ 445 | errMessage: errMsgs, 446 | } 447 | } 448 | 449 | func testCases(cfg Config) ([]TestCase, error) { 450 | var tcs []TestCase 451 | content, err := readSpecs(cfg.getSpecsPath()) 452 | if err != nil { 453 | return tcs, err 454 | } 455 | 456 | for name, c := range content { 457 | tc, err := parseTestCase(name, c) 458 | if err != nil { 459 | return tcs, err 460 | } 461 | tcs = append(tcs, tc) 462 | } 463 | 464 | return tcs, nil 465 | } 466 | 467 | func parseTestCase(name string, in []byte) (TestCase, error) { 468 | tc := TestCase{} 469 | if err := json.Unmarshal(in, &tc); err != nil { 470 | return tc, err 471 | } 472 | tc.Name = name 473 | 474 | return tc, nil 475 | } 476 | 477 | func newRequest(in Input) (*http.Request, error) { 478 | var body io.Reader 479 | 480 | if in.Body != nil { 481 | var b []byte 482 | switch in.Body.(type) { 483 | case string: 484 | b = []byte(in.Body.(string)) 485 | default: 486 | b, _ = json.Marshal(in.Body) 487 | } 488 | body = bytes.NewBuffer(b) 489 | } 490 | 491 | req, err := http.NewRequest(in.Method, in.URL, body) 492 | if err != nil { 493 | return nil, err 494 | } 495 | 496 | if host, ok := in.Header["Host"]; ok { 497 | req.Host = host 498 | } 499 | 500 | for k, v := range in.Header { 501 | req.Header.Add(k, v) 502 | } 503 | return req, nil 504 | } 505 | 506 | func readSpecs(dirPath string) (map[string][]byte, error) { 507 | data := map[string][]byte{} 508 | files, err := os.ReadDir(dirPath) 509 | if err != nil { 510 | return data, err 511 | } 512 | 513 | for _, file := range files { 514 | if !strings.HasSuffix(file.Name(), ".json") { 515 | continue 516 | } 517 | content, err := os.ReadFile(path.Join(dirPath, file.Name())) 518 | if err != nil { 519 | return data, err 520 | } 521 | data[file.Name()[:len(file.Name())-5]] = content 522 | } 523 | return data, nil 524 | } 525 | 526 | var defaultCmdBuilder krakendCmdBuilder 527 | 528 | type krakendCmdBuilder struct{} 529 | 530 | func (k krakendCmdBuilder) New(cfg *Config) *exec.Cmd { 531 | cmd := exec.Command(cfg.getBinPath(), "run", "-d", "-c", cfg.getCfgPath()) 532 | cmd.Env = k.getEnviron(cfg) 533 | return cmd 534 | } 535 | 536 | func (krakendCmdBuilder) getEnviron(cfg *Config) []string { 537 | environ := []string{"USAGE_DISABLE=1"} 538 | 539 | var patterns []*regexp.Regexp 540 | for _, pattern := range strings.Split(cfg.getEnvironPatterns(), ",") { 541 | re, err := regexp.Compile(pattern) 542 | if err != nil { 543 | continue 544 | } 545 | patterns = append(patterns, re) 546 | } 547 | 548 | for _, candidate := range os.Environ() { 549 | for _, pattern := range patterns { 550 | if pattern.MatchString(candidate) { 551 | environ = append(environ, candidate) 552 | break 553 | } 554 | } 555 | } 556 | 557 | return environ 558 | } 559 | 560 | var DefaultBackendBuilder mockBackendBuilder 561 | 562 | type mockBackendBuilder struct{} 563 | 564 | func (mockBackendBuilder) New(cfg *Config) http.Server { 565 | mux := http.NewServeMux() 566 | 567 | mux.HandleFunc("/param_forwarding/", checkXForwardedFor(http.HandlerFunc(echoEndpoint))) 568 | mux.HandleFunc("/xml", checkXForwardedFor(http.HandlerFunc(xmlEndpoint))) 569 | mux.HandleFunc("/collection/", checkXForwardedFor(http.HandlerFunc(collectionEndpoint))) 570 | mux.HandleFunc("/delayed/", checkXForwardedFor(delayedEndpoint(cfg.getDelay(), http.HandlerFunc(echoEndpoint)))) 571 | mux.HandleFunc("/redirect/", checkXForwardedFor(http.HandlerFunc(redirectEndpoint))) 572 | mux.HandleFunc("/jwk/symmetric", http.HandlerFunc(symmetricJWKEndpoint)) 573 | 574 | return http.Server{ // skipcq: GO-S2112 575 | Addr: fmt.Sprintf(":%v", cfg.getBackendPort()), 576 | Handler: mux, 577 | } 578 | } 579 | 580 | func collectionEndpoint(rw http.ResponseWriter, r *http.Request) { 581 | rw.Header().Add("Content-Type", "application/json") 582 | var res []interface{} 583 | 584 | for i := 0; i < 10; i++ { 585 | res = append(res, map[string]interface{}{ 586 | "path": r.URL.Path, 587 | "i": i, 588 | }) 589 | } 590 | 591 | json.NewEncoder(rw).Encode(res) 592 | } 593 | 594 | func checkXForwardedFor(h http.Handler) http.HandlerFunc { 595 | return func(rw http.ResponseWriter, r *http.Request) { 596 | if ip := net.ParseIP(r.Header.Get("X-Forwarded-For")); ip == nil || !ip.IsLoopback() { 597 | http.Error(rw, "invalid X-Forwarded-For", 400) 598 | return 599 | } 600 | h.ServeHTTP(rw, r) 601 | } 602 | } 603 | 604 | func delayedEndpoint(d time.Duration, h http.Handler) http.HandlerFunc { 605 | return func(rw http.ResponseWriter, req *http.Request) { 606 | <-time.After(d) 607 | h.ServeHTTP(rw, req) 608 | } 609 | } 610 | 611 | func xmlEndpoint(rw http.ResponseWriter, _ *http.Request) { 612 | rw.Header().Add("Content-Type", "application/xml; charset=utf-8") 613 | rw.Write([]byte(` 614 | 615 | Elliot 616 | 617 | https://facebook.com 618 | https://twitter.com 619 | https://youtube.com 620 | 621 | `)) 622 | } 623 | 624 | func echoEndpoint(rw http.ResponseWriter, r *http.Request) { 625 | rw.Header().Add("Content-Type", "application/json") 626 | rw.Header().Add("Set-Cookie", "test1=test1") 627 | rw.Header().Add("Set-Cookie", "test2=test2") 628 | r.Header.Del("X-Forwarded-For") 629 | resp := map[string]interface{}{ 630 | "path": r.URL.Path, 631 | "query": r.URL.Query(), 632 | "headers": r.Header, 633 | "foo": 42, 634 | } 635 | 636 | if r.URL.Query().Get("dump_body") == "1" { 637 | b, _ := io.ReadAll(r.Body) 638 | r.Body.Close() 639 | resp["body"] = string(b) 640 | } 641 | 642 | json.NewEncoder(rw).Encode(resp) 643 | } 644 | 645 | func redirectEndpoint(rw http.ResponseWriter, r *http.Request) { 646 | u := r.URL 647 | u.Path = "/param_forwarding/" 648 | 649 | status, ok2 := r.URL.Query()["status"] 650 | code := 301 651 | if !ok2 || status[0] != "301" { 652 | var err error 653 | code, err = strconv.Atoi(status[0]) 654 | if err != nil { 655 | http.Error(rw, err.Error(), http.StatusBadRequest) 656 | return 657 | } 658 | } 659 | http.Redirect(rw, r, u.String(), code) 660 | } 661 | 662 | func symmetricJWKEndpoint(rw http.ResponseWriter, _ *http.Request) { 663 | rw.Header().Add("Content-Type", "application/json") 664 | rw.Write([]byte(`{ 665 | "keys": [ 666 | { 667 | "kty": "oct", 668 | "alg": "A128KW", 669 | "k": "GawgguFyGrWKav7AX4VKUg", 670 | "kid": "sim1" 671 | }, 672 | { 673 | "kty": "oct", 674 | "k": "AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow", 675 | "kid": "sim2", 676 | "alg": "HS256" 677 | } 678 | ] 679 | }`)) 680 | } 681 | -------------------------------------------------------------------------------- /tests/integration_example_test.go: -------------------------------------------------------------------------------- 1 | package tests 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | func ExampleNewIntegration() { 8 | runner, tcs, err := NewIntegration(nil, nil, nil) 9 | if err != nil { 10 | fmt.Println(err) 11 | return 12 | } 13 | defer runner.Close() 14 | 15 | for _, tc := range tcs { 16 | if err := runner.Check(tc); err != nil { 17 | fmt.Printf("%s: %s", tc.Name, err.Error()) 18 | return 19 | } 20 | } 21 | 22 | // output: 23 | // signal: killed 24 | } 25 | -------------------------------------------------------------------------------- /tests/integration_test.go: -------------------------------------------------------------------------------- 1 | package tests 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestNewIntegration(t *testing.T) { 8 | runner, tcs, err := NewIntegration(nil, nil, nil) 9 | if err != nil { 10 | t.Error(err) 11 | return 12 | } 13 | defer runner.Close() 14 | 15 | for _, tc := range tcs { 16 | tc := tc 17 | t.Run(tc.Name, func(t *testing.T) { 18 | if err := runner.Check(tc); err != nil { 19 | t.Error(err) 20 | } 21 | }) 22 | } 23 | } 24 | --------------------------------------------------------------------------------