├── .github
├── NSIS
│ ├── kapow.ico
│ └── windows.nsi
├── dependabot.yml
├── go
│ └── Dockerfile
├── revokers
│ ├── 44AB0A1631645936
│ ├── 679F65C4563CF5BA
│ └── A06E2A1A3F63AA6D
└── workflows
│ ├── codeql-analysis.yml
│ └── test_and_release.yml
├── .gitignore
├── .goreleaser.yml
├── AUTHORS.rst
├── CONTRIBUTING.rst
├── Dockerfile
├── LICENSE
├── Makefile
├── README.md
├── docs
├── Makefile
├── Pipfile
├── Pipfile.lock
├── README.md
├── make.bat
├── release-notes
│ ├── RELEASE-v0.3.0.md
│ ├── RELEASE-v0.4.0.md
│ ├── RELEASE-v0.5.0.md
│ ├── RELEASE-v0.5.1.md
│ ├── RELEASE-v0.5.2.md
│ ├── RELEASE-v0.5.3.md
│ └── RELEASE-v0.5.4.md
└── source
│ ├── _static
│ ├── browser.png
│ ├── kapow-quick-overview.png
│ ├── logo-200px.png
│ ├── logo.png
│ ├── network.png
│ ├── request_life_cycle.png
│ ├── request_life_cycle.xcf
│ └── sequence.png
│ ├── concepts
│ ├── interfaces.rst
│ ├── philosophy.rst
│ ├── request_life_cycle.rst
│ ├── resource_tree.rst
│ ├── route_matching.rst
│ ├── routes.rst
│ └── toc.rst
│ ├── conf.py
│ ├── examples
│ ├── handling_http_requests.rst
│ ├── https_mtls.rst
│ ├── managing_routes.rst
│ ├── shell_tricks.rst
│ ├── toc.rst
│ ├── using_json.rst
│ └── working_with_init_programs.rst
│ ├── index.rst
│ ├── latextoc.rst
│ ├── the_project
│ ├── install_and_configure.rst
│ ├── quickstart.rst
│ ├── security.rst
│ └── toc.rst
│ └── tutorial
│ ├── index.rst
│ ├── materials
│ └── backup_db.sh
│ ├── toc.rst
│ ├── tutorial00.rst
│ ├── tutorial01.rst
│ ├── tutorial02.rst
│ ├── tutorial03.rst
│ ├── tutorial04.rst
│ ├── tutorial05.rst
│ └── tutorial06.rst
├── examples
├── advanced
│ ├── 01_DocumentConverter
│ │ ├── DocumentConverter
│ │ ├── README.md
│ │ └── index.html
│ ├── 02_NetworkScanner
│ │ ├── NetworkScanner
│ │ └── README.md
│ └── 03_NetworkSniffer
│ │ ├── NetworkSniffer
│ │ └── README.md
├── basic
│ ├── 01_HelloWorld
│ │ ├── HelloWorld
│ │ └── README.md
│ ├── 02_FixLogGrep
│ │ ├── FixLogGrep
│ │ └── README.md
│ ├── 03_DynamicLogGrep
│ │ ├── DynamicLogGrep
│ │ └── README.md
│ └── 04_SystemMonitor
│ │ ├── README.md
│ │ └── SystemMonitor
├── docker
│ └── awscli
│ │ └── Dockerfile
└── mTLS
│ └── README.md
├── go.mod
├── go.sum
├── internal
├── certs
│ └── certs.go
├── client
│ ├── client_test.go
│ ├── get.go
│ ├── get_test.go
│ ├── route_add.go
│ ├── route_add_test.go
│ ├── route_list.go
│ ├── route_list_test.go
│ ├── route_remove.go
│ ├── route_remove_test.go
│ ├── set.go
│ └── set_test.go
├── cmd
│ ├── get.go
│ ├── route.go
│ ├── runner.go
│ ├── runner_windows.go
│ ├── server.go
│ ├── set.go
│ └── validations.go
├── http
│ ├── reason.go
│ ├── reason_test.go
│ ├── request.go
│ └── request_test.go
├── logger
│ └── logger.go
└── server
│ ├── control
│ ├── control.go
│ ├── control_test.go
│ ├── entrypoint.go
│ ├── entrypoint_windows.go
│ └── server.go
│ ├── data
│ ├── decorator.go
│ ├── decorator_test.go
│ ├── resource.go
│ ├── resource_test.go
│ ├── server.go
│ ├── server_test.go
│ ├── state.go
│ ├── state_test.go
│ └── testdata
│ │ └── client_chain.crt
│ ├── httperror
│ ├── error.go
│ └── error_test.go
│ ├── model
│ ├── handler.go
│ └── route.go
│ ├── server.go
│ └── user
│ ├── mux
│ ├── gorillize.go
│ ├── gorillize_test.go
│ ├── handlerbuilder.go
│ ├── handlerbuilder_test.go
│ ├── mux.go
│ └── mux_test.go
│ ├── server.go
│ ├── spawn
│ ├── spawn.go
│ └── spawn_test.go
│ ├── state.go
│ └── state_test.go
├── main.go
├── release-key.gpg
├── spec
├── README.md
└── test
│ ├── .envrc
│ ├── .gherkin-lintrc
│ ├── Dockerfile
│ ├── Makefile
│ ├── README.rst
│ ├── features
│ ├── control
│ │ ├── append
│ │ │ ├── error_malformed.feature
│ │ │ ├── error_unprocessable.feature
│ │ │ └── success.feature
│ │ ├── delete
│ │ │ ├── error_notfound.feature
│ │ │ ├── list_order.feature
│ │ │ └── success.feature
│ │ ├── get
│ │ │ ├── error_notfound.feature
│ │ │ └── success.feature
│ │ ├── insert
│ │ │ ├── error_malformed.feature
│ │ │ ├── error_unprocessable.feature
│ │ │ ├── list_order.feature
│ │ │ └── success.feature
│ │ ├── list
│ │ │ └── success.feature
│ │ └── mtls.feature
│ ├── data
│ │ ├── handler
│ │ │ ├── error_handleridnotfound.feature
│ │ │ ├── error_invalidresource.feature
│ │ │ ├── error_itemnotfound.feature
│ │ │ └── success.feature
│ │ ├── request
│ │ │ └── success.feature
│ │ └── response
│ │ │ └── success.feature
│ ├── environment.py
│ └── steps
│ │ ├── comparedict.py
│ │ ├── get_environment.py
│ │ ├── jsonexample.py
│ │ ├── steps.py
│ │ └── testinghandler.py
│ ├── kapow
│ └── Dockerfile
│ ├── node-dependencies.nix
│ ├── node-env.nix
│ ├── node-packages.json
│ ├── node-packages.nix
│ └── shell.nix
├── test
└── runwindowsrun.go
├── testutils
├── jaillover
│ └── main.go
└── poc
│ ├── .gitignore
│ ├── README.rst
│ ├── kapow
│ └── requirements.txt
└── tools
└── validsslclient
/.github/NSIS/kapow.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BBVA/kapow/3cef9f0cd25ab41f683a883d3ae2ce042e162d59/.github/NSIS/kapow.ico
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | # Hi, Dependabot!
2 | version: 2
3 | updates:
4 | - package-ecosystem: "github-actions"
5 | directory: "/"
6 | schedule:
7 | interval: "daily"
8 |
9 | - package-ecosystem: "gomod"
10 | directory: "/"
11 | schedule:
12 | interval: "daily"
13 |
14 | - package-ecosystem: "docker"
15 | directory: "/.github/go"
16 | schedule:
17 | interval: "daily"
18 |
19 | - package-ecosystem: "pip"
20 | directory: "/"
21 | schedule:
22 | interval: "daily"
23 | ignore:
24 | - dependency-name: "*"
25 |
--------------------------------------------------------------------------------
/.github/go/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM golang:1.22.5
2 |
--------------------------------------------------------------------------------
/.github/revokers/44AB0A1631645936:
--------------------------------------------------------------------------------
1 | -----BEGIN PGP PUBLIC KEY BLOCK-----
2 |
3 | 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
51 | =Z27G
52 | -----END PGP PUBLIC KEY BLOCK-----
53 |
--------------------------------------------------------------------------------
/.github/revokers/679F65C4563CF5BA:
--------------------------------------------------------------------------------
1 | -----BEGIN PGP PUBLIC KEY BLOCK-----
2 |
3 | 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
65 | =6uBS
66 | -----END PGP PUBLIC KEY BLOCK-----
67 |
--------------------------------------------------------------------------------
/.github/workflows/codeql-analysis.yml:
--------------------------------------------------------------------------------
1 | # For most projects, this workflow file will not need changing; you simply need
2 | # to commit it to your repository.
3 | #
4 | # You may wish to alter this file to override the set of languages analyzed,
5 | # or to provide custom queries or build logic.
6 | #
7 | # ******** NOTE ********
8 | # We have attempted to detect the languages in your repository. Please check
9 | # the `language` matrix defined below to confirm you have the correct set of
10 | # supported CodeQL languages.
11 | #
12 | name: "CodeQL"
13 |
14 | on:
15 | push:
16 | branches: [ master ]
17 | pull_request:
18 | # The branches below must be a subset of the branches above
19 | branches: [ master ]
20 | schedule:
21 | - cron: '28 19 * * 4'
22 |
23 | jobs:
24 | analyze:
25 | name: Analyze
26 | runs-on: ubuntu-latest
27 | permissions:
28 | actions: read
29 | contents: read
30 | security-events: write
31 |
32 | strategy:
33 | fail-fast: false
34 | matrix:
35 | language: [ 'go' ]
36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
37 | # Learn more about CodeQL language support at https://git.io/codeql-language-support
38 |
39 | steps:
40 | - name: Checkout repository
41 | uses: actions/checkout@v4.1.7
42 |
43 | # Initializes the CodeQL tools for scanning.
44 | - name: Initialize CodeQL
45 | uses: github/codeql-action/init@v3
46 | with:
47 | languages: ${{ matrix.language }}
48 | # If you wish to specify custom queries, you can do so here or in a config file.
49 | # By default, queries listed here will override any specified in a config file.
50 | # Prefix the list here with "+" to use these queries and those in the config file.
51 | # queries: ./path/to/local/query, your-org/your-repo/queries@main
52 |
53 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
54 | # If this step fails, then you should remove it and run the build manually (see below)
55 | - name: Autobuild
56 | uses: github/codeql-action/autobuild@v3
57 |
58 | # ℹ️ Command-line programs to run using the OS shell.
59 | # 📚 https://git.io/JvXDl
60 |
61 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
62 | # and modify them (or add more) to build your code if your project
63 | # uses a compiled language
64 |
65 | #- run: |
66 | # make bootstrap
67 | # make release
68 |
69 | - name: Perform CodeQL Analysis
70 | uses: github/codeql-action/analyze@v3
71 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | __pycache__
2 |
3 | output
4 | build
5 | .vscode
6 |
7 | docs/build
8 |
9 | node_modules
10 |
11 | *.swp
12 |
--------------------------------------------------------------------------------
/.goreleaser.yml:
--------------------------------------------------------------------------------
1 | before:
2 | hooks:
3 | - go mod tidy
4 |
5 | project_name: kapow
6 |
7 | builds:
8 | - id: kapow
9 | flags:
10 | - -trimpath
11 | env:
12 | - CGO_ENABLED=0
13 | goos:
14 | - darwin
15 | - linux
16 | - windows
17 | - freebsd
18 | goarch:
19 | - 386
20 | - amd64
21 | - arm
22 | - arm64
23 | ignore:
24 | - goos: darwin
25 | goarch: 386
26 | archives:
27 | - format: binary
28 | checksum:
29 | name_template: '{{ .ProjectName }}-{{ .Version }}-SHA512SUMS'
30 | algorithm: sha512
31 | snapshot:
32 | name_template: "{{ .Tag }}-next"
33 | changelog:
34 | filters:
35 | exclude:
36 | - '^(chore|docs?|fix|refactor|style|test|typo|wip)(\([^)]+\))?:'
37 |
38 | dockers:
39 | - goos: linux
40 | goarch: amd64
41 | goarm: ''
42 |
43 | image_templates:
44 | - "bbvalabsci/kapow:latest"
45 | - "bbvalabsci/kapow:{{ .Tag }}"
46 | - "bbvalabsci/kapow:v{{ .Major }}"
47 |
48 | skip_push: true
49 |
50 | release:
51 | draft: false
52 | prerelease: auto
53 |
--------------------------------------------------------------------------------
/AUTHORS.rst:
--------------------------------------------------------------------------------
1 | Sorted by ``awk '{ print length(), $0 | "sort -n" }'``:
2 |
3 | - Luis Saiz
4 | - Emilio Belmar
5 | - César Gallego
6 | - Hector Hurtado
7 | - Sara de Pablos
8 | - pancho horrillo
9 | - Daniel García (cr0hn)
10 | - Roberto Abdelkader Martínez Pérez
11 |
--------------------------------------------------------------------------------
/CONTRIBUTING.rst:
--------------------------------------------------------------------------------
1 | ************
2 | Contributing
3 | ************
4 |
5 | Want to hack on ``Kapow!``? Awesome! Here are a few instructions to get you
6 | started. They are probably not perfect, please let us know if anything feels
7 | wrong or incomplete.
8 |
9 | In general, we follow the "fork-and-pull" Git workflow.
10 |
11 | 1. **Fork** this repo
12 | 2. **Clone** the project to your own machine
13 | 3. **Commit** changes to your own branch
14 | 4. **Push** your work back up to your fork
15 | 5. Submit a **Pull request** so that we can review your changes
16 |
17 | NOTE: Be sure to merge the latest commits from **upstream** before making a pull
18 | request!
19 |
20 | If your pull request is not accepted on the first try, don't be discouraged! If
21 | there's a problem with the implementation, hopefully you'll receive feedback on
22 | what to improve.
23 |
24 | Submit unit tests for your changes. Go has a great testing framework built
25 | in; use it! Take a look at existing tests for inspiration. Run the full test
26 | suite on your branch before submitting a pull request.
27 |
28 | Make sure you include relevant updates or additions to documentation
29 | when creating or modifying features.
30 |
31 | Before creating a Pull Request
32 | ------------------------------
33 |
34 | Please, run the tests :)
35 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM scratch
2 | COPY kapow /
3 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | .PHONY: lint build test jaillover race coverage install acceptance deps docker clean
2 |
3 | GOCMD=go
4 | GOBUILD=$(GOCMD) build -trimpath
5 | GOINSTALL=$(GOCMD) install -trimpath
6 | GOGET=$(GOCMD) get
7 | GOTEST=$(GOCMD) test
8 | GOTOOL=$(GOCMD) tool
9 | GOLANGLINT=golangci-lint
10 | PROJECTREPO=github.com/BBVA/kapow
11 |
12 | BUILD_DIR=./build
13 | OUTPUT_DIR=./output
14 | TMP_DIR=/tmp
15 | DOCS_DIR=./doc
16 | DOCKER_DIR=./docker
17 |
18 | BINARY_NAME=kapow
19 |
20 | all: lint test race build
21 |
22 | lint:
23 | $(GOLANGLINT) run
24 |
25 | build: deps
26 | mkdir -p $(BUILD_DIR)
27 | CGO_ENABLED=0 $(GOBUILD) -o $(BUILD_DIR)/$(BINARY_NAME) -v
28 |
29 | test: build jaillover
30 | $(GOTEST) -v -coverprofile=$(TMP_DIR)/c.out ./...
31 |
32 | jaillover:
33 | $(GOINSTALL) -v $(PROJECTREPO)/testutils/$@
34 |
35 | race: build
36 | $(GOTEST) -race -v ./...
37 |
38 | coverage: test race
39 | mkdir -p $(OUTPUT_DIR)
40 | $(GOTOOL) cover -html=$(TMP_DIR)/c.out -o $(OUTPUT_DIR)/coverage.html
41 |
42 | install: build
43 | CGO_ENABLED=0 $(GOINSTALL) ./...
44 |
45 | acceptance: build
46 | cd ./spec/test && PATH=$(PWD)/build:$$PATH nix-shell --command make
47 |
48 | deps:
49 | @echo "deps here"
50 |
51 | clean:
52 | rm -rf $(BUILD_DIR) $(OUTPUT_DIR) $(DOCKER_DIR)/*
53 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
If you can script it, you can HTTP it.
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | ---
14 |
15 | ## What's *Kapow!*
16 |
17 | Say we have a nice cozy **shell command** that solves our problem. *Kapow!* lets
18 | us easily **turn that into an HTTP API**.
19 |
20 |
21 | ### Let's see this with an example
22 |
23 | We want to expose **log entries** for files not found on our **Apache Web
24 | Server**, as an HTTP API. With *Kapow!* we just need to write this
25 | *executable* script:
26 |
27 | ``` console
28 | [apache-host]$ cat search-apache-errors
29 | #!/usr/bin/env sh
30 | kapow route add /apache-errors - <<-'EOF'
31 | cat /var/log/apache2/access.log | grep 'File does not exist' | kapow set /response/body
32 | EOF
33 | [apache-host]$ chmod +x search-apache-errors
34 | ```
35 |
36 | and then, run it using *Kapow!*
37 |
38 | ```bash
39 | [apache-host]$ kapow server search-apache-errors
40 | ```
41 |
42 | finally, we can read from the just-defined endpoint:
43 |
44 | ```bash
45 | [another-host]$ curl http://apache-host:8080/apache-errors
46 | [Fri Feb 01 22:07:57.154391 2019] [core:info] [pid 7:tid 140284200093440] [client 172.17.0.1:50756] AH00128: File does not exist: /usr/var/www/mysite/favicon.ico
47 | [Fri Feb 01 22:07:57.808291 2019] [core:info] [pid 8:tid 140284216878848] [client 172.17.0.1:50758] AH00128: File does not exist: /usr/var/www/mysite/favicon.ico
48 | [Fri Feb 01 22:07:57.878149 2019] [core:info] [pid 8:tid 140284208486144] [client 172.17.0.1:50758] AH00128: File does not exist: /usr/var/www/mysite/favicon.ico
49 | ...
50 | ```
51 |
52 | ### Why *Kapow!* shines in these cases
53 |
54 | - We can share information **without having to grant SSH access** to anybody.
55 | - We can share information easily **over HTTP**.
56 | - We can effectively **limit** what gets executed.
57 |
58 |
59 | ## Documentation
60 |
61 | You can find the complete documentation and examples [here](https://kapow.readthedocs.io).
62 |
63 | ## Security
64 |
65 | Please consider the following
66 | [Security Concerns](https://kapow.readthedocs.io/en/stable/the_project/security.html#security-concerns)
67 | **before** using *Kapow!*
68 |
69 | If you are not 100% sure about what you are doing we recommend not using *Kapow!*
70 |
71 | ## Authors
72 |
73 | *Kapow!* is being developed by [BBVA-Labs Security team members](https://github.com/BBVA/kapow/blob/master/AUTHORS.rst).
74 |
75 | *Kapow!* is Open Source Software and available under the [Apache 2
76 | license](https://raw.githubusercontent.com/BBVA/kapow/master/LICENSE).
77 |
78 |
79 | ## Contributions
80 |
81 | Contributions are of course welcome. See
82 | [CONTRIBUTING](https://raw.githubusercontent.com/BBVA/kapow/master/CONTRIBUTING.rst)
83 | or skim existing tickets to see where you could help out.
84 |
85 | ---
86 |
87 | * [Did you spot the UUoC?](https://github.com/BBVA/kapow/issues/118) Congrats! We are hoping [to win](http://porkmail.org/era/unix/award.html) this year :)
88 |
--------------------------------------------------------------------------------
/docs/Makefile:
--------------------------------------------------------------------------------
1 | # Minimal makefile for Sphinx documentation
2 | #
3 |
4 | # You can set these variables from the command line, and also
5 | # from the environment for the first two.
6 | SPHINXOPTS ?=
7 | SPHINXBUILD ?= sphinx-build
8 | SOURCEDIR = source
9 | BUILDDIR = build
10 |
11 | # Put it first so that "make" without argument is like "make help".
12 | help:
13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
14 |
15 | .PHONY: help Makefile
16 |
17 | # Catch-all target: route all unknown targets to Sphinx using the new
18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
19 | %: Makefile
20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
21 |
--------------------------------------------------------------------------------
/docs/Pipfile:
--------------------------------------------------------------------------------
1 | [[source]]
2 | name = "pypi"
3 | url = "https://pypi.org/simple"
4 | verify_ssl = true
5 |
6 | [dev-packages]
7 |
8 | [packages]
9 | sphinx = "*"
10 | sphinx-rtd-theme = "*"
11 |
12 | [requires]
13 | python_version = "3.10"
14 |
--------------------------------------------------------------------------------
/docs/README.md:
--------------------------------------------------------------------------------
1 | This directory contains the documentation _source code_. To see the rendered docs go to http://kapow.readthedocs.io
2 |
--------------------------------------------------------------------------------
/docs/make.bat:
--------------------------------------------------------------------------------
1 | @ECHO OFF
2 |
3 | pushd %~dp0
4 |
5 | REM Command file for Sphinx documentation
6 |
7 | if "%SPHINXBUILD%" == "" (
8 | set SPHINXBUILD=sphinx-build
9 | )
10 | set SOURCEDIR=source
11 | set BUILDDIR=build
12 |
13 | if "%1" == "" goto help
14 |
15 | %SPHINXBUILD% >NUL 2>NUL
16 | if errorlevel 9009 (
17 | echo.
18 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
19 | echo.installed, then set the SPHINXBUILD environment variable to point
20 | echo.to the full path of the 'sphinx-build' executable. Alternatively you
21 | echo.may add the Sphinx directory to PATH.
22 | echo.
23 | echo.If you don't have Sphinx installed, grab it from
24 | echo.http://sphinx-doc.org/
25 | exit /b 1
26 | )
27 |
28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
29 | goto end
30 |
31 | :help
32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
33 |
34 | :end
35 | popd
36 |
--------------------------------------------------------------------------------
/docs/release-notes/RELEASE-v0.3.0.md:
--------------------------------------------------------------------------------
1 | Kapow! v0.3.0
2 |
3 | ## Features
4 |
5 | * Feature parity with the original PoC (written in Python), with some exceptions
6 | explained in the next section.
7 |
8 | * Built and tested against Go 1.13.5.
9 |
10 | * (Almost) no bytes were harmed during the development of this release.
11 |
12 |
13 | ## Known issues and limitations
14 |
15 | ### User server
16 |
17 | * [#76][i76] Only plain `http` is supported for now, since `https` support is
18 | not yet complete. The `kapow server` flags `--certfile` and `--keyfile` are
19 | present but non-functional yet.
20 |
21 |
22 | ### Data API
23 |
24 | * [#73][i73] `/response/body` and `/response/stream` behave identically for now.
25 |
26 | * [#92][i92] `KAPOW_DATA_URL` is always set to its default value of
27 | `http://localhost:8082`, and not to the actual value, as specified by the
28 | `kapow server --data-bind "host:port"` invocation.
29 |
30 |
31 | ### Windows®
32 |
33 | * [#83][i83] `kapow server file.pow` will try to run `file.pow` through `bash`, not `cmd`
34 | or `powershell`.
35 | * `kapow route` default entrypoint is `/bin/sh`, so in order to use `cmd` or
36 | `powershell`, it must be explicitly set.
37 |
38 |
39 | [i73]: https://github.com/BBVA/kapow/issues/73
40 | [i76]: https://github.com/BBVA/kapow/issues/76
41 | [i83]: https://github.com/BBVA/kapow/issues/83
42 | [i92]: https://github.com/BBVA/kapow/issues/92
43 |
--------------------------------------------------------------------------------
/docs/release-notes/RELEASE-v0.4.0.md:
--------------------------------------------------------------------------------
1 | Kapow! v0.4.0
2 |
3 | ## Features
4 |
5 | * [#76][i76] Implement `https` support in the user server.
6 |
7 | * [#104][i104] Implement TLS mutual auth with x509 certs.
8 |
9 | * Built and tested against Go 1.14.
10 |
11 |
12 | ## Known issues and limitations
13 |
14 | ### Data API
15 |
16 | * [#73][i73] `/response/body` and `/response/stream` behave identically for now.
17 |
18 | * [#92][i92] `KAPOW_DATA_URL` is always set to its default value of
19 | `http://localhost:8082`, and not to the actual value, as specified by the
20 | `kapow server --data-bind "host:port"` invocation.
21 |
22 |
23 | ### Windows®
24 |
25 | * [#83][i83] `kapow server file.pow` will try to run `file.pow` through `bash`, not `cmd`
26 | or `powershell`.
27 | * `kapow route` default entrypoint is `/bin/sh`, so in order to use `cmd` or
28 | `powershell`, it must be explicitly set.
29 |
30 |
31 | [i73]: https://github.com/BBVA/kapow/issues/73
32 | [i76]: https://github.com/BBVA/kapow/issues/76
33 | [i83]: https://github.com/BBVA/kapow/issues/83
34 | [i92]: https://github.com/BBVA/kapow/issues/92
35 | [i104]: https://github.com/BBVA/kapow/issues/104
36 |
--------------------------------------------------------------------------------
/docs/release-notes/RELEASE-v0.5.0.md:
--------------------------------------------------------------------------------
1 | Kapow! v0.5.0
2 |
3 | ## Features
4 |
5 | * [#89][i89] Wrong environment variables exported to process
6 |
7 | * [#98][i98] Implement a proper logging system (Partially solved. Added a logging feature for scripts in User-server for aid in debugging)
8 |
9 | * [#92][i92] Spawn package uses static default value for KAPOW_DATA_URL
10 |
11 | * [#102][i102] Handle race condition between Control API and pow files
12 |
13 | * [#105][i105] Handle unexpected exit of servers
14 |
15 |
16 | ## Known issues and limitations
17 |
18 | ### Data API
19 |
20 | * [#73][i73] `/response/body` and `/response/stream` behave identically for now.
21 |
22 |
23 | ### Windows®
24 |
25 | * [#83][i83] `kapow server file.pow` will try to run `file.pow` through `bash`, not `cmd`
26 | or `powershell`.
27 | * `kapow route` default entrypoint is `/bin/sh`, so in order to use `cmd` or
28 | `powershell`, it must be explicitly set.
29 |
30 |
31 | [i73]: https://github.com/BBVA/kapow/issues/73
32 | [i83]: https://github.com/BBVA/kapow/issues/83
33 | [i89]: https://github.com/BBVA/kapow/issues/89
34 | [i98]: https://github.com/BBVA/kapow/issues/98
35 | [i92]: https://github.com/BBVA/kapow/issues/92
36 | [i102]: https://github.com/BBVA/kapow/issues/102
37 | [i105]: https://github.com/BBVA/kapow/issues/105
38 |
--------------------------------------------------------------------------------
/docs/release-notes/RELEASE-v0.5.1.md:
--------------------------------------------------------------------------------
1 | Kapow! v0.5.1
2 |
3 | ## Features
4 |
5 | * Re-release of v0.5.0 with missing release-key.gpg added, as well as debugging doc example
6 |
7 | * [#89][i89] Wrong environment variables exported to process
8 |
9 | * [#98][i98] Implement a proper logging system (Partially solved. Added a logging feature for scripts in User-server for aid in debugging)
10 |
11 | * [#92][i92] Spawn package uses static default value for KAPOW_DATA_URL
12 |
13 | * [#102][i102] Handle race condition between Control API and pow files
14 |
15 | * [#105][i105] Handle unexpected exit of servers
16 |
17 |
18 | ## Known issues and limitations
19 |
20 | ### Data API
21 |
22 | * [#73][i73] `/response/body` and `/response/stream` behave identically for now.
23 |
24 |
25 | ### Windows®
26 |
27 | * [#83][i83] `kapow server file.pow` will try to run `file.pow` through `bash`, not `cmd`
28 | or `powershell`.
29 | * `kapow route` default entrypoint is `/bin/sh`, so in order to use `cmd` or
30 | `powershell`, it must be explicitly set.
31 |
32 |
33 | [i73]: https://github.com/BBVA/kapow/issues/73
34 | [i83]: https://github.com/BBVA/kapow/issues/83
35 | [i89]: https://github.com/BBVA/kapow/issues/89
36 | [i98]: https://github.com/BBVA/kapow/issues/98
37 | [i92]: https://github.com/BBVA/kapow/issues/92
38 | [i102]: https://github.com/BBVA/kapow/issues/102
39 | [i105]: https://github.com/BBVA/kapow/issues/105
40 |
--------------------------------------------------------------------------------
/docs/release-notes/RELEASE-v0.5.2.md:
--------------------------------------------------------------------------------
1 | Kapow! v0.5.2
2 |
3 | ## Features
4 |
5 | * Fix handling of misbehaving http clients
6 |
7 | ## Known issues and limitations
8 |
9 | ### Data API
10 |
11 | * [#73][i73] `/response/body` and `/response/stream` behave identically for now.
12 |
13 |
14 | ### Windows®
15 |
16 | * [#83][i83] `kapow server file.pow` will try to run `file.pow` through `bash`, not `cmd`
17 | or `powershell`.
18 | * `kapow route` default entrypoint is `/bin/sh`, so in order to use `cmd` or
19 | `powershell`, it must be explicitly set.
20 |
21 |
22 | [i73]: https://github.com/BBVA/kapow/issues/73
23 | [i83]: https://github.com/BBVA/kapow/issues/83
24 |
--------------------------------------------------------------------------------
/docs/release-notes/RELEASE-v0.5.3.md:
--------------------------------------------------------------------------------
1 | Kapow! v0.5.3
2 |
3 | ## Features
4 |
5 | * Rebuild againts Go 1.4.3
6 |
7 | ## Known issues and limitations
8 |
9 | ### Data API
10 |
11 | * [#73][i73] `/response/body` and `/response/stream` behave identically for now.
12 |
13 |
14 | ### Windows®
15 |
16 | * [#83][i83] `kapow server file.pow` will try to run `file.pow` through `bash`, not `cmd`
17 | or `powershell`.
18 | * `kapow route` default entrypoint is `/bin/sh`, so in order to use `cmd` or
19 | `powershell`, it must be explicitly set.
20 |
21 |
22 | [i73]: https://github.com/BBVA/kapow/issues/73
23 | [i83]: https://github.com/BBVA/kapow/issues/83
24 |
--------------------------------------------------------------------------------
/docs/release-notes/RELEASE-v0.5.4.md:
--------------------------------------------------------------------------------
1 | Kapow! v0.5.4
2 |
3 | ## Features
4 |
5 | * Rebuild againts Go 1.14.4
6 |
7 | ## Known issues and limitations
8 |
9 | ### Data API
10 |
11 | * [#73][i73] `/response/body` and `/response/stream` behave identically for now.
12 |
13 |
14 | ### Windows®
15 |
16 | * [#83][i83] `kapow server file.pow` will try to run `file.pow` through `bash`, not `cmd`
17 | or `powershell`.
18 | * `kapow route` default entrypoint is `/bin/sh`, so in order to use `cmd` or
19 | `powershell`, it must be explicitly set.
20 |
21 |
22 | [i73]: https://github.com/BBVA/kapow/issues/73
23 | [i83]: https://github.com/BBVA/kapow/issues/83
24 |
--------------------------------------------------------------------------------
/docs/source/_static/browser.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BBVA/kapow/3cef9f0cd25ab41f683a883d3ae2ce042e162d59/docs/source/_static/browser.png
--------------------------------------------------------------------------------
/docs/source/_static/kapow-quick-overview.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BBVA/kapow/3cef9f0cd25ab41f683a883d3ae2ce042e162d59/docs/source/_static/kapow-quick-overview.png
--------------------------------------------------------------------------------
/docs/source/_static/logo-200px.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BBVA/kapow/3cef9f0cd25ab41f683a883d3ae2ce042e162d59/docs/source/_static/logo-200px.png
--------------------------------------------------------------------------------
/docs/source/_static/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BBVA/kapow/3cef9f0cd25ab41f683a883d3ae2ce042e162d59/docs/source/_static/logo.png
--------------------------------------------------------------------------------
/docs/source/_static/network.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BBVA/kapow/3cef9f0cd25ab41f683a883d3ae2ce042e162d59/docs/source/_static/network.png
--------------------------------------------------------------------------------
/docs/source/_static/request_life_cycle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BBVA/kapow/3cef9f0cd25ab41f683a883d3ae2ce042e162d59/docs/source/_static/request_life_cycle.png
--------------------------------------------------------------------------------
/docs/source/_static/request_life_cycle.xcf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BBVA/kapow/3cef9f0cd25ab41f683a883d3ae2ce042e162d59/docs/source/_static/request_life_cycle.xcf
--------------------------------------------------------------------------------
/docs/source/_static/sequence.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BBVA/kapow/3cef9f0cd25ab41f683a883d3ae2ce042e162d59/docs/source/_static/sequence.png
--------------------------------------------------------------------------------
/docs/source/concepts/interfaces.rst:
--------------------------------------------------------------------------------
1 | *Kapow!* HTTP Interfaces
2 | ========================
3 |
4 | ``kapow server`` sets up three HTTP server interfaces, each with a distinct and
5 | clear purpose.
6 |
7 |
8 | .. _http-user-interface:
9 |
10 | HTTP User Interface
11 | -------------------
12 |
13 | The `HTTP User Interface` is used to serve final user requests.
14 |
15 | By default it binds to address ``0.0.0.0`` and port ``8080``, but that can be
16 | changed via the ``--bind`` flag.
17 |
18 |
19 | .. _https-control-interface:
20 |
21 | HTTPS Control Interface
22 | -----------------------
23 |
24 | The `HTTPS Control Interface` is used by the command ``kapow route`` to
25 | administer the list of system routes.
26 |
27 | This interface uses mTLS by default (double-pinned autogenerated certs).
28 |
29 | By default it binds to address ``127.0.0.1`` and port ``8081``, but that can be
30 | changed via the ``--control-bind`` flag. If this is the case, consider
31 | also ``--control-reachable-addr`` which will configure the autogenerated
32 | certificate to match that address.
33 |
34 |
35 | .. _http-data-interface:
36 |
37 | HTTP Data Interface
38 | -------------------
39 |
40 | The `HTTP Data Interface` is used by the commands ``kapow get`` and ``kapow
41 | set`` to exchange the data for a particular request.
42 |
43 | By default it binds to address ``127.0.0.1`` and port ``8082``, but that can be
44 | changed via the ``--data-bind`` flag.
45 |
--------------------------------------------------------------------------------
/docs/source/concepts/philosophy.rst:
--------------------------------------------------------------------------------
1 | Philosophy
2 | ==========
3 |
4 |
5 | Single Static Binary
6 | --------------------
7 |
8 | - Deployment is then as simple as it gets.
9 |
10 | - `Docker`-friendly.
11 |
12 |
13 | Shell Agnostic
14 | --------------
15 |
16 | - *Kapow!*, like John Snow, knows nothing, and makes no assumptions about the
17 | shell you are using. It only spawns executables.
18 |
19 | - You are free to implement a client to the Data API directly if you are so
20 | inclined. The spec provides all the necessary details.
21 |
22 |
23 | Not a Silver Bullet
24 | -------------------
25 |
26 | You should not use *Kapow!* if your project requires complex business logic.
27 |
28 | If you try to encode business logic in a shell script, you will **deeply**
29 | regret it soon enough.
30 |
31 | *Kapow!* is designed for automating simple stuff.
32 |
33 |
34 | Interoperability over Performance
35 | ---------------------------------
36 |
37 | We want *Kapow!* to be as performant as possible, but not at the cost of
38 | flexibility. This is the reason why our :ref:`Data API
39 | ` leverages HTTP
40 | instead of a lighter protocol for example.
41 |
42 | When we have to choose between making things faster or more
43 | interoperable the latter usually wins.
44 |
--------------------------------------------------------------------------------
/docs/source/concepts/request_life_cycle.rst:
--------------------------------------------------------------------------------
1 | Request Life Cycle
2 | ==================
3 |
4 | This section describes the sequence of events happening for each request
5 | answered by the :ref:`http-user-interface`.
6 |
7 | .. image:: ../_static/request_life_cycle.png
8 |
9 |
10 | 1. request
11 | ----------
12 |
13 | The user makes a request to the :ref:`http-user-interface`.
14 |
15 | - The request is matched against the route table.
16 |
17 | - :program:`kapow` provides a `HANDLER_ID` to identify this request and don't
18 | mix it with other requests that could be running concurrently.
19 |
20 |
21 | 2. spawn
22 | --------
23 |
24 | :program:`kapow` spawns the executable specified as entrypoint in the matching
25 | route.
26 |
27 | The default entrypoint is :command:`/bin/sh`; let's focus on this workflow.
28 |
29 | The spawned entrypoint is run with the following variables added to its
30 | environment:
31 |
32 | - :envvar:`KAPOW_HANDLER_ID`: Containing the `HANDLER_ID`
33 | - :envvar:`KAPOW_DATA_URL`: With the URL of the :ref:`http-data-interface`
34 | - :envvar:`KAPOW_CONTROL_URL`: With the URL of the :ref:`https-control-interface`
35 |
36 |
37 | 3. ``kapow set /response/body banana``
38 | --------------------------------------
39 |
40 | During the lifetime of the shell, the :ref:`request and response resources
41 | ` are available via these commands:
42 |
43 | - ``kapow get /request/...``
44 |
45 | - ``kapow set /response/...``
46 |
47 | These commands use the aforementioned environment variables to read data
48 | from the user request and to write the response. They accept data either as
49 | arguments or from `stdin`.
50 |
51 |
52 | 4. exit
53 | -------
54 |
55 | The shell dies. Long live the shell!
56 |
57 |
58 | 5. response
59 | -----------
60 |
61 | :program:`kapow` finalizes the original request. Enjoy your banana now.
62 |
--------------------------------------------------------------------------------
/docs/source/concepts/route_matching.rst:
--------------------------------------------------------------------------------
1 | Route Matching
2 | ==============
3 |
4 | *Kapow!* maintains a :ref:`route ` table with a list of routes as provided by the user,
5 | and uses it to determine which handler an incoming request should be dispatched
6 | to.
7 |
8 | Each incoming request is matched against the routes in the route table in
9 | strict order. For each route in the route table, the criteria are checked.
10 | If the request does not match, the next route in the route list is examined.
11 |
--------------------------------------------------------------------------------
/docs/source/concepts/routes.rst:
--------------------------------------------------------------------------------
1 | .. _routes:
2 |
3 | Routes
4 | ======
5 |
6 | A *Kapow!* route specifies the matching criteria for an incoming request on
7 | the :ref:`http-user-interface`, and the details to handle it.
8 |
9 | *Kapow!* implements a *route table* where all routes reside.
10 |
11 | A route can be set like this:
12 |
13 | .. code-block:: console
14 |
15 | $ kapow route add \
16 | -X POST \
17 | '/register/{username}' \
18 | -e '/bin/bash -c' \
19 | -c 'touch /var/lib/mydb/"$(kapow get /request/matches/username)"' \
20 | | jq
21 | {
22 | "id": "deadbeef-0d09-11ea-b18e-106530610c4d",
23 | "method": "POST",
24 | "url_pattern": "/register/{username}",
25 | "entrypoint": "/bin/bash -c",
26 | "command": "touch /var/lib/mydb/\"$(kapow get /request/matches/username)\""
27 | }
28 |
29 | Let's use this example to discuss its elements.
30 |
31 |
32 | Elements
33 | --------
34 |
35 | ``id`` Route Element
36 | ~~~~~~~~~~~~~~~~~~~~
37 |
38 | Uniquely identifies each route. It is used for instance by ``kapow route remove
39 | ``.
40 |
41 | .. note::
42 |
43 | The current implementation of *Kapow!* autogenerates a `UUID` for this field.
44 | In the future the user will be able to specify a custom value.
45 |
46 |
47 | ``method`` Route Element
48 | ~~~~~~~~~~~~~~~~~~~~~~~~
49 |
50 | Specifies the HTTP method for the route to match the incoming request.
51 |
52 | Note that the route shown above will only match a ``POST`` request.
53 |
54 |
55 | ``url_pattern`` Route Element
56 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
57 |
58 | It matches the `path` component of the `URL` of the incoming request.
59 |
60 | It can contain regex placeholders for easily capturing fragments of the path.
61 |
62 | In the route shown above, a request with a URL ``/register/joe`` would match,
63 | assigning `joe` to the placeholder ``username``.
64 |
65 | *Kapow!* leverages `Gorilla Mux`_ for managing routes. For the full story, see
66 | https://github.com/gorilla/mux
67 |
68 |
69 | .. _entrypoint-route-element:
70 |
71 | ``entrypoint`` Route Element
72 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
73 |
74 | This sets the executable to be spawned, along with any arguments required.
75 |
76 | In the route shown above, the entrypoint that will be run is ``/bin/bash -c``,
77 | which is an incomplete recipe. It is then completed by the :ref:`command
78 | element `.
79 |
80 | .. note::
81 |
82 | The semantics of this element closely match the `Dockerfile`'s `ENTRYPOINT`_
83 | directive.
84 |
85 |
86 | .. _command-route-element:
87 |
88 | ``command`` Route Element
89 | ~~~~~~~~~~~~~~~~~~~~~~~~~
90 |
91 | This is an optional last argument to be passed to the
92 | :ref:`entrypoint `.
93 |
94 | In the route shown above, it completes the ``entrypoint`` to form the final
95 | incantation to be executed:
96 |
97 | .. code-block:: bash
98 |
99 | /bin/bash -c 'touch /var/lib/mydb/"$(kapow get /request/matches/username)"'
100 |
101 | .. note::
102 |
103 | The semantics of this element closely match the `Dockerfile`'s `CMD`_
104 | directive.
105 |
106 |
107 | Matching Algorithm
108 | ------------------
109 |
110 | *Kapow!* leverages `Gorilla Mux`_ for this task. Check their documentation for
111 | the gory details.
112 |
113 |
114 | .. _ENTRYPOINT: https://docs.docker.com/engine/reference/builder/#entrypoint
115 | .. _CMD: https://docs.docker.com/engine/reference/builder/#cmd
116 | .. _Gorilla Mux: https://www.gorillatoolkit.org/pkg/mux
117 |
--------------------------------------------------------------------------------
/docs/source/concepts/toc.rst:
--------------------------------------------------------------------------------
1 | Concepts
2 | ========
3 |
4 | This section is a contains a reference of all the important *Kapow!*
5 | concepts that you should know.
6 |
7 | .. toctree::
8 | :hidden:
9 |
10 | interfaces
11 | philosophy
12 | request_life_cycle
13 | resource_tree
14 | route_matching
15 | routes
16 |
--------------------------------------------------------------------------------
/docs/source/conf.py:
--------------------------------------------------------------------------------
1 | # Configuration file for the Sphinx documentation builder.
2 | #
3 | # This file only contains a selection of the most common options. For a full
4 | # list see the documentation:
5 | # https://www.sphinx-doc.org/en/master/usage/configuration.html
6 |
7 | # -- Path setup --------------------------------------------------------------
8 |
9 | # If extensions (or modules to document with autodoc) are in another directory,
10 | # add these directories to sys.path here. If the directory is relative to the
11 | # documentation root, use os.path.abspath to make it absolute, like shown here.
12 | #
13 | # import os
14 | # import sys
15 | # sys.path.insert(0, os.path.abspath('.'))
16 | import os
17 | import re
18 |
19 | # -- Project information -----------------------------------------------------
20 |
21 | project = 'Kapow!'
22 | copyright = '2019, BBVA Innovation Labs'
23 | author = 'BBVA Innovation Labs'
24 |
25 | # The full version, including alpha/beta/rc tags.
26 | try:
27 | release = re.sub('^v', '', os.popen('git describe --tags').read().strip())
28 | except:
29 | release = 'unknown'
30 |
31 | try:
32 | # The short X.Y.Z-rcN version.
33 | version = re.search('^(\d+\.\d+\.\d+(:?-rc\d+)?)', release).group(0)
34 | except:
35 | version = release
36 |
37 |
38 | # -- General configuration ---------------------------------------------------
39 |
40 | # Add any Sphinx extension module names here, as strings. They can be
41 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
42 | # ones.
43 | extensions = [
44 | 'sphinx.ext.todo',
45 | 'sphinx.ext.imgconverter'
46 | ]
47 |
48 | # Add any paths that contain templates here, relative to this directory.
49 | templates_path = ['_templates']
50 |
51 | # List of patterns, relative to source directory, that match files and
52 | # directories to ignore when looking for source files.
53 | # This pattern also affects html_static_path and html_extra_path.
54 | exclude_patterns = []
55 |
56 | rst_prolog = """
57 | .. role:: tech(code)
58 | :class: xref
59 |
60 | .. role:: nref-option(code)
61 | :class: xref
62 |
63 | .. default-role:: tech
64 |
65 | """
66 |
67 | # -- Options for HTML output -------------------------------------------------
68 |
69 | # The theme to use for HTML and HTML Help pages. See the documentation for
70 | # a list of builtin themes.
71 | #
72 | html_theme = "sphinx_rtd_theme"
73 | html_logo = "_static/logo-200px.png"
74 | html_theme_options = {
75 | 'logo_only': True,
76 | 'collapse_navigation': False,
77 | 'navigation_depth': 3,
78 | 'includehidden': True,
79 | 'titles_only': False
80 |
81 | }
82 |
83 | # Add any paths that contain custom static files (such as style sheets) here,
84 | # relative to this directory. They are copied after the builtin static files,
85 | # so a file named "default.css" will overwrite the builtin "default.css".
86 | html_static_path = ['_static']
87 |
88 | # https://stackoverflow.com/a/56448499
89 | master_doc = 'index'
90 |
91 | latex_logo = '_static/logo.png'
92 | latex_documents = [
93 | ('latextoc',
94 | 'kapow.tex',
95 | 'Kapow! Documentation',
96 | 'BBVA Innovation Labs',
97 | 'manual',
98 | True)
99 | ]
100 |
101 | man_pages = [
102 | ('concepts/resource_tree',
103 | 'kapow-resources',
104 | 'Kapow! Resource Tree Reference',
105 | 'BBVA Innovation Labs',
106 | 1),
107 | ('examples/examples',
108 | 'kapow-examples',
109 | 'Kapow! Usage Examples',
110 | 'BBVA Innovation Labs',
111 | 1),
112 | ]
113 |
--------------------------------------------------------------------------------
/docs/source/examples/managing_routes.rst:
--------------------------------------------------------------------------------
1 | Managing Routes
2 | ===============
3 |
4 | Adding New Routes
5 | -----------------
6 |
7 | .. warning::
8 |
9 | Be aware that if you register more than one route with exactly the
10 | same path, only the first route added will be used.
11 |
12 | GET route
13 | +++++++++
14 |
15 | Defining a route:
16 |
17 | .. code-block:: console
18 | :linenos:
19 |
20 | $ kapow route add /my/route -c 'echo hello world | kapow set /response/body'
21 |
22 |
23 | Calling route:
24 |
25 | .. code-block:: console
26 | :linenos:
27 |
28 | $ curl http://localhost:8080/my/route
29 | hello world
30 |
31 | POST route
32 | ++++++++++
33 |
34 | Defining a route:
35 |
36 | .. code-block:: console
37 | :linenos:
38 |
39 | $ kapow route add -X POST /echo -c 'kapow get /request/body | kapow set /response/body'
40 |
41 |
42 | Calling a route:
43 |
44 | .. code-block:: console
45 | :linenos:
46 |
47 | $ curl -d 'hello world' -X POST http://localhost:8080/echo
48 | hello world
49 |
50 |
51 | Capturing Parts of the URL
52 | ++++++++++++++++++++++++++
53 |
54 | Defining a route:
55 |
56 | .. code-block:: console
57 | :linenos:
58 |
59 | $ kapow route add '/echo/{message}' -c 'kapow get /request/matches/message | kapow set /response/body'
60 |
61 |
62 | Calling a route:
63 |
64 | .. code-block:: console
65 | :linenos:
66 |
67 | $ curl http://localhost:8080/echo/hello%20world
68 | hello world
69 |
70 |
71 | Listing Routes
72 | --------------
73 |
74 | You can list the active routes in the *Kapow!* server.
75 |
76 | .. _listing-routes-example:
77 |
78 | .. code-block:: console
79 | :linenos:
80 |
81 | $ kapow route list
82 | [{"id":"20c98328-0b82-11ea-90a8-784f434dfbe2","method":"GET","url_pattern":"/echo/{message}","entrypoint":"/bin/sh -c","command":"kapow get /request/matches/message | kapow set /response/body"}]
83 |
84 | Or, if you want human-readable output, you can use :program:`jq`:
85 |
86 | .. code-block:: console
87 | :linenos:
88 |
89 | $ kapow route list | jq
90 | [
91 | {
92 | "id": "20c98328-0b82-11ea-90a8-784f434dfbe2",
93 | "method": "GET",
94 | "url_pattern": "/echo/{message}",
95 | "entrypoint": "/bin/sh -c",
96 | "command": "kapow get /request/matches/message | kapow set /response/body",
97 | }
98 | ]
99 |
100 |
101 | .. note::
102 |
103 | *Kapow!* has a :ref:`https-control-interface`, bound by default to
104 | ``localhost:8081``.
105 |
106 |
107 | Deleting Routes
108 | ---------------
109 |
110 | You need the ID of a route to delete it.
111 | Running the command used in the :ref:`listing routes example
112 | `, you can obtain the ID of the route, and then delete
113 | it by typing:
114 |
115 | .. code-block:: console
116 | :linenos:
117 |
118 | $ kapow route remove 20c98328-0b82-11ea-90a8-784f434dfbe2
119 |
120 |
121 |
--------------------------------------------------------------------------------
/docs/source/examples/shell_tricks.rst:
--------------------------------------------------------------------------------
1 | Shell Tricks
2 | ============
3 |
4 | How to Execute Two Processes in Parallel
5 | ----------------------------------------
6 |
7 | We want to :command:`ping` two machines parallel. *Kapow!* can get IP addresses
8 | from query params:
9 |
10 | .. code-block:: console
11 | :linenos:
12 |
13 | $ cat parallel-route
14 | #!/usr/bin/env sh
15 | kapow route add '/parallel/{ip1}/{ip2}' - <<-'EOF'
16 | ping -c 1 -- "$(kapow get /request/matches/ip1)" | kapow set /response/body &
17 | ping -c 1 -- "$(kapow get /request/matches/ip2)" | kapow set /response/body &
18 | wait
19 | EOF
20 |
21 | Calling with :program:`curl`:
22 |
23 | .. code-block:: console
24 | :linenos:
25 |
26 | $ curl -v http://localhost:8080/parallel/10.0.0.1/10.10.10.1
27 |
28 | Script debugging
29 | ----------------
30 |
31 | Bash provides the ``set -x`` builtin command that "After expanding each simple command,
32 | for command, case command, select command, or arithmetic for command, display the
33 | expanded value of PS4, followed by the command and its expanded arguments or associated
34 | word list". This feature can be used to help debugging the init programs and,
35 | together the ``--debug`` option in the server sub-command, the scripts executed
36 | in user requests.
37 |
--------------------------------------------------------------------------------
/docs/source/examples/toc.rst:
--------------------------------------------------------------------------------
1 | Examples
2 | ========
3 |
4 | .. toctree::
5 |
6 | working_with_init_programs
7 | managing_routes
8 | handling_http_requests
9 | using_json
10 | shell_tricks
11 | https_mtls
12 |
--------------------------------------------------------------------------------
/docs/source/examples/using_json.rst:
--------------------------------------------------------------------------------
1 | Using JSON
2 | ==========
3 |
4 | Modify JSON by Using Shell Commands
5 | -----------------------------------
6 |
7 | .. note::
8 |
9 | Nowadays Web services are `JSON`-based, so making your script `JSON` aware is
10 | probably a good choice. In order to be able to extract data from a `JSON`
11 | document as well as composing `JSON` documents from a script, you can leverage
12 | `jq `_.
13 |
14 |
15 | Example #1
16 | ++++++++++
17 |
18 | In this example our *Kapow!* service will receive a `JSON` value with an incorrect
19 | date, then our init program will fix it and return the correct value to the user.
20 |
21 | .. code-block:: console
22 | :linenos:
23 |
24 | $ cat fix_date
25 | #!/usr/bin/env sh
26 | kapow route add -X POST /fix-date - <<-'EOF'
27 | kapow set /response/headers/Content-Type application/json
28 | kapow get /request/body | jq --arg newdate "$(date +'%Y-%m-%d_%H-%M-%S')" '.incorrectDate=$newdate' | kapow set /response/body
29 | EOF
30 |
31 | Call the service with :program:`curl`:
32 |
33 | .. code-block:: console
34 | :linenos:
35 |
36 | $ curl -X POST http://localhost:8080/fix-date -H 'Content-Type: application/json' -d '{"incorrectDate": "no way, Jose"}'
37 | {
38 | "incorrectDate": "2019-11-22_10-42-06"
39 | }
40 |
41 |
42 | Example #2
43 | ++++++++++
44 |
45 | In this example we extract the ``name`` field from the incoming `JSON` document in
46 | order to generate a two-attribute `JSON` response.
47 |
48 | .. code-block:: console
49 |
50 | $ cat echo-attribute
51 | #!/usr/bin/env sh
52 | kapow route add -X POST /echo-attribute - <<-'EOF'
53 | JSON_WHO=$(kapow get /request/body | jq -r .name)
54 |
55 | kapow set /response/headers/Content-Type application/json
56 | kapow set /response/status 200
57 |
58 | jq --arg greet Hello --arg value "${JSON_WHO:-World}" --null-input '{ greet: $greet, to: $value }' | kapow set /response/body
59 | EOF
60 |
61 | Call the service with :program:`curl`:
62 |
63 | .. code-block:: console
64 | :linenos:
65 | :emphasize-lines: 4
66 |
67 | $ curl -X POST http://localhost:8080/echo-attribute -H 'Content-Type: application/json' -d '{"name": "MyName"}'
68 | {
69 | "greet": "Hello",
70 | "to": "MyName"
71 | }
72 |
--------------------------------------------------------------------------------
/docs/source/examples/working_with_init_programs.rst:
--------------------------------------------------------------------------------
1 | Working with Init Scripts
2 | =========================
3 |
4 | Starting *Kapow!* using an init script
5 | --------------------------------------
6 |
7 | An init program, which can be just a shell script, allows you to make calls to
8 | the ``kapow route`` command.
9 |
10 | .. code-block:: console
11 | :linenos:
12 |
13 | $ kapow server example-init-program
14 |
15 | With the :file:`example-init-program`:
16 |
17 | .. code-block:: console
18 | :linenos:
19 |
20 | $ cat example-init-program
21 | #!/usr/bin/env sh
22 | #
23 | # This is a simple example of an init program
24 | #
25 | echo '[*] Starting my init program'
26 |
27 | # We add 2 Kapow! routes
28 | kapow route add /my/route -c 'echo hello world | kapow set /response/body'
29 | kapow route add -X POST /echo -c 'kapow get /request/body | kapow set /response/body'
30 |
31 | .. note::
32 |
33 | *Kapow!* can be fully configured using just init scripts
34 |
35 |
36 | Writing Multiline Routes
37 | ------------------------
38 |
39 | If you need to write more complex actions, you can leverage multiline routes:
40 |
41 | .. code-block:: console
42 | :linenos:
43 |
44 | $ cat multiline-route
45 | #!/usr/bin/env sh
46 | kapow route add /log_and_stuff - <<-'EOF'
47 | echo this is a quite long sentence and other stuff | tee log.txt | kapow set /response/body
48 | cat log.txt | kapow set /response/body
49 | EOF
50 |
51 | .. warning::
52 |
53 | Be aware of the **"-"** at the end of the ``kapow route add`` command.
54 | It tells ``kapow route add`` to read commands from `stdin`.
55 |
56 | .. warning::
57 |
58 | If you want to learn more about multiline usage, see: `Here Doc
59 | `_
60 |
61 |
62 | Keeping Things Tidy
63 | -------------------
64 |
65 | Sometimes things grow, and keeping things tidy is the only way to mantain the
66 | whole thing.
67 |
68 | You can distribute your endpoints in several init programs. And you can keep
69 | the whole thing documented in one html file, served with *Kapow!*.
70 |
71 | .. code-block:: console
72 | :linenos:
73 |
74 | $ cat index-route
75 | #!/usr/bin/env sh
76 | kapow route add / - <<-'EOF'
77 | cat howto.html | kapow set /response/body
78 | EOF
79 |
80 | source ./info_stuff
81 | source ./other_endpoints
82 |
83 | You can import other shell script libraries with `source`.
84 |
85 |
86 | Debugging Init Programs/Scripts
87 | -------------------------------
88 |
89 | Since *Kapow!* redirects the standard output and the standard error of the init
90 | program given on server startup to its own, you can leverage ``set -x`` to see
91 | the commands that are being executed, and use that for debugging.
92 |
93 | To support debugging user request executions, the server subcommand has a
94 | ``--debug`` option flag that prompts *Kapow!* to redirect both the script's
95 | standard output and standard error to *Kapow!*'s standard output, so you can
96 | leverage ``set -x`` the same way as with init programs.
97 |
98 |
99 | .. code-block:: console
100 |
101 | $ cat withdebug-route
102 | #!/usr/bin/env sh
103 | kapow route add / - <<-'EOF'
104 | set -x
105 | echo "This will be seen in the log"
106 | echo "Hi HTTP" | kapow set /response/body
107 | EOF
108 |
109 | $ kapow server --debug withdebug-route
110 |
--------------------------------------------------------------------------------
/docs/source/index.rst:
--------------------------------------------------------------------------------
1 | Welcome to *Kapow!*
2 | ===================
3 |
4 | .. image:: https://github.com/BBVA/kapow/actions/workflows/test_and_release.yml/badge.svg
5 | :target: https://github.com/BBVA/kapow/actions/workflows/test_and_release.yml
6 | .. image:: https://goreportcard.com/badge/github.com/bbva/kapow
7 | :target: https://goreportcard.com/report/github.com/bbva/kapow
8 | .. image:: https://img.shields.io/github/issues/BBVA/kapow
9 | :target: https://github.com/BBVA/kapow/issues/
10 | .. image:: https://img.shields.io/github/v/release/BBVA/kapow?include_prereleases
11 | :target: https://github.com/BBVA/kapow/releases
12 |
13 | **If you can script it, you can HTTP it**
14 |
15 |
16 | What's *Kapow!*
17 | ---------------
18 |
19 | Think of that software that you need but **only runs in the command
20 | line**. *Kapow!* lets you wrap it into an `HTTP API` **real easy**.
21 |
22 | .. image:: _static/kapow-quick-overview.png
23 | :width: 80%
24 | :align: center
25 |
26 | Want to know more?
27 | Check the :ref:`Quick Start Guide ` section for a longer
28 | explanation of what *Kapow!* does.
29 |
30 |
31 | Authors
32 | -------
33 |
34 | *Kapow!* is being developed by the `BBVA-Labs Security team`_.
35 |
36 |
37 | License
38 | -------
39 |
40 | *Kapow!* is Open Source Software and available under the `Apache 2 license`_.
41 |
42 |
43 | Contributions
44 | -------------
45 |
46 | Contributions are of course welcome. See `CONTRIBUTING`_ or skim
47 | `existing tickets`_ to see where you could help out.
48 |
49 |
50 | Contents
51 | ========
52 |
53 | .. toctree::
54 | :maxdepth: 2
55 | :caption: The Project
56 |
57 | the_project/quickstart
58 | the_project/security
59 | the_project/install_and_configure
60 |
61 | .. toctree::
62 | :maxdepth: 2
63 | :caption: Tutorial
64 |
65 | tutorial/index
66 | tutorial/tutorial00
67 | tutorial/tutorial01
68 | tutorial/tutorial02
69 | tutorial/tutorial03
70 | tutorial/tutorial04
71 | tutorial/tutorial05
72 | tutorial/tutorial06
73 |
74 | .. toctree::
75 | :maxdepth: 2
76 | :caption: Usage Examples
77 |
78 | examples/working_with_init_programs
79 | examples/managing_routes
80 | examples/handling_http_requests
81 | examples/using_json
82 | examples/shell_tricks
83 | examples/https_mtls
84 |
85 |
86 | .. toctree::
87 | :maxdepth: 2
88 | :caption: Concepts
89 |
90 | concepts/interfaces
91 | concepts/philosophy
92 | concepts/request_life_cycle
93 | concepts/resource_tree
94 | concepts/route_matching
95 | concepts/routes
96 |
97 | Indices and Tables
98 | ==================
99 |
100 | * :ref:`genindex`
101 | * :ref:`modindex`
102 | * :ref:`search`
103 |
104 | .. _BBVA-Labs Security team: https://github.com/BBVA/kapow/blob/master/AUTHORS.rst
105 | .. _Apache 2 license: https://raw.githubusercontent.com/BBVA/kapow/master/LICENSE
106 | .. _CONTRIBUTING: https://github.com/BBVA/kapow/blob/master/CONTRIBUTING.rst
107 | .. _existing tickets: https://github.com/BBVA/kapow/labels/good%20first%20issue
108 |
--------------------------------------------------------------------------------
/docs/source/latextoc.rst:
--------------------------------------------------------------------------------
1 | :orphan:
2 |
3 | .. toctree::
4 | :maxdepth: 3
5 |
6 | the_project/toc
7 | tutorial/toc
8 | examples/toc
9 | concepts/toc
10 |
--------------------------------------------------------------------------------
/docs/source/the_project/toc.rst:
--------------------------------------------------------------------------------
1 | The Project
2 | ===========
3 |
4 | This section will introduce you to *Kapow!* basics.
5 |
6 | .. toctree::
7 |
8 | quickstart
9 | security
10 | install_and_configure
11 |
--------------------------------------------------------------------------------
/docs/source/tutorial/index.rst:
--------------------------------------------------------------------------------
1 | *Kapow!* Tutorial
2 | =================
3 |
4 | This tutorial will help you get more familiar with *Kapow!*.
5 |
6 | It tells you the story of a junior ops person landing a new job in a small
7 | company. Teaming up with an experienced senior, they'll face many challenges
8 | of increasing difficulty. With *Kapow!* at their side, they will be able to
9 | pass all the hurdles.
10 |
11 | You just need to follow the steps and execute the code shown in the tutorial
12 | to learn *the Kapow! way*.
13 |
14 | Enjoy the ride!
15 |
--------------------------------------------------------------------------------
/docs/source/tutorial/materials/backup_db.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | {
4 | echo --------------------------------------------------------------------------------
5 | echo "[$(date)] Starting backup procedure"
6 | for i in $(seq 1 10)
7 | do
8 | echo "[$(date)] Backing up table ${i}..."
9 | sleep .2
10 | done
11 | echo "[$(date)] Backup finished"
12 | } >> /tmp/backup_db.log
13 |
14 | echo 'Backup done!'
15 |
--------------------------------------------------------------------------------
/docs/source/tutorial/toc.rst:
--------------------------------------------------------------------------------
1 | .. include:: index.rst
2 |
3 | .. toctree::
4 | :caption: Chapters
5 |
6 | tutorial00
7 | tutorial01
8 | tutorial02
9 | tutorial03
10 | tutorial04
11 | tutorial05
12 | tutorial06
13 |
--------------------------------------------------------------------------------
/docs/source/tutorial/tutorial00.rst:
--------------------------------------------------------------------------------
1 | Your First Day at Work
2 | ======================
3 |
4 | **Senior**
5 |
6 | Welcome to *ACME Inc.* This is your first day here, right?
7 |
8 | **Junior**
9 |
10 | Hi! Yes! And I am eager to start working. What will be my first task?
11 |
12 | **Senior**
13 |
14 | First, let me help you get acquainted with our infrastructure.
15 |
16 | **Junior**
17 |
18 | OK.
19 |
20 | **Senior**
21 |
22 | We have two Linux boxes that provide services to our employees.
23 |
24 | #. The Corporate Server: Provides email, database and web services.
25 |
26 | #. The Backup Server: It is used to store backup of the important
27 | company data.
28 |
29 | **Junior**
30 |
31 | That's it? OK, just like Google, then.
32 |
33 | **Senior**
34 |
35 | Smartass...
36 |
37 | **Junior**
38 |
39 | (chuckles nervously).
40 |
41 | **Senior**
42 |
43 | Well, I think is time for you to start with your first task. It just so
44 | happens that we received another request to backup the database from the
45 | projects team.
46 |
--------------------------------------------------------------------------------
/docs/source/tutorial/tutorial02.rst:
--------------------------------------------------------------------------------
1 | What have we done?
2 | ==================
3 |
4 | **Senior**
5 |
6 | Hey, I come from seeing our project team mates. They're delighted with their
7 | new toy, but they miss something.
8 |
9 | I forgot to tell you that after the backup is run they need to review the log
10 | file to check that everything went OK.
11 |
12 | **Junior**
13 |
14 | Makes sense. Do you think that *Kapow!* can help with this? I have the
15 | feeling that this is the right way to go about it...
16 |
17 | **Senior**
18 |
19 | Sure! Let's take a look at the documentation to see how we can tweak the
20 | logic of the request.
21 |
22 | **Junior**
23 |
24 | Got it! There're a :ref:`lot of resources to work with `.
25 | I see that we can write to the response. Do you think this will work for us?
26 |
27 | **Senior**
28 |
29 | Yeah, the team is used to :command:`cat` the log file contents to see what
30 | happened in the last execution:
31 |
32 | .. code-block:: console
33 |
34 | $ cat /tmp/backup_db.log
35 |
36 | I've made it easy for you. Are you up to it?
37 |
38 | **Junior**
39 |
40 | Let me try add this to our init program:
41 |
42 | .. code-block:: console
43 |
44 | #!/usr/bin/env sh
45 | kapow route add /db/backup_logs -c 'cat /tmp/backup_db.log | kapow set /response/body'
46 |
47 | **Senior**
48 |
49 | Looks good to me, clean and simple, and it is a very good idea to use ``GET``
50 | here as it won't change anything in the server. Let's restart *Kapow!* and try it.
51 |
52 | **Junior**
53 |
54 | Wooow! I get back the content of the file. If they liked the first one
55 | they're going to loooove this.
56 |
57 | **Senior**
58 |
59 | Agreed. And with this, I think we are done for the day...
60 |
--------------------------------------------------------------------------------
/docs/source/tutorial/tutorial03.rst:
--------------------------------------------------------------------------------
1 | We need to filter
2 | =================
3 |
4 | **Senior**
5 |
6 | Hiya! How're you doing this morning? I've got a new challenge from our
7 | grateful mates.
8 |
9 | As time goes on from the last log rotation, the size of the log file gets
10 | bigger and bigger. Furthermore, they want to limit the output of the file to
11 | pick only some records, and only from the end of the file. We need to do
12 | something to help them as they are wasting a lot of time reviewing the output.
13 |
14 | **Junior**
15 |
16 | I have a feeling that this is going to entail some serious *bash-foo*. What
17 | do you think?
18 |
19 | **Senior**
20 |
21 | Sure! But in addition to some good shell plumbing we're going to squeeze
22 | *Kapow!*'s superpowers a litle bit more to get a really good solution.
23 |
24 | Can you take a look at *Kapow!*'s documentation to see if something can be
25 | done?
26 |
27 | **Junior**
28 |
29 | I've read in the documentation that there is a way to get access to the data
30 | coming in the request. Do you think we can use this to let them choose how
31 | to do the filtering?
32 |
33 | **Senior**
34 |
35 | Sounds great! How have we lived without *Kapow!* all this time?
36 |
37 | As they requested, we can offer them a parameter to filter the registers
38 | they want to pick, and another parameter to limit the output size in lines.
39 |
40 | **Junior**
41 |
42 | Sounds about right. Now we have to make some modifications to our last
43 | endpoint definition to add this new feature. Let's get cracking!
44 |
45 | **Senior**
46 |
47 | Well, we got it again, this is exactly what they need:
48 |
49 | .. code-block:: bash
50 |
51 | kapow route add /db/backup_logs -c 'grep -- "$(kapow get /request/params/filter)" /tmp/backup_db.log \
52 | | tail -n "$(kapow get /request/params/lines)" \
53 | | kapow set /response/body'
54 |
55 | It looks a bit weird, but we'll have time to revise the style later. Please
56 | make some tests on your laptop before we publish it on the **Corporate Server**.
57 | Remember to send them an example URL with the parameters they can use to
58 | filter and limit the amount of lines they get.
59 |
60 | **Junior**
61 |
62 | OK, should look like this, doesn't it?
63 |
64 | .. code-block:: console
65 |
66 | $ curl 'http://localhost:8080/db/backup_logs?filter=rows%20inserted&lines=200'
67 |
68 | **Senior**
69 |
70 | Exactly. Another great day helping the company advance. Let's go grab a
71 | beer to celebrate!
72 |
--------------------------------------------------------------------------------
/docs/source/tutorial/tutorial06.rst:
--------------------------------------------------------------------------------
1 | Securing the server
2 | ===================
3 |
4 | **Senior**
5 |
6 | Hi... I hope you rested last night!
7 |
8 | Come on, I need your help here!
9 |
10 | **Junior**
11 |
12 | Good morning! What's the matter? Sounds worrying
13 |
14 | **Senior**
15 |
16 | We forgot to take the most basic security measures when deploying our services.
17 | Every body at the company can access the services and the information is
18 | transferred in clear text.
19 |
20 | **Junior**
21 |
22 | Oh! Damn, you're right! You think we can do anything to solve this mess?
23 |
24 | **Senior**
25 |
26 | Yes, I'm pretty sure that those smart guys have thought on that when building
27 | Kapow! Have a look at the :ref:`documentation `.
28 |
29 | **Junior**
30 |
31 | Got it! They did it, here're the instictions to start a server with HTTPS support.
32 |
33 | It's amazing! It says we can even use mTLS to control access, really promising.
34 |
35 | **Senior**
36 |
37 | Ok, ok... First thigs first. We need to get a server certificate to start
38 | working with HTTPS. Fortunately we can ask for one to the CA we use for the
39 | other servers. Let's pick up one for development, they're quick to get.
40 |
41 | **Junior**
42 |
43 | Yeah! I'll change the startup script to configure HTTPS:
44 |
45 | .. code-block:: console
46 |
47 | $ kapow server --keyfile /etc/kapow/tls/keyfile \
48 | --certfile /etc/kapow/tls/certfile \
49 | /etc/kapow/awesome-route
50 |
51 | It's easy, please copy the private key file and certificate chain to `/etc/kapow/tls` and we can restart.
52 |
53 | **Senior**
54 |
55 | Great! it's working, communications are secured. Let's say everybody to change
56 | from http to https.
57 |
58 | **Junior**
59 |
60 | Ok, did it. What are the steps to follow to limit access by using mTLS?
61 |
62 | **Senior**
63 |
64 | Besides configuring the server we need to provide the users with their own
65 | client certificates and private keys so they can configure their browsers and
66 | the application server.
67 |
68 | **Junior**
69 |
70 | Yes, please give me the CA certificate that will issue our client certificates
71 | and I'll change the startup script again
72 |
73 | .. code-block:: console
74 |
75 | $ kapow server --keyfile /etc/kapow/tls/keyfile \
76 | --certfile /etc/kapow/tls/certfile \
77 | --clientauth true \
78 | --clientcafile /etc/kapow/tls/clientCAfile \
79 | /etc/kapow/awesome-route
80 |
81 | Done!
82 |
83 | **Senior**
84 |
85 | Ok, let's communicate the changes to all the affected teams before we restart
86 |
87 | **Junior**
88 |
89 | Oh God, After all we're starting to look like Google
90 |
91 | (chuckles)
92 |
--------------------------------------------------------------------------------
/examples/advanced/01_DocumentConverter/DocumentConverter:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | kapow route add '/format/input' - <<-'EOF'
4 | kapow set /response/headers/Content-Type application/json
5 | pandoc --list-input-formats \
6 | | jq --raw-input --slurp 'split("\n") | .[0:-1]' \
7 | | kapow set /response/body
8 | EOF
9 |
10 | kapow route add '/format/output' - <<-'EOF'
11 | kapow set /response/headers/Content-Type application/json
12 | pandoc --list-output-formats \
13 | | jq --raw-input --slurp 'split("\n") | .[0:-1]' \
14 | | kapow set /response/body
15 | EOF
16 |
17 | kapow route add -X POST --entrypoint '/bin/zsh -c' '/convert' - <<-'EOF'
18 | kapow set /response/headers/Content-Type application/octet-stream
19 | kapow set /response/headers/Content-Disposition "attachment; filename=$(kapow get /request/files/inputfile/filename).$(kapow get /request/form/to)"
20 | pandoc --from=$(kapow get /request/form/from) \
21 | --to=$(kapow get /request/form/to) \
22 | --output=>(kapow set /response/body) \
23 | =(kapow get /request/files/inputfile/content)
24 | EOF
25 |
26 | kapow route add / - <<-'EOF'
27 | kapow set /response/headers/Location /index.html
28 | kapow set /response/status 301
29 | EOF
30 |
31 | kapow route add /index.html - <<-'EOF'
32 | kapow set /response/headers/Content-Type text/html
33 | kapow set /response/body < index.html
34 | EOF
35 |
--------------------------------------------------------------------------------
/examples/advanced/01_DocumentConverter/README.md:
--------------------------------------------------------------------------------
1 | # Document Converter (pandoc) as a Service
2 |
3 | A small web gui for [pandoc](https://pandoc.org) that allows to convert between text formats.
4 |
5 | ## How to run it
6 |
7 | ```
8 | $ kapow server DocumentConverter
9 | ```
10 |
11 |
12 | ## How to consume it
13 |
14 | 1. Visit http://localhost:8080/ from your web browser.
15 | 1. Select and input format. E.g. Markdown
16 | 1. Select and output format. E.g. HTML
17 | 1. Select a file from your disk. E.g. README.md
18 | 1. Press "Convert!"
19 | 1. Enjoy!
20 |
--------------------------------------------------------------------------------
/examples/advanced/01_DocumentConverter/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
46 |
47 |
48 |
54 |
55 |
56 |
--------------------------------------------------------------------------------
/examples/advanced/02_NetworkScanner/NetworkScanner:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | kapow route add -X POST '/scan' - <<-'EOF'
4 | PORTS=$(kapow get /request/form/ports)
5 | IP=$(kapow get /request/form/ip)
6 | WEBHOOK=$(kapow get /request/form/webhook)
7 | JOBID=$(uuidgen | tr -d '\n')
8 |
9 | (nmap -Pn -n -p "${PORTS:-8080}" -oX "${JOBID}.running.xml" -- "${IP:-127.0.0.1}";
10 | mv "${JOBID}.running.xml" "${JOBID}.done.xml";
11 | [ ! -z "$WEBHOOK" ] && curl -s -F "data=@${JOBID}.done.xml" -F "jobid=${JOBID}" "$WEBHOOK";
12 | ) &
13 |
14 | kapow set /response/headers/Content-Type application/json
15 | jq -n --arg jobid "$JOBID" '{"job": $jobid}' | kapow set /response/body
16 | EOF
17 |
18 | kapow route add -X GET '/scan/{jobid}' - <<-'EOF'
19 | JOBID=$(kapow get /request/matches/jobid)
20 | [ -f "${JOBID}.running.xml" ] && kapow set /response/status 202 && exit 0
21 | if [ -f "${JOBID}.done.xml" ]; then
22 | kapow set /response/headers/Content-Type application/xml
23 | kapow set /response/body < "${JOBID}.done.xml"
24 | else
25 | kapow set /response/status 404
26 | kapow set /response/body "Scan $JOBID not found"
27 | fi
28 | EOF
29 |
30 |
--------------------------------------------------------------------------------
/examples/advanced/02_NetworkScanner/README.md:
--------------------------------------------------------------------------------
1 | # Network Scanner (nmap) as a Service
2 |
3 | Run a long network scan in background with support for webhook on completion.
4 |
5 | * The user can define the destination IP and port(s).
6 | * The service answers immediately with a `jobid`.
7 | * If a webhook url is defined it will be called on completion with the result and the jobid.
8 | * At any moment the user can request the status of the scan at /scan/{jobid}
9 |
10 | ## How to run it
11 |
12 | ```
13 | $ kapow server NetworkScanner
14 | ```
15 |
16 |
17 | ## How to consume it
18 |
19 | * Scan your own host
20 | ```
21 | $ curl --data 'ports=1-65535&ip=127.0.0.1' http://localhost:8080/scan
22 | {
23 | "job": "dba2edbc-527d-453a-9c25-0608bb8f06da"
24 | }
25 | ```
26 |
27 | * Grab the result
28 |
29 | ```
30 | $ curl -v http://localhost:8080/scan/dba2edbc-527d-453a-9c25-0608bb8f06da
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 | ```
58 |
59 | *If you receive a 202 it means the scan is still on progress*
60 |
--------------------------------------------------------------------------------
/examples/advanced/03_NetworkSniffer/NetworkSniffer:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | kapow route add /sniff -c 'tcpdump -i any -U -s0 -w - "not portrange 8080-8082" | kapow set /response/body'
4 |
--------------------------------------------------------------------------------
/examples/advanced/03_NetworkSniffer/README.md:
--------------------------------------------------------------------------------
1 | # Network Sniffer (tcpdump) as a Service
2 |
3 | Provides an HTTP service that allows the user to sniff the network in real time. The packet capture data is served as an HTTP stream that can be injected to a packet analysis tool on the fly.
4 |
5 |
6 | ## How to run it
7 |
8 | For the sake of simplicity, run:
9 |
10 | ```
11 | $ sudo -E kapow server NetworkSniffer
12 | ```
13 |
14 | In a production environment, tcpdump should be run with the appropiate
15 | permissions, but kapow can (and should) run as an unprivileged user.
16 |
17 |
18 | ## How to consume it
19 |
20 | ```
21 | $ curl http://localhost:8080/sniff | sudo -E wireshark -k -i -
22 | ```
23 |
--------------------------------------------------------------------------------
/examples/basic/01_HelloWorld/HelloWorld:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | kapow route add /helloworld -c "echo 'Hello World!' | kapow set /response/body"
4 |
--------------------------------------------------------------------------------
/examples/basic/01_HelloWorld/README.md:
--------------------------------------------------------------------------------
1 | # Hello World!
2 |
3 | A simple "Hello World!" type example.
4 |
5 |
6 | ## How to run it
7 |
8 | ```
9 | $ kapow server HelloWorld
10 | ```
11 |
12 |
13 | ## How to consume it
14 |
15 | ```
16 | $ curl http://localhost:8080/helloworld
17 | Hello World!
18 | ```
19 |
--------------------------------------------------------------------------------
/examples/basic/02_FixLogGrep/FixLogGrep:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | kapow route add /apache-errors - <<-'EOF'
4 | cat /var/log/apache2/access.log | grep 'File does not exist' | kapow set /response/body
5 | EOF
6 |
--------------------------------------------------------------------------------
/examples/basic/02_FixLogGrep/README.md:
--------------------------------------------------------------------------------
1 | # Fix Log Grep as a Service
2 |
3 | A simple service that exposes log entries for files not found on our Apache Web Server.
4 |
5 | ## How to run it
6 |
7 | ```
8 | $ kapow server FixLogGrep
9 | ```
10 |
11 |
12 | ## How to consume it
13 |
14 | ```
15 | $ curl http://localhost:8080/apache-errors
16 | [Fri Feb 01 22:07:57.154391 2019] [core:info] [pid 7:tid 140284200093440] [client 172.17.0.1:50756] AH00128: File does not exist: /usr/var/www/mysite/favicon.ico
17 | [Fri Feb 01 22:07:57.808291 2019] [core:info] [pid 8:tid 140284216878848] [client 172.17.0.1:50758] AH00128: File does not exist: /usr/var/www/mysite/favicon.ico
18 | [Fri Feb 01 22:07:57.878149 2019] [core:info] [pid 8:tid 140284208486144] [client 172.17.0.1:50758] AH00128: File does not exist: /usr/var/www/mysite/favicon.ico
19 | ```
20 |
--------------------------------------------------------------------------------
/examples/basic/03_DynamicLogGrep/DynamicLogGrep:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | kapow route add /apache-logs - <<-'EOF'
4 | cat /var/log/apache2/access.log | grep -- "$(kapow get /request/params/pattern)" | kapow set /response/body
5 | EOF
6 |
--------------------------------------------------------------------------------
/examples/basic/03_DynamicLogGrep/README.md:
--------------------------------------------------------------------------------
1 | # Dynamic Log Grep as a Service
2 |
3 | A simple service that exposes log entries that matches **with a given text** on our Apache Web Server.
4 |
5 | ## How to run it
6 |
7 | ```
8 | $ kapow server DynamicLogGrep
9 | ```
10 |
11 |
12 | ## How to consume it
13 |
14 | ```
15 | $ curl http://localhost:8080/apache-logs?pattern=Chrome
16 | 70.127.254.161 - - [17/May/2015:22:05:19 +0000] "GET /reset.css HTTP/1.1" 200 1015 "http://www.semicomplete.com/projects/xdotool/" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1700.107 Safari/537.36"
17 | 70.127.254.161 - - [17/May/2015:22:05:27 +0000] "GET /style2.css HTTP/1.1" 200 4877 "http://www.semicomplete.com/projects/xdotool/" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1700.107 Safari/537.36"
18 | 70.127.254.161 - - [17/May/2015:22:05:19 +0000] "GET /images/jordan-80.png HTTP/1.1" 200 6146 "http://www.semicomplete.com/projects/xdotool/" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1700.107 Safari/537.36"
19 | 70.127.254.161 - - [17/May/2015:22:05:25 +0000] "GET /images/web/2009/banner.png HTTP/1.1" 200 52315 "http://www.semicomplete.com/projects/xdotool/" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1700.107 Safari/537.36"
20 | 70.127.254.161 - - [17/May/2015:22:05:56 +0000] "GET /favicon.ico HTTP/1.1" 200 3638 "-" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1700.107 Safari/537.36"
21 | ```
22 |
--------------------------------------------------------------------------------
/examples/basic/04_SystemMonitor/SystemMonitor:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | kapow route add '/file/{path:.*}' -c 'ls -la -- "/$(kapow get /request/matches/path)" | kapow set /response/body'
4 |
5 | kapow route add /process -c 'ps -aux | kapow set /response/body'
6 |
7 | kapow route add /cpu -c 'kapow set /response/body < /proc/cpuinfo'
8 |
9 | kapow route add /memory -c 'free -m | kapow set /response/body'
10 |
11 | kapow route add /disk/usage -c 'df -h | kapow set /response/body'
12 |
13 | kapow route add /disk/mounts -c 'mount | kapow set /response/body'
14 |
15 | kapow route add /socket -c 'ss -pluton | kapow set /response/body'
16 |
17 | kapow route add /kernel/messages - <<-'EOF'
18 | kapow set /response/headers/X-Content-Type-Options nosniff
19 | kapow set /response/headers/Content-Type text/plain
20 | dmesg | kapow set /response/body
21 | EOF
22 |
23 | kapow route add /systemd/journal - <<-'EOF'
24 | kapow set /response/headers/X-Content-Type-Options nosniff
25 | kapow set /response/headers/Content-Type text/plain
26 | journalctl -n1000 | kapow set /response/body
27 | EOF
28 |
--------------------------------------------------------------------------------
/examples/docker/awscli/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM bbvalabsci/kapow:latest as kp
2 | FROM amazon/aws-cli:latest
3 |
4 | COPY --from=kp /kapow /usr/bin/kapow
5 |
6 | ENTRYPOINT kapow
7 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/BBVA/kapow
2 |
3 | go 1.20
4 |
5 | require (
6 | github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf
7 | github.com/google/uuid v1.6.0
8 | github.com/gorilla/mux v1.8.1
9 | github.com/spf13/cobra v1.8.1
10 | gopkg.in/h2non/gock.v1 v1.1.2
11 | )
12 |
13 | require (
14 | github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 // indirect
15 | github.com/inconshreveable/mousetrap v1.1.0 // indirect
16 | github.com/spf13/pflag v1.0.5 // indirect
17 | )
18 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
2 | github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf h1:7+FW5aGwISbqUtkfmIpZJGRgNFg2ioYPvFaUxdqpDsg=
3 | github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf/go.mod h1:RpwtwJQFrIEPstU94h88MWPXP2ektJZ8cZ0YntAmXiE=
4 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
5 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
6 | github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
7 | github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
8 | github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw=
9 | github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI=
10 | github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
11 | github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
12 | github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32 h1:W6apQkHrMkS0Muv8G/TipAy/FJl/rCYT0+EuS8+Z0z4=
13 | github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms=
14 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
15 | github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
16 | github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
17 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
18 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
19 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
20 | gopkg.in/h2non/gock.v1 v1.1.2 h1:jBbHXgGBK/AoPVfJh5x4r/WxIrElvbLel8TCZkkZJoY=
21 | gopkg.in/h2non/gock.v1 v1.1.2/go.mod h1:n7UGz/ckNChHiK05rDoiC4MYSunEC/lyaUm2WWaDva0=
22 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
23 |
--------------------------------------------------------------------------------
/internal/certs/certs.go:
--------------------------------------------------------------------------------
1 | package certs
2 |
3 | import (
4 | "bytes"
5 | "crypto"
6 | "crypto/rand"
7 | "crypto/rsa"
8 | "crypto/x509"
9 | "crypto/x509/pkix"
10 | "encoding/pem"
11 | "math/big"
12 | "net"
13 | "time"
14 |
15 | "github.com/BBVA/kapow/internal/logger"
16 | )
17 |
18 | type Cert struct {
19 | X509Cert *x509.Certificate
20 | PrivKey crypto.PrivateKey
21 | SignedCert []byte
22 | }
23 |
24 | func (c Cert) SignedCertPEMBytes() []byte {
25 |
26 | PEM := new(bytes.Buffer)
27 | err := pem.Encode(PEM, &pem.Block{
28 | Type: "CERTIFICATE",
29 | Bytes: c.SignedCert,
30 | })
31 | if err != nil {
32 | logger.L.Fatal(err)
33 | }
34 |
35 | return PEM.Bytes()
36 | }
37 |
38 | func (c Cert) PrivateKeyPEMBytes() []byte {
39 | PEM := new(bytes.Buffer)
40 | err := pem.Encode(PEM, &pem.Block{
41 | Type: "RSA PRIVATE KEY",
42 | Bytes: x509.MarshalPKCS1PrivateKey(c.PrivKey.(*rsa.PrivateKey)),
43 | })
44 | if err != nil {
45 | logger.L.Fatal(err)
46 | }
47 |
48 | return PEM.Bytes()
49 | }
50 |
51 | func GenCert(name, altName string, isServer bool) Cert {
52 |
53 | usage := x509.ExtKeyUsageClientAuth
54 | if isServer {
55 | usage = x509.ExtKeyUsageServerAuth
56 | }
57 |
58 | var dnsNames []string
59 | var ipAddresses []net.IP
60 | if altName != "" {
61 | if ipAddr := net.ParseIP(altName); ipAddr != nil {
62 | ipAddresses = []net.IP{ipAddr}
63 | } else {
64 | dnsNames = []string{altName}
65 | }
66 | }
67 |
68 | cert := &x509.Certificate{
69 | SerialNumber: big.NewInt(1),
70 | DNSNames: dnsNames,
71 | IPAddresses: ipAddresses,
72 | Subject: pkix.Name{
73 | CommonName: name,
74 | },
75 | NotBefore: time.Now(),
76 | NotAfter: time.Now().AddDate(10, 0, 0),
77 | IsCA: false,
78 | BasicConstraintsValid: true,
79 | ExtKeyUsage: []x509.ExtKeyUsage{
80 | usage,
81 | },
82 | }
83 |
84 | certPrivKey, err := rsa.GenerateKey(rand.Reader, 4096)
85 | if err != nil {
86 | logger.L.Fatal(err)
87 | }
88 |
89 | certBytes, err := x509.CreateCertificate(rand.Reader, cert, cert, &certPrivKey.PublicKey, certPrivKey)
90 | if err != nil {
91 | logger.L.Fatal(err)
92 | }
93 |
94 | return Cert{
95 | X509Cert: cert,
96 | PrivKey: certPrivKey,
97 | SignedCert: certBytes,
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/internal/client/client_test.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Banco Bilbao Vizcaya Argentaria, S.A.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package client
18 |
19 | import (
20 | "os"
21 | "testing"
22 |
23 | "github.com/BBVA/kapow/internal/http"
24 | )
25 |
26 | func TestMain(m *testing.M) {
27 | http.ControlClientGenerator = nil
28 | os.Exit(m.Run())
29 | }
30 |
--------------------------------------------------------------------------------
/internal/client/get.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Banco Bilbao Vizcaya Argentaria, S.A.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package client
18 |
19 | import (
20 | "io"
21 |
22 | "github.com/BBVA/kapow/internal/http"
23 | )
24 |
25 | // GetData will perform the request and write the results on the provided writer
26 | func GetData(host, id, path string, w io.Writer) error {
27 | url := host + "/handlers/" + id + path
28 | return http.Get(url, nil, w, nil)
29 | }
30 |
--------------------------------------------------------------------------------
/internal/client/get_test.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Banco Bilbao Vizcaya Argentaria, S.A.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package client
18 |
19 | import (
20 | "bytes"
21 | "net/http"
22 | "testing"
23 |
24 | gock "gopkg.in/h2non/gock.v1"
25 | )
26 |
27 | func TestWriteContentToWriter(t *testing.T) {
28 | defer gock.Off()
29 | gock.New("http://localhost").
30 | Get("/handlers/HANDLER_BAR/request/body").
31 | Reply(http.StatusOK).
32 | BodyString("FOO")
33 |
34 | var b bytes.Buffer
35 | err := GetData("http://localhost", "HANDLER_BAR", "/request/body", &b)
36 |
37 | if err != nil {
38 | t.Errorf("Unexpected error: %q", err)
39 | }
40 |
41 | if !bytes.Equal(b.Bytes(), []byte("FOO")) {
42 | t.Errorf("Received content mismatch: %q != %q", b.Bytes(), []byte("FOO"))
43 | }
44 |
45 | if !gock.IsDone() {
46 | t.Error("No expected endpoint called")
47 | }
48 | }
49 |
50 | func TestPropagateHTTPError(t *testing.T) {
51 | defer gock.Off()
52 | gock.New("http://localhost").
53 | Get("/handlers/HANDLER_BAR/request/body").
54 | Reply(http.StatusTeapot)
55 |
56 | err := GetData(
57 | "http://localhost", "HANDLER_BAR", "/request/body", nil)
58 |
59 | if err == nil {
60 | t.Errorf("Expected error not returned")
61 | }
62 |
63 | if !gock.IsDone() {
64 | t.Error("No expected endpoint called")
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/internal/client/route_add.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Banco Bilbao Vizcaya Argentaria, S.A.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package client
18 |
19 | import (
20 | "bytes"
21 | "encoding/json"
22 | "io"
23 |
24 | "github.com/BBVA/kapow/internal/http"
25 | )
26 |
27 | // AddRoute will add a new route in kapow
28 | func AddRoute(host, path, method, entrypoint, command string, w io.Writer) error {
29 | url := host + "/routes"
30 | payload := map[string]string{
31 | "method": method,
32 | "url_pattern": path,
33 | "command": command,
34 | }
35 | if entrypoint != "" {
36 | payload["entrypoint"] = entrypoint
37 | }
38 | body, _ := json.Marshal(payload)
39 | return http.Post(url, bytes.NewReader(body), w, http.ControlClientGenerator, http.AsJSON)
40 | }
41 |
--------------------------------------------------------------------------------
/internal/client/route_add_test.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Banco Bilbao Vizcaya Argentaria, S.A.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package client
18 |
19 | import (
20 | "net/http"
21 | "testing"
22 |
23 | gock "gopkg.in/h2non/gock.v1"
24 | )
25 |
26 | func TestSuccessOnCorrectRoute(t *testing.T) {
27 | defer gock.Off()
28 | gock.New("http://localhost").
29 | Post("/routes").
30 | MatchType("json").
31 | JSON(map[string]string{
32 | "method": "GET",
33 | "url_pattern": "/hello",
34 | "command": "echo Hello World | kapow set /response/body",
35 | }).
36 | Reply(http.StatusCreated).
37 | JSON(map[string]string{})
38 |
39 | err := AddRoute(
40 | "http://localhost",
41 | "/hello", "GET", "", "echo Hello World | kapow set /response/body", nil)
42 | if err != nil {
43 | t.Errorf("Unexpected error: %s", err)
44 | }
45 |
46 | if !gock.IsDone() {
47 | t.Error("Expected endpoint call not made")
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/internal/client/route_list.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Banco Bilbao Vizcaya Argentaria, S.A.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package client
18 |
19 | import (
20 | "io"
21 |
22 | "github.com/BBVA/kapow/internal/http"
23 | )
24 |
25 | // ListRoutes queries the kapow! instance for the routes that are registered
26 | func ListRoutes(host string, w io.Writer) error {
27 | url := host + "/routes"
28 | return http.Get(url, nil, w, http.ControlClientGenerator)
29 | }
30 |
--------------------------------------------------------------------------------
/internal/client/route_list_test.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Banco Bilbao Vizcaya Argentaria, S.A.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package client
18 |
19 | import (
20 | "bytes"
21 | "net/http"
22 | "testing"
23 |
24 | gock "gopkg.in/h2non/gock.v1"
25 | )
26 |
27 | func TestListRoutesOKEmpty(t *testing.T) {
28 | defer gock.Off()
29 | gock.New("http://localhost:8080").
30 | Get("/routes").
31 | Reply(http.StatusOK)
32 |
33 | err := ListRoutes("http://localhost:8080", nil)
34 | if err != nil {
35 | t.Errorf("Unexpected error %q", err)
36 | }
37 |
38 | if !gock.IsDone() {
39 | t.Errorf("No endpoint called")
40 | }
41 | }
42 |
43 | func TestListRoutesOKSome(t *testing.T) {
44 | defer gock.Off()
45 | gock.New("http://localhost:8080").
46 | Get("/routes").
47 | Reply(http.StatusOK).
48 | JSON([]map[string]string{
49 | {"foo": "bar"},
50 | {"bar": "foo"},
51 | })
52 |
53 | var b bytes.Buffer
54 | err := ListRoutes("http://localhost:8080", &b)
55 | if err != nil {
56 | t.Errorf("Unexpected error: %q", err)
57 | } else if !bytes.Equal(
58 | b.Bytes(), []byte(`[{"foo":"bar"},{"bar":"foo"}]`+"\n")) {
59 | t.Errorf("Unexpected error: got %q, want %q",
60 | b.String(), `[{"foo":"bar"},{"bar":"foo"}]`+"\n")
61 | }
62 |
63 | if !gock.IsDone() {
64 | t.Errorf("No endpoint called")
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/internal/client/route_remove.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Banco Bilbao Vizcaya Argentaria, S.A.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package client
18 |
19 | import (
20 | "github.com/BBVA/kapow/internal/http"
21 | )
22 |
23 | // RemoveRoute removes a registered route in Kapow! server
24 | func RemoveRoute(host, id string) error {
25 | url := host + "/routes/" + id
26 | return http.Delete(url, nil, nil, http.ControlClientGenerator)
27 | }
28 |
--------------------------------------------------------------------------------
/internal/client/route_remove_test.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Banco Bilbao Vizcaya Argentaria, S.A.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package client
18 |
19 | import (
20 | "net/http"
21 | "testing"
22 |
23 | gock "gopkg.in/h2non/gock.v1"
24 | )
25 |
26 | func TestRemoveRouteOKExistent(t *testing.T) {
27 | defer gock.Off()
28 | gock.New("http://localhost:8080").
29 | Delete("/routes/ROUTE_FOO").
30 | Reply(http.StatusNoContent)
31 |
32 | err := RemoveRoute("http://localhost:8080", "ROUTE_FOO")
33 | if err != nil {
34 | t.Errorf("unexpected error: %q", err)
35 | }
36 |
37 | if !gock.IsDone() {
38 | t.Errorf("No endpoint called")
39 | }
40 | }
41 |
42 | func TestRemoveRouteErrorNonExistent(t *testing.T) {
43 | defer gock.Off()
44 | gock.New("http://localhost:8080").
45 | Delete("/routes/ROUTE_BAD").
46 | Reply(http.StatusNotFound).
47 | BodyString(`{"reason": "Route Not Found"}`)
48 |
49 | err := RemoveRoute("http://localhost:8080", "ROUTE_BAD")
50 | if err == nil {
51 | t.Errorf("Error not reported for nonexistent route")
52 | } else if err.Error() != "Route Not Found" {
53 | t.Errorf(`Error mismatch: got %q, want "Not Found"`, err)
54 | }
55 |
56 | if !gock.IsDone() {
57 | t.Errorf("No endpoint called")
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/internal/client/set.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Banco Bilbao Vizcaya Argentaria, S.A.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package client
18 |
19 | import (
20 | "io"
21 |
22 | "github.com/BBVA/kapow/internal/http"
23 | )
24 |
25 | func SetData(host, handlerID, path string, r io.Reader) error {
26 | url := host + "/handlers/" + handlerID + path
27 | return http.Put(url, r, nil, nil)
28 | }
29 |
--------------------------------------------------------------------------------
/internal/client/set_test.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Banco Bilbao Vizcaya Argentaria, S.A.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package client_test
18 |
19 | import (
20 | "net/http"
21 | "strings"
22 | "testing"
23 |
24 | gock "gopkg.in/h2non/gock.v1"
25 |
26 | "github.com/BBVA/kapow/internal/client"
27 | )
28 |
29 | // Test an HTTP OK request
30 | func TestSetDataSuccessOnCorrectRequest(t *testing.T) {
31 | defer gock.Off()
32 | gock.New("http://localhost:8080").
33 | Put("/HANDLER_FOO/response/status/code").
34 | Reply(http.StatusOK)
35 |
36 | if err := client.SetData(
37 | "http://localhost:8080",
38 | "HANDLER_FOO",
39 | "/response/status/code",
40 | strings.NewReader("200"),
41 | ); err != nil {
42 | t.Error("Unexpected error")
43 | }
44 |
45 | if !gock.IsDone() {
46 | t.Errorf("No endpoint called")
47 | }
48 | }
49 |
50 | // Test that Not Found errors are detected when an invalid handler id is sent
51 | func TestSetDataErrIfBadHandlerID(t *testing.T) {
52 | defer gock.Off()
53 | gock.New("http://localhost:8080").
54 | Put("/HANDLER_BAD/response/status/code").
55 | Reply(http.StatusNotFound).
56 | BodyString(`{"reason": "Handler ID Not Found"}`)
57 |
58 | if err := client.SetData(
59 | "http://localhost:8080",
60 | "HANDLER_BAD",
61 | "/response/status/code",
62 | strings.NewReader("200"),
63 | ); err == nil {
64 | t.Error("Expected error not present")
65 | } else if err.Error() != "Handler ID Not Found" {
66 | t.Errorf(`Error mismatch: expected "Handler ID Not Found", got %q`, err)
67 | }
68 |
69 | if !gock.IsDone() {
70 | t.Errorf("No endpoint called")
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/internal/cmd/get.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Banco Bilbao Vizcaya Argentaria, S.A.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package cmd
18 |
19 | import (
20 | "os"
21 |
22 | "github.com/spf13/cobra"
23 |
24 | "github.com/BBVA/kapow/internal/client"
25 | "github.com/BBVA/kapow/internal/logger"
26 | )
27 |
28 | // GetCmd is the command line interface for get kapow data operation
29 | var GetCmd = &cobra.Command{
30 | Use: "get [flags] resource",
31 | Short: "Retrieve a Kapow! resource",
32 | Long: "Retrieve a Kapow! resource for the current request",
33 | Args: cobra.ExactArgs(1),
34 | PreRunE: handlerIDRequired,
35 | Run: func(cmd *cobra.Command, args []string) {
36 | dataURL, _ := cmd.Flags().GetString("data-url")
37 | handler, _ := cmd.Flags().GetString("handler")
38 |
39 | err := client.GetData(dataURL, handler, args[0], os.Stdout)
40 | if err != nil {
41 | logger.L.Fatal(err)
42 | }
43 | },
44 | }
45 |
46 | func init() {
47 | GetCmd.Flags().String("data-url", getEnv("KAPOW_DATA_URL", "http://localhost:8082"), "Kapow! data interface URL")
48 | GetCmd.Flags().String("handler", getEnv("KAPOW_HANDLER_ID", ""), "Kapow! handler ID")
49 | }
50 |
--------------------------------------------------------------------------------
/internal/cmd/route.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Banco Bilbao Vizcaya Argentaria, S.A.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package cmd
18 |
19 | import (
20 | "io"
21 | "os"
22 |
23 | "github.com/BBVA/kapow/internal/client"
24 | "github.com/BBVA/kapow/internal/logger"
25 |
26 | "github.com/spf13/cobra"
27 | )
28 |
29 | // RouteCmd is the command line interface for kapow route handling
30 | var RouteCmd = &cobra.Command{
31 | Use: "route [action]",
32 | }
33 |
34 | func init() {
35 | var routeListCmd = &cobra.Command{
36 | Use: "list [flags]",
37 | Short: "List the current Kapow! routes",
38 | Run: func(cmd *cobra.Command, args []string) {
39 | controlURL, _ := cmd.Flags().GetString("control-url")
40 |
41 | if err := client.ListRoutes(controlURL, os.Stdout); err != nil {
42 | logger.L.Fatal(err)
43 | }
44 | },
45 | }
46 | routeListCmd.Flags().String("control-url", getEnv("KAPOW_CONTROL_URL", "https://localhost:8081"), "Kapow! control interface URL")
47 |
48 | // TODO: Manage args for url_pattern and command_file (2 exact args)
49 | var routeAddCmd = &cobra.Command{
50 | Use: "add [flags] url_pattern [command_file]",
51 | Short: "Add a route",
52 | Args: cobra.RangeArgs(1, 2),
53 | Run: func(cmd *cobra.Command, args []string) {
54 | controlURL, _ := cmd.Flags().GetString("control-url")
55 | method, _ := cmd.Flags().GetString("method")
56 | command, _ := cmd.Flags().GetString("command")
57 | entrypoint, _ := cmd.Flags().GetString("entrypoint")
58 | urlPattern := args[0]
59 |
60 | if len(args) > 1 && command == "" {
61 | commandFile := args[1]
62 | var buf []byte
63 | var err error
64 | if commandFile == "-" {
65 | buf, err = io.ReadAll(os.Stdin)
66 | } else {
67 | buf, err = os.ReadFile(commandFile)
68 | }
69 | if err != nil {
70 | logger.L.Fatal(err)
71 | }
72 | command = string(buf)
73 | }
74 |
75 | if err := client.AddRoute(controlURL, urlPattern, method, entrypoint, command, os.Stdout); err != nil {
76 | logger.L.Fatal(err)
77 | }
78 | },
79 | }
80 | // TODO: Add default values for flags and remove path flag
81 | routeAddCmd.Flags().String("control-url", getEnv("KAPOW_CONTROL_URL", "https://localhost:8081"), "Kapow! control interface URL")
82 | routeAddCmd.Flags().StringP("method", "X", "GET", "HTTP method to accept")
83 | routeAddCmd.Flags().StringP("entrypoint", "e", "", "Command to execute")
84 | routeAddCmd.Flags().StringP("command", "c", "", "Command to pass to the shell")
85 |
86 | var routeRemoveCmd = &cobra.Command{
87 | Use: "remove [flags] route_id",
88 | Short: "Remove the given route",
89 | Args: cobra.ExactArgs(1),
90 | Run: func(cmd *cobra.Command, args []string) {
91 | controlURL, _ := cmd.Flags().GetString("control-url")
92 |
93 | if err := client.RemoveRoute(controlURL, args[0]); err != nil {
94 | logger.L.Fatal(err)
95 | }
96 | },
97 | }
98 | routeRemoveCmd.Flags().String("control-url", getEnv("KAPOW_CONTROL_URL", "https://localhost:8081"), "Kapow! control interface URL")
99 |
100 | RouteCmd.AddCommand(routeListCmd)
101 | RouteCmd.AddCommand(routeAddCmd)
102 | RouteCmd.AddCommand(routeRemoveCmd)
103 | }
104 |
--------------------------------------------------------------------------------
/internal/cmd/runner.go:
--------------------------------------------------------------------------------
1 | // +build !windows
2 |
3 | package cmd
4 |
5 | import (
6 | "os/exec"
7 | )
8 |
9 | func BuildCmd(path string) *exec.Cmd {
10 | return exec.Command(path)
11 | }
12 |
--------------------------------------------------------------------------------
/internal/cmd/runner_windows.go:
--------------------------------------------------------------------------------
1 | package cmd
2 |
3 | import (
4 | "os/exec"
5 | )
6 |
7 | func BuildCmd(path string) *exec.Cmd {
8 | return exec.Command("cmd.exe", "/c", path)
9 | }
10 |
--------------------------------------------------------------------------------
/internal/cmd/set.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Banco Bilbao Vizcaya Argentaria, S.A.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package cmd
18 |
19 | import (
20 | "io"
21 | "os"
22 | "strings"
23 |
24 | "github.com/BBVA/kapow/internal/client"
25 | "github.com/BBVA/kapow/internal/logger"
26 |
27 | "github.com/spf13/cobra"
28 | )
29 |
30 | // SetCmd is the command line interface for set kapow data operation
31 | var SetCmd = &cobra.Command{
32 | Use: "set [flags] resource [value]",
33 | Short: "Set a Kapow! resource value",
34 | Long: "Set a Kapow! resource value for the current request",
35 | Args: cobra.RangeArgs(1, 2),
36 | PreRunE: handlerIDRequired,
37 | Run: func(cmd *cobra.Command, args []string) {
38 | var r io.Reader
39 | dataURL, _ := cmd.Flags().GetString("data-url")
40 | handler, _ := cmd.Flags().GetString("handler")
41 | path, args := args[0], args[1:]
42 |
43 | if len(args) == 1 {
44 | r = strings.NewReader(args[0])
45 | } else {
46 | r = os.Stdin
47 | }
48 |
49 | if err := client.SetData(dataURL, handler, path, r); err != nil {
50 | logger.L.Fatal(err)
51 | }
52 | },
53 | }
54 |
55 | func init() {
56 | SetCmd.Flags().String("data-url", getEnv("KAPOW_DATA_URL", "http://localhost:8082"), "Kapow! data interface URL")
57 | SetCmd.Flags().String("handler", getEnv("KAPOW_HANDLER_ID", ""), "Kapow! handler ID")
58 | }
59 |
--------------------------------------------------------------------------------
/internal/cmd/validations.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Banco Bilbao Vizcaya Argentaria, S.A.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package cmd
18 |
19 | import (
20 | "errors"
21 | "os"
22 |
23 | "github.com/spf13/cobra"
24 | )
25 |
26 | func getEnv(key, fallback string) string {
27 | value, exists := os.LookupEnv(key)
28 | if !exists {
29 | return fallback
30 | }
31 | return value
32 | }
33 |
34 | func handlerIDRequired(cmd *cobra.Command, args []string) error {
35 | handler, _ := cmd.Flags().GetString("handler")
36 | if handler == "" {
37 | return errors.New("--handler or KAPOW_HANDLER_ID is mandatory")
38 | }
39 | return nil
40 | }
41 |
--------------------------------------------------------------------------------
/internal/http/reason.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Banco Bilbao Vizcaya Argentaria, S.A.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package http
18 |
19 | import (
20 | "encoding/json"
21 | "errors"
22 | "io"
23 | "net/http"
24 |
25 | "github.com/BBVA/kapow/internal/server/httperror"
26 | )
27 |
28 | // Reason returns the reason phrase embedded within the JSON error
29 | // body, or an error if no reason can be extracted
30 | func Reason(r *http.Response) (string, error) {
31 | body, err := io.ReadAll(r.Body)
32 | if err != nil {
33 | return "", errors.New("error reading response's body")
34 | }
35 |
36 | reason := &httperror.ServerErrMessage{}
37 | err = json.Unmarshal(body, reason)
38 | if err != nil {
39 | return "", errors.New("error unmarshaling JSON")
40 | }
41 |
42 | if reason.Reason == "" {
43 | return "", errors.New("no reason")
44 | }
45 |
46 | return reason.Reason, nil
47 | }
48 |
--------------------------------------------------------------------------------
/internal/http/reason_test.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Banco Bilbao Vizcaya Argentaria, S.A.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package http
18 |
19 | import (
20 | "io"
21 | nethttp "net/http"
22 | "strings"
23 | "testing"
24 | )
25 |
26 | func TestReasonExtractsReasonFromJSON(t *testing.T) {
27 | r := &nethttp.Response{
28 | Status: "200 OK",
29 | Body: io.NopCloser(
30 | strings.NewReader(
31 | `{"reason": "Because reasons", "foo": "bar"}`,
32 | ),
33 | ),
34 | }
35 |
36 | reason, _ := Reason(r)
37 |
38 | if reason != "Because reasons" {
39 | t.Errorf(`reason mismatch, want "Because reasons", got %q`, reason)
40 | }
41 | }
42 |
43 | func TestReasonErrorsOnJSONWithNoReason(t *testing.T) {
44 | r := &nethttp.Response{
45 | Status: "200 OK",
46 | Body: io.NopCloser(
47 | strings.NewReader(
48 | `{"madness": "Because madness", "foo": "bar"}`,
49 | ),
50 | ),
51 | }
52 |
53 | _, err := Reason(r)
54 |
55 | if err == nil {
56 | t.Error("error not reported")
57 | }
58 | }
59 |
60 | func TestReasonErrorsOnJSONWithEmptyReason(t *testing.T) {
61 | r := &nethttp.Response{
62 | Body: io.NopCloser(
63 | strings.NewReader(
64 | `{"reason": "", "foo": "bar"}`,
65 | ),
66 | ),
67 | }
68 |
69 | _, err := Reason(r)
70 |
71 | if err == nil {
72 | t.Error("error not reported")
73 | }
74 | }
75 |
76 | func TestReasonErrorsOnNoJSON(t *testing.T) {
77 | r := &nethttp.Response{
78 | Body: io.NopCloser(
79 | strings.NewReader(""),
80 | ),
81 | }
82 |
83 | _, err := Reason(r)
84 |
85 | if err == nil {
86 | t.Error("error not reported")
87 | }
88 | }
89 |
90 | func TestReasonErrorsOnInvalidJSON(t *testing.T) {
91 | r := &nethttp.Response{
92 | Body: io.NopCloser(
93 | strings.NewReader(
94 | `{"reason": "Because reasons", "cliffhanger...`,
95 | ),
96 | ),
97 | }
98 |
99 | _, err := Reason(r)
100 |
101 | if err == nil {
102 | t.Error("error not reported")
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/internal/logger/logger.go:
--------------------------------------------------------------------------------
1 | package logger
2 |
3 | import (
4 | "fmt"
5 | "log"
6 | "os"
7 | "time"
8 | )
9 |
10 | var A *log.Logger
11 | var L *log.Logger
12 |
13 | func init() {
14 | A = log.New(os.Stdout, "", 0)
15 | L = log.New(os.Stderr, "", log.LstdFlags|log.Lmicroseconds|log.LUTC)
16 | }
17 |
18 | // See https://en.wikipedia.org/wiki/Common_Log_Format
19 | // Tested with love against CLFParser https://pypi.org/project/clfparser/
20 | func LogAccess(clientAddr, handlerId, user, method, resource, version string, status int, bytesSent int64, referer, userAgent string) {
21 | clientAddr = dashIfEmpty(clientAddr)
22 | handlerId = dashIfEmpty(handlerId)
23 | user = dashIfEmpty(user)
24 | referer = dashIfEmpty(referer)
25 | userAgent = dashIfEmpty(userAgent)
26 | firstLine := fmt.Sprintf("%s %s %s", method, resource, version)
27 | ts := time.Now().UTC().Format("02/Jan/2006:15:04:05 -0700") // Amazing date format layout! Not.
28 | A.Printf("%s %s %s [%s] %q %d %d %q %q\n",
29 | clientAddr,
30 | handlerId,
31 | user,
32 | ts,
33 | firstLine,
34 | status,
35 | bytesSent,
36 | referer,
37 | userAgent)
38 | }
39 |
40 | func dashIfEmpty(value string) string {
41 | if value == "" {
42 | return "-"
43 | }
44 | return value
45 | }
46 |
--------------------------------------------------------------------------------
/internal/server/control/entrypoint.go:
--------------------------------------------------------------------------------
1 | // +build !windows
2 |
3 | package control
4 |
5 | var defaultEntrypoint = "/bin/sh -c"
6 |
--------------------------------------------------------------------------------
/internal/server/control/entrypoint_windows.go:
--------------------------------------------------------------------------------
1 | package control
2 |
3 | var defaultEntrypoint = "cmd.exe /c"
4 |
--------------------------------------------------------------------------------
/internal/server/control/server.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Banco Bilbao Vizcaya Argentaria, S.A.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package control
18 |
19 | import (
20 | "crypto/tls"
21 | "crypto/x509"
22 | "net"
23 | "net/http"
24 | "sync"
25 |
26 | "github.com/BBVA/kapow/internal/certs"
27 | "github.com/BBVA/kapow/internal/logger"
28 | )
29 |
30 | // Run Starts the control server listening in bindAddr
31 | func Run(bindAddr string, wg *sync.WaitGroup, serverCert, clientCert certs.Cert) {
32 |
33 | caCertPool := x509.NewCertPool()
34 | caCertPool.AppendCertsFromPEM(clientCert.SignedCertPEMBytes())
35 |
36 | ln, err := net.Listen("tcp", bindAddr)
37 | if err != nil {
38 | logger.L.Fatal(err)
39 | }
40 |
41 | server := &http.Server{
42 | Addr: bindAddr,
43 | TLSConfig: &tls.Config{
44 | Certificates: []tls.Certificate{
45 | tls.Certificate{
46 | Certificate: [][]byte{serverCert.SignedCert},
47 | PrivateKey: serverCert.PrivKey,
48 | Leaf: serverCert.X509Cert,
49 | },
50 | },
51 | ClientAuth: tls.RequireAndVerifyClientCert,
52 | ClientCAs: caCertPool,
53 | },
54 | Handler: configRouter(),
55 | }
56 |
57 | // Signal startup
58 | logger.L.Printf("ControlServer listening at %s\n", bindAddr)
59 | wg.Done()
60 |
61 | // Listen to HTTPS connections with the server certificate and wait
62 | logger.L.Fatal(server.ServeTLS(ln, "", ""))
63 | }
64 |
--------------------------------------------------------------------------------
/internal/server/data/decorator.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Banco Bilbao Vizcaya Argentaria, S.A.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package data
18 |
19 | import (
20 | "net/http"
21 |
22 | "github.com/BBVA/kapow/internal/server/httperror"
23 | "github.com/BBVA/kapow/internal/server/model"
24 | "github.com/gorilla/mux"
25 | )
26 |
27 | type resourceHandler func(http.ResponseWriter, *http.Request, *model.Handler)
28 |
29 | func lockResponseWriter(fn resourceHandler) resourceHandler {
30 | return func(w http.ResponseWriter, r *http.Request, h *model.Handler) {
31 | h.Writing.Lock()
32 | defer h.Writing.Unlock()
33 | fn(w, r, h)
34 | }
35 | }
36 |
37 | func checkHandler(fn resourceHandler) func(http.ResponseWriter, *http.Request) {
38 | return func(w http.ResponseWriter, r *http.Request) {
39 | handlerID := mux.Vars(r)["handlerID"]
40 | if h, ok := Handlers.Get(handlerID); ok {
41 | fn(w, r, h)
42 | } else {
43 | httperror.ErrorJSON(w, "Handler ID Not Found", http.StatusNotFound)
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/internal/server/data/server.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Banco Bilbao Vizcaya Argentaria, S.A.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package data
18 |
19 | import (
20 | "net"
21 | "net/http"
22 | "sync"
23 |
24 | "github.com/BBVA/kapow/internal/logger"
25 | "github.com/BBVA/kapow/internal/server/httperror"
26 | "github.com/gorilla/mux"
27 | )
28 |
29 | type routeSpec struct {
30 | route string
31 | method string
32 | rh resourceHandler
33 | }
34 |
35 | func configRouter(rs []routeSpec) (r *mux.Router) {
36 | r = mux.NewRouter()
37 | for _, s := range rs {
38 | r.HandleFunc(s.route, checkHandler(s.rh)).Methods(s.method)
39 | }
40 | r.HandleFunc(
41 | "/handlers/{handlerID}/{resource:.*}",
42 | func(w http.ResponseWriter, r *http.Request) {
43 | httperror.ErrorJSON(w, "Invalid Resource Path", http.StatusBadRequest)
44 | })
45 |
46 | r.NotFoundHandler = http.HandlerFunc(func(w http.ResponseWriter, h *http.Request) {
47 | httperror.ErrorJSON(w, "Data server: Not found", http.StatusNotFound)
48 | })
49 |
50 | r.MethodNotAllowedHandler = http.HandlerFunc(func(w http.ResponseWriter, h *http.Request) {
51 | httperror.ErrorJSON(w, "Data server: Method not allowed", http.StatusMethodNotAllowed)
52 | })
53 |
54 | return r
55 | }
56 |
57 | func Run(bindAddr string, wg *sync.WaitGroup) {
58 | rs := []routeSpec{
59 | // request
60 | {"/handlers/{handlerID}/request/method", "GET", getRequestMethod},
61 | {"/handlers/{handlerID}/request/host", "GET", getRequestHost},
62 | {"/handlers/{handlerID}/request/version", "GET", getRequestVersion},
63 | {"/handlers/{handlerID}/request/path", "GET", getRequestPath},
64 | {"/handlers/{handlerID}/request/remote", "GET", getRequestRemote},
65 | {"/handlers/{handlerID}/request/matches/{name}", "GET", getRequestMatches},
66 | {"/handlers/{handlerID}/request/params/{name}", "GET", getRequestParams},
67 | {"/handlers/{handlerID}/request/headers/{name}", "GET", getRequestHeaders},
68 | {"/handlers/{handlerID}/request/cookies/{name}", "GET", getRequestCookies},
69 | {"/handlers/{handlerID}/request/form/{name}", "GET", getRequestForm},
70 | {"/handlers/{handlerID}/request/files/{name}/filename", "GET", getRequestFileName},
71 | {"/handlers/{handlerID}/request/files/{name}/content", "GET", getRequestFileContent},
72 | {"/handlers/{handlerID}/request/body", "GET", getRequestBody},
73 |
74 | // route
75 | {"/handlers/{handlerID}/route/id", "GET", getRouteId},
76 |
77 | // SSL stuff
78 | {"/handlers/{handlerID}/ssl/client/i/dn", "GET", getSSLClietnDN},
79 |
80 | // response
81 | {"/handlers/{handlerID}/response/status", "PUT", lockResponseWriter(setResponseStatus)},
82 | {"/handlers/{handlerID}/response/headers/{name}", "PUT", lockResponseWriter(setResponseHeaders)},
83 | {"/handlers/{handlerID}/response/cookies/{name}", "PUT", lockResponseWriter(setResponseCookies)},
84 | {"/handlers/{handlerID}/response/body", "PUT", lockResponseWriter(setResponseBody)},
85 | {"/handlers/{handlerID}/response/stream", "PUT", lockResponseWriter(setResponseBody)},
86 |
87 | // logging
88 | {"/handlers/{handlerID}/server/log/{prefix}", "PUT", setServerLog},
89 | {"/handlers/{handlerID}/server/log", "PUT", setServerLog},
90 | }
91 |
92 | listener, err := net.Listen("tcp", bindAddr)
93 | if err != nil {
94 | logger.L.Fatal(err)
95 | }
96 |
97 | // Signal startup
98 | logger.L.Printf("DataServer listening at %s\n", bindAddr)
99 | wg.Done()
100 |
101 | logger.L.Fatal(http.Serve(listener, configRouter(rs)))
102 | }
103 |
--------------------------------------------------------------------------------
/internal/server/data/server_test.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Banco Bilbao Vizcaya Argentaria, S.A.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package data
18 |
19 | import (
20 | "encoding/json"
21 | "fmt"
22 | "io"
23 | "net/http"
24 | "net/http/httptest"
25 | "testing"
26 |
27 | "github.com/BBVA/kapow/internal/server/httperror"
28 | "github.com/BBVA/kapow/internal/server/model"
29 | )
30 |
31 | func checkErrorResponse(r *http.Response, expectedErrcode int, expectedReason string) []error {
32 | errList := make([]error, 0)
33 |
34 | if r.StatusCode != expectedErrcode {
35 | errList = append(errList, fmt.Errorf("HTTP status mismatch. Expected: %d, got: %d", expectedErrcode, r.StatusCode))
36 | }
37 |
38 | if v := r.Header.Get("Content-Type"); v != "application/json; charset=utf-8" {
39 | errList = append(errList, fmt.Errorf("Content-Type header mismatch. Expected: %q, got: %q", "application/json; charset=utf-8", v))
40 | }
41 |
42 | errMsg := httperror.ServerErrMessage{}
43 | if bodyBytes, err := io.ReadAll(r.Body); err != nil {
44 | errList = append(errList, fmt.Errorf("Unexpected error reading response body: %v", err))
45 | } else if err := json.Unmarshal(bodyBytes, &errMsg); err != nil {
46 | errList = append(errList, fmt.Errorf("Response body contains invalid JSON entity: %v", err))
47 | } else if errMsg.Reason != expectedReason {
48 | errList = append(errList, fmt.Errorf("Unexpected reason in response. Expected: %q, got: %q", expectedReason, errMsg.Reason))
49 | }
50 |
51 | return errList
52 | }
53 |
54 | func TestConfigRouterReturnsRouterWithDecoratedRoutes(t *testing.T) {
55 | var handlerID string
56 | rs := []routeSpec{
57 | {
58 | "/handlers/{handlerID}/dummy",
59 | "GET",
60 | func(w http.ResponseWriter, r *http.Request, h *model.Handler) { handlerID = h.ID },
61 | },
62 | }
63 | Handlers = New()
64 | Handlers.Add(&model.Handler{ID: "FOO"})
65 | m := configRouter(rs)
66 |
67 | m.ServeHTTP(httptest.NewRecorder(), httptest.NewRequest("GET", "/handlers/FOO/dummy", nil))
68 |
69 | if handlerID != "FOO" {
70 | t.Errorf(`Handler ID mismatch. Expected "FOO". Got %q`, handlerID)
71 | }
72 | }
73 |
74 | func TestConfigRouterReturnsRouterThat400sOnUnconfiguredResources(t *testing.T) {
75 | m := configRouter([]routeSpec{})
76 | w := httptest.NewRecorder()
77 |
78 | m.ServeHTTP(w, httptest.NewRequest("GET", "/handlers/FOO/dummy", nil))
79 |
80 | for _, e := range checkErrorResponse(w.Result(), http.StatusBadRequest, "Invalid Resource Path") {
81 | t.Error(e)
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/internal/server/data/state.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Banco Bilbao Vizcaya Argentaria, S.A.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package data
18 |
19 | import (
20 | "sync"
21 |
22 | "github.com/BBVA/kapow/internal/server/model"
23 | )
24 |
25 | type safeHandlerMap struct {
26 | hs map[string]*model.Handler
27 | m *sync.RWMutex
28 | }
29 |
30 | // Singleton containing all current handlers as a safeHandlerMap
31 | var Handlers = New()
32 |
33 | // New creates a read-to-use safeHandlerMap
34 | func New() safeHandlerMap {
35 | return safeHandlerMap{
36 | hs: make(map[string]*model.Handler),
37 | m: &sync.RWMutex{},
38 | }
39 | }
40 |
41 | func (shm *safeHandlerMap) Add(h *model.Handler) {
42 | shm.m.Lock()
43 | shm.hs[h.ID] = h
44 | shm.m.Unlock()
45 | }
46 |
47 | func (shm *safeHandlerMap) Remove(id string) {
48 | shm.m.Lock()
49 | delete(shm.hs, id)
50 | shm.m.Unlock()
51 | }
52 |
53 | func (shm *safeHandlerMap) Get(id string) (*model.Handler, bool) {
54 | shm.m.RLock()
55 | h, ok := shm.hs[id]
56 | shm.m.RUnlock()
57 | return h, ok
58 | }
59 |
60 | func (shm *safeHandlerMap) ListIDs() (ids []string) {
61 | shm.m.RLock()
62 | defer shm.m.RUnlock()
63 | for id := range shm.hs {
64 | ids = append(ids, id)
65 | }
66 | return
67 | }
68 |
--------------------------------------------------------------------------------
/internal/server/data/testdata/client_chain.crt:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE-----
2 | MIIDdTCCAl0CAQIwDQYJKoZIhvcNAQELBQAwgZ8xCzAJBgNVBAYTAkVTMQ8wDQYD
3 | VQQIDAZNYWRyaWQxDzANBgNVBAcMBk1hZHJpZDENMAsGA1UECgwEQkJWQTEYMBYG
4 | A1UECwwPSW5ub3ZhdGlvbiBMYWJzMR0wGwYDVQQDDBRTZWN1cml0eS1DQS5iYnZh
5 | LmNvbTEmMCQGCSqGSIb3DQEJARYXc2VjdXJpdHkuZ3JvdXBAYmJ2YS5jb20wHhcN
6 | MjAwMTIzMTQwODUxWhcNMjEwMTIyMTQwODUxWjBhMQswCQYDVQQGEwJFUzEPMA0G
7 | A1UECAwGTWFkcmlkMQ0wCwYDVQQKDARCQlZBMRgwFgYDVQQLDA9Jbm5vdmF0aW9u
8 | IExhYnMxGDAWBgNVBAMMD0thcG93ISBjbGllbnQgMTCCASIwDQYJKoZIhvcNAQEB
9 | BQADggEPADCCAQoCggEBAJKXoqOe0S1i8c0bDLGsvibSpDmWkb/2oXn4qn8XtlLF
10 | PSY69qeqkeLZov0nVV6zenag9Vh99uy7M4kw/pFqYv1eViDvV6I0wyKEzXNyeQmL
11 | O11YUWfP38T+Usw0JY0Pau+ewSQkurtHmGRWC5fAgPxiyi03hD3V3eHPR60V5yE6
12 | 1wYoLqz6xJ7nkXVyVLdg6wekrMkpos3ciA3Roco4m5fbXbVGYrx8E97byT9yyKzI
13 | kvFjQ0T4+67dPuWW+juoD0lOwfNu1WtY4PPnkUuzjUftKzD1t/a4zsLcFLafuuVR
14 | f4qb21o2pqDOWxMMZGrceS5uEF0nI1wtdw+XMtFHdFcCAwEAATANBgkqhkiG9w0B
15 | AQsFAAOCAQEAsnPW8pCIiejBwjQ4TTNPo5wiRNOib69ANj2lHE1gidO8HA29/ssF
16 | U7jbcxCQf0/flv+JddnSJzmeFhrt15CL6nOZ1whSqVA1W1dAno0RYNiPUILofq50
17 | zKNUVF+eYz24nksdI87d9j1Zri2H91p+gA1pBnIxBE8zgXZ5+u7FUrA41HOuVyAy
18 | 55EwUDloVg4WBeddb8Y/mPgXNHS7ZB0Z13+bLHeSkSWWV3Gw1OtLHJEv/j+/9K5O
19 | KoJCyO4xSkKP7/nYYKCed4grIfOAu7iHqN/Ok9yuAOm0tbgwKmzw9EGga82YCH0M
20 | jfd8wfVCqiZvW9SUa11fM/Np9/+04QtlNA==
21 | -----END CERTIFICATE-----
22 | -----BEGIN CERTIFICATE-----
23 | MIIEITCCAwmgAwIBAgIUMAqlEXi1gcpy97bWApqtBwgqEvgwDQYJKoZIhvcNAQEL
24 | BQAwgZ8xCzAJBgNVBAYTAkVTMQ8wDQYDVQQIDAZNYWRyaWQxDzANBgNVBAcMBk1h
25 | ZHJpZDENMAsGA1UECgwEQkJWQTEYMBYGA1UECwwPSW5ub3ZhdGlvbiBMYWJzMR0w
26 | GwYDVQQDDBRTZWN1cml0eS1DQS5iYnZhLmNvbTEmMCQGCSqGSIb3DQEJARYXc2Vj
27 | dXJpdHkuZ3JvdXBAYmJ2YS5jb20wHhcNMjAwMTIyMTcxNzUwWhcNMjAwMjIxMTcx
28 | NzUwWjCBnzELMAkGA1UEBhMCRVMxDzANBgNVBAgMBk1hZHJpZDEPMA0GA1UEBwwG
29 | TWFkcmlkMQ0wCwYDVQQKDARCQlZBMRgwFgYDVQQLDA9Jbm5vdmF0aW9uIExhYnMx
30 | HTAbBgNVBAMMFFNlY3VyaXR5LUNBLmJidmEuY29tMSYwJAYJKoZIhvcNAQkBFhdz
31 | ZWN1cml0eS5ncm91cEBiYnZhLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
32 | AQoCggEBAMM8xmTpUg2PXV6zPohGOKLiXHP3nQMfkoYLRwFYcKq0WnYmaKmIK5T0
33 | gCLmiQMCNv/NV8+aIkd2HuTPBnobFLbyUCN9yf2Gj81wxeRydBwbjaj0dpB1Jx9W
34 | 5OdMBFxGIKmnqVN/z784Ma9cj+tv0t5LYpWIxnEgnBaiuMkQnwJFyv6aL1VNRmW7
35 | zFMAqOiMishMKb/0UaSW53ZBFjCqmhquZ7CZYLsaB+mMDv1fOXf8jSX/WrcCCkvQ
36 | HX04/9HxNVIe3a0Zl8CPfyUOd9njVl1VRDgjljUyMeMM4zo31enpwWOuhpfI6jU5
37 | AB7If6xufHyN8FCvKpDy9Z9Sp5Ww1o8CAwEAAaNTMFEwHQYDVR0OBBYEFOfc0yly
38 | jUPuoy54Ods9KKt5CITsMB8GA1UdIwQYMBaAFOfc0ylyjUPuoy54Ods9KKt5CITs
39 | MA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBALvzf14HF78rowNA
40 | XuczbkgKLbNJam0YC3ZxoB/pxcwfSZCf4E7+FAWKfmwrNqZv2PweOvDfP4Rx4T1x
41 | VLWDr0qtcsol7gOPga8HD/1zTgK096rYs5pCxQabgIpmHhzDUnjBrQyds8U4sakP
42 | 3xzuy/eZ/ozDzCGZn8HzqYHDTcEfypMSdZUUDgN4vVE3Il3AZRSEG9X/Ov4W8tLF
43 | dofY/JDVObXV0DAms9xcux8BfAslh8NNytP3q+uneIeuJT/eRQu+Z5GRB+mbL8X6
44 | DmXeSSkMiVeFgD1qg9VZVSBsihBpWpJakcxvXOOj5fY2t5ovW6TR2jDjW5XjHs82
45 | idlvJUw=
46 | -----END CERTIFICATE-----
47 |
--------------------------------------------------------------------------------
/internal/server/httperror/error.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Banco Bilbao Vizcaya Argentaria, S.A.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package httperror
18 |
19 | import (
20 | "encoding/json"
21 | "net/http"
22 | )
23 |
24 | // A ServerErrMessage represents the reason why the error happened
25 | type ServerErrMessage struct {
26 | Reason string `json:"reason"`
27 | }
28 |
29 | // ErrorJSON writes the provided error as a JSON body to the provided
30 | // http.ResponseWriter, after setting the appropriate Content-Type header
31 | func ErrorJSON(w http.ResponseWriter, error string, code int) {
32 | body, _ := json.Marshal(
33 | ServerErrMessage{
34 | Reason: error,
35 | },
36 | )
37 | w.Header().Set("Content-Type", "application/json; charset=utf-8")
38 | w.WriteHeader(code)
39 | _, _ = w.Write(body)
40 | }
41 |
--------------------------------------------------------------------------------
/internal/server/httperror/error_test.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Banco Bilbao Vizcaya Argentaria, S.A.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package httperror_test
18 |
19 | import (
20 | "encoding/json"
21 | "io"
22 | "net/http"
23 | "net/http/httptest"
24 | "testing"
25 |
26 | "github.com/BBVA/kapow/internal/server/httperror"
27 | )
28 |
29 | func TestErrorJSONSetsAppJsonContentType(t *testing.T) {
30 | w := httptest.NewRecorder()
31 |
32 | httperror.ErrorJSON(w, "Not Important Here", 500)
33 |
34 | if v := w.Result().Header.Get("Content-Type"); v != "application/json; charset=utf-8" {
35 | t.Errorf("Content-Type header mismatch. Expected: %q, got: %q", "application/json; charset=utf-8", v)
36 | }
37 | }
38 |
39 | func TestErrorJSONSetsRequestedStatusCode(t *testing.T) {
40 | w := httptest.NewRecorder()
41 |
42 | httperror.ErrorJSON(w, "Not Important Here", http.StatusGone)
43 |
44 | if v := w.Result().StatusCode; v != http.StatusGone {
45 | t.Errorf("Status code mismatch. Expected: %d, got: %d", http.StatusGone, v)
46 | }
47 | }
48 |
49 | func TestErrorJSONSetsBodyCorrectly(t *testing.T) {
50 | expectedReason := "Something Not Found"
51 | w := httptest.NewRecorder()
52 |
53 | httperror.ErrorJSON(w, expectedReason, http.StatusNotFound)
54 |
55 | errMsg := httperror.ServerErrMessage{}
56 | if bodyBytes, err := io.ReadAll(w.Result().Body); err != nil {
57 | t.Errorf("Unexpected error reading response body: %v", err)
58 | } else if err := json.Unmarshal(bodyBytes, &errMsg); err != nil {
59 | t.Errorf("Response body contains invalid JSON entity: %v", err)
60 | } else if errMsg.Reason != expectedReason {
61 | t.Errorf("Unexpected reason in response. Expected: %q, got: %q", expectedReason, errMsg.Reason)
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/internal/server/model/handler.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Banco Bilbao Vizcaya Argentaria, S.A.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package model
18 |
19 | import (
20 | "net/http"
21 | "sync"
22 | )
23 |
24 | // Handler represents an open HTTP connection in the User Server.
25 | //
26 | // This struct contains the connection Writer and Request to be managed
27 | // by endpoints of the Data Server.
28 | type Handler struct {
29 | // ID is unique identifier of the request.
30 | ID string
31 |
32 | // Route is the original route that matched this request.
33 | Route
34 |
35 | // Writing is a mutex that prevents two goroutines from writing at
36 | // the same time in the response.
37 | Writing sync.Mutex
38 |
39 | // Request is a pointer to the in-progress request.
40 | Request *http.Request
41 |
42 | // Writer is the original http.ResponseWriter of the request.
43 | Writer http.ResponseWriter
44 |
45 | // Status is the returned status code
46 | Status int
47 |
48 | // SentBytes is the number of sent bytes
49 | SentBytes int64
50 |
51 | // The transfer of the body has started
52 | BodyOut bool
53 | }
54 |
55 | // TODO: add a Handler smart constructor to initialize Status to 200
56 |
--------------------------------------------------------------------------------
/internal/server/model/route.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Banco Bilbao Vizcaya Argentaria, S.A.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package model
18 |
19 | // Route contains the data needed to represent a Kapow! user route.
20 | type Route struct {
21 | // ID is the unique identifier of the Route.
22 | ID string `json:"id,omitempty"`
23 |
24 | // Method is the HTTP method that will match this Route.
25 | Method string `json:"method"`
26 |
27 | // Pattern is the gorilla/mux path pattern that will match this
28 | // Route.
29 | Pattern string `json:"url_pattern"`
30 |
31 | // Entrypoint is the string that will be executed when the Route
32 | // match.
33 | //
34 | // This string will be split according to the shell parsing rules to
35 | // be passed as a list to exec.Command.
36 | Entrypoint string `json:"entrypoint,omitempty"`
37 |
38 | // Command is the last argument to be passed to exec.Command when
39 | // executing the Entrypoint
40 | Command string `json:"command"`
41 |
42 | // Index is this route position in the server's routes list.
43 | // It is an output field, its value is ignored as input.
44 | Index int `json:"index"`
45 |
46 | Debug bool `json:"debug,omitempty"`
47 | }
48 |
--------------------------------------------------------------------------------
/internal/server/server.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Banco Bilbao Vizcaya Argentaria, S.A.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package server
18 |
19 | import (
20 | "sync"
21 |
22 | "github.com/BBVA/kapow/internal/certs"
23 | "github.com/BBVA/kapow/internal/server/control"
24 | "github.com/BBVA/kapow/internal/server/data"
25 | "github.com/BBVA/kapow/internal/server/user"
26 | )
27 |
28 | type ServerConfig struct {
29 | ControlBindAddr,
30 | DataBindAddr,
31 | UserBindAddr,
32 | KeyFile,
33 | CertFile,
34 | ClientCaFile string
35 |
36 | ClientAuth,
37 | Debug bool
38 |
39 | ControlServerCert certs.Cert
40 | ControlClientCert certs.Cert
41 | }
42 |
43 | // StartServer Starts one instance of each server in a goroutine and remains listening on a channel for trace events generated by them
44 | func StartServer(config ServerConfig) {
45 | var wg = sync.WaitGroup{}
46 | wg.Add(3)
47 | go control.Run(config.ControlBindAddr, &wg, config.ControlServerCert, config.ControlClientCert)
48 | go data.Run(config.DataBindAddr, &wg)
49 | go user.Run(config.UserBindAddr, &wg, config.CertFile, config.KeyFile, config.ClientCaFile, config.ClientAuth, config.Debug)
50 |
51 | // Wait for servers signals in order to return
52 | wg.Wait()
53 | }
54 |
--------------------------------------------------------------------------------
/internal/server/user/mux/gorillize.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Banco Bilbao Vizcaya Argentaria, S.A.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package mux
18 |
19 | import (
20 | "net/http"
21 |
22 | "github.com/gorilla/mux"
23 |
24 | "github.com/BBVA/kapow/internal/logger"
25 | "github.com/BBVA/kapow/internal/server/model"
26 | )
27 |
28 | func gorillize(rs []model.Route, buildHandler func(model.Route) http.Handler) *mux.Router {
29 | m := mux.NewRouter()
30 |
31 | for _, r := range rs {
32 | m.Handle(r.Pattern, buildHandler(r)).Methods(r.Method)
33 | }
34 | m.NotFoundHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
35 | w.WriteHeader(http.StatusNotFound)
36 | logger.LogAccess(
37 | r.RemoteAddr,
38 | "-",
39 | "-",
40 | r.Method,
41 | r.RequestURI,
42 | r.Proto,
43 | http.StatusNotFound,
44 | 0,
45 | r.Header.Get("Referer"),
46 | r.Header.Get("User-Agent"),
47 | )
48 | })
49 | m.MethodNotAllowedHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
50 | w.WriteHeader(http.StatusMethodNotAllowed)
51 | logger.LogAccess(
52 | r.RemoteAddr,
53 | "-",
54 | "-",
55 | r.Method,
56 | r.RequestURI,
57 | r.Proto,
58 | http.StatusMethodNotAllowed,
59 | 0,
60 | r.Header.Get("Referer"),
61 | r.Header.Get("User-Agent"),
62 | )
63 | })
64 | return m
65 | }
66 |
--------------------------------------------------------------------------------
/internal/server/user/mux/handlerbuilder.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Banco Bilbao Vizcaya Argentaria, S.A.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package mux
18 |
19 | import (
20 | "bufio"
21 | "net/http"
22 | "os"
23 |
24 | "github.com/google/uuid"
25 |
26 | "github.com/BBVA/kapow/internal/logger"
27 | "github.com/BBVA/kapow/internal/server/data"
28 | "github.com/BBVA/kapow/internal/server/model"
29 | "github.com/BBVA/kapow/internal/server/user/spawn"
30 | )
31 |
32 | var spawner = spawn.Spawn
33 | var idGenerator = uuid.NewUUID
34 |
35 | func handlerBuilder(route model.Route) http.Handler {
36 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
37 | id, err := idGenerator()
38 | if err != nil {
39 | w.WriteHeader(http.StatusInternalServerError)
40 | return
41 | }
42 |
43 | h := &model.Handler{
44 | ID: id.String(),
45 | Route: route,
46 | Request: r,
47 | Writer: w,
48 | Status: 200,
49 | }
50 |
51 | data.Handlers.Add(h)
52 | defer data.Handlers.Remove(h.ID)
53 |
54 | if route.Debug {
55 | var stdOutR, stdOutW *os.File
56 | stdOutR, stdOutW, err = os.Pipe()
57 | defer stdOutW.Close()
58 | if err != nil {
59 | logger.L.Println(err)
60 | return
61 | }
62 | var stdErrR, stdErrW *os.File
63 | stdErrR, stdErrW, err = os.Pipe()
64 | defer stdErrW.Close()
65 | if err != nil {
66 | logger.L.Println(err)
67 | return
68 | }
69 |
70 | go logStream(h.ID, "stdout", stdOutR)
71 | go logStream(h.ID, "stderr", stdErrR)
72 |
73 | err = spawner(h, stdOutW, stdErrW)
74 | } else {
75 | err = spawner(h, nil, nil)
76 | }
77 |
78 | // In case of the user not setting /request/body
79 | if !h.BodyOut {
80 | if h.Status != 0 {
81 | h.Writer.WriteHeader(h.Status)
82 | }
83 | h.BodyOut = true
84 | }
85 |
86 | if err != nil {
87 | logger.L.Println(err)
88 | }
89 |
90 | if r != nil {
91 | logger.LogAccess(
92 | r.RemoteAddr,
93 | h.ID,
94 | "-",
95 | r.Method,
96 | r.RequestURI,
97 | r.Proto,
98 | h.Status,
99 | h.SentBytes,
100 | r.Header.Get("Referer"),
101 | r.Header.Get("User-Agent"),
102 | )
103 | }
104 | })
105 | }
106 |
107 | func logStream(handlerId string, streamName string, stream *os.File) {
108 | defer stream.Close()
109 | scanner := bufio.NewScanner(stream)
110 | for scanner.Scan() {
111 | logger.L.Printf("%s %s: %s", handlerId, streamName, scanner.Text())
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/internal/server/user/mux/mux.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Banco Bilbao Vizcaya Argentaria, S.A.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package mux
18 |
19 | import (
20 | "net/http"
21 | "sync"
22 |
23 | "github.com/gorilla/mux"
24 |
25 | "github.com/BBVA/kapow/internal/server/model"
26 | )
27 |
28 | type SwappableMux struct {
29 | m sync.RWMutex
30 | root *mux.Router
31 | }
32 |
33 | func New() *SwappableMux {
34 | return &SwappableMux{
35 | root: gorillize([]model.Route{}, handlerBuilder),
36 | }
37 | }
38 |
39 | func (sm *SwappableMux) get() *mux.Router {
40 | sm.m.RLock()
41 | defer sm.m.RUnlock()
42 |
43 | return sm.root
44 | }
45 |
46 | func (sm *SwappableMux) set(mux *mux.Router) {
47 | sm.m.Lock()
48 | sm.root = mux
49 | sm.m.Unlock()
50 | }
51 |
52 | func (sm *SwappableMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
53 | sm.get().ServeHTTP(w, r)
54 | }
55 |
56 | func (sm *SwappableMux) Update(rs []model.Route) {
57 | sm.set(gorillize(rs, handlerBuilder))
58 | }
59 |
--------------------------------------------------------------------------------
/internal/server/user/server.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Banco Bilbao Vizcaya Argentaria, S.A.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package user
18 |
19 | import (
20 | "crypto/tls"
21 | "crypto/x509"
22 | "fmt"
23 | "net"
24 | "net/http"
25 | "os"
26 | "sync"
27 |
28 | "github.com/BBVA/kapow/internal/logger"
29 | "github.com/BBVA/kapow/internal/server/user/mux"
30 | )
31 |
32 | // Server is a singleton that stores the http.Server for the user package
33 | var Server = http.Server{
34 | Handler: mux.New(),
35 | }
36 |
37 | var DebugEndpoints bool
38 |
39 | // Run finishes configuring Server and runs ListenAndServe on it
40 | func Run(bindAddr string, wg *sync.WaitGroup, certFile, keyFile, cliCaFile string, cliAuth, debug bool) {
41 |
42 | Server = http.Server{
43 | Addr: bindAddr,
44 | Handler: mux.New(),
45 | }
46 |
47 | if debug {
48 | Routes.SetDebug()
49 | }
50 |
51 | listener, err := net.Listen("tcp", bindAddr)
52 | if err != nil {
53 | logger.L.Fatal(err)
54 | }
55 |
56 | if (certFile != "") && (keyFile != "") {
57 | if cliAuth {
58 | if Server.TLSConfig == nil {
59 | Server.TLSConfig = &tls.Config{}
60 | }
61 |
62 | var err error
63 | Server.TLSConfig.ClientCAs, err = loadCertificatesFromFile(cliCaFile)
64 | if err != nil {
65 | logger.L.Fatalf("UserServer failed to load CA certs: %s\n", err)
66 | } else {
67 | CAStore := "System store"
68 | if Server.TLSConfig.ClientCAs != nil {
69 | CAStore = cliCaFile
70 | }
71 | logger.L.Printf("UserServer using CA certs from %s\n", CAStore)
72 | Server.TLSConfig.ClientAuth = tls.RequireAndVerifyClientCert
73 | }
74 | }
75 |
76 | // Signal startup
77 | logger.L.Printf("UserServer listening at %s\n", bindAddr)
78 | wg.Done()
79 |
80 | logger.L.Fatal(Server.ServeTLS(listener, certFile, keyFile))
81 | } else {
82 | // Signal startup
83 | logger.L.Printf("UserServer listening at %s\n", bindAddr)
84 | wg.Done()
85 |
86 | logger.L.Fatal(Server.Serve(listener))
87 | }
88 | }
89 |
90 | func loadCertificatesFromFile(certFile string) (pool *x509.CertPool, err error) {
91 | if certFile != "" {
92 | var caCerts []byte
93 | caCerts, err = os.ReadFile(certFile)
94 | if err == nil {
95 | pool = x509.NewCertPool()
96 | if !pool.AppendCertsFromPEM(caCerts) {
97 | err = fmt.Errorf("Invalid certificate file %s", certFile)
98 | }
99 | }
100 | }
101 |
102 | return
103 | }
104 |
--------------------------------------------------------------------------------
/internal/server/user/spawn/spawn.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Banco Bilbao Vizcaya Argentaria, S.A.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package spawn
18 |
19 | import (
20 | "errors"
21 | "io"
22 | "os"
23 | "os/exec"
24 |
25 | "github.com/google/shlex"
26 |
27 | "github.com/BBVA/kapow/internal/server/model"
28 | )
29 |
30 | func Spawn(h *model.Handler, stdout io.Writer, stderr io.Writer) error {
31 | if h.Route.Entrypoint == "" {
32 | return errors.New("Entrypoint cannot be empty")
33 | }
34 | args, err := shlex.Split(h.Route.Entrypoint)
35 | if err != nil {
36 | return err
37 | }
38 |
39 | if h.Route.Command != "" {
40 | args = append(args, h.Route.Command)
41 | }
42 |
43 | cmd := exec.Command(args[0], args[1:]...)
44 | if stdout != nil {
45 | cmd.Stdout = stdout
46 | }
47 | if stderr != nil {
48 | cmd.Stderr = stderr
49 | }
50 | cmd.Env = os.Environ()
51 | cmd.Env = append(cmd.Env, "KAPOW_HANDLER_ID="+h.ID)
52 |
53 | err = cmd.Run()
54 |
55 | return err
56 | }
57 |
--------------------------------------------------------------------------------
/internal/server/user/state.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Banco Bilbao Vizcaya Argentaria, S.A.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package user
18 |
19 | import (
20 | "errors"
21 | "sync"
22 |
23 | "github.com/BBVA/kapow/internal/server/model"
24 | "github.com/BBVA/kapow/internal/server/user/mux"
25 | )
26 |
27 | type safeRouteList struct {
28 | rs []model.Route
29 | m *sync.RWMutex
30 | globalDebug bool
31 | }
32 |
33 | var Routes safeRouteList = New()
34 |
35 | func New() safeRouteList {
36 | return safeRouteList{
37 | rs: []model.Route{},
38 | m: &sync.RWMutex{},
39 | }
40 | }
41 |
42 | func (srl *safeRouteList) SetDebug() {
43 | srl.globalDebug = true
44 | }
45 |
46 | func (srl *safeRouteList) Append(r model.Route) model.Route {
47 | srl.m.Lock()
48 | r.Index = len(srl.rs)
49 | r.Debug = srl.globalDebug || r.Debug
50 | srl.rs = append(srl.rs, r)
51 | srl.m.Unlock()
52 |
53 | Server.Handler.(*mux.SwappableMux).Update(srl.Snapshot())
54 |
55 | return r
56 | }
57 |
58 | func (srl *safeRouteList) Snapshot() []model.Route {
59 | srl.m.RLock()
60 | defer srl.m.RUnlock()
61 |
62 | rs := make([]model.Route, len(srl.rs))
63 | copy(rs, srl.rs)
64 | return rs
65 | }
66 |
67 | func (srl *safeRouteList) List() []model.Route {
68 | rs := srl.Snapshot()
69 | for i := 0; i < len(rs); i++ {
70 | rs[i].Index = i
71 | }
72 | return rs
73 | }
74 |
75 | func (srl *safeRouteList) Delete(ID string) error {
76 | // TODO: Refactor with `defer` if applicable
77 | srl.m.Lock()
78 | for i := 0; i < len(srl.rs); i++ {
79 | if srl.rs[i].ID == ID {
80 | srl.rs = append(srl.rs[:i], srl.rs[i+1:]...)
81 | srl.m.Unlock()
82 | Server.Handler.(*mux.SwappableMux).Update(srl.Snapshot())
83 | return nil
84 |
85 | }
86 | }
87 | srl.m.Unlock()
88 | return errors.New("Route not found")
89 | }
90 |
91 | func (srl *safeRouteList) Get(ID string) (r model.Route, err error) {
92 | srl.m.RLock()
93 | defer srl.m.RUnlock()
94 | for _, r = range srl.rs {
95 | if r.ID == ID {
96 | return
97 | }
98 | }
99 |
100 | err = errors.New("Route not found")
101 | return
102 | }
103 |
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Banco Bilbao Vizcaya Argentaria, S.A.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package main
18 |
19 | import (
20 | "github.com/spf13/cobra"
21 |
22 | "github.com/BBVA/kapow/internal/cmd"
23 | "github.com/BBVA/kapow/internal/logger"
24 | )
25 |
26 | func main() {
27 | var kapowCmd = &cobra.Command{Use: "kapow [action]"}
28 |
29 | kapowCmd.AddCommand(cmd.ServerCmd)
30 | kapowCmd.AddCommand(cmd.GetCmd)
31 | kapowCmd.AddCommand(cmd.SetCmd)
32 | kapowCmd.AddCommand(cmd.RouteCmd)
33 |
34 | err := kapowCmd.Execute()
35 | if err != nil {
36 | logger.L.Fatalln(err)
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/release-key.gpg:
--------------------------------------------------------------------------------
1 | -----BEGIN PGP PUBLIC KEY BLOCK-----
2 |
3 | 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
64 | 9r7Wg1ayT+mq8kzKV0xf61j6aVIGX+4HUiNE
65 | =/glm
66 | -----END PGP PUBLIC KEY BLOCK-----
67 |
--------------------------------------------------------------------------------
/spec/test/.envrc:
--------------------------------------------------------------------------------
1 | use_nix
2 |
--------------------------------------------------------------------------------
/spec/test/.gherkin-lintrc:
--------------------------------------------------------------------------------
1 | {
2 | "no-empty-file": "off",
3 | "no-files-without-scenarios" : "off",
4 | "no-unnamed-features": "off",
5 |
6 | "no-unnamed-scenarios": "on",
7 | "no-dupe-scenario-names": ["on", "in-feature"],
8 | "no-dupe-feature-names": "on",
9 | "no-partially-commented-tag-lines": "on",
10 | "indentation": [
11 | "on", {
12 | "Scenario": 2,
13 | "Background": 2,
14 | "given": 4,
15 | "when": 4,
16 | "then": 4,
17 | "and": 6,
18 | "but": 6,
19 | "example": 6,
20 | "Examples": 4,
21 | "scenario tag": 2}
22 | ],
23 | "no-trailing-spaces": "on",
24 | "new-line-at-eof": ["on", "yes"],
25 | "no-multiple-empty-lines": "on",
26 | "no-scenario-outlines-without-examples": "on",
27 | "name-length": ["on", {"Feature": 80, "Step": 120, "Scenario": 80}],
28 | "no-restricted-tags": ["on", {"tags": ["@watch", "@wip"]}],
29 | "use-and": "on",
30 | "no-duplicate-tags": "on",
31 | "no-superfluous-tags": "on",
32 | "no-homogenous-tags": "on",
33 | "one-space-between-tags": "on",
34 | "no-unused-variables": "on",
35 | "no-background-only-scenario": "on",
36 | "no-empty-background": "on",
37 | "scenario-size": ["on", { "steps-length": {"Background": 15, "Scenario": 15}}]
38 | }
39 |
--------------------------------------------------------------------------------
/spec/test/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM nixos/nix:2.3.6
2 |
3 | # Install CircleCI requirements for base images
4 | # https://circleci.com/docs/2.0/custom-images/
5 | # RUN apk upgrade --update-cache \
6 | # && apk add git openssh-server tar gzip ca-certificates
7 |
8 | # Install Kapow! Spec Test Suite
9 | RUN mkdir -p /usr/src/ksts
10 | WORKDIR /usr/src/ksts
11 | COPY features /usr/src/ksts/features
12 | # COPY Pipfile Pipfile.lock /usr/src/ksts/
13 | # RUN pip install --upgrade pip \
14 | # && pip install pipenv \
15 | # && pipenv install --deploy --system \
16 | # && rm -f Pipfile Pipfile.lock
17 | COPY ./*.nix ./
18 | ENTRYPOINT [ "nix-shell", "--command" ]
19 |
--------------------------------------------------------------------------------
/spec/test/Makefile:
--------------------------------------------------------------------------------
1 | .PHONY: all lint wip test fix catalog
2 |
3 | all: checkbin test
4 |
5 | lint:
6 | gherkin-lint
7 | wip:
8 | KAPOW_DEBUG_TESTS=1 behave --stop --wip -k
9 | test: lint
10 | behave --no-capture --tags=~@skip
11 | fix: lint
12 | KAPOW_DEBUG_TESTS=1 behave --stop --no-capture --tags=~@skip
13 | catalog:
14 | behave --format steps.usage --dry-run --no-summary -q
15 | checkbin:
16 | @which kapow >/dev/null || (echo "ERROR: Your kapow binary is not present in PATH" && exit 1)
17 | testpoc:
18 | PATH=../../testutils/poc:$$PATH behave --no-capture --tags=~@skip
19 | wippoc:
20 | PATH=../../testutils/poc:$$PATH behave --no-capture --tags=@wip -k
21 |
--------------------------------------------------------------------------------
/spec/test/README.rst:
--------------------------------------------------------------------------------
1 | Kapow Spec Test Suite
2 | =====================
3 |
4 | This is a generic test suite to be run against ANY Kapow!
5 | implementation.
6 |
7 |
8 | Prerequisites
9 | -------------
10 |
11 | First of all you need a working ``kapow server`` installed to run the
12 | tests against it:
13 |
14 | Check that your installation is correct by typing this in a shell:
15 |
16 | .. code-block:: bash
17 |
18 | kapow --help
19 |
20 | You should see a help screen. If not fix it before continuing.
21 |
22 | You also need ``gherkin-lint``. Run:
23 |
24 | .. code-block:: bash
25 |
26 | npm install -g gherkin-lint
27 |
28 |
29 | How to run the test suite?
30 | ==========================
31 |
32 | With the above requisites in place and with your command shell in this
33 | very directory run:
34 |
35 | .. code-block:: bash
36 |
37 | make
38 |
39 | This should test the current installed kapow implementation and output
40 | something like:
41 |
42 | .. code-block:: none
43 |
44 | 13 features passed, 0 failed, 0 skipped
45 | 23 scenarios passed, 0 failed, 0 skipped
46 | 99 steps passed, 0 failed, 0 skipped, 0 undefined
47 | Took 0m23.553s
48 |
49 |
50 | Troubleshooting
51 | ---------------
52 |
53 | Environment customization
54 | ~~~~~~~~~~~~~~~~~~~~~~~~~
55 |
56 | You can customize some of the test behavior with the following
57 | environment variables:
58 |
59 | * ``KAPOW_BOOT_TIMEOUT``: Timeout in ms for the server to be ready
60 | * ``KAPOW_SERVER_CMD``: The full command line to start a non-interactive
61 | listening kapow server. By default: ``kapow server``
62 | * ``KAPOW_CONTROL_URL``: URL of the Control API. By default: ``http://localhost:8081``
63 | * ``KAPOW_DATA_URL``: URL of the Data API. By default: ``http://localhost:8082``
64 | * ``KAPOW_USER_URL``: URL of the User Server. By default: ``http://localhost:8080``
65 |
66 |
67 | Fixing tests one by one
68 | ~~~~~~~~~~~~~~~~~~~~~~~
69 |
70 | If you use ``make fix`` instead of ``make`` the first failing test will stop
71 | the execution, giving you a chance of inspecting the result. Also the
72 | tests will be in DEBUG mode displaying all requests made to the server.
73 |
74 |
75 | How to develop new tests?
76 | =========================
77 |
78 | Developing new steps
79 | --------------------
80 |
81 | First of all execute ``make catalog`` this will display all the existing
82 | steps and associates features. If none of the steps fits your needs
83 | write a new one in your feature and run ``make catalog`` again.
84 | The detected new step will trigger the output of a new section with a
85 | template for the step definition to be implemented (you can copy and
86 | paste it removing the ``u'`` unicode symbol of the strings).
87 |
88 |
89 | Developing new step definitions
90 | -------------------------------
91 |
92 | To make you life easier, mark the feature or scenario you are working on
93 | with the tag ``@wip`` and use ``make wip`` to run only your
94 | scenario/feature.
95 |
96 | 1. Paste the step definition template you just copied at the end of the
97 | file ``steps/steps.py``.
98 | 2. Run ``make wip`` to test that the step is triggered. You should see a
99 | ``NotImplementedError`` exception.
100 | 3. Implement your step.
101 | 4. Run ``make wip`` again.
102 |
103 | When you finish implementing your step definitions remove the ``@wip`` of
104 | your feature/scenario and run ``make`` to test everything together.
105 |
--------------------------------------------------------------------------------
/spec/test/features/control/append/error_malformed.feature:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright 2019 Banco Bilbao Vizcaya Argentaria, S.A.
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 | #
16 | Feature: Kapow! server reject append requests with malformed JSON bodies.
17 | Kapow! server will reject to append a route when
18 | it receives a malformed json document in the
19 | request body.
20 |
21 | Scenario: Error because a malformed JSON document.
22 | If a request comes with an invalid JSON document
23 | the server will respond with a bad request error.
24 |
25 | Given I have a running Kapow! server
26 | When I try to append with this malformed JSON document:
27 | """
28 | Hi! I am an invalid JSON document.
29 | """
30 | Then I get 400 as response code
31 | And the response header "Content-Type" contains "application/json"
32 | And I get the following response body:
33 | """
34 | {
35 | "reason": "Malformed JSON"
36 | }
37 | """
38 |
--------------------------------------------------------------------------------
/spec/test/features/control/append/error_unprocessable.feature:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright 2019 Banco Bilbao Vizcaya Argentaria, S.A.
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 | #
16 | Feature: Kapow! server rejects requests with semantic errors.
17 | Kapow! server will refuse to append routes when
18 | it receives a valid json document not conforming
19 | to the specification.
20 |
21 | Scenario: Error because lacking mandatory fields.
22 | If a request lacks any mandatory field the server
23 | responds with an error.
24 |
25 | Given I have a running Kapow! server
26 | When I append the route:
27 | """
28 | {
29 | "entrypoint": "/bin/sh -c",
30 | "command": "ls -la / | kapow set /response/body"
31 | }
32 | """
33 | Then I get 422 as response code
34 | And the response header "Content-Type" contains "application/json"
35 | And I get the following response body:
36 | """
37 | {
38 | "reason": "Invalid Route"
39 | }
40 | """
41 |
42 | Scenario: Error because bad route format.
43 | If a request contains an invalid expression in the
44 | field url_pattern the server responds with an error.
45 |
46 | Given I have a running Kapow! server
47 | When I append the route:
48 | """
49 | {
50 | "method": "GET",
51 | "url_pattern": "+123--",
52 | "entrypoint": "/bin/sh -c",
53 | "command": "ls -la / | kapow set /response/body"
54 | }
55 | """
56 | Then I get 422 as response code
57 | And the response header "Content-Type" contains "application/json"
58 | And I get the following response body:
59 | """
60 | {
61 | "reason": "Invalid Route"
62 | }
63 | """
64 |
--------------------------------------------------------------------------------
/spec/test/features/control/append/success.feature:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright 2019 Banco Bilbao Vizcaya Argentaria, S.A.
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 | #
16 | Feature: Append new routes in Kapow! server.
17 | Appending routes allows users to configure the server. New
18 | routes are added at the end of the list of existing routes.
19 |
20 | Scenario: Append the first route.
21 | A just started server or one with all routes removed,
22 | will create a new list of routes. The newly created rule
23 | will be at index 0.
24 |
25 | Given I have a just started Kapow! server
26 | When I append the route:
27 | """
28 | {
29 | "method": "GET",
30 | "url_pattern": "/foo",
31 | "entrypoint": "/bin/sh -c",
32 | "command": "ls -la / | kapow set /response/body"
33 | }
34 | """
35 | Then I get 201 as response code
36 | And I get "Created" as response reason phrase
37 | And I get the following response body:
38 | """
39 | {
40 | "method": "GET",
41 | "url_pattern": "/foo",
42 | "entrypoint": "/bin/sh -c",
43 | "command": "ls -la / | kapow set /response/body",
44 | "index": 0,
45 | "id": ANY
46 | }
47 | """
48 |
49 | Scenario: Append another route.
50 | Appending routes on a non empty list will create new routes
51 | at the end of the list.
52 |
53 | Given I have a Kapow! server with the following routes:
54 | | method | url_pattern | entrypoint | command |
55 | | GET | /foo | /bin/sh -c | ls -la / \| kapow set /response/body |
56 | | GET | /qux/{dirname} | /bin/sh -c | ls -la /request/params/dirname \| kapow set /response/body |
57 | When I append the route:
58 | """
59 | {
60 | "method": "GET",
61 | "url_pattern": "/baz",
62 | "entrypoint": "/bin/sh -c",
63 | "command": "ls -la /etc | kapow set /response/body"
64 | }
65 | """
66 | Then I get 201 as response code
67 | And I get "Created" as response reason phrase
68 | And I get the following response body:
69 | """
70 | {
71 | "method": "GET",
72 | "url_pattern": "/baz",
73 | "entrypoint": "/bin/sh -c",
74 | "command": "ls -la /etc | kapow set /response/body",
75 | "index": 2,
76 | "id": ANY
77 | }
78 | """
79 |
--------------------------------------------------------------------------------
/spec/test/features/control/delete/error_notfound.feature:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright 2019 Banco Bilbao Vizcaya Argentaria, S.A.
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 | #
16 | Feature: Fail to delete a route in Kapow! server.
17 | When trying to delete a route that not exists in the server
18 | the server respons with an error.
19 |
20 | Scenario: Delete a nonexistent route.
21 | A request of removing a nonexistent route
22 | will trigger a not found error.
23 |
24 | Given I have a just started Kapow! server
25 | When I delete the route with id "xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx"
26 | Then I get 404 as response code
27 | And the response header "Content-Type" contains "application/json"
28 | And I get the following response body:
29 | """
30 | {
31 | "reason": "Route Not Found"
32 | }
33 | """
34 |
--------------------------------------------------------------------------------
/spec/test/features/control/delete/success.feature:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright 2019 Banco Bilbao Vizcaya Argentaria, S.A.
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 | #
16 | Feature: Delete routes in Kapow! server.
17 | Deleting routes allows users to remove undesired
18 | routes from the server.
19 |
20 | Scenario: Delete a route.
21 | Routes are removed from the sever by specifying their id.
22 |
23 | Given I have a Kapow! server with the following routes:
24 | | method | url_pattern | entrypoint | command |
25 | | GET | /foo | /bin/sh -c | ls -la / \| kapow set /response/body |
26 | | GET | /qux/{dirname} | /bin/sh -c | ls -la /request/params/dirname \| kapow set /response/body |
27 | When I delete the first route
28 | Then I get 204 as response code
29 | And I get "No Content" as response reason phrase
30 |
--------------------------------------------------------------------------------
/spec/test/features/control/get/error_notfound.feature:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright 2019 Banco Bilbao Vizcaya Argentaria, S.A.
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 | #
16 | Feature: Fail to retrieve route details in Kapow! server.
17 | When trying to get route details for a route that
18 | does no exist the server responds with an error.
19 |
20 | Scenario: Try to get details for a nonexistent route.
21 | A request for retrieving details for a nonexistent
22 | route will trigger a not found error.
23 |
24 | Given I have a just started Kapow! server
25 | When I get the route with id "xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx"
26 | Then I get 404 as response code
27 | And the response header "Content-Type" contains "application/json"
28 | And I get the following response body:
29 | """
30 | {
31 | "reason": "Route Not Found"
32 | }
33 | """
34 |
--------------------------------------------------------------------------------
/spec/test/features/control/get/success.feature:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright 2019 Banco Bilbao Vizcaya Argentaria, S.A.
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 | #
16 | Feature: Retrieve route details in Kapow! server.
17 | Users can retrieve route details from the server
18 | by specifying its id.
19 |
20 | Scenario: Retrieve route details.
21 | Get route details by id.
22 |
23 | Given I have a Kapow! server with the following routes:
24 | | method | url_pattern | entrypoint | command |
25 | | GET | /foo | /bin/sh -c | ls -la / \| kapow set /response/body |
26 | | GET | /qux/{dirname} | /bin/sh -c | ls -la /request/params/dirname \| kapow set /response/body |
27 | When I get the first route
28 | Then I get 200 as response code
29 | And I get "OK" as response reason phrase
30 | And I get the following response body:
31 | """
32 | {
33 | "method": "GET",
34 | "url_pattern": "/foo",
35 | "entrypoint": "/bin/sh -c",
36 | "command": "ls -la / | kapow set /response/body",
37 | "index": 0,
38 | "id": ANY
39 | }
40 | """
41 |
--------------------------------------------------------------------------------
/spec/test/features/control/insert/error_malformed.feature:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright 2019 Banco Bilbao Vizcaya Argentaria, S.A.
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 | #
16 | @skip
17 | Feature: Kapow! server rejects insertion requests with malformed JSON bodies.
18 | Kapow! server will reject to insert a route when
19 | it receives a malformed JSON document in the
20 | request body.
21 |
22 | Scenario: Error because of malformed JSON document.
23 | If a request comes with an invalid JSON document
24 | the server will respond with a bad request error.
25 |
26 | Given I have a running Kapow! server
27 | When I try to insert with this JSON document:
28 | """
29 | {
30 | "method" "GET",
31 | "url_pattern": /hello,
32 | "entrypoint": null
33 | "command": "echo Hello
34 | World | kapow set /response/body",
35 | "index": 0,
36 | "id": "xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx"
37 | }
38 | """
39 | Then I get 400 as response code
40 | And the response header "Content-Type" contains "application/json"
41 | And I get the following response body:
42 | """
43 | {
44 | "reason": "Malformed JSON"
45 | }
46 | """
47 |
--------------------------------------------------------------------------------
/spec/test/features/control/insert/error_unprocessable.feature:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright 2019 Banco Bilbao Vizcaya Argentaria, S.A.
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 | #
16 | @skip
17 | Feature: Kapow! server rejects insertion requests with semantic errors.
18 | Kapow! server will refuse to insert routes when
19 | it receives a valid JSON but not conforming document.
20 |
21 | Scenario: Error because lacking mandatory fields.
22 | If a request lacks any mandatory fields the server
23 | responds with an error.
24 |
25 | Given I have a running Kapow! server
26 | When I insert the route:
27 | """
28 | {
29 | "entrypoint": "/bin/sh -c",
30 | "command": "ls -la / | kapow set /response/body"
31 | }
32 | """
33 | Then I get 422 as response code
34 | And I get "Invalid Route" as response reason phrase
35 |
36 | Scenario: Error because wrong route specification.
37 | If a request contains an invalid expression in the
38 | url_pattern field the server responds with an error.
39 |
40 | Given I have a running Kapow! server
41 | When I insert the route:
42 | """
43 | {
44 | "method": "GET",
45 | "url_pattern": "+123--",
46 | "entrypoint": "/bin/sh -c",
47 | "command": "ls -la / | kapow set /response/body",
48 | "index": 0
49 | }
50 | """
51 | Then I get 422 as response code
52 | And I get "Invalid Route" as response reason phrase
53 |
54 | Scenario: Error because negative index specified.
55 | If a request contains a negative number in the
56 | index field the server responds with an error.
57 |
58 | Given I have a running Kapow! server
59 | When I insert the route:
60 | """
61 | {
62 | "method": "GET",
63 | "url_pattern": "+123--",
64 | "entrypoint": "/bin/sh -c",
65 | "command": "ls -la / | kapow set /response/body",
66 | "index": -1
67 | }
68 | """
69 | Then I get 422 as response code
70 | And the response header "Content-Type" contains "application/json"
71 | And I get the following response body:
72 | """
73 | {
74 | "reason": "Invalid Route"
75 | }
76 | """
77 |
--------------------------------------------------------------------------------
/spec/test/features/control/insert/success.feature:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright 2019 Banco Bilbao Vizcaya Argentaria, S.A.
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 | #
16 | @skip
17 | Feature: Insert new routes in Kapow! server.
18 | Inserting routes allows users to configure the server. New
19 | routes could be inserted at the beginning or before any
20 | existing route of the routes list.
21 |
22 | Background:
23 | Given I have a Kapow! server with the following routes:
24 | | method | url_pattern | entrypoint | command |
25 | | GET | /foo | /bin/sh -c | ls -la / \| kapow set /response/body |
26 | | GET | /qux/{dirname} | /bin/sh -c | ls -la /request/params/dirname \| kapow set /response/body |
27 |
28 | Scenario: Insert a route at the beginning.
29 | A route can be inserted at the beginning of the list
30 | by specifying an index 0 in the request.
31 |
32 | When I insert the route:
33 | """
34 | {
35 | "method": "GET",
36 | "url_pattern": "/bar",
37 | "entrypoint": "/bin/sh -c",
38 | "command": "ls -la /var | kapow set /response/body",
39 | "index": 0
40 | }
41 | """
42 | Then I get 201 as response code
43 | And I get "Created" as response reason phrase
44 | And I get the following response body:
45 | """
46 | {
47 | "method": "GET",
48 | "url_pattern": "/bar",
49 | "entrypoint": "/bin/sh -c",
50 | "command": "ls -la /var | kapow set /response/body",
51 | "index": 0,
52 | "id": ANY
53 | }
54 | """
55 |
56 | Scenario: Insert a route in the middle.
57 | A route can be inserted in the middle of the list
58 | by specifying an index less or equal to the last
59 | index in the request.
60 |
61 | When I insert the route:
62 | """
63 | {
64 | "method": "GET",
65 | "url_pattern": "/bar",
66 | "entrypoint": "/bin/sh -c",
67 | "command": "ls -la /var | kapow set /response/body",
68 | "index": 1
69 | }
70 | """
71 | Then I get 201 as response code
72 | And I get "Created" as response reason phrase
73 | And I get the following response body:
74 | """
75 | {
76 | "method": "GET",
77 | "url_pattern": "/bar",
78 | "entrypoint": "/bin/sh -c",
79 | "command": "ls -la /var | kapow set /response/body",
80 | "index": 1,
81 | "id": ANY
82 | }
83 | """
84 |
--------------------------------------------------------------------------------
/spec/test/features/control/list/success.feature:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright 2019 Banco Bilbao Vizcaya Argentaria, S.A.
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 | #
16 | @server
17 | Feature: Listing routes in a Kapow! server.
18 | Listing routes allows users to know what URLs are
19 | available on a Kapow! server. The List endpoint returns
20 | a list of the routes the server has configured.
21 |
22 | Scenario: List routes on a fresh started server.
23 | A just started or with all routes removed Kapow! server,
24 | will show an empty list of routes.
25 |
26 | Given I have a just started Kapow! server
27 | When I request a routes listing
28 | Then I get 200 as response code
29 | And I get "OK" as response reason phrase
30 | And I get the following response body:
31 | """
32 | []
33 | """
34 |
35 | Scenario: List routes on a server with routes loaded.
36 | After some route creation/insertion operations the server
37 | must return an ordered list of routes stored.
38 |
39 | Given I have a Kapow! server with the following routes:
40 | | method | url_pattern | entrypoint | command |
41 | | GET | /foo | /bin/sh -c | ls -la / \| kapow set /response/body |
42 | | GET | /qux/{dirname} | /bin/sh -c | ls -la /request/params/dirname \| kapow set /response/body |
43 | When I request a routes listing
44 | Then I get 200 as response code
45 | And I get "OK" as response reason phrase
46 | And I get the following response body:
47 | """
48 | [
49 | {
50 | "method": "GET",
51 | "url_pattern": "/foo",
52 | "entrypoint": "/bin/sh -c",
53 | "command": "ls -la / | kapow set /response/body",
54 | "index": 0,
55 | "id": ANY
56 | },
57 | {
58 | "method": "GET",
59 | "url_pattern": "/qux/{dirname}",
60 | "entrypoint": "/bin/sh -c",
61 | "command": "ls -la /request/params/dirname | kapow set /response/body",
62 | "index": 1,
63 | "id": ANY
64 | }
65 | ]
66 | """
67 |
--------------------------------------------------------------------------------
/spec/test/features/control/mtls.feature:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright 2021 Banco Bilbao Vizcaya Argentaria, S.A.
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 | #
16 |
17 | Feature: Communications with the control interface are secured with mTLS.
18 | Trust is anchored via certificate pinning.
19 | The Kapow! server only allows connections from trusted clients.
20 | The Kapow! clients only establish connections to trusted servers.
21 |
22 | @server
23 | Scenario: Reject clients not providing a certificate.
24 |
25 | Given I have a running Kapow! server
26 | When I try to connect to the control API without providing a certificate
27 | Then I get a connection error
28 |
29 | @server
30 | Scenario: Reject clients providing an invalid certificate.
31 |
32 | Given I have a running Kapow! server
33 | When I try to connect to the control API providing an invalid certificate
34 | Then I get a connection error
35 |
36 | @client
37 | Scenario: Connect to servers providing a valid certificate.
38 | A valid certificate is the one provided via envvars.
39 |
40 | Given a test HTTPS server on the control port
41 | When I run the following command
42 | """
43 | $ kapow route list
44 | """
45 | And the HTTPS server receives a "GET" request to "/routes"
46 | And the server responds with
47 | | field | value |
48 | | status | 200 |
49 | | headers.Content-Type | application/json |
50 | | body | [] |
51 | Then the command exits with "0"
52 |
53 | @client
54 | Scenario: Reject servers providing an invalid certificate.
55 |
56 | Given a test HTTPS server on the control port
57 | When I run the following command (with invalid certs)
58 | """
59 | $ kapow route list
60 | """
61 | Then the command exits immediately with "1"
62 |
63 | @server
64 | Scenario Outline: The control server is accessible through an alternative address
65 | The automatically generated certificated contains the Alternate Name
66 | provided via the `--control-reachable-addr` parameter.
67 |
68 | Given I launch the server with the following extra arguments
69 | """
70 | --control-reachable-addr ""
71 | """
72 | When I inspect the automatically generated control server certificate
73 | Then the extension "Subject Alternative Name" contains "" of type ""
74 |
75 | Examples:
76 | | reachable_addr | value | type |
77 | | localhost:8081 | localhost | DNSName |
78 | | 127.0.0.1:8081 | 127.0.0.1 | IPAddress |
79 | | foo.bar:8081 | foo.bar | DNSName |
80 | | 4.2.2.4:8081 | 4.2.2.4 | IPAddress |
81 | | [2600::]:8081 | 2600:: | IPAddress |
82 |
83 |
84 | @e2e
85 | Scenario: Control server dialog using mTLS
86 | If the user provides the corresponding certificates to the
87 | `kapow route` subcommand, the communication should be possible.
88 |
89 | Given I have a just started Kapow! server
90 | When I run the following command (setting the control certs environment variables)
91 | """
92 | $ kapow route list
93 |
94 | """
95 | Then the command exits with "0"
96 |
--------------------------------------------------------------------------------
/spec/test/features/data/handler/error_handleridnotfound.feature:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright 2019 Banco Bilbao Vizcaya Argentaria, S.A.
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 | #
16 | Feature: Fail to retrieve resources from nonexistent handler in Kapow! server.
17 | If trying to access a nonexistent handler then the
18 | server responds with a not found error.
19 |
20 | Scenario: Try to get a valid resource path from a nonexistent handler.
21 | A request to retrieve a resource from a
22 | nonexistent handler will trigger
23 | a handler ID not found error.
24 |
25 | Given I have a running Kapow! server
26 | When I get the resource "/request/path" for the handler with id "XXXXXXXXXX"
27 | Then I get 404 as response code
28 | And the response header "Content-Type" contains "application/json"
29 | And I get the following response body:
30 | """
31 | {
32 | "reason": "Handler ID Not Found"
33 | }
34 | """
35 |
--------------------------------------------------------------------------------
/spec/test/features/data/handler/error_invalidresource.feature:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright 2019 Banco Bilbao Vizcaya Argentaria, S.A.
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 | #
16 | Feature: Fail to retrieve an invalid resource for a handler in Kapow! server.
17 | If trying to access an invalid resource for a handler
18 | then the server responds with an error.
19 |
20 | Scenario: Try to get an inexistent resource from a handler.
21 | A request to retrieve an invalid resource
22 | from a handler will trigger a invalid resource error.
23 |
24 | Given I have a Kapow! server with the following testing routes:
25 | | method | url_pattern |
26 | | GET | /foo |
27 | When I send a request to the testing route "/foo"
28 | And I get the resource "/invented/path"
29 | Then I get 400 as response code
30 | And the response header "Content-Type" contains "application/json"
31 | And I get the following response body:
32 | """
33 | {
34 | "reason": "Invalid Resource Path"
35 | }
36 | """
37 |
--------------------------------------------------------------------------------
/spec/test/features/data/handler/error_itemnotfound.feature:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright 2019 Banco Bilbao Vizcaya Argentaria, S.A.
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 | #
16 | Feature: Fail to retrieve nonexistent resource items in Kapow! server.
17 | If trying to access a nonexistent resource item
18 | then the server responds with a no content error.
19 |
20 | Scenario: Try to get a nonexistent resource item from a handler.
21 | A request to retrieve a nonexistent resource
22 | item from a handler will trigger a no content
23 | error.
24 |
25 | Given I have a Kapow! server with the following testing routes:
26 | | method | url_pattern |
27 | | GET | /foo |
28 | When I send a request to the testing route "/foo"
29 | And I get the resource "/request/params/meloinvento"
30 | Then I get 404 as response code
31 | And the response header "Content-Type" contains "application/json"
32 | And I get the following response body:
33 | """
34 | {
35 | "reason": "Resource Item Not Found"
36 | }
37 | """
38 |
--------------------------------------------------------------------------------
/spec/test/features/data/handler/success.feature:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright 2019 Banco Bilbao Vizcaya Argentaria, S.A.
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 | #
16 | Feature: Retrieve a resource from a handler in Kapow! server.
17 | Users can retrieve request handler resources
18 | from the server by specifying the handler id
19 | and the resource path.
20 |
21 | Scenario: Retrieve a resource.
22 | Get the "request/path" resource for the current
23 | request through the handler id.
24 |
25 | Given I have a Kapow! server with the following testing routes:
26 | | method | url_pattern |
27 | | GET | /foo |
28 | When I send a request to the testing route "/foo"
29 | And I get the resource "/request/path"
30 | Then I get 200 as response code
31 | And I get "OK" as response reason phrase
32 | And I get the following response raw body:
33 | """
34 | /foo
35 | """
36 |
37 | Scenario: Retrieve a resource item.
38 | Get the "request/params/name" item resource for
39 | the current request through the handler id.
40 |
41 | Given I have a Kapow! server with the following testing routes:
42 | | method | url_pattern |
43 | | GET | /foo |
44 | When I send a request to the testing route "/foo?name=bar"
45 | And I get the resource "/request/params/name"
46 | Then I get 200 as response code
47 | And I get "OK" as response reason phrase
48 | And I get the following response raw body:
49 | """
50 | bar
51 | """
52 |
--------------------------------------------------------------------------------
/spec/test/features/data/request/success.feature:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright 2019 Banco Bilbao Vizcaya Argentaria, S.A.
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 | #
16 | Feature: Retrieve request resources from a handler in Kapow! server.
17 | Users can retrieve request resources by
18 | specifying the handler id and the
19 | resource path.
20 |
21 | Scenario Outline: Retrieve different resources for the current request.
22 | Get the following resources for the
23 | current request through the current
24 | handler.
25 |
26 | Given I have a Kapow! server with the following testing routes:
27 | | method | url_pattern |
28 | | GET | /foo/{path} |
29 | When I send a request to the testing route "/foo/matchVal1" adding:
30 | | fieldType | name | value |
31 | | parameter | par1 | paramVal1 |
32 | | header | head1 | headVal1 |
33 | | cookie | cook1 | cookieVal1 |
34 | | body | | bodyVal1 |
35 | And I get the resource ""
36 | Then I get 200 as response code
37 | And I get "OK" as response reason phrase
38 | And I get the following response raw body:
39 | """
40 |
41 | """
42 |
43 | Examples:
44 | | resourcePath | value |
45 | | /request/method | GET |
46 | | /request/path | /foo/matchVal1 |
47 | | /request/host | localhost:8080 |
48 | | /request/matches/path | matchVal1 |
49 | | /request/params/par1 | paramVal1 |
50 | | /request/headers/head1 | headVal1 |
51 | | /request/cookies/cook1 | cookieVal1 |
52 | | /request/body | bodyVal1 |
53 |
--------------------------------------------------------------------------------
/spec/test/features/data/response/success.feature:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright 2019 Banco Bilbao Vizcaya Argentaria, S.A.
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 | #
16 | Feature: Setting values for handler response resources in Kapow! server.
17 | Users can set the values in the response
18 | resources by specifying the handler id
19 | and the resource path.
20 |
21 | Scenario: Set status code for the current response.
22 | Set the status code through the current
23 | handler.
24 |
25 | Given I have a Kapow! server with the following testing routes:
26 | | method | url_pattern |
27 | | GET | /foo |
28 | When I send a request to the testing route "/foo"
29 | And I set the resource "/response/status" with value "418"
30 | And I release the testing request
31 | Then I get 418 as response code in the testing request
32 |
33 | Scenario Outline: Set different resources for the current response.
34 | Set the following resources for the current
35 | response through the current handler.
36 |
37 | Given I have a Kapow! server with the following testing routes:
38 | | method | url_pattern |
39 | | GET | /foo |
40 | When I send a request to the testing route "/foo"
41 | And I set the resource "" with value ""
42 | And I release the testing request
43 | Then I get 200 as response code
44 | And I get "OK" as response reason phrase
45 | And I get the value "" for the response "" named "" in the testing request
46 |
47 | Examples:
48 | | resourcePath | value | fieldType | elementName |
49 | | /response/headers/head1 | headVal1 | header | head1 |
50 | | /response/cookies/cook1 | cookVal1 | cookie | cook1 |
51 | | /response/body | bodyValue1 | body | - |
52 | | /response/stream | bodyValue2 | body | - |
53 |
54 | Scenario: Overwrite a resource for the current response.
55 | Write twice a on a resource, such as a gzip middleware would require:
56 | kapow get /response/body | gzip -c | kapow set /response/body
57 | although for simplicity, we'll just try overwriting the status code.
58 |
59 | Given I have a Kapow! server with the following testing routes:
60 | | method | url_pattern |
61 | | GET | /foo |
62 | When I send a request to the testing route "/foo"
63 | And I set the resource "/response/status" with value "418"
64 | And I set the resource "/response/status" with value "200"
65 | And I release the testing request
66 | Then I get 200 as response code in the testing request
67 |
--------------------------------------------------------------------------------
/spec/test/features/environment.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright 2019 Banco Bilbao Vizcaya Argentaria, S.A.
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 | #
16 | import tempfile
17 | import os
18 | import signal
19 | from contextlib import suppress
20 |
21 | def tmpfifo():
22 | while True:
23 | fifo_path = tempfile.mktemp() # The usage mkfifo make this safe
24 | try:
25 | os.mkfifo(fifo_path)
26 | except OSError:
27 | # The file already exist
28 | pass
29 | else:
30 | break
31 |
32 | return fifo_path
33 |
34 |
35 | def before_scenario(context, scenario):
36 | context.handler_fifo_path = tmpfifo()
37 | context.init_script_fifo_path = tmpfifo()
38 |
39 |
40 | def after_scenario(context, scenario):
41 | # Real Kapow! server being tested
42 | if hasattr(context, 'server'):
43 | context.server.terminate()
44 | context.server.wait()
45 |
46 | os.unlink(context.handler_fifo_path)
47 | os.unlink(context.init_script_fifo_path)
48 |
49 | # Mock HTTP server for testing
50 | if hasattr(context, 'httpserver'):
51 | context.response_ready.set()
52 | context.httpserver.shutdown()
53 | context.httpserver_thread.join()
54 |
55 | if getattr(context, 'testing_handler_pid', None) is not None:
56 | with suppress(ProcessLookupError):
57 | os.kill(int(context.testing_handler_pid), signal.SIGTERM)
58 |
--------------------------------------------------------------------------------
/spec/test/features/steps/comparedict.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright 2019 Banco Bilbao Vizcaya Argentaria, S.A.
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 | #
16 | from functools import singledispatch
17 | from itertools import zip_longest
18 | from jsonexample import ANY
19 |
20 |
21 | def assert_same_type(f):
22 | def wrapper(a, b):
23 | if type(a) != type(b):
24 | raise TypeError(f"Non-matching types {a!r} != {b!r}")
25 | return f(a, b)
26 | return wrapper
27 |
28 |
29 | @singledispatch
30 | @assert_same_type
31 | def is_subset(model, obj):
32 | if model == obj:
33 | return True
34 | else:
35 | raise ValueError(f"Non-matching values {model!r} != {obj!r}")
36 |
37 |
38 | @is_subset.register(dict)
39 | @assert_same_type
40 | def _(model, obj):
41 | for key, value in model.items():
42 | if key not in obj or not is_subset(value, obj[key]):
43 | raise ValueError(f"Non-matching dicts {model!r} != {obj!r}")
44 | return True
45 |
46 |
47 | @is_subset.register(list)
48 | @assert_same_type
49 | def _(model, obj):
50 | for a, b in zip_longest(model, obj):
51 | if not is_subset(a, b):
52 | raise ValueError(f"Non-matching list member {a!r} in {b!r}")
53 | return True
54 |
55 |
56 | @is_subset.register(ANY)
57 | def _(model, obj):
58 | return True
59 |
--------------------------------------------------------------------------------
/spec/test/features/steps/get_environment.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | import json
4 | import os
5 | import sys
6 |
7 | if __name__ == '__main__':
8 | with open(os.environ['SPECTEST_FIFO'], 'w') as fifo:
9 | json.dump(dict(os.environ), fifo)
10 |
--------------------------------------------------------------------------------
/spec/test/features/steps/jsonexample.py:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright 2019 Banco Bilbao Vizcaya Argentaria, S.A.
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 | #
16 | import json
17 | from functools import partial
18 | import re
19 |
20 |
21 | class ANY:
22 | pass
23 |
24 |
25 | class ExampleDecoder(json.JSONDecoder):
26 | def __init__(self, *args, **kwargs):
27 | super().__init__(*args, object_hook=self.object_hook, **kwargs)
28 |
29 | def decode(self, s, *args, **kwargs):
30 | s = re.sub(r'(\W)(ANY)(\W)', r'\1{"_type": "ANY"}\3', s)
31 | return super().decode(s, *args, **kwargs)
32 |
33 | def object_hook(self, dct):
34 | if dct.get('_type', None) == 'ANY':
35 | return ANY()
36 | else:
37 | return dct
38 |
39 | loads = partial(json.loads, cls=ExampleDecoder)
40 |
--------------------------------------------------------------------------------
/spec/test/features/steps/testinghandler.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | #
4 | # Copyright 2019 Banco Bilbao Vizcaya Argentaria, S.A.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # http://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | import signal
20 | import sys
21 | import os
22 |
23 |
24 | if __name__ == '__main__':
25 | test_runner_fifo = sys.argv[1]
26 | with open(test_runner_fifo, 'w') as other_side:
27 | other_side.write(f"{os.getpid()};{os.environ['KAPOW_HANDLER_ID']}\n")
28 |
29 | signal.pause()
30 |
--------------------------------------------------------------------------------
/spec/test/kapow/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM kapow-spec-test
2 | ADD https://github.com/BBVA/kapow/releases/download/v0.5.4/kapow_0.5.4_linux_amd64 /usr/bin/kapow
3 | RUN chmod 755 /usr/bin/kapow
4 |
--------------------------------------------------------------------------------
/spec/test/node-dependencies.nix:
--------------------------------------------------------------------------------
1 | # This file has been generated by node2nix 1.8.0. Do not edit!
2 |
3 | {pkgs ? import {
4 | inherit system;
5 | }, system ? builtins.currentSystem, nodejs ? pkgs."nodejs-12_x"}:
6 |
7 | let
8 | nodeEnv = import ./node-env.nix {
9 | inherit (pkgs) stdenv python2 utillinux runCommand writeTextFile;
10 | inherit nodejs;
11 | libtool = if pkgs.stdenv.isDarwin then pkgs.darwin.cctools else null;
12 | };
13 | in
14 | import ./node-packages.nix {
15 | inherit (pkgs) fetchurl fetchgit;
16 | inherit nodeEnv;
17 | }
--------------------------------------------------------------------------------
/spec/test/node-packages.json:
--------------------------------------------------------------------------------
1 | [
2 | "gherkin-lint"
3 | ]
4 |
--------------------------------------------------------------------------------
/spec/test/shell.nix:
--------------------------------------------------------------------------------
1 | { pkgs ? import (builtins.fetchTarball {
2 | name = "nixos-20.09-2021-01-15";
3 | url = "https://github.com/nixos/nixpkgs/archive/cd63096d6d887d689543a0b97743d28995bc9bc3.tar.gz";
4 | sha256 = "1wg61h4gndm3vcprdcg7rc4s1v3jkm5xd7lw8r2f67w502y94gcy";
5 | }) {} }:
6 | let
7 | environconfig = pkgs.python38Packages.buildPythonPackage rec {
8 | pname = "environconfig";
9 | version = "1.7.0";
10 |
11 | src = pkgs.python38Packages.fetchPypi {
12 | inherit pname version;
13 | sha256 = "087amqnqsx7d816adszd1424kma1kx9lfnzffr140wvy7a50vi86";
14 | };
15 | meta = {
16 | homepage = "https://github.com/buguroo/environconfig";
17 | description = "Environment variables made easy";
18 | };
19 | };
20 |
21 | pythonDependencies = [
22 | pkgs.python38Packages.behave
23 | pkgs.python38Packages.requests
24 | environconfig
25 | ];
26 |
27 | nodeDependencies = (pkgs.callPackage ./node-dependencies.nix {});
28 | in
29 | pkgs.mkShell {
30 | buildInputs = [
31 | pkgs.python38
32 | pythonDependencies
33 | pkgs.gnumake
34 | pkgs.which
35 | nodeDependencies.gherkin-lint
36 | ];
37 | }
38 |
--------------------------------------------------------------------------------
/test/runwindowsrun.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "os"
6 | "os/exec"
7 | )
8 |
9 | func main() {
10 | cmd := exec.Command("rundll32.exe", "url.dll,FileProtocolHandler", os.Args[1])
11 | err := cmd.Start()
12 | if err != nil {
13 | fmt.Println(err)
14 | }
15 | err = cmd.Wait()
16 | if err != nil {
17 | fmt.Println(err)
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/testutils/jaillover/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "log"
7 | "os"
8 | "strings"
9 | )
10 |
11 | // An Output represents the execution context,
12 | // meaning the command line and the environment
13 | type Output struct {
14 | Cmdline []string `json:"cmdline"`
15 | Env map[string]string `json:"env"`
16 | }
17 |
18 | func getEnvMap() map[string]string {
19 | env := make(map[string]string)
20 | for _, e := range os.Environ() {
21 | s := strings.SplitN(e, "=", 2)
22 | env[s[0]] = s[1]
23 | }
24 | return env
25 | }
26 |
27 | func main() {
28 | o := Output{
29 | Cmdline: os.Args,
30 | Env: getEnvMap(),
31 | }
32 | res, err := json.Marshal(o)
33 | if err != nil {
34 | log.Fatalf("JSON marshal failed %+v", err)
35 | }
36 | fmt.Println(string(res))
37 | if len(os.Args) > 1 && os.Args[1] == "--miserably-fail" {
38 | fmt.Fprintln(os.Stderr, "jailover miserably failed")
39 | os.Exit(1)
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/testutils/poc/.gitignore:
--------------------------------------------------------------------------------
1 | build
2 | *.egg-info
3 | dist
4 |
--------------------------------------------------------------------------------
/testutils/poc/README.rst:
--------------------------------------------------------------------------------
1 | Kapow! PoC
2 | ==========
3 |
4 | This directory contains a somewhat working Kapow! implementation written
5 | in Python.
6 |
7 | The purpose of this implementation is to be an experimentation playfield
8 | for the developers, allowing us to discuss and test new features
9 | quickly.
10 |
11 | Try the software within this directory with caution, anything can break
12 | at any moment.
13 |
14 |
15 | .. code-block:: none
16 |
17 | _,-._
18 | ; ___ : ,------------------------------.
19 | ,--' (. .) '--.__ | |
20 | _; ||| \ | Arrr!! Ye be warned! |
21 | '._,-----''';=.____," | |
22 | /// < o> |##| | |
23 | (o \`--' //`-----------------------------'
24 | ///\ >>>> _\ <<<< //
25 | --._>>>>>>>><<<<<<<< /
26 | ___() >>>[||||]<<<<
27 | `--'>>>>>>>><<<<<<<
28 | >>>>>>><<<<<<
29 | >>>>><<<<<
30 | >>ctr<<
31 |
32 |
33 | Running the Spec Test Suite
34 | ---------------------------
35 |
36 | Sitting in this directory run ``make`` to run the Kapow! Python PoC
37 | against the Spec Test Suite.
38 |
--------------------------------------------------------------------------------
/testutils/poc/requirements.txt:
--------------------------------------------------------------------------------
1 | aiohttp==3.9.4
2 | requests==2.31.0
3 | click==7.0
4 |
--------------------------------------------------------------------------------
/tools/validsslclient:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | set -e
4 |
5 | user=$(kapow get /ssl/client/i/dn)
6 |
7 | while read -r dn
8 | do
9 | [ -z "$dn" ] && continue
10 | if [ "$user" = "$dn" ]; then
11 | kapow set /server/log/validsslclient "Found valid user: '$user'"
12 | exit 0
13 | fi
14 | done
15 |
16 | kapow set /response/status 403 # Forbidden
17 | kapow set /server/log/validsslclient "Invalid user: '$user'"
18 | exit 127
19 |
--------------------------------------------------------------------------------