Welcome to Kapow!¶
If you can script it, you can HTTP it
What’s Kapow!¶
Think of that software that you need but only runs in the command
line. Kapow! lets you wrap it into an HTTP API
real easy.

Want to know more? Check the Quick Start Guide section for a longer explanation of what Kapow! does.
Authors¶
Kapow! is being developed by the BBVA-Labs Security team.
License¶
Kapow! is Open Source Software and available under the Apache 2 license.
Contributions¶
Contributions are of course welcome. See CONTRIBUTING or skim existing tickets to see where you could help out.
Contents¶
Quick Start Guide¶
We’ll explain a simple example to help you understand what Kapow! can do and why it is so awesome :-).
Scenario¶
In this example we’ll consider that our scenario is a corporate network like this:

Our organization has an external host that acts as a bridge between our intranet an the public Internet.
Goal
Our team must be able to check if the Internal Host is alive on an ongoing basis.
Limitations and Constraints¶
- We don’t want to grant access to the External Host to anybody.
- We don’t want to manage VPNs or any similar solutions to access Internal Host from the Internet.
- We want to limit the actions that a user can perform in our intranet while it is checking if Internal Host is alive.
- We want to use the most standard mechanism. Easy to use and automate.
- We don’t have a budget to invest in a custom solution.
The Desired Solution¶
After analyzing the problem and with our goal in mind, we conclude that it is enough to use a simple ping to Internal Host.
So, the next step is to analyze how to perform the ping.
Accessing via SSH to External Host¶
If we choose this option, then, for every person that needs to check the status
of Internal Host we need to create a user in the External Host and
grant them ssh
access.
Verdict
This is not a good idea, because:
- We’d need to manage users (violates a constraint).
- We’d need to grant users access to a host (violates a constraint).
- We would not be able to control what options the user could provide to ping (violates a constraint).
Develop and Deploy a Custom Solution¶
OK, this approach could be the best choice for our organization, but:
- We’d need to start a new project, develop, test, manage and maintain it.
- We’d need to wait for for the development to be production ready.
- We’d need a budget. Even if we have developers in our organization, their time it’s not free.
Verdict
This is not a good idea, because:
- We’d need to spend money (violates a constraint).
- We’d need to spend time (and time is money, see reason #1).
Using Kapow! (spoiler: it’s the winner!)¶
OK, let’s analyze Kapow! and check if it is compatible with our constraints:
- Kapow! is Open Source, so it’s also free as in beer.
- By using Kapow! we don’t need to code our own solution, so we don’t have to waste time.
- By using Kapow! we can run any command in the External Host, limiting the command parameters, so it’s safe.
- By using Kapow! we can launch any system command as an
HTTP API
easily, so we don’t need to grant login access to External Host to anybody.
Verdict
Kapow! is the best choice, because it satisfies all of our requirements.
Using Kapow!¶
In order to get our example scenario working we need to follow the steps below.
Install Kapow!¶
Follow the installation instructions.
Write a ping.pow
File¶
Kapow! uses plain text files (called pow
files) where the endpoints you want
to expose are defined.
For each endpoint, you can decide which commands get executed.
For our example we need a file like this:
$ cat ping.pow
kapow route add /ping -c 'ping -c 1 10.10.10.100 | kapow set /response/body'
Let’s dissect this beast piece by piece:
kapow route add /ping
- adds a newHTTP API
endpoint at/ping
path in the Kapow! server. You have to use theGET
method to invoke the endpoint.-c
- after this parameter, we write the system command that Kapow! will run each time the endpoint is invoked.ping -c 1 10.10.10.100
- sends oneICMP ping packet
to the Internal Host.| kapow set /response/body
- writes the output of ping to the body of the response, so you can see it.
Launch the Service¶
At this point, we only need to launch kapow with our ping.pow
:
$ kapow server ping.pow
Kapow! can expose the user interface through HTTPS, to do this provide the corresponding key and certificates chain paths at startup:
$ kapow server --keyfile path/to/keyfile --certfile path/to/certfile ping.pow
Consume the Service¶
Now we can call our newly created endpoint by using our favorite HTTP
client.
In this example we’re using curl:
$ curl http://external.host/ping
PING 10.10.100 (10.10.100): 56 data bytes
64 bytes from 10.10.100: icmp_seq=0 ttl=55 time=1.425 ms
et voilà !, if you’re using HTTPS don’t forget to provide the CA certificate if needed:
$ curl --cacert path/to/CAfile https://external.host/ping
PING 10.10.100 (10.10.100): 56 data bytes
64 bytes from 10.10.100: icmp_seq=0 ttl=55 time=1.425 ms
Under the Hood¶
To understand what’s happening under the hood with Kapow! let’s see the following diagram:

As you can see, Kapow! provides the necessary mojo to turn a system
command into an HTTP API
.
Security Concerns Working with Parameters¶
Special care has to be taken when using parameters provided by the user when composing command line invocations.
Sanitizing user input is not a new problem, but in the case of Kapow!, we have to take into account also the way that the shell parses its arguments, as well as the way the command itself interprets them, in order to get it right.
Warning
It is imperative that the user input is sanitized properly if we are going feed it as a parameter to a command line program.
Parameter Injection Attacks¶
When you resolve variable values be careful to tokenize correctly by using double quotes. Otherwise you could be vulnerable to parameter injection attacks.
This example is VULNERABLE to parameter injection
In this example, an attacker can inject arbitrary parameters to ls.
1 2 3 4 | $ cat command-injection.pow
kapow route add '/vulnerable/{value}' - <<-'EOF'
ls $(kapow get /request/matches/value) | kapow set /response/body
EOF
|
Exploiting using curl:
1 | $ curl http://localhost:8080/vulnerable/-lai%20hello
|
This example is NOT VULNERABLE to parameter injection
Note how we add double quotes when we recover value data from the request:
1 2 3 4 | $ cat command-injection.pow
kapow route add '/not-vulnerable/{value}' - <<-'EOF'
ls -- "$(kapow get /request/matches/value)" | kapow set /response/body
EOF
|
Warning
Quotes around parameters only protect against the injection of additional arguments, but not against turning a non-option into option or vice-versa. Note that for many commands we can leverage double-dash to signal the end of the options. See the “Security Concern” section on the docs.
Parameter Mangling Attacks¶
In order to understand what we mean by parameter mangling let’s consider the following route:
#!/bin/sh
kapow route add /find -c <<-'EOF'
BASEPATH=$(kapow get /request/params/path)
find "$BASEPATH" | kapow set /response/body
EOF
The expected use for this endpoint is something like this:
$ curl http://kapow-host/find?path=/tmp
/tmp
/tmp/.X0-lock
/tmp/.Test-unix
/tmp/.font-unix
/tmp/.XIM-unix
/tmp/.ICE-unix
/tmp/.X11-unix
/tmp/.X11-unix/X0
Let’s suppose that a malicious attacker gets access to this service and makes this request:
$ curl http://kapow-host/find?path=-delete
Let’s see what happens:
The command that will eventually be executed by bash is:
find -delete | kapow set /response/body
This will silently delete all the files below the current directory, no questions asked. Probably not what you expected.
This happens because find has the last word on how to interpret its
arguments. For find, the argument -delete
is not a path.
Let’s see how we can handle this particular case:
#!/bin/sh
kapow route add /find -c <<-'EOF'
USERINPUT=$(kapow get /request/params/path)
BASEPATH=$(dirname -- "$USERINPUT")/$(basename -- "$USERINPUT")
find "$BASEPATH" | kapow set /response/body
EOF
Note
Since this is critical for keeping your Kapow! services secure, we are working on a way to make this more transparent and safe, while at the same time keeping it Kapowy.
Installing Kapow!¶
Kapow! has a reference implementation in Go
that is under active
development right now. If you want to start using Kapow! you can choose from
several options.
Download and Install a Binary¶
Binaries for several platforms are available from the releases section, visit the latest release page and download the binary corresponding to the platfom and architecture you want to install Kapow! in.
GNU/Linux¶
Install the downloaded binary using the following command as a privileged user.
# install kapow__linux_amd64 /usr/local/bin/kapow
macOS¶
Install the downloaded binary using the following command as a privileged user.
# install kapow__darwin_amd64 /usr/local/bin/kapow
Windows®¶
Unzip the downloaded zipfile and move kapow.exe
to a directory of your choice;
then update the system PATH
variable to include that directory.
Note that the zipfile also includes the LICENSE.
Install the package with go get
¶
If you already have installed and configured the go runtime in the host where you want to run Kapow!, simply run:
$ go get -v github.com/BBVA/kapow
Note that Kapow! leverages Go modules, so you can target specific releases:
$ GO111MODULE=on go get -v github.com/BBVA/kapow@v go: finding github.com v go: finding github.com/BBVA v go: finding github.com/BBVA/kapow v go: downloading github.com/BBVA/kapow v go: extracting github.com/BBVA/kapow v github.com/google/shlex github.com/google/uuid github.com/spf13/pflag github.com/BBVA/kapow/internal/server/httperror github.com/BBVA/kapow/internal/http github.com/BBVA/kapow/internal/server/model github.com/BBVA/kapow/internal/client github.com/gorilla/mux github.com/BBVA/kapow/internal/server/user/spawn github.com/BBVA/kapow/internal/server/data github.com/BBVA/kapow/internal/server/user/mux github.com/BBVA/kapow/internal/server/user github.com/BBVA/kapow/internal/server/control github.com/spf13/cobra github.com/BBVA/kapow/internal/server github.com/BBVA/kapow/internal/cmd github.com/BBVA/kapow
Include Kapow! in your Container Image¶
If you want to include Kapow! in a Docker
image, you can add the binary
directly from the releases section. Below is an example Dockerfile
that
includes Kapow!.
FROM debian:stable-slim ADD https://github.com/BBVA/kapow/releases/download/v/kapow__linux_amd64 /usr/bin/kapow RUN chmod 755 /usr/bin/kapow ENTRYPOINT ["/usr/bin/kapow"]
If the container is intended for running the server and you want to dinamically
configure it, remember to include a --control-bind
param with an external
bind address (e.g., 0.0.0.0
) and to map all the needed ports in order to get
access to the control interface.
After building the image you can run the container with:
$ docker run --rm -i -p 8080:8080 -v $(pwd)/whatever.pow:/opt/whatever.pow kapow:latest server /opt/whatever.pow
With the -v
parameter we map a local file into the container’s filesystem so
we can use it to configure our Kapow! server on startup.
Kapow! Tutorial¶
This tutorial will help you get more familiar with Kapow!.
It tells you the story of a junior ops person landing a new job in a small company. Teaming up with an experienced senior, they’ll face many challenges of increasing difficulty. With Kapow! at their side, they will be able to pass all the hurdles.
You just need to follow the steps and execute the code shown in the tutorial to learn the Kapow! way.
Enjoy the ride!
Your First Day at Work¶
Senior
Welcome to ACME Inc. This is your first day here, right?
Junior
Hi! Yes! And I am eager to start working. What will be my first task?
Senior
First, let me help you get acquainted with our infrastructure.
Junior
OK.
Senior
We have two Linux boxes that provide services to our employees.
- The Corporate Server: Provides email, database and web services.
- The Backup Server: It is used to store backup of the important company data.
Junior
That’s it? OK, just like Google, then.
Senior
Smartass…
Junior
(chuckles nervously).
Senior
Well, I think is time for you to start with your first task. It just so happens that we received another request to backup the database from the projects team.
Let’s Backup that Database!¶
Junior
A Backup? Don’t you have this kind of things already automated?
Senior
Well, is not that simple. We of course have periodic backups. But, our project team ask us for a backup every time a project is finished.
I’ve already prepared a script to do the task. Before executing it in production, why don’t you
download it
and test it in your laptop?$ curl --output backup_db.sh https://raw.githubusercontent.com/BBVA/kapow/master/docs/source/tutorial/materials/backup_db.sh $ chmod u+x backup_db.sh
Junior
(after a few minutes)
OK, done! I just run it and I got this output:
$ ./backup_db.sh Backup done! Your log file is at /tmp/backup_db.log
Senior
That’s right. That script performed the backup and stored it into the Backup Server and appended some information into the backup log file at
/tmp/backup_db.log
.Now you can ssh into the Corporate Server and make the real backup.
Junior
Wait, wait… how long have you been doing this?
Senior
This procedure was already here when I arrived.
Junior
And why don’t they do it themselves? I mean, what do you contribute to the process?
Senior
I am the only one allowed to ssh into the Corporate Server, for obvious reasons.
Junior
Why do you need to ssh in the first place? Couldn’t it be done without ssh?
Senior
Actually, it could be done with a promising new tool I’ve just found… Kapow!
Is a tool that allows you to publish scripts as
HTTP
services. If we use it here we can give them the ability to do the backup whenever they want.
Junior
Sounds like less work for me. I like it!
Senior
OK then, let’s it try on your laptop first.
First of all, you have to follow the installation instructions.
Junior
I’ve just installed it in my laptop, but I don’t understand how all of this is going to work.
Senior
Don’t worry, it is pretty easy. Basically we will provide anHTTP
endpoint managed by Kapow! at the Corporate Server; when the project team wants to perform a backup they only need to call the endpoint and Kapow! will call the backup script.
Junior
It seems pretty easy. How can I create the endpoint?
Senior
First you have to start a fresh server. Please run this in your laptop:
$ kapow server
Warning
It is important that you run this command in the same directory in which you downloaded
backup_db.sh
.
Junior
Done! But it doesn’t seem to do anything…
Senior
Now you have the port
8080
open, but no endpoints have been defined yet. To define our endpoint you have to run this in another terminal:$ kapow route add -X PUT /db/backup -e ./backup_db.sh
This will create an endpoint accessible via
http://localhost:8080/db/backup
. This endpoint has to be invoked with thePUT
method to prevent accidental calls.
Junior
Cool! Do we need to do all this stuff every time we start the Corporate Server?
Senior
Not at all. The creators of Kapow! have thought of everything. You can put all your route definitions in a special script file and pass it to the server on startup. They call those files
pow
files and they have.pow
extension.It should look something like:
$ cat backup.pow kapow route add -X PUT /db/backup -e ./backup_db.shAnd then you can start Kapow! with it:
$ kapow server backup.pow
Junior
Great! Now it says:
$ kapow server backup.pow 2019/11/26 11:40:01 Running powfile: "backup.pow" {"id":"19bb4ac7-1039-11ea-aa00-106530610c4d","method":"PUT","url_pattern":"/db/backup","entrypoint":"./backup_db.sh","command":"","index":0} 2019/11/26 11:40:01 Done running powfile: "backup.pow"I understand that this is proof that we have the endpoint available.
Senior
That appears to be the case, but we better check it.
Call it with curl:
$ curl -X PUT http://localhost:8080/db/backup
Junior
Yay! I can see the log file at/tmp/backup_db.log
Senior
That’s great. I am going to install all this in the Corporate Server and forget about the old procedure.
That enough for your first day! Go home now and get some rest.
What have we done?¶
Senior
Hey, I come from seeing our project team mates. They’re delighted with their new toy, but they miss something.
I forgot to tell you that after the backup is run they need to review the log file to check that everything went OK.
Junior
Makes sense. Do you think that Kapow! can help with this? I have the feeling that this is the right way to go about it…
Senior
Sure! Let’s take a look at the documentation to see how we can tweak the logic of the request.
Junior
Got it! There’re a lot of resources to work with. I see that we can write to the response. Do you think this will work for us?
Senior
Yeah, the team is used to cat the log file contents to see what happened in the last execution:
$ cat /tmp/backup_db.log
I’ve made it easy for you. Are you up to it?
Junior
Let me try add this to our
pow
file:kapow route add /db/backup_logs -c 'cat /tmp/backup_db.log | kapow set /response/body'
Senior
Looks good to me, clean and simple, and it is a very good idea to useGET
here as it won’t change anything in the server. Let’s restart Kapow! and try it.
Junior
Wooow! I get back the content of the file. If they liked the first one they’re going to loooove this.
Senior
Agreed. And with this, I think we are done for the day…
We need to filter¶
Senior
Hiya! How’re you doing this morning? I’ve got a new challenge from our grateful mates.
As time goes on from the last log rotation, the size of the log file gets bigger and bigger. Furthermore, they want to limit the output of the file to pick only some records, and only from the end of the file. We need to do something to help them as they are wasting a lot of time reviewing the output.
Junior
I have a feeling that this is going to entail some serious bash-foo. What do you think?
Senior
Sure! But in addition to some good shell plumbing we’re going to squeeze Kapow!’s superpowers a litle bit more to get a really good solution.
Can you take a look at Kapow!’s documentation to see if something can be done?
Junior
I’ve read in the documentation that there is a way to get access to the data coming in the request. Do you think we can use this to let them choose how to do the filtering?
Senior
Sounds great! How have we lived without Kapow! all this time?
As they requested, we can offer them a parameter to filter the registers they want to pick, and another parameter to limit the output size in lines.
Junior
Sounds about right. Now we have to make some modifications to our last endpoint definition to add this new feature. Let’s get cracking!
Senior
Well, we got it again, this is exactly what they need:
kapow route add /db/backup_logs -c 'grep -- "$(kapow get /request/params/filter)" /tmp/backup_db.log \ | tail -n "$(kapow get /request/params/lines)" \ | kapow set /response/body'It looks a bit weird, but we’ll have time to revise the style later. Please make some tests on your laptop before we publish it on the Corporate Server. Remember to send them an example URL with the parameters they can use to filter and limit the amount of lines they get.
Junior
OK, should look like this, doesn’t it?
$ curl 'http://localhost:8080/db/backup_logs?filter=rows%20inserted&lines=200'
Senior
Exactly. Another great day helping the company advance. Let’s go grab a beer to celebrate!
I Need my Report!¶
Junior
Good morning!
You look very busy, what’s going on?
Senior
I am finishing the capacity planning report. Let me just mail it… Done!
Today I am going to teach you how to write this report so we can split the workload.
Junior
Oh. That sounds… fun. OK, tell me about this report.
Senior
Here at ACME Inc. we take capacity planning seriously. It is important that our employees always have the best resources to accomplish their job.
We prepare a report with some statistics about the load of our servers. This way we know when we have to buy another one.
Junior
I see this company scales up just like Google…
Senior
Smartass…
Junior
(chuckles)
Senior
We have a procedure:
ssh
into the machine.- Execute the following commands copying its output for later filling in the report:
hostname
anddate
: To include in the report.free -m
: To know if we have to buy more RAM.uptime
: To see the load of the system.df -h
: Just in case we need another hard disk drive.- Copy all this in an email and send it to Susan, the operations manager.
Junior
And why can’t Susanssh
into the server herself to see all of this?
Senior
She doesn’t have time for this. She is a manager, and she is very busy!
Junior
Well, I guess we can make a Kapow! endpoint to let her see all this information from the browser. This way she doesn’t need to waste any time asking us.
I started to write it already:
kapow route add /capacityreport -c 'hostname | kapow set /response/body; date | kapow set /response/body; free -m | kapow set /response/body; uptime | kapow set /response/body; df -h | kapow set /response/body'
Senior
Not good enough!
First of all, that code is not readable. And the output would be something like:
corporate-server Tue 26 Nov 2019 01:03:44 PM CET total used free shared buff/cache available Mem: 31967 2286 23473 729 6207 28505 Swap: 0 0 0 13:03:44 up 5:57, 1 user, load average: 0.76, 0.63, 0.45 Filesystem Size Used Avail Use% Mounted on dev 16G 0 16G 0% /dev run 16G 1.7M 16G 1% /runWhich is also very difficult to read!
What Susan is used to see is more like this:
Hostname: ... the output of `hostname` ... ================================================================================ Date: ... the output of `date` ... ================================================================================ Memory: ... the output of `free -m` ... ================================================================================ ... and so on ...
Junior
All right, what about this?
kapow route add /capacityreport -c 'hostname | kapow set /response/body; echo ================================================================================ | kapow set /response/body; ...'
Senior
That fixes the issue for Susan, but makes it worse for us.
What about a HEREDOC to help us make the code more readable?
Junior
A HEREwhat?
Senior
A HEREDOC or here document is the method Unix shells use to express multi-line literals.
They look like this:
$ cat <<-'EOF' you can put more than one line here EOFThe shell will put the data between the first
EOF
and the secondEOF
as thestdin
of the cat process.
Junior
OK, I understand. That’s cool, by the way.
So, if I want to use this with Kapow!, I have to make it read the script from
stdin
. To do this I know that I have to put a-
at the end.Let me try:
kapow route add /capacityreport - <<-'EOF' hostname | kapow set /response/body echo ================================================================================ | kapow set /response/body date | kapow set /response/body echo ================================================================================ | kapow set /response/body free -m | kapow set /response/body echo ================================================================================ | kapow set /response/body uptime | kapow set /response/body echo ================================================================================ | kapow set /response/body df -h | kapow set /response/body echo ================================================================================ | kapow set /response/body EOF
Senior
That would work. Nevertheless I am not yet satisfied.
What about all the repeated
kapow set /response/body
statements? Do you think we could do any better?
Junior
Maybe we can redirect all output to a file and use the file as the input ofkapow set /response/body
.
Senior
There is a better way. You can make use of another neat bash feature: command grouping.
Command grouping allows you to execute several commands treating the group as one single command.
You can use this way:
{ command1; command2; } | command3
Junior
What about this:
kapow route add /capacityreport - <<-'EOF' { hostname echo ================================================================================ date echo ================================================================================ free -m echo ================================================================================ uptime echo ================================================================================ df -h echo ================================================================================ } | kapow set /response/body EOF
Senior
Nice! Now I am not worried about maintaining that script. Good job!
Junior
You know me. Whatever it takes to avoid writing reports ;-)
(both chuckle).
Sharing the Stats¶
Junior
Good morning!
Senior
Just about time… We are in trouble!
The report stuff was a complete success, so much so that now Susan has hired a frontend developer to create a custom dashboard to see the stats in real time.
Now we have to provide the backend for the solution.
Junior
And what’s the problem?
Senior
We are not developers! What are we doing writing a backend?
Junior
Just chill out. Can’t be that difficult… What do they need, exactly?
Senior
We have to provide a new endpoint to serve the same data but inJSON
format.
Junior
So, we have half of the work done already!
What about this?
kapow route add /capacitystats - <<-'EOF' echo "{\"memory\": \"`free -m`\"}" | kapow set /response/body EOF
Senior
For starters, that’s not valid
JSON
. The output would be something like:$ echo "{\"memory\": \"`free -m`\"}" {"memory": " total used free shared buff/cache available Mem: 31967 3121 21680 980 7166 27418 Swap: 0 0 0"}You can’t add new lines inside a
JSON
string that way, you have to escape the new line characters as\n
.
Junior
Are you sure?
Senior
See for yourself.
$ echo "{\"memory\": \"`free -m`\"}" | jq parse error: Invalid string: control characters from U+0000 through U+001F must be escaped at line 3, column 44
Junior
jq? What is that command?
Senior
jq is a wonderful tool for working withJSON
data from the command line. With jq you can extract data from aJSON
document and it also allows you to generate a well-formedJSON
document.
Junior
Let’s use it, then!
How can we generate a
JSON
document with jq?
Senior
To generate a document we use the
-n
option:$ jq -n '{"mykey": "myvalue"}' { "mykey": "myvalue" }
Junior
That does not seem very useful. The output is just the same.
Senior
Bear with me, it gets better. You can add variables to the
JSON
and jq will escape them for you.$ jq -n --arg myvar "$(echo -n myvalue)" '{"mykey": $myvar}' { "mykey": "myvalue" }
Junior
Sweet! That’s just what I need.
(hacks away for a few minutes).
What do you think of this?
$ jq -n --arg host "$(hostname)" --arg date "$(date)" --arg memory "$(free -m)" --arg load "$(uptime)" --arg disk "$(df -h)" '{"hostname": $host, "date": $date, "memory": $memory, "load": $load, "disk": $disk}' { "hostname": "junior-host", "date": "Tue 26 Nov 2019 05:27:24 PM CET", "memory": " total used free shared buff/cache available\nMem: 31967 3114 21744 913 7109 27492\nSwap: 0 0 0", "load": " 17:27:24 up 10:21, 1 user, load average: 0.20, 0.26, 0.27", "disk": "Filesystem Size Used Avail Use% Mounted on\ndev 16G 0 16G 0% /dev" }
Senior
That is the output we have to produce, right. But the code is far from readable. And you also forgot about adding the endpoint.
Can we do any better?
Junior
That’s easy:
kapow route add /capacitystats - <<-'EOF' jq -n \ --arg hostname "$(hostname)" \ --arg date "$(date)" \ --arg memory "$(free -m)" \ --arg load "$(uptime)" \ --arg disk "$(df -h)" \ '{"hostname": $hostname, "date": $date, "memory": $memory, "load": $load, "disk": $disk}' \ | kapow set /response/body EOFWhat do you think?
Senior
I’m afraid you forgot an important detail.
Junior
I don’t think so! theJSON
is well-formed and it contains all the required data. And the code is quite readable.
Senior
You are right, but you are not usingHTTP
correctly. You have to set theContent-Type
header to let your client know the format of the data you are outputting.
Junior
Oh, I see. Let me try again:
kapow route add /capacitystats - <<-'EOF' jq -n \ --arg hostname "$(hostname)" \ --arg date "$(date)" \ --arg memory "$(free -m)" \ --arg load "$(uptime)" \ --arg disk "$(df -h)" \ '{"hostname": $hostname, "date": $date, "memory": $memory, "load": $load, "disk": $disk}' \ | kapow set /response/body echo application/json | kapow set /response/headers/Content-Type EOF
Senior
Better. Just a couple of details.
You have to set the headers before writing to the body. This is because the body can be so big that Kapow! is forced to start sending it out.
In cases where you want to set a small piece of data (like the header), it is better not to use
stdin
. Kapow! provides a secondary syntax for these cases:$ kapow set <resource> <value>
Junior
Something like this?
kapow route add /capacitystats - <<-'EOF' kapow set /response/headers/Content-Type application/json jq -n \ --arg hostname "$(hostname)" \ --arg date "$(date)" \ --arg memory "$(free -m)" \ --arg load "$(uptime)" \ --arg disk "$(df -h)" \ '{"hostname": $hostname, "date": $date, "memory": $memory, "load": $load, "disk": $disk}' \ | kapow set /response/body EOF
Senior
That’s perfect! Now, let’s upload this to the Corporate Server and tell the frontend developer about it.
Securing the server¶
Senior
Hi… I hope you rested last night!
Come on, I need your help here!
Junior
Good morning! What’s the matter? Sounds worrying
Senior
We forgot to take the most basic security measures when deploying our services. Every body at the company can access the services and the information is transferred in clear text.
Junior
Oh! Damn, you’re right! You think we can do anything to solve this mess?
Senior
Yes, I’m pretty sure that those smart guys have thought on that when building Kapow! Have a look at the documentation.
Junior
Got it! They did it, here’re the instictions to start a server with HTTPS support.
It’s amazing! It says we can even use mTLS to control access, really promising.
Senior
Ok, ok… First thigs first. We need to get a server certificate to start working with HTTPS. Fortunately we can ask for one to the CA we use for the other servers. Let’s pick up one for development, they’re quick to get.
Junior
Yeah! I’ll change the startup script to configure HTTPS:
$ kapow server --keyfile /etc/kapow/tls/keyfile --certfile /etc/kapow/tls/certfile /etc/kapow/awesome.pow
It’s easy, please copy the private key file and certificate chain to
/etc/kapow/tls
and we can restart.
Senior
Great! it’s working, communications are secured. Let’s say everybody to change from http to https.
Junior
Ok, did it. What are the steps to follow to limit access by using mTLS?
Senior
Besides configuring the server we need to provide the users with their own client certificates and private keys so they can configure their browsers and the application server.
Junior
Yes, please give me the CA certificate that will issue our client certificates and I’ll change the startup script again
$ kapow server --keyfile /etc/kapow/tls/keyfile --certfile /etc/kapow/tls/certfile --clientauth=true --clientcafile /etc/kapow/tls/clientCAfile /etc/kapow/awesome.powDone!
Senior
Ok, let’s communicate the changes to all the affected teams before we restart
Junior
Oh God, After all we’re starting to look like Google
(chuckles)
Working with pow Files¶
Starting Kapow! using a pow file¶
A pow
file is just a bash script, where you make calls to the
kapow route
command.
1 | $ kapow server example.pow
|
With the example.pow
:
1 2 3 4 5 6 7 8 9 | $ cat example.pow
#
# This is a simple example of a pow file
#
echo '[*] Starting my script'
# We add 2 Kapow! routes
kapow route add /my/route -c 'echo hello world | kapow set /response/body'
kapow route add -X POST /echo -c 'kapow get /request/body | kapow set /response/body'
|
Note
Kapow! can be fully configured using just pow
files
Load More Than One pow File¶
You can load more than one pow
file at time. This can help you keep
your pow
files tidy.
1 2 3 | $ ls pow-files/
example-1.pow example-2.pow
$ kapow server <(cat pow-files/*.pow)
|
Writing Multiline pow Files¶
If you need to write more complex actions, you can leverage multiline commands:
1 2 3 4 5 | $ cat multiline.pow
kapow route add /log_and_stuff - <<-'EOF'
echo this is a quite long sentence and other stuff | tee log.txt | kapow set /response/body
cat log.txt | kapow set /response/body
EOF
|
Warning
Be aware of the “-“ at the end of the kapow route add
command.
It tells kapow route add
to read commands from stdin
.
Warning
If you want to learn more about multiline usage, see: Here Doc
Keeping Things Tidy¶
Sometimes things grow, and keeping things tidy is the only way to mantain the whole thing.
You can distribute your endpoints in several pow files. And you can keep the whole thing documented in one html file, served with Kapow!.
1 2 3 4 5 6 7 | $ cat index.pow
kapow route add / - <<-'EOF'
cat howto.html | kapow set /response/body
EOF
source ./info_stuff.pow
source ./other_endpoints.pow
|
As you can see, the pow
files can be imported into another pow
file using
source. In fact, a pow
file is just a regular shell script.
Debugging scripts¶
Since Kapow! redirects the standard output and the standard error of the pow
file given on server startup to its own, you can leverage set -x
to see the
commands that are being executed, and use that for debugging.
To support debugging user request executions, the server subcommand has a
--debug
option flag that prompts Kapow! to redirect both the script’s
standard output and standard error to Kapow!’s standard output, so you can
leverage set -x
the same way as with pow
files.
$ cat withdebug.pow
kapow route add / - <<-'EOF'
set -x
echo "This will be seen in the log"
echo "Hi HTTP" | kapow set /response/body
EOF
$ kapow server --debug withdebug.pow
Managing Routes¶
Adding New Routes¶
Warning
Be aware that if you register more than one route with exactly the same path, only the first route added will be used.
GET route¶
Defining a route:
1 | $ kapow route add /my/route -c 'echo hello world | kapow set /response/body'
|
Calling route:
1 2 | $ curl http://localhost:8080/my/route
hello world
|
POST route¶
Defining a route:
1 | $ kapow route add -X POST /echo -c 'kapow get /request/body | kapow set /response/body'
|
Calling a route:
1 2 | $ curl -d 'hello world' -X POST http://localhost:8080/echo
hello world
|
Capturing Parts of the URL¶
Defining a route:
1 | $ kapow route add '/echo/{message}' -c 'kapow get /request/matches/message | kapow set /response/body'
|
Calling a route:
1 2 | $ curl http://localhost:8080/echo/hello%20world
hello world
|
Listing Routes¶
You can list the active routes in the Kapow! server.
1 2 | $ kapow route list
[{"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"}]
|
Or, if you want human-readable output, you can use jq:
1 2 3 4 5 6 7 8 9 10 | $ kapow route list | jq
[
{
"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",
}
]
|
Note
Kapow! has a HTTP Control Interface, bound by default to
localhost:8081
.
Deleting Routes¶
You need the ID of a route to delete it. Running the command used in the listing routes example, you can obtain the ID of the route, and then delete it by typing:
1 | $ kapow route remove 20c98328-0b82-11ea-90a8-784f434dfbe2
|
Handling HTTP Requests¶
Add or Modify an HTTP Header¶
You may want to add some extra HTTP header to the response.
In this example we’ll be adding the header X-Content-Type-Options
to the response.
1 2 3 4 5 6 7 8 9 | $ cat sniff.pow
kapow route add /sec-hello-world - <<-'EOF'
kapow set /response/headers/X-Content-Type-Options nosniff
kapow set /response/headers/Content-Type text/plain
echo this will be interpreted as plain text | kapow set /response/body
EOF
$ kapow server nosniff.pow
|
Testing with curl:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | $ curl -v http://localhost:8080/sec-hello-world
* Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 8080 (#0)
> GET /sec-hello-word HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.54.0
> Accept: */*
>
< HTTP/1.1 200 OK
< X-Content-Type-Options: nosniff
< Date: Wed, 20 Nov 2019 10:56:46 GMT
< Content-Length: 24
< Content-Type: text/plain
<
this will be interpreted as plain text
|
Warning
Please be aware that if you don’t explicitly specify the value of
the Content-Type
header, Kapow! will guess it, effectively
negating the effect of the X-Content-Type-Options
header.
Note
You can read more about the X-Content-Type-Options: nosniff
header here.
Upload Files¶
Example #1¶
Uploading a file using Kapow! is very simple:
1 2 3 4 | $ cat upload.pow
kapow route add -X POST /upload-file - <<-'EOF'
kapow get /request/files/data/content | kapow set /response/body
EOF
|
1 2 3 4 | $ cat results.json
{"hello": "world"}
$ curl -X POST -H 'Content-Type: multipart/form-data' -F data=@results.json http://localhost:8080/upload-file
{"hello": "world"}
|
Example #2¶
In this example we reply the line count of the file received in the request:
1 2 3 4 5 6 7 8 9 10 11 12 13 | $ cat count-file-lines.pow
kapow route add -X POST /count-file-lines - <<-'EOF'
# Get sent file
FNAME=$(kapow get /request/files/myfile/filename)
# Counting file lines
LCOUNT=$(kapow get /request/files/myfile/content | wc -l)
kapow set /response/status 200
echo "$FNAME has $LCOUNT lines" | kapow set /response/body
EOF
|
1 2 3 4 5 | $ cat file.txt
hello
World
$ curl -F myfile=@file.txt http://localhost:8080/count-file-lines
file.txt has 2 lines
|
Sending HTTP error codes¶
You can specify custom status code for HTTP
response:
1 2 3 4 5 | $ cat error.pow
kapow route add /error - <<-'EOF'
kapow set /response/status 401
echo -n '401 error' | kapow set /response/body
EOF
|
Testing with curl:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | $ curl -v http://localhost:8080/error
* Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 8080 (#0)
> GET /error HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.54.0
> Accept: */*
>
< HTTP/1.1 401 Unauthorized
< Date: Wed, 20 Nov 2019 14:06:44 GMT
< Content-Length: 10
< Content-Type: text/plain; charset=utf-8
<
401 error
|
How to redirect using HTTP¶
In this example we’ll redirect our users to Google
:
1 2 3 4 5 | $ cat redirect.pow
kapow route add /redirect - <<-'EOF'
kapow set /response/headers/Location https://google.com
kapow set /response/status 301
EOF
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | $ curl -v http://localhost:8080/redirect
* Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 8080 (#0)
> GET /redirect HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.54.0
> Accept: */*
>
< HTTP/1.1 301 Moved Permanently
< Location: http://google.com
< Date: Wed, 20 Nov 2019 11:39:24 GMT
< Content-Length: 0
<
* Connection #0 to host localhost left intact
|
Manage Cookies¶
If you track down some user state, Kapow! allows you manage Request/Response Cookies.
In the next example we’ll set a cookie:
1 2 3 4 5 6 7 8 9 10 | $ cat cookie.pow
kapow route add /setcookie - <<-'EOF'
CURRENT_STATUS=$(kapow get /request/cookies/kapow-status)
if [ -z "$CURRENT_STATUS" ]; then
kapow set /response/cookies/Kapow-Status 'Kapow Cookie Set'
fi
echo -n OK | kapow set /response/body
EOF
|
Calling with curl:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | $ curl -v http://localhost:8080/setcookie
* Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 8080 (#0)
> GET /setcookie HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.54.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Set-Cookie: Kapow-Status="Kapow Cookie Set"
< Date: Fri, 22 Nov 2019 10:44:42 GMT
< Content-Length: 3
< Content-Type: text/plain; charset=utf-8
<
OK
* Connection #0 to host localhost left intact
|
Using JSON¶
Modify JSON by Using Shell Commands¶
Note
Nowadays Web services are JSON
-based, so making your script JSON
aware is
probably a good choice. In order to be able to extract data from a JSON
document as well as composing JSON
documents from a script, you can leverage
jq.
Example #1¶
In this example our Kapow! service will receive a JSON
value with an incorrect
date, then our pow
file will fix it and return the correct value to the user.
1 2 3 4 5 | $ cat fix_date.pow
kapow route add -X POST /fix-date - <<-'EOF'
kapow set /response/headers/Content-Type application/json
kapow get /request/body | jq --arg newdate "$(date +'%Y-%m-%d_%H-%M-%S')" '.incorrectDate=$newdate' | kapow set /response/body
EOF
|
Call the service with curl:
1 2 3 4 | $ curl -X POST http://localhost:8080/fix-date -H 'Content-Type: application/json' -d '{"incorrectDate": "no way, Jose"}'
{
"incorrectDate": "2019-11-22_10-42-06"
}
|
Example #2¶
In this example we extract the name
field from the incoming JSON
document in
order to generate a two-attribute JSON
response.
$ cat echo-attribute.pow
kapow route add -X POST /echo-attribute - <<-'EOF'
JSON_WHO=$(kapow get /request/body | jq -r .name)
kapow set /response/headers/Content-Type application/json
kapow set /response/status 200
jq --arg greet Hello --arg value "${JSON_WHO:-World}" --null-input '{ greet: $greet, to: $value }' | kapow set /response/body
EOF
Call the service with curl:
1 2 3 4 5 | $ curl -X POST http://localhost:8080/echo-attribute -H 'Content-Type: application/json' -d '{"name": "MyName"}'
{
"greet": "Hello",
"to": "MyName"
}
|
Shell Tricks¶
How to Execute Two Processes in Parallel¶
We want to ping two machines parallel. Kapow! can get IP addresses from query params:
1 2 3 4 5 6 | $ cat parallel.pow
kapow route add '/parallel/{ip1}/{ip2}' - <<-'EOF'
ping -c 1 -- "$(kapow get /request/matches/ip1)" | kapow set /response/body &
ping -c 1 -- "$(kapow get /request/matches/ip2)" | kapow set /response/body &
wait
EOF
|
Calling with curl:
1 | $ curl -v http://localhost:8080/parallel/10.0.0.1/10.10.10.1
|
Script debugging¶
Bash provides the set -x
builtin command that “After expanding each simple command,
for command, case command, select command, or arithmetic for command, display the
expanded value of PS4, followed by the command and its expanded arguments or associated
word list”. This feature can be used to help debugging the .pow
scripts and, together
the --debug
option in the server sub-command, the scripts executed in user requests.
Using HTTPS and mTLS with Kapow!¶
Kapow! can be accesed over HTTPS and use mutual TLS for authentication. Right now there are two possibilities to configure HTTPS/mTLS on a running server.
Note
In the following sections we refer to the host running the Kapow! server as
kapow:8080
.
For testing purposes you can generate a self-signed certificate with the following command:
$ openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -nodes
Using Kapow! built in capabilities¶
In this section we present the option flags that the Kapow! server command provides in order to set up a server with HTTPS and/or mTLS.
Enabling HTTPS¶
When starting the server we can provide the private key and the certificate
chain presented to the clients by giving to the server the paths to the
corresponding files using the --keyfile
and --certfile
option flags in the
command line:
$ kapow server --keyfile path/to/keyfile --certfile path/to/certfile foobar.pow
Now Kapow! is listening on its default port (8080) accepting requests over HTTPS. You can test it with the following command:
$ curl --cacert path/to/CAfile https://localhost:8080/endpoint
Where path/to/CAfile
is the path to the file containing the CA certificate
that issued Kapow!’s certificate.
Enabling mTLS¶
Once we have Kapow! configured to use HTTPS we can, optionally, activate mTLS so we can reject client connections that do not present a valid client certificate. Currently only issuer CA matching is supported, but in a near future we’ll be able to filter by DN in order to get more fine grained authentication.
In order to activate mTLS we have to provide Kapow! server command with the
CA certificate issuing the client certificates we want to accept with the
--clientcafile
option flag and toggle the --clientauth=true
option flag:
$ kapow server --keyfile path/to/keyfile --certfile path/to/certfile --clientauth=true --clientcafile path/to/clientCAfile foobar.pow
With this configuration Kapow! will reject connections that do not present a client certificate or one certificate not issued by the specified CA. You can test it with the following command:
$ curl --cacert path/to/CAfile --cert path/to/clientcredentials https://localhost:8080/endpoint
Where path/to/clientcredentials
is the path to the file in PKCS#12 format
containing the client certificate and private key. If you have the certificate
and the private key in different files you can use the –key option to specify
it independently.
Kapow! Behind a Reverse Proxy¶
Although Kapow! supports HTTPS
you can use a reverse proxy to serve a
Kapow! service via HTTPS
.
In this section we present a series of reverse proxy configurations that augment the capabilities of Kapow!.
Caddy¶
Automatic Let’s Encrypt Certificate
Caddy
automatically enablesHTTPS
usingLet's Encrypt
certificates given that some criteria are met.yourpublicdomain.example proxy / kapow:8080
Automatic Self-signed Certificate
If you want
Caddy
to automatically generate a self-signed certificate for testing you can use the following configuration.yourdomain.example proxy / kapow:8080 tls self_signed
Custom Certificate
If you already have a valid certificate for your server use this configuration.
yourdomain.example proxy / kapow:8080 tls /path/to/cert.pem /path/to/key.pem
In order to enable mutual TLS authentication read the Caddy documentation.
HAProxy¶
With the following configuration you can run HAProxy
with a custom
certificate.
frontend myserver.local
bind *:443 ssl crt /path/to/myserver.local.pem
mode http
default_backend nodes
backend nodes
mode http
server kapow1 kapow:8080
Note
You can produce myserver.local.pem
from the certificates in
previous examples with this command:
$ cat /path/to/cert.pem /path/to/key.pem > /path/to/myserver.local.pem
In order to enable mutual TLS authentication read the HAProxy documentation.
nginx¶
With the following configuration you can run nginx
with a custom
certificate.
server {
listen 443 ssl;
server_name myserver.local;
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/key.pem;
location / {
proxy_pass http://kapow:8080;
}
}
In order to enable mutual TLS authentication read the `Nginx documentation`_.
Kapow! HTTP Interfaces¶
kapow server
sets up three HTTP server interfaces, each with a distinct and
clear purpose.
HTTP User Interface¶
The HTTP User Interface
is used to serve final user requests.
By default it binds to address 0.0.0.0
and port 8080
, but that can be
changed via the --bind
flag.
HTTP Control Interface¶
The HTTP Control Interface
is used by the command kapow route
to
administer the list of system routes.
By default it binds to address 127.0.0.1
and port 8081
, but that can be
changed via the --control-bind
flag.
HTTP Data Interface¶
The HTTP Data Interface
is used by the commands kapow get
and kapow
set
to exchange the data for a particular request.
By default it binds to address 127.0.0.1
and port 8082
, but that can be
changed via the --data-bind
flag.
Philosophy¶
Single Static Binary¶
- Deployment is then as simple as it gets.
Docker
-friendly.
Shell Agnostic¶
- Kapow!, like John Snow, knows nothing, and makes no assumptions about the shell you are using. It only spawns executables.
- You are free to implement a client to the Data API directly if you are so inclined. The spec provides all the necessary details.
Not a Silver Bullet¶
You should not use Kapow! if your project requires complex business logic.
If you try to encode business logic in a shell script, you will deeply regret it soon enough.
Kapow! is designed for automating simple stuff.
Interoperability over Performance¶
We want Kapow! to be as performant as possible, but not at the cost of flexibility. This is the reason why our Data API leverages HTTP instead of a lighter protocol for example.
When we have to choose between making things faster or more interoperable the latter usually wins.
Request Life Cycle¶
This section describes the sequence of events happening for each request answered by the HTTP User Interface.

1. request¶
The user makes a request to the HTTP User Interface.
- The request is matched against the route table.
- kapow provides a
HANDLER_ID
to identify this request and don’t mix it with other requests that could be running concurrently.
2. spawn¶
kapow spawns the executable specified as entrypoint in the matching route.
The default entrypoint is /bin/sh; let’s focus on this workflow.
The spawned entrypoint is run with the following variables added to its environment:
KAPOW_HANDLER_ID
: Containing theHANDLER_ID
KAPOW_DATAAPI_URL
: With the URL of the HTTP Data InterfaceKAPOW_CONTROLAPI_URL
: With the URL of the HTTP Control Interface
3. kapow set /response/body banana
¶
During the lifetime of the shell, the request and response resources are available via these commands:
kapow get /request/...
kapow set /response/...
These commands use the aforementioned environment variables to read data
from the user request and to write the response. They accept data either as
arguments or from stdin
.
4. exit¶
The shell dies. Long live the shell!
5. response¶
kapow finalizes the original request. Enjoy your banana now.
The Kapow! Resource Tree¶
This is the model that Kapow! uses to expose the internals of the user request being serviced.
We use this tree to get access to any data that comes in the request, as well as to compose the response.
We access the resource tree easily with the kapow set
and kapow get
subcommands.
Overview¶
/
│
├─ request
│ ├──── method HTTP Method used (GET, POST)
│ ├──── host Host part of the URL
│ ├──── version HTTP version of the request
│ ├──── path Complete URL path (URL-unquoted)
│ ├──── remote IP address of client
│ ├──── matches
│ │ └──── <name> Previously matched URL path parts
│ ├──── params
│ │ └──── <name> URL parameters (after the "?" symbol)
│ ├──── headers
│ │ └──── <name> HTTP request headers
│ ├──── cookies
│ │ └──── <name> HTTP request cookie
│ ├──── form
│ │ └──── <name> Value of the form field with name <name>
│ ├──── files
│ │ └──── <name>
│ │ └──── filename Original file name of the file uploaded in the form field <name>
│ │ └──── content The contents of the file uploaded in the form field <name>
│ └──── body HTTP request body
│
|─ ssl
│ └──── client
│ └──── i
│ └──── dn Subject's DN common name coming ina request through SSL with mTLS
│─ route
│ └──── id Id of the route that matched this request.
│
|─ server
│ └──── log Log to server stderr
│ └──── <prefix> Optional prefix to the message
│
└─ response
├──── status HTTP status code
├──── headers
│ └──── <name> HTTP response headers
├──── cookies
│ └──── <name> HTTP request cookie
└──── body Response body
Resources¶
/request/method
Resource¶
The HTTP method of the incoming request.
Sample Usage¶
If the user runs:
$ curl -X POST http://kapow.example:8080
then, when handling the request:
$ kapow get /request/method
POST
/request/host
Resource¶
The Host
header as defined in the HTTP/1.1 spec of the incoming request.
Sample Usage¶
If the user runs:
$ curl http://kapow.example:8080
then, when handling the request:
$ kapow get /request/host
kapow.example
/request/version
Resource¶
The HTTP version of the incoming request.
Sample Usage¶
If the user runs:
$ curl --http1.0 http://kapow.example:8080
then, when handling the request:
$ kapow get /request/version
HTTP/1.0
/request/path
Resource¶
Contains the path substring of the URL.
Sample Usage¶
If the user runs:
$ curl http://kapow.example:8080/foo/bar?qux=1
then, when handling the request:
$ kapow get /request/path
/foo/bar
/request/remote
Resource¶
The IP address of the host making the incoming request.
Sample Usage¶
If the user runs:
$ curl http://kapow.example:8080
then, when handling the request:
$ kapow get /request/remote
192.168.100.156
/request/matches/<name>
Resource¶
Contains the part of the URL captured by the pattern name
.
Sample Usage¶
For a route defined like this:
$ kapow route add /foo/{mymatch}/bar
if the user runs:
$ curl http://kapow.example:8080/foo/1234/bar
then, when handling the request:
$ kapow get /request/matches/mymatch
1234
/request/params/<name>
Resource¶
Contains the value of the URL parameter name
Note
In the reference implementation only the first parameter’s value can be accessed in the case of multiple values coming in the request.
Sample Usage¶
If the user runs:
$ curl http://kapow.example:8080/foo?myparam=bar
then, when handling the request:
$ kapow get /request/params/myparam
bar
/request/headers/<name>
Resource¶
Contains the value of the HTTP header name
of the incoming request.
Note
In the reference implementation only the first header’s value can be accessed in the case of multiple values coming in the request.
Sample Usage¶
If the user runs:
$ curl -H X-My-Header=Bar http://kapow.example:8080/
then, when handling the request:
$ kapow get /request/headers/X-My-Header
Bar
/request/cookies/<name>
Resource¶
Contains the value of the HTTP cookie name
of the incoming request.
Sample Usage¶
If the user runs:
$ curl --cookie MYCOOKIE=Bar http://kapow.example:8080/
then, when handling the request:
$ kapow get /request/cookies/MYCOOKIE
Bar
/request/form/<name>
Resource¶
Contains the value of the field name
of the incoming request.
Note
In the reference implementation there are some caveats:
- Only the first form field’s value can be accessed in the case of multiple values coming in the request.
- In order to get access to the form data a correct ‘Content-Type’ header must be present in the request (‘application/x-www-form-urlencoded’ or ‘multipart/form-data’)
Sample Usage¶
If the user runs:
$ curl -F -d myfield=foo http://kapow.example:8080/
then, when handling the request:
$ kapow get /request/form/myfield
foo
/request/files/<name>/filename
Resource¶
Contains the name of the file uploaded through the incoming request.
Note
In the reference implementation to get access to the multipart data a correct Content-Type header must be present in the request (multipart/form-data or multipart/mixed).
Sample Usage¶
If the user runs:
$ curl -F 'myfile=@filename.txt' http://kapow.example:8080/
then, when handling the request:
$ kapow get /request/files/myfile/filename
filename.txt
/request/files/<name>/content
Resource¶
Contents of the file that is being uploaded in the incoming request.
Note
In the reference implementation to get access to the multipart data a correct Content-Type header must be present in the request (multipart/form-data or multipart/mixed).
Sample Usage¶
If the user runs:
$ curl -F 'myfile=@filename.txt' http://kapow.example:8080/
then, when handling the request:
$ kapow get /request/files/myfile/content
...filename.txt contents...
/request/body
Resource¶
Raw contents of the incoming request HTTP body.
Sample Usage¶
If the user runs:
$ curl --data-raw foobar http://kapow.example:8080/
then, when handling the request:
$ kapow get /request/body
foobar
/ssl/client/i/dn
Resource¶
The IP address of the host making the incoming request.
Sample Usage¶
If the user runs:
$ curl --cacert path/to/CAfile --cert path/to/clientcredentials http://kapow.example:8080
using a client certificate with DN=subject@example.net then, when handling the request:
$ kapow get /ssl/client/i/dn
subject@example.net
/route/id
Resource¶
The ID of the original route that matched this request..
Sample Usage¶
If the user runs:
$ curl http://kapow.example:8080/
then, when handling the request:
$ kapow get /route/id
ecd5d63f-f28b-11ea-ac55-ec21e5089c1f
/server/log/<prefix>
¶
Allows logging to Kapow!’s server stderr:
$ kapow set /server/log/my_banana_peeler 'banana too slippery!'
… meanwhile, in the server…
2020/12/18 11:15:49.437642 620846c6-4122-11eb-abeb-002b671b12f9 my_banana_peeler: banana too slippery!
Note that the ‘/my_banana_peeler’ part is an optional arbitrary prefix, and can be omitted.
/response/status
Resource¶
Contains the status code given in the user response.
Note
In the reference implementation there are some caveats:
- The status code value must be between 100 and 999.
- There is no way of writing reason phrase in the status line of the response.
Sample Usage¶
If during the request handling:
$ kapow set /response/status 418
then the response will have the status code 418 I am a Teapot
.
/response/headers/<name>
Resource¶
Contains the value of the header name
in the user response.
Note
At this moment header values are only appended, there is no way of reset the values once set.
Sample Usage¶
If during the request handling:
$ kapow set /response/headers/X-My-Header Foo
then the response will contain an HTTP header named X-My-Header
with
value Foo
.
Route Matching¶
Kapow! maintains a route table with a list of routes as provided by the user, and uses it to determine which handler an incoming request should be dispatched to.
Each incoming request is matched against the routes in the route table in strict order. For each route in the route table, the criteria are checked. If the request does not match, the next route in the route list is examined.
Routes¶
A Kapow! route specifies the matching criteria for an incoming request on the HTTP User Interface, and the details to handle it.
Kapow! implements a route table where all routes reside.
A route can be set like this:
$ kapow route add \
-X POST \
'/register/{username}' \
-e '/bin/bash -c' \
-c 'touch /var/lib/mydb/"$(kapow get /request/matches/username)"' \
| jq
{
"id": "deadbeef-0d09-11ea-b18e-106530610c4d",
"method": "POST",
"url_pattern": "/register/{username}",
"entrypoint": "/bin/bash -c",
"command": "touch /var/lib/mydb/\"$(kapow get /request/matches/username)\""
}
Let’s use this example to discuss its elements.
Elements¶
id
Route Element¶
Uniquely identifies each route. It is used for instance by kapow route remove
<route_id>
.
Note
The current implementation of Kapow! autogenerates a UUID
for this field.
In the future the user will be able to specify a custom value.
method
Route Element¶
Specifies the HTTP method for the route to match the incoming request.
Note that the route shown above will only match a POST
request.
url_pattern
Route Element¶
It matches the path
component of the URL
of the incoming request.
It can contain regex placeholders for easily capturing fragments of the path.
In the route shown above, a request with a URL /register/joe
would match,
assigning joe
to the placeholder username
.
Kapow! leverages Gorilla Mux for managing routes. For the full story, see https://github.com/gorilla/mux#examples
entrypoint
Route Element¶
This sets the executable to be spawned, along with any arguments required.
In the route shown above, the entrypoint that will be run is /bin/bash -c
,
which is an incomplete recipe. It is then completed by the command
element.
Note
The semantics of this element closely match the Dockerfile
’s ENTRYPOINT
directive.
command
Route Element¶
This is an optional last argument to be passed to the entrypoint.
In the route shown above, it completes the entrypoint
to form the final
incantation to be executed:
/bin/bash -c 'touch /var/lib/mydb/"$(kapow get /request/matches/username)"'
Note
The semantics of this element closely match the Dockerfile
’s CMD
directive.
Matching Algorithm¶
Kapow! leverages Gorilla Mux for this task. Check their documentation for the gory details.