From 0c16b5472f027407cb09b078689a2c0e9be534e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roberto=20Abdelkader=20Mart=C3=ADnez=20P=C3=A9rez?= Date: Wed, 23 Dec 2020 12:40:04 +0100 Subject: [PATCH 1/4] feat: run multiple init programs - We log their outputs and status codes - Windows is supported as well, leveraging cmd.exe /c Co-authored-by: pancho horrillo --- internal/cmd/runner.go | 11 ++++++ internal/cmd/runner_windows.go | 9 +++++ internal/cmd/server.go | 63 ++++++++++++++++++++++++---------- test/runwindowsrun.go | 19 ++++++++++ 4 files changed, 84 insertions(+), 18 deletions(-) create mode 100644 internal/cmd/runner.go create mode 100644 internal/cmd/runner_windows.go create mode 100644 test/runwindowsrun.go diff --git a/internal/cmd/runner.go b/internal/cmd/runner.go new file mode 100644 index 00000000..4ba17f55 --- /dev/null +++ b/internal/cmd/runner.go @@ -0,0 +1,11 @@ +// +build !windows + +package cmd + +import ( + "os/exec" +) + +func BuildCmd(path string) *exec.Cmd { + return exec.Command(path) +} diff --git a/internal/cmd/runner_windows.go b/internal/cmd/runner_windows.go new file mode 100644 index 00000000..04f368ab --- /dev/null +++ b/internal/cmd/runner_windows.go @@ -0,0 +1,9 @@ +package cmd + +import ( + "os/exec" +) + +func BuildCmd(path string) *exec.Cmd { + return exec.Command("cmd.exe", "/c", path) +} diff --git a/internal/cmd/server.go b/internal/cmd/server.go index 270b6b05..d4f7d3e0 100644 --- a/internal/cmd/server.go +++ b/internal/cmd/server.go @@ -17,9 +17,11 @@ package cmd import ( + "bufio" "errors" + "io" "os" - "os/exec" + "sync" "github.com/spf13/cobra" @@ -57,23 +59,8 @@ var ServerCmd = &cobra.Command{ server.StartServer(sConf) - if len(args) > 0 { - powfile := args[0] - _, err := os.Stat(powfile) - if os.IsNotExist(err) { - logger.L.Fatalf("%s does not exist", powfile) - } - logger.L.Printf("Running powfile: %q\n", powfile) - kapowCMD := exec.Command("bash", powfile) - kapowCMD.Stdout = os.Stdout - kapowCMD.Stderr = os.Stderr - kapowCMD.Env = os.Environ() - - err = kapowCMD.Run() - if err != nil { - logger.L.Fatal(err) - } - logger.L.Printf("Done running powfile: %q\n", powfile) + for _, path := range args { + go Run(path) } select {} @@ -112,3 +99,43 @@ func validateServerCommandArguments(cmd *cobra.Command, args []string) error { return nil } + +func Run(path string) { + logger.L.Printf("Running init program %+q", path) + cmd := BuildCmd(path) + cmd.Env = os.Environ() + + var wg sync.WaitGroup + if stdout, err := cmd.StdoutPipe(); err == nil { + wg.Add(1) + go logPipe(path, "stdout", stdout, &wg) + } + if stderr, err := cmd.StderrPipe(); err == nil { + wg.Add(1) + go logPipe(path, "stderr", stderr, &wg) + } + err := cmd.Start() + if err != nil { + logger.L.Fatalf("Unable to run init program %+q: %s", path, err) + } + + wg.Wait() + err = cmd.Wait() + if err != nil { + logger.L.Printf("Init program exited with error: %s", err) + } else { + logger.L.Printf("Init program %+q finished OK", path) + } +} + +func logPipe(path, name string, pipe io.ReadCloser, wg *sync.WaitGroup) { + defer wg.Done() + in := bufio.NewScanner(pipe) + + for in.Scan() { + logger.L.Printf("%+q (%s): %s", path, name, in.Text()) + } + if err := in.Err(); err != nil { + logger.L.Printf("Error reading from %+q’s %s: %s", path, name, err) + } +} diff --git a/test/runwindowsrun.go b/test/runwindowsrun.go new file mode 100644 index 00000000..5e4cb971 --- /dev/null +++ b/test/runwindowsrun.go @@ -0,0 +1,19 @@ +package main + +import ( + "fmt" + "os" + "os/exec" +) + +func main() { + cmd := exec.Command("rundll32.exe", "url.dll,FileProtocolHandler", os.Args[1]) + err := cmd.Start() + if err != nil { + fmt.Println(err) + } + err = cmd.Wait() + if err != nil { + fmt.Println(err) + } +} From e72c65c85949bf1a999582d8f861afb6fb84da50 Mon Sep 17 00:00:00 2001 From: pancho horrillo Date: Wed, 23 Dec 2020 18:44:11 +0100 Subject: [PATCH 2/4] feat: os-dependent default entrypoint MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Roberto Abdelkader Martínez Pérez --- internal/client/route_add.go | 10 +++++++--- internal/client/route_add_test.go | 1 - internal/cmd/route.go | 2 +- internal/server/control/control.go | 4 ++++ internal/server/control/entrypoint.go | 5 +++++ internal/server/control/entrypoint_windows.go | 3 +++ internal/server/model/route.go | 6 +++--- 7 files changed, 23 insertions(+), 8 deletions(-) create mode 100644 internal/server/control/entrypoint.go create mode 100644 internal/server/control/entrypoint_windows.go diff --git a/internal/client/route_add.go b/internal/client/route_add.go index f1cdffef..b79e8a05 100644 --- a/internal/client/route_add.go +++ b/internal/client/route_add.go @@ -27,10 +27,14 @@ import ( // AddRoute will add a new route in kapow func AddRoute(host, path, method, entrypoint, command string, w io.Writer) error { url := host + "/routes" - body, _ := json.Marshal(map[string]string{ + payload := map[string]string{ "method": method, "url_pattern": path, - "entrypoint": entrypoint, - "command": command}) + "command": command, + } + if entrypoint != "" { + payload["entrypoint"] = entrypoint + } + body, _ := json.Marshal(payload) return http.Post(url, "application/json", bytes.NewReader(body), w) } diff --git a/internal/client/route_add_test.go b/internal/client/route_add_test.go index e66779f4..ca48086b 100644 --- a/internal/client/route_add_test.go +++ b/internal/client/route_add_test.go @@ -31,7 +31,6 @@ func TestSuccessOnCorrectRoute(t *testing.T) { JSON(map[string]string{ "method": "GET", "url_pattern": "/hello", - "entrypoint": "", "command": "echo Hello World | kapow set /response/body", }). Reply(http.StatusCreated). diff --git a/internal/cmd/route.go b/internal/cmd/route.go index 4d9e720c..e6310af6 100644 --- a/internal/cmd/route.go +++ b/internal/cmd/route.go @@ -80,7 +80,7 @@ func init() { // TODO: Add default values for flags and remove path flag routeAddCmd.Flags().String("control-url", getEnv("KAPOW_CONTROL_URL", "http://localhost:8081"), "Kapow! control interface URL") routeAddCmd.Flags().StringP("method", "X", "GET", "HTTP method to accept") - routeAddCmd.Flags().StringP("entrypoint", "e", "/bin/sh -c", "Command to execute") + routeAddCmd.Flags().StringP("entrypoint", "e", "", "Command to execute") routeAddCmd.Flags().StringP("command", "c", "", "Command to pass to the shell") var routeRemoveCmd = &cobra.Command{ diff --git a/internal/server/control/control.go b/internal/server/control/control.go index 693b14e1..377fd668 100644 --- a/internal/server/control/control.go +++ b/internal/server/control/control.go @@ -132,6 +132,10 @@ func addRoute(res http.ResponseWriter, req *http.Request) { return } + if route.Entrypoint == "" { + route.Entrypoint = defaultEntrypoint + } + route.ID = id.String() created := funcAdd(route) diff --git a/internal/server/control/entrypoint.go b/internal/server/control/entrypoint.go new file mode 100644 index 00000000..c88d83ee --- /dev/null +++ b/internal/server/control/entrypoint.go @@ -0,0 +1,5 @@ +// +build !windows + +package control + +var defaultEntrypoint = "/bin/sh -c" diff --git a/internal/server/control/entrypoint_windows.go b/internal/server/control/entrypoint_windows.go new file mode 100644 index 00000000..cc9633c2 --- /dev/null +++ b/internal/server/control/entrypoint_windows.go @@ -0,0 +1,3 @@ +package control + +var defaultEntrypoint = "cmd.exe /c" diff --git a/internal/server/model/route.go b/internal/server/model/route.go index 53b9ed53..dd283ddf 100644 --- a/internal/server/model/route.go +++ b/internal/server/model/route.go @@ -19,7 +19,7 @@ package model // Route contains the data needed to represent a Kapow! user route. type Route struct { // ID is the unique identifier of the Route. - ID string `json:"id"` + ID string `json:"id,omitempty"` // Method is the HTTP method that will match this Route. Method string `json:"method"` @@ -33,7 +33,7 @@ type Route struct { // // This string will be split according to the shell parsing rules to // be passed as a list to exec.Command. - Entrypoint string `json:"entrypoint"` + Entrypoint string `json:"entrypoint,omitempty"` // Command is the last argument to be passed to exec.Command when // executing the Entrypoint @@ -43,5 +43,5 @@ type Route struct { // It is an output field, its value is ignored as input. Index int `json:"index"` - Debug bool `json:"debug"` + Debug bool `json:"debug,omitempty"` } From 26fa12c87151ba657b9e5f8b9f84fe968105cc3a Mon Sep 17 00:00:00 2001 From: pancho horrillo Date: Thu, 24 Dec 2020 13:30:23 +0100 Subject: [PATCH 3/4] doc: drop mentions to .pow files ($deity REST their soul) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Roberto Abdelkader Martínez Pérez --- Makefile | 6 -- README.md | 13 +-- .../examples/handling_http_requests.rst | 20 +++-- docs/source/examples/https_mtls.rst | 4 +- docs/source/examples/shell_tricks.rst | 8 +- docs/source/examples/toc.rst | 2 +- docs/source/examples/using_json.rst | 8 +- ...les.rst => working_with_init_programs.rst} | 80 ++++++++--------- docs/source/index.rst | 2 +- .../the_project/install_and_configure.rst | 2 +- docs/source/the_project/quickstart.rst | 19 ++-- docs/source/the_project/security.rst | 6 +- docs/source/tutorial/tutorial01.rst | 16 ++-- docs/source/tutorial/tutorial02.rst | 3 +- docs/source/tutorial/tutorial06.rst | 10 ++- ...ocumentConverter.pow => DocumentConverter} | 0 .../advanced/01_DocumentConverter/README.md | 2 +- .../{NetworkScanner.pow => NetworkScanner} | 0 examples/advanced/02_NetworkScanner/README.md | 2 +- .../{NetworkSniffer.pow => NetworkSniffer} | 2 +- examples/advanced/03_NetworkSniffer/README.md | 2 +- .../{HelloWorld.pow => HelloWorld} | 0 examples/basic/01_HelloWorld/README.md | 2 +- .../{FixLogGrep.pow => FixLogGrep} | 2 +- examples/basic/02_FixLogGrep/README.md | 2 +- .../{DynamicLogGrep.pow => DynamicLogGrep} | 0 examples/basic/03_DynamicLogGrep/README.md | 2 +- examples/basic/04_SystemMonitor/README.md | 4 +- .../{SystemMonitor.pow => SystemMonitor} | 0 internal/cmd/server.go | 2 +- spec/README.md | 90 ++++++++++--------- 31 files changed, 162 insertions(+), 149 deletions(-) rename docs/source/examples/{working_with_pow_files.rst => working_with_init_programs.rst} (51%) rename examples/advanced/01_DocumentConverter/{DocumentConverter.pow => DocumentConverter} (100%) rename examples/advanced/02_NetworkScanner/{NetworkScanner.pow => NetworkScanner} (100%) rename examples/advanced/03_NetworkSniffer/{NetworkSniffer.pow => NetworkSniffer} (85%) rename examples/basic/01_HelloWorld/{HelloWorld.pow => HelloWorld} (100%) rename examples/basic/02_FixLogGrep/{FixLogGrep.pow => FixLogGrep} (88%) rename examples/basic/03_DynamicLogGrep/{DynamicLogGrep.pow => DynamicLogGrep} (100%) rename examples/basic/04_SystemMonitor/{SystemMonitor.pow => SystemMonitor} (100%) diff --git a/Makefile b/Makefile index d4bd8cf4..4a22e19c 100644 --- a/Makefile +++ b/Makefile @@ -48,11 +48,5 @@ acceptance: install deps: @echo "deps here" -docker: build - cp $(BUILD_DIR)/$(BINARY_NAME) $(DOCKER_DIR)/ - cp $(DOCS_DIR)/*.pow $(DOCKER_DIR)/ - cd $(DOCKER_DIR) && docker build -t kapow . - cd .. - clean: rm -rf $(BUILD_DIR) $(OUTPUT_DIR) $(DOCKER_DIR)/* diff --git a/README.md b/README.md index 02592b58..0933d17e 100644 --- a/README.md +++ b/README.md @@ -21,19 +21,22 @@ us easily **turn that into an HTTP API**. ### Let's see this with an example We want to expose **log entries** for files not found on our **Apache Web -Server**, as an HTTP API. With *Kapow!* we just need to write this file: +Server**, as an HTTP API. With *Kapow!* we just need to write this +*executable* script: -```bash -[apache-host]$ cat search-apache-errors.pow +``` console +[apache-host]$ cat search-apache-errors +#!/usr/bin/env sh kapow route add /apache-errors - <<-'EOF' - cat /var/log/apache2/access.log | grep 'File does not exist' | kapow set /response/body + cat /var/log/apache2/access.log | grep 'File does not exist' | kapow set /response/body EOF +[apache-host]$ chmod +x search-apache-errors ``` and then, run it using *Kapow!* ```bash -[apache-host]$ kapow server --bind 0.0.0.0:8080 search-apache-errors.pow +[apache-host]$ kapow server --bind 0.0.0.0:8080 search-apache-errors ``` finally, we can read from the just-defined endpoint: diff --git a/docs/source/examples/handling_http_requests.rst b/docs/source/examples/handling_http_requests.rst index 246001ee..a5384289 100644 --- a/docs/source/examples/handling_http_requests.rst +++ b/docs/source/examples/handling_http_requests.rst @@ -11,7 +11,8 @@ In this example we'll be adding the header ``X-Content-Type-Options`` to the res .. code-block:: console :linenos: - $ cat sniff.pow + $ cat sniff-route + #!/usr/bin/env sh kapow route add /sec-hello-world - <<-'EOF' kapow set /response/headers/X-Content-Type-Options nosniff kapow set /response/headers/Content-Type text/plain @@ -19,7 +20,7 @@ In this example we'll be adding the header ``X-Content-Type-Options`` to the res echo this will be interpreted as plain text | kapow set /response/body EOF - $ kapow server nosniff.pow + $ kapow server nosniff-route Testing with :program:`curl`: @@ -67,7 +68,8 @@ Uploading a file using *Kapow!* is very simple: .. code-block:: console :linenos: - $ cat upload.pow + $ cat upload-route + #!/usr/bin/env sh kapow route add -X POST /upload-file - <<-'EOF' kapow get /request/files/data/content | kapow set /response/body EOF @@ -89,7 +91,8 @@ In this example we reply the line count of the file received in the request: .. code-block:: console :linenos: - $ cat count-file-lines.pow + $ cat count-file-lines + #!/usr/bin/env sh kapow route add -X POST /count-file-lines - <<-'EOF' # Get sent file @@ -121,7 +124,8 @@ You can specify custom status code for `HTTP` response: .. code-block:: console :linenos: - $ cat error.pow + $ cat error-route + #!/usr/bin/env sh kapow route add /error - <<-'EOF' kapow set /response/status 401 echo -n '401 error' | kapow set /response/body @@ -158,7 +162,8 @@ In this example we'll redirect our users to `Google`: .. code-block:: console :linenos: - $ cat redirect.pow + $ cat redirect + #!/usr/bin/env sh kapow route add /redirect - <<-'EOF' kapow set /response/headers/Location https://google.com kapow set /response/status 301 @@ -196,7 +201,8 @@ In the next example we'll set a cookie: .. code-block:: console :linenos: - $ cat cookie.pow + $ cat cookie + #!/usr/bin/env sh kapow route add /setcookie - <<-'EOF' CURRENT_STATUS=$(kapow get /request/cookies/kapow-status) diff --git a/docs/source/examples/https_mtls.rst b/docs/source/examples/https_mtls.rst index aaf464e0..be54981e 100644 --- a/docs/source/examples/https_mtls.rst +++ b/docs/source/examples/https_mtls.rst @@ -33,7 +33,7 @@ command line: .. code-block:: console - $ kapow server --keyfile path/to/keyfile --certfile path/to/certfile foobar.pow + $ kapow server --keyfile path/to/keyfile --certfile path/to/certfile foobar-route Now *Kapow!* is listening on its default port (8080) accepting requests over HTTPS. You can test it with the following command: @@ -60,7 +60,7 @@ CA certificate issuing the client certificates we want to accept with the .. code-block:: console - $ kapow server --keyfile path/to/keyfile --certfile path/to/certfile --clientauth=true --clientcafile path/to/clientCAfile foobar.pow + $ kapow server --keyfile path/to/keyfile --certfile path/to/certfile --clientauth=true --clientcafile path/to/clientCAfile foobar-route 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 diff --git a/docs/source/examples/shell_tricks.rst b/docs/source/examples/shell_tricks.rst index a0ebfde8..d7da6b02 100644 --- a/docs/source/examples/shell_tricks.rst +++ b/docs/source/examples/shell_tricks.rst @@ -10,7 +10,8 @@ from query params: .. code-block:: console :linenos: - $ cat parallel.pow + $ cat parallel-route + #!/usr/bin/env sh 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 & @@ -30,5 +31,6 @@ 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. +word list". This feature can be used to help debugging the init programs and, +together the ``--debug`` option in the server sub-command, the scripts executed +in user requests. diff --git a/docs/source/examples/toc.rst b/docs/source/examples/toc.rst index f40602a3..9db3f0dc 100644 --- a/docs/source/examples/toc.rst +++ b/docs/source/examples/toc.rst @@ -3,7 +3,7 @@ Examples .. toctree:: - working_with_pow_files + working_with_init_programs managing_routes handling_http_requests using_json diff --git a/docs/source/examples/using_json.rst b/docs/source/examples/using_json.rst index 4c5dbb5a..90e02258 100644 --- a/docs/source/examples/using_json.rst +++ b/docs/source/examples/using_json.rst @@ -16,12 +16,13 @@ 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. +date, then our init program will fix it and return the correct value to the user. .. code-block:: console :linenos: - $ cat fix_date.pow + $ cat fix_date + #!/usr/bin/env sh 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 @@ -46,7 +47,8 @@ order to generate a two-attribute `JSON` response. .. code-block:: console - $ cat echo-attribute.pow + $ cat echo-attribute + #!/usr/bin/env sh kapow route add -X POST /echo-attribute - <<-'EOF' JSON_WHO=$(kapow get /request/body | jq -r .name) diff --git a/docs/source/examples/working_with_pow_files.rst b/docs/source/examples/working_with_init_programs.rst similarity index 51% rename from docs/source/examples/working_with_pow_files.rst rename to docs/source/examples/working_with_init_programs.rst index 8343f4ba..ac352662 100644 --- a/docs/source/examples/working_with_pow_files.rst +++ b/docs/source/examples/working_with_init_programs.rst @@ -1,27 +1,28 @@ -Working with pow Files -====================== +Working with Init Scripts +========================= -Starting *Kapow!* using a pow file ----------------------------------- +Starting *Kapow!* using an init script +-------------------------------------- -A :file:`pow` file is just a :command:`bash` script, where you make calls to the -``kapow route`` command. +An init program, which can be just a shell script, allows you to make calls to +the ``kapow route`` command. .. code-block:: console :linenos: - $ kapow server example.pow + $ kapow server example-init-program -With the :file:`example.pow`: +With the :file:`example-init-program`: .. code-block:: console :linenos: - $ cat example.pow + $ cat example-init-program + #!/usr/bin/env sh # - # This is a simple example of a pow file + # This is a simple example of an init program # - echo '[*] Starting my script' + echo '[*] Starting my init program' # We add 2 Kapow! routes kapow route add /my/route -c 'echo hello world | kapow set /response/body' @@ -29,32 +30,19 @@ With the :file:`example.pow`: .. note:: - *Kapow!* can be fully configured using just :file:`pow` files + *Kapow!* can be fully configured using just init scripts -Load More Than One pow File ---------------------------- +Writing Multiline Routes +------------------------ -You can load more than one :file:`pow` file at time. This can help you keep -your :file:`pow` files tidy. +If you need to write more complex actions, you can leverage multiline routes: .. code-block:: console :linenos: - $ 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: - -.. code-block:: console - :linenos: - - $ cat multiline.pow + $ cat multiline-route + #!/usr/bin/env sh 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 @@ -77,43 +65,45 @@ 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!*. +You can distribute your endpoints in several init programs. And you can keep +the whole thing documented in one html file, served with *Kapow!*. .. code-block:: console :linenos: - $ cat index.pow + $ cat index-route + #!/usr/bin/env sh kapow route add / - <<-'EOF' cat howto.html | kapow set /response/body EOF - source ./info_stuff.pow - source ./other_endpoints.pow + source ./info_stuff + source ./other_endpoints + +You can import other shell script libraries with `source`. -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 ------------------ +Debugging Init Programs/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. +Since *Kapow!* redirects the standard output and the standard error of the init +program 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. +leverage ``set -x`` the same way as with init programs. .. code-block:: console - $ cat withdebug.pow + $ cat withdebug-route + #!/usr/bin/env sh 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 + $ kapow server --debug withdebug-route diff --git a/docs/source/index.rst b/docs/source/index.rst index d8616cce..4ab8a7b5 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -75,7 +75,7 @@ Contents :maxdepth: 2 :caption: Usage Examples - examples/working_with_pow_files + examples/working_with_init_programs examples/managing_routes examples/handling_http_requests examples/using_json diff --git a/docs/source/the_project/install_and_configure.rst b/docs/source/the_project/install_and_configure.rst index e724293c..04abaf33 100644 --- a/docs/source/the_project/install_and_configure.rst +++ b/docs/source/the_project/install_and_configure.rst @@ -111,7 +111,7 @@ After building the image you can run the container with: .. code-block:: console - $ docker run --rm -i -p 8080:8080 -v $(pwd)/whatever.pow:/opt/whatever.pow kapow:latest server /opt/whatever.pow + $ docker run --rm -i -p 8080:8080 -v $(pwd)/whatever-route:/opt/whatever-route kapow:latest server /opt/whatever-route 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. diff --git a/docs/source/the_project/quickstart.rst b/docs/source/the_project/quickstart.rst index 463bc773..a9336cce 100644 --- a/docs/source/the_project/quickstart.rst +++ b/docs/source/the_project/quickstart.rst @@ -116,10 +116,10 @@ Install *Kapow!* Follow the :ref:`installation instructions `. -Write a :file:`ping.pow` File -+++++++++++++++++++++++++++++ +Write an Init Program :file:`ping-route` ++++++++++++++++++++++++++++++++++++++++ -*Kapow!* uses plain text files (called `pow` files) where the endpoints you want +*Kapow!* uses init programs/scripts where the endpoints you want to expose are defined. For each endpoint, you can decide which commands get executed. @@ -128,11 +128,15 @@ For our example we need a file like this: .. code-block:: console - $ cat ping.pow + $ chmod +x ping-route + $ cat ping-route + #!/usr/bin/env sh kapow route add /ping -c 'ping -c 1 10.10.10.100 | kapow set /response/body' Let's dissect this beast piece by piece: +#. ``#!/usr/bin/env sh`` - shebang line so that the kernel knows which + interpreter to use #. ``kapow route add /ping`` - adds a new `HTTP API` endpoint at ``/ping`` path in the *Kapow!* server. You have to use the ``GET`` method to invoke the endpoint. @@ -147,18 +151,19 @@ Let's dissect this beast piece by piece: Launch the Service ++++++++++++++++++ -At this point, we only need to launch :program:`kapow` with our :file:`ping.pow`: +At this point, we only need to launch :program:`kapow` with our +:file:`ping-route`: .. code-block:: console - $ kapow server ping.pow + $ kapow server ping-route *Kapow!* can expose the user interface through HTTPS, to do this provide the corresponding key and certificates chain paths at startup: .. code-block:: console - $ kapow server --keyfile path/to/keyfile --certfile path/to/certfile ping.pow + $ kapow server --keyfile path/to/keyfile --certfile path/to/certfile ping-route Consume the Service diff --git a/docs/source/the_project/security.rst b/docs/source/the_project/security.rst index b844b9df..26344214 100644 --- a/docs/source/the_project/security.rst +++ b/docs/source/the_project/security.rst @@ -28,7 +28,8 @@ In this example, an attacker can inject arbitrary parameters to :command:`ls`. .. code-block:: console :linenos: - $ cat command-injection.pow + $ cat command-injection + #!/usr/bin/env sh kapow route add '/vulnerable/{value}' - <<-'EOF' ls $(kapow get /request/matches/value) | kapow set /response/body EOF @@ -48,7 +49,8 @@ request: .. code-block:: console :linenos: - $ cat command-injection.pow + $ cat command-injection + #!/usr/bin/env sh kapow route add '/not-vulnerable/{value}' - <<-'EOF' ls -- "$(kapow get /request/matches/value)" | kapow set /response/body EOF diff --git a/docs/source/tutorial/tutorial01.rst b/docs/source/tutorial/tutorial01.rst index 434772ba..efc16197 100644 --- a/docs/source/tutorial/tutorial01.rst +++ b/docs/source/tutorial/tutorial01.rst @@ -138,22 +138,22 @@ Let's Backup that Database! **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 :file:`pow` files and they have - :file:`.pow` extension. + all your route definitions on init programs, which can be shell scripts, and + pass them to the server on startup. It should look something like: .. code-block:: console - $ cat backup.pow + $ cat backup-route + #!/usr/bin/env sh kapow route add -X PUT /db/backup -e ./backup_db.sh And then you can start *Kapow!* with it: .. code-block:: console - $ kapow server backup.pow + $ kapow server backup-route **Junior** @@ -161,10 +161,10 @@ Let's Backup that Database! .. code-block:: console - $ kapow server backup.pow - 2019/11/26 11:40:01 Running powfile: "backup.pow" + $ kapow server backup-route + 2019/11/26 11:40:01 Running init program: "backup-route" {"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" + 2019/11/26 11:40:01 Done running init program: "backup-route" I understand that this is proof that we have the endpoint available. diff --git a/docs/source/tutorial/tutorial02.rst b/docs/source/tutorial/tutorial02.rst index 24cc0433..dc72dc8f 100644 --- a/docs/source/tutorial/tutorial02.rst +++ b/docs/source/tutorial/tutorial02.rst @@ -37,10 +37,11 @@ What have we done? **Junior** - Let me try add this to our :file:`pow` file: + Let me try add this to our init program: .. code-block:: console + #!/usr/bin/env sh kapow route add /db/backup_logs -c 'cat /tmp/backup_db.log | kapow set /response/body' **Senior** diff --git a/docs/source/tutorial/tutorial06.rst b/docs/source/tutorial/tutorial06.rst index 0b18f2b6..3c9c5caf 100644 --- a/docs/source/tutorial/tutorial06.rst +++ b/docs/source/tutorial/tutorial06.rst @@ -44,7 +44,9 @@ Securing the server .. code-block:: console - $ kapow server --keyfile /etc/kapow/tls/keyfile --certfile /etc/kapow/tls/certfile /etc/kapow/awesome.pow + $ kapow server --keyfile /etc/kapow/tls/keyfile \ + --certfile /etc/kapow/tls/certfile \ + /etc/kapow/awesome-route It's easy, please copy the private key file and certificate chain to `/etc/kapow/tls` and we can restart. @@ -70,7 +72,11 @@ Securing the server .. code-block:: console - $ kapow server --keyfile /etc/kapow/tls/keyfile --certfile /etc/kapow/tls/certfile --clientauth=true --clientcafile /etc/kapow/tls/clientCAfile /etc/kapow/awesome.pow + $ kapow server --keyfile /etc/kapow/tls/keyfile \ + --certfile /etc/kapow/tls/certfile \ + --clientauth true \ + --clientcafile /etc/kapow/tls/clientCAfile \ + /etc/kapow/awesome-route Done! diff --git a/examples/advanced/01_DocumentConverter/DocumentConverter.pow b/examples/advanced/01_DocumentConverter/DocumentConverter similarity index 100% rename from examples/advanced/01_DocumentConverter/DocumentConverter.pow rename to examples/advanced/01_DocumentConverter/DocumentConverter diff --git a/examples/advanced/01_DocumentConverter/README.md b/examples/advanced/01_DocumentConverter/README.md index 3f376cd8..79fc4100 100644 --- a/examples/advanced/01_DocumentConverter/README.md +++ b/examples/advanced/01_DocumentConverter/README.md @@ -5,7 +5,7 @@ A small web gui for [pandoc](https://pandoc.org) that allows to convert between ## How to run it ``` -$ kapow server DocumentConverter.pow +$ kapow server DocumentConverter ``` diff --git a/examples/advanced/02_NetworkScanner/NetworkScanner.pow b/examples/advanced/02_NetworkScanner/NetworkScanner similarity index 100% rename from examples/advanced/02_NetworkScanner/NetworkScanner.pow rename to examples/advanced/02_NetworkScanner/NetworkScanner diff --git a/examples/advanced/02_NetworkScanner/README.md b/examples/advanced/02_NetworkScanner/README.md index 7b3c2ffc..21a5f772 100644 --- a/examples/advanced/02_NetworkScanner/README.md +++ b/examples/advanced/02_NetworkScanner/README.md @@ -10,7 +10,7 @@ Run a long network scan in background with support for webhook on completion. ## How to run it ``` -$ kapow server NetworkScanner.pow +$ kapow server NetworkScanner ``` diff --git a/examples/advanced/03_NetworkSniffer/NetworkSniffer.pow b/examples/advanced/03_NetworkSniffer/NetworkSniffer similarity index 85% rename from examples/advanced/03_NetworkSniffer/NetworkSniffer.pow rename to examples/advanced/03_NetworkSniffer/NetworkSniffer index 6ccbf744..881e5d39 100755 --- a/examples/advanced/03_NetworkSniffer/NetworkSniffer.pow +++ b/examples/advanced/03_NetworkSniffer/NetworkSniffer @@ -1,3 +1,3 @@ -#!/bin/sh +#!/usr/bin/env sh kapow route add /sniff -c 'tcpdump -i any -U -s0 -w - "not portrange 8080-8082" | kapow set /response/body' diff --git a/examples/advanced/03_NetworkSniffer/README.md b/examples/advanced/03_NetworkSniffer/README.md index 7dd0347f..98ad47f2 100644 --- a/examples/advanced/03_NetworkSniffer/README.md +++ b/examples/advanced/03_NetworkSniffer/README.md @@ -8,7 +8,7 @@ Provides an HTTP service that allows the user to sniff the network in real time. For the sake of simplicity, run: ``` -$ sudo -E kapow server NetworkSniffer.pow +$ sudo -E kapow server NetworkSniffer ``` In a production environment, tcpdump should be run with the appropiate diff --git a/examples/basic/01_HelloWorld/HelloWorld.pow b/examples/basic/01_HelloWorld/HelloWorld similarity index 100% rename from examples/basic/01_HelloWorld/HelloWorld.pow rename to examples/basic/01_HelloWorld/HelloWorld diff --git a/examples/basic/01_HelloWorld/README.md b/examples/basic/01_HelloWorld/README.md index b8255f3e..5918d66f 100644 --- a/examples/basic/01_HelloWorld/README.md +++ b/examples/basic/01_HelloWorld/README.md @@ -6,7 +6,7 @@ A simple "Hello World!" type example. ## How to run it ``` -$ kapow server HelloWorld.pow +$ kapow server HelloWorld ``` diff --git a/examples/basic/02_FixLogGrep/FixLogGrep.pow b/examples/basic/02_FixLogGrep/FixLogGrep similarity index 88% rename from examples/basic/02_FixLogGrep/FixLogGrep.pow rename to examples/basic/02_FixLogGrep/FixLogGrep index c0f28e45..f0d922fe 100755 --- a/examples/basic/02_FixLogGrep/FixLogGrep.pow +++ b/examples/basic/02_FixLogGrep/FixLogGrep @@ -1,4 +1,4 @@ -#!/bin/sh +#!/usr/bin/env sh kapow route add /apache-errors - <<-'EOF' cat /var/log/apache2/access.log | grep 'File does not exist' | kapow set /response/body diff --git a/examples/basic/02_FixLogGrep/README.md b/examples/basic/02_FixLogGrep/README.md index 4d77e031..340aa5ec 100644 --- a/examples/basic/02_FixLogGrep/README.md +++ b/examples/basic/02_FixLogGrep/README.md @@ -5,7 +5,7 @@ A simple service that exposes log entries for files not found on our Apache Web ## How to run it ``` -$ kapow server FixLogGrep.pow +$ kapow server FixLogGrep ``` diff --git a/examples/basic/03_DynamicLogGrep/DynamicLogGrep.pow b/examples/basic/03_DynamicLogGrep/DynamicLogGrep similarity index 100% rename from examples/basic/03_DynamicLogGrep/DynamicLogGrep.pow rename to examples/basic/03_DynamicLogGrep/DynamicLogGrep diff --git a/examples/basic/03_DynamicLogGrep/README.md b/examples/basic/03_DynamicLogGrep/README.md index 7fdf5c5c..d27ac0af 100644 --- a/examples/basic/03_DynamicLogGrep/README.md +++ b/examples/basic/03_DynamicLogGrep/README.md @@ -5,7 +5,7 @@ A simple service that exposes log entries that matches **with a given text** on ## How to run it ``` -$ kapow server DynamicLogGrep.pow +$ kapow server DynamicLogGrep ``` diff --git a/examples/basic/04_SystemMonitor/README.md b/examples/basic/04_SystemMonitor/README.md index bb73d52b..bf9a8541 100644 --- a/examples/basic/04_SystemMonitor/README.md +++ b/examples/basic/04_SystemMonitor/README.md @@ -5,7 +5,7 @@ Expose several system properties and logs. ## How to run it ``` -$ kapow server SystemMonitor.pow +$ kapow server SystemMonitor ``` @@ -39,7 +39,7 @@ drwxr-xr-x 15 root root 4096 Jan 27 07:34 .. ``` $ curl -s http://localhost:8080/process -nil 46717 0.0 0.0 111224 8196 pts/2 Sl 16:48 0:00 kapow server SystemMonitor.pow +nil 46717 0.0 0.0 111224 8196 pts/2 Sl 16:48 0:00 kapow server SystemMonitor root 47405 0.0 0.0 0 0 ? I 16:50 0:00 [kworker/3:1-mm_percpu_wq] root 47406 0.0 0.0 0 0 ? I 16:50 0:00 [kworker/0:1] root 47819 0.0 0.0 0 0 ? I 16:52 0:00 [kworker/7:2-mm_percpu_wq] diff --git a/examples/basic/04_SystemMonitor/SystemMonitor.pow b/examples/basic/04_SystemMonitor/SystemMonitor similarity index 100% rename from examples/basic/04_SystemMonitor/SystemMonitor.pow rename to examples/basic/04_SystemMonitor/SystemMonitor diff --git a/internal/cmd/server.go b/internal/cmd/server.go index d4f7d3e0..4686661d 100644 --- a/internal/cmd/server.go +++ b/internal/cmd/server.go @@ -31,7 +31,7 @@ import ( // ServerCmd is the command line interface for kapow server var ServerCmd = &cobra.Command{ - Use: "server [optional flags] [optional pow file(s)]", + Use: "server [optional flags] [optional init program(s)]", Short: "Start a kapow server", Long: `Start a Kapow server with a client interface, a data interface and an admin interface`, diff --git a/spec/README.md b/spec/README.md index c200f0c7..bd16e4e3 100644 --- a/spec/README.md +++ b/spec/README.md @@ -219,7 +219,7 @@ field must be an escaped JSON string. } ] ``` -* **Sample Call**: `$ curl $KAPOW_URL/routes` +* **Sample Call**: `$ curl $KAPOW_DATA_URL/routes` * **Notes**: Currently all routes are returned; in the future, a filter may be accepted. @@ -260,7 +260,7 @@ A new id is created for the appended route so it can be referenced later. * **Code**: `422`; **Reason**: `Invalid Route` * **Sample Call**:
```sh - $ curl -X POST --data-binary @- $KAPOW_URL/routes < ```sh - $ curl -X PUT --data-binary @- $KAPOW_URL/routes < ```sh - $ curl -X DELETE $KAPOW_URL/routes/ROUTE_1f186c92_f906_4506_9788_a1f541b11d0f + $ curl -X DELETE $KAPOW_DATA_URL/routes/ROUTE_1f186c92_f906_4506_9788_a1f541b11d0f ``` * **Notes**: @@ -374,7 +374,7 @@ Retrieves the information about the route identified by `{id}`. * **Code**: `404`; Reason: `Route Not Found` * **Sample Call**:
```sh - $ curl -X GET $KAPOW_URL/routes/ROUTE_1f186c92_f906_4506_9788_a1f541b11d0f + $ curl -X GET $KAPOW_DATA_URL/routes/ROUTE_1f186c92_f906_4506_9788_a1f541b11d0f ``` * **Notes**: @@ -610,29 +610,30 @@ Commands: This command runs the Kapow! server, which is the core of Kapow!. If run without parameters, it will run an unconfigured server. It can accept a path -to a `pow` file, which is a shell script that contains commands to configure -the Kapow! server. - -The `pow` can leverage the `kapow route` command, which is used to define a route. -The `kapow route` command needs a way to reach the Kapow! server, and for that, -`kapow` provides the `KAPOW_URL` variable in the environment of the -aforementioned shell script. - -Every time the kapow server receives a request, it will spawn a process to -handle it, according to the specified entrypoint, `/bin/sh -c` by default, and then -execute the specified command. This command is tasked with processing the -incoming request, and can leverage the `request` and `response` commands to -easily access the `HTTP Request` and `HTTP Response`, respectively. - -In order for `request` and `response` to do their job, they require a way to -reach the Kapow! server, as well as a way to identify the current request being -served. Thus, the Kapow! server adds the `KAPOW_URL` and `KAPOW_HANDLER_ID` to the +to an executable file, the init program, which can be a shell script that +contains commands to configure the *Kapow!* server. + +The init program can leverage the `kapow route` command, which is used to define +a route. The `kapow route` command needs a way to reach the *Kapow!* server, +and for that, `kapow` provides the `KAPOW_DATA_URL` variable in the environment +of the aforementioned init program. + +Every time the *Kapow!* server receives a request, it will spawn a process to +handle it, according to the specified entrypoint, `/bin/sh -c` by default in +unices, and `cmd.exe /c` in Windows®, and then execute the specified command. +This command is tasked with processing the incoming request, and can leverage +the `get` and `set` commands to easily access the `HTTP Request` and `HTTP +Response`, respectively. + +In order for `get` and `set` to do their job, they require a way to reach the +*Kapow!* server, as well as a way to identify the current request being served. +Thus, the *Kapow!* server adds the `KAPOW_DATA_URL` and `KAPOW_HANDLER_ID` to the process' environment. #### Example -```sh -$ kapow server /path/to/service.pow +``` console +$ kapow server /path/to/service ``` @@ -654,11 +655,11 @@ To deregister a route you must provide a *route_id*. #### **Environment** -- `KAPOW_URL` +- `KAPOW_DATA_URL` #### **Help** -```sh +``` console $ kapow route --help Usage: kapow route [OPTIONS] COMMAND [ARGS]... @@ -669,7 +670,7 @@ Commands: add remove ``` -```sh +``` console $ kapow route add --help Usage: kapow route add [OPTIONS] URL_PATTERN [COMMAND_FILE] @@ -680,7 +681,7 @@ Options: --url TEXT --help Show this message and exit. ``` -```sh +``` console $ kapow route remove --help Usage: kapow route remove [OPTIONS] ROUTE_ID @@ -691,8 +692,8 @@ Options: #### Example -```sh -kapow route add -X GET '/list/{ip}' -c 'nmap -sL $(kapow get /request/matches/ip) | kapow set /response/body' +``` console +$ kapow route add -X GET '/list/{ip}' -c 'nmap -sL $(kapow get /request/matches/ip) | kapow set /response/body' ``` ### `request` @@ -701,14 +702,14 @@ Exposes the requests' resources. #### **Environment** -- `KAPOW_URL` +- `KAPOW_DATA_URL` - `KAPOW_HANDLER_ID` #### Example -```sh -# Access the body of the request -kapow get /request/body +``` console +$ # Access the body of the request +$ kapow get /request/body ``` @@ -718,27 +719,28 @@ Exposes the response's resources. #### **Environment** -- `KAPOW_URL` +- `KAPOW_DATA_URL` - `KAPOW_HANDLER_ID` #### Example -```sh -# Write to the body of the response -echo 'Hello, World!' | kapow set /response/body +``` console +$ # Write to the body of the response +$ echo 'Hello, World!' | kapow set /response/body ``` ## An End-to-End Example -```sh -$ cat nmap.kpow +``` console +$ cat nmap-route +#!/usr/bin/env sh kapow route add -X GET '/list/{ip}' -c 'nmap -sL $(kapow get /request/matches/ip) | kapow set /response/body' ``` -```sh -$ kapow ./nmap.kapow +``` console +$ kapow server ./nmap-route ``` -```sh -$ curl $KAPOW_URL/list/127.0.0.1 +``` console +$ curl $KAPOW_DATA_URL/list/127.0.0.1 Starting Nmap 7.70 ( https://nmap.org ) at 2019-05-30 14:45 CEST Nmap scan report for localhost (127.0.0.1) Host is up (0.00011s latency). From 68a25e68b8b89d71c5af00f83644ea84d1b9cf69 Mon Sep 17 00:00:00 2001 From: pancho horrillo Date: Thu, 24 Dec 2020 14:16:39 +0100 Subject: [PATCH 4/4] feat: kapow server honors --debug MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Roberto Abdelkader Martínez Pérez --- internal/cmd/server.go | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/internal/cmd/server.go b/internal/cmd/server.go index 4686661d..df2efcd4 100644 --- a/internal/cmd/server.go +++ b/internal/cmd/server.go @@ -60,7 +60,7 @@ var ServerCmd = &cobra.Command{ server.StartServer(sConf) for _, path := range args { - go Run(path) + go Run(path, sConf.Debug) } select {} @@ -100,19 +100,21 @@ func validateServerCommandArguments(cmd *cobra.Command, args []string) error { return nil } -func Run(path string) { +func Run(path string, debug bool) { logger.L.Printf("Running init program %+q", path) cmd := BuildCmd(path) cmd.Env = os.Environ() var wg sync.WaitGroup - if stdout, err := cmd.StdoutPipe(); err == nil { - wg.Add(1) - go logPipe(path, "stdout", stdout, &wg) - } - if stderr, err := cmd.StderrPipe(); err == nil { - wg.Add(1) - go logPipe(path, "stderr", stderr, &wg) + if debug { + if stdout, err := cmd.StdoutPipe(); err == nil { + wg.Add(1) + go logPipe(path, "stdout", stdout, &wg) + } + if stderr, err := cmd.StderrPipe(); err == nil { + wg.Add(1) + go logPipe(path, "stderr", stderr, &wg) + } } err := cmd.Start() if err != nil {