├── .github └── workflows │ └── build-and-push-docker-multiarch-image.yml ├── Dockerfile ├── LICENSE ├── README.md ├── blogpost.md ├── entrypoint.sh ├── index.html ├── kubernetes ├── README.md └── multiool-daemonset.yml ├── leatherman-wave.jpg └── nginx.conf /.github/workflows/build-and-push-docker-multiarch-image.yml: -------------------------------------------------------------------------------- 1 | name: Docker Image Build and Push - CI Workflow 2 | 3 | on: 4 | push: 5 | branches: 6 | # master handles: "latest"; and tags "minimal" and "alpine-minimal" 7 | - master 8 | tags: 9 | - minimal 10 | - alpine-minimal 11 | 12 | jobs: 13 | build-and-push-muti-arch-docker-image: 14 | runs-on: ubuntu-latest 15 | env: 16 | DOCKER_IMAGE: network-multitool 17 | DOCKERHUB_ORGNAME: wbitt 18 | DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }} 19 | # 20 | # DOCKERHUB_USERNAME and DOCKERHUB_TOKEN are to be set in the GITHUB WEB UI, 21 | # under RepoName -> Settings -> Secrets 22 | 23 | steps: 24 | 25 | - name: Checkout 26 | uses: actions/checkout@v2 27 | 28 | - name: Prepare Docker Image Tags 29 | id: prep 30 | run: | 31 | SHORT_REF=$(basename ${GITHUB_REF}) 32 | SHORT_HASH=${GITHUB_SHA::7} 33 | TAGS="" 34 | if [[ ! -z "${SHORT_REF}" && "${SHORT_REF}" == "master" ]]; then 35 | echo "Found git commit on master branch. Setting docker image tag as: 'latest'" 36 | TAG=${DOCKERHUB_ORGNAME}/${DOCKER_IMAGE}:latest 37 | fi 38 | if [[ ! -z "${SHORT_REF}" && "${SHORT_REF}" != "master" ]]; then 39 | echo "Setting docker image tag as: '${SHORT_REF}'" 40 | TAG=${DOCKERHUB_ORGNAME}/${DOCKER_IMAGE}:${SHORT_REF} 41 | fi 42 | TAGS="${TAG},${DOCKERHUB_ORGNAME}/${DOCKER_IMAGE}:${SHORT_HASH}" 43 | echo "Complete Docker image-name and tags are setup as: ${TAGS}" 44 | echo ::set-output name=tags::${TAGS} 45 | 46 | - name: Set up QEMU 47 | uses: docker/setup-qemu-action@v1 48 | 49 | - name: Set up Docker Buildx 50 | uses: docker/setup-buildx-action@v1 51 | 52 | - name: Login to DockerHub 53 | uses: docker/login-action@v1 54 | with: 55 | username: ${{ secrets.DOCKERHUB_USERNAME }} 56 | password: ${{ secrets.DOCKERHUB_TOKEN }} 57 | 58 | - name: Build and push 59 | uses: docker/build-push-action@v2 60 | with: 61 | context: . 62 | file: ./Dockerfile 63 | platforms: linux/386,linux/amd64,linux/arm/v7,linux/arm64,linux/ppc64le,linux/s390x 64 | push: true 65 | tags: ${{ steps.prep.outputs.tags }} 66 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:3.18 2 | 3 | MAINTAINER Kamran Azeem & Henrik Høegh (kamranazeem@gmail.com, henrikrhoegh@gmail.com) 4 | 5 | EXPOSE 80 443 1180 11443 6 | 7 | # Install some tools in the container and generate self-signed SSL certificates. 8 | # Packages are listed in alphabetical order, for ease of readability and ease of maintenance. 9 | RUN apk update \ 10 | && apk add bash bind-tools busybox-extras curl \ 11 | iproute2 iputils jq mtr \ 12 | net-tools nginx openssl \ 13 | perl-net-telnet procps tcpdump tcptraceroute wget \ 14 | && mkdir /certs /docker \ 15 | && chmod 700 /certs \ 16 | && openssl req \ 17 | -x509 -newkey rsa:2048 -nodes -days 3650 \ 18 | -keyout /certs/server.key -out /certs/server.crt -subj '/CN=localhost' 19 | 20 | 21 | # Copy a simple index.html to eliminate text (index.html) noise which comes with default nginx image. 22 | # (I created an issue for this purpose here: https://github.com/nginxinc/docker-nginx/issues/234) 23 | 24 | COPY index.html /usr/share/nginx/html/ 25 | 26 | 27 | # Copy a custom/simple nginx.conf which contains directives 28 | # to redirected access_log and error_log to stdout and stderr. 29 | # Note: Don't use '/etc/nginx/conf.d/' directory for nginx virtual hosts anymore. 30 | # This 'include' will be moved to the root context in Alpine 3.14. 31 | 32 | COPY nginx.conf /etc/nginx/nginx.conf 33 | 34 | COPY entrypoint.sh /docker/entrypoint.sh 35 | 36 | 37 | # Start nginx in foreground: 38 | CMD ["/usr/sbin/nginx", "-g", "daemon off;"] 39 | 40 | 41 | 42 | # Note: If you have not included the "bash" package, then it is "mandatory" to add "/bin/sh" 43 | # in the ENTNRYPOINT instruction. 44 | # Otherwise you will get strange errors when you try to run the container. 45 | # Such as: 46 | # standard_init_linux.go:219: exec user process caused: no such file or directory 47 | 48 | # Run the startup script as ENTRYPOINT, which does few things and then starts nginx. 49 | ENTRYPOINT ["/bin/sh", "/docker/entrypoint.sh"] 50 | 51 | 52 | 53 | 54 | 55 | ################################################################################################### 56 | 57 | # Build and Push (to dockerhub) instructions: 58 | # ------------------------------------------- 59 | # docker build -t local/network-multitool . 60 | # docker tag local/network-multitool praqma/network-multitool 61 | # docker login 62 | # docker push praqma/network-multitool 63 | 64 | 65 | # Pull (from dockerhub): 66 | # ---------------------- 67 | # docker pull praqma/network-multitool 68 | 69 | 70 | # Usage - on Docker: 71 | # ------------------ 72 | # docker run --rm -it praqma/network-multitool /bin/bash 73 | # OR 74 | # docker run -d praqma/network-multitool 75 | # OR 76 | # docker run -p 80:80 -p 443:443 -d praqma/network-multitool 77 | # OR 78 | # docker run -e HTTP_PORT=1180 -e HTTPS_PORT=11443 -p 1180:1180 -p 11443:11443 -d praqma/network-multitool 79 | 80 | 81 | # Usage - on Kubernetes: 82 | # --------------------- 83 | # kubectl run multitool --image=praqma/network-multitool 84 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Praqma 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WBITT Network-Multitool (Formerly `praqma/Network-MultiTool`) 2 | 3 | A (**multi-arch**) multitool for container/network testing and troubleshooting. The main docker image is based on Alpine Linux. There is a Fedora variant to be used in environments which require the image to be based only on RedHat Linux, or any of it's derivatives. 4 | 5 | The container image contains lots of tools, as well as a `nginx` web server, which listens on port `80` and `443` by default. The web server helps to run this container-image in a straight-forward way, so you can simply `exec` into the container and use various tools. 6 | 7 | ## Note about name/org change: 8 | In September 2016, I created this tool and maintained it with [Henrik](https://github.com/hoeghh) - as `praqma/network-multitool`. During 2020-2021 Praqma was bought by another company, and the new management did not want to promote the **"Praqma"** brand. This meant the network-multitool's git and docker repositories must go. It was decided by the representatives of the company at that time to hand over the ownership/maintenance of this project to me, so I can continue maintaining it. Apart from a small change in the repository name, nothing in the tool has changed. 9 | 10 | The docker repository to pull this image is now: [https://hub.docker.com/r/wbitt/network-multitool](https://hub.docker.com/r/wbitt/network-multitool) 11 | 12 | Or: 13 | 14 | ``` 15 | docker pull wbitt/network-multitool 16 | ``` 17 | 18 | 19 | ## Supported platforms: 20 | * linux/386 21 | * linux/amd64 22 | * linux/arm/v7 23 | * linux/arm64 24 | 25 | ## Downloadable from Docker Hub: 26 | * [https://hub.docker.com/r/wbitt/network-multitool](https://hub.docker.com/r/wbitt/network-multitool) (An automated multi-arch build) 27 | 28 | ## Variants / image tags: 29 | * **latest**, minimal, alpine-minimal ( The main/default **'minimal'** image - Alpine based ) 30 | * extra, alpine-extra (Alpine based image - with **extra tools** ) 31 | * openshift , openshift-minimal (openshift compatible - **minimal**) - Ports: **1180, 11443** 32 | * openshift-extra (openshift compatible with **extra tools**) - Ports: **1180, 11443** 33 | * fedora, fedora-minimal ( **'Minimal'** Fedora based image ) 34 | 35 | 36 | ### Important notes about openshift variant: 37 | Openshift is very strict about how a container image should run. So, the **openshift variant** of the multitool has the following limitations / changes: 38 | 39 | * Runs as non-root ; which means some tools (e.g. `traceroute`, `tcptraceroute`, etc, will not work) 40 | * Listens on ports `1180` and `11443` - **not** `80` and `443` 41 | * Some executable files are manually set as `setuid`, so those tools remain usable. Tools set with `setuid` are: 42 | * apk 43 | * arping 44 | * busybox 45 | * mii-tool 46 | * tcpdump 47 | * tcptraceroute 48 | * traceroute 49 | * tshark 50 | 51 | Remember, this *multitool* is purely a troubleshooting tool, and should be used as such. It is not designed to abuse openshift (or any system's) security, nor should it be used to do so. 52 | 53 | 54 | ## Tools included in "latest, minimal, alpine-minimal , openshift, openshift-minimal": 55 | * apk package manager 56 | * Nginx Web Server (port `80`, port `443`) - with customizable ports! 57 | * awk, cut, diff, find, grep, sed, vi editor, wc 58 | * curl, wget 59 | * dig, nslookup 60 | * ip, ifconfig, route 61 | * traceroute, tracepath, mtr, tcptraceroute (for layer 4 packet tracing) 62 | * ping, arp, arping 63 | * ps, netstat 64 | * gzip, cpio, tar 65 | * telnet client 66 | * tcpdump 67 | * jq 68 | * bash 69 | 70 | **Size:** 16 MB compressed, 38 MB uncompressed 71 | 72 | ## Tools included in "extra, alpine-extra, openshift-extra": 73 | All tools from "minimal", plus: 74 | * iperf3 75 | * ethtool, mii-tool, route 76 | * nmap 77 | * ss 78 | * tshark 79 | * ssh client, lftp client, rsync, scp 80 | * netcat (nc), socat 81 | * ApacheBench (ab) 82 | * mysql & postgresql client 83 | * git 84 | 85 | **Size:** 64 MB compressed, 220 MB uncompressed 86 | 87 | 88 | ## Tools included in "fedora, fedora-minimal": 89 | * YUM package manager 90 | * Nginx Web Server (port 80, port 443) - customizable ports! 91 | * wget, curl 92 | * dig, nslookup 93 | * ip, ifconfig, route, traceroute, tracepath, mtr 94 | * ping, arp, arping 95 | * ps, netstat 96 | * gzip, cpio, tar 97 | * telnet client 98 | * awk, cut, diff, find, grep, sed, vi editor, wc 99 | * jq 100 | * `/bin/sh` shell interpreter - not `/bin/bash` 101 | 102 | **Size:** 72 MB uncompressed 103 | 104 | 105 | **Note:** The SSL certificates are generated for "localhost", are self signed, and placed in `/certs/` directory. During your testing, ignore the certificate warning/error. While using curl, you can use `-k` to ignore SSL certificate warnings/errors. 106 | 107 | ------ 108 | 109 | # How to use this image? 110 | ## How to use this image in normal **container/pod network** ? 111 | 112 | ### Docker: 113 | ``` 114 | $ docker run -d wbitt/network-multitool 115 | ``` 116 | 117 | Then: 118 | 119 | ``` 120 | $ docker exec -it container-name /bin/bash 121 | ``` 122 | 123 | 124 | ### Kubernetes: 125 | 126 | Create single pod - without a deployment: 127 | ``` 128 | $ kubectl run multitool --image=wbitt/network-multitool 129 | ``` 130 | 131 | Create a deployment: 132 | ``` 133 | $ kubectl create deployment multitool --image=wbitt/network-multitool 134 | ``` 135 | 136 | Then: 137 | ``` 138 | $ kubectl exec -it pod-name /bin/bash 139 | ``` 140 | 141 | **Note:** You can pass additional parameter `--namespace=` to the above kubectl commands. 142 | 143 | 144 | ### Openshift: 145 | 146 | ``` 147 | $ oc new-project test-project-1 148 | 149 | $ oc new-app wbitt/network-multitool:openshift --name multitool-openshift 150 | 151 | $ oc status 152 | 153 | $ oc get pods 154 | 155 | $ oc logs pod-name 156 | 157 | $ oc exec -it pod-name /bin/sh 158 | 159 | $ oc port-forward pod-name 1180:1180 11443:11443 160 | ``` 161 | 162 | 163 | ## How to use this image on **host network** ? 164 | 165 | Sometimes you want to do testing using the **host network**. This can be achieved by running the multitool using host networking. 166 | 167 | 168 | ### Docker: 169 | ``` 170 | $ docker run --network host -d wbitt/network-multitool 171 | ``` 172 | 173 | **Note:** If port 80 and/or 443 are already busy on the host, then use pass the extra arguments to multitool, so it can listen on a different port, as shown below: 174 | 175 | ``` 176 | $ docker run --network host -e HTTP_PORT=1180 -e HTTPS_PORT=11443 -d wbitt/network-multitool 177 | ``` 178 | 179 | ### Kubernetes: 180 | For Kubernetes, there is YAML/manifest file `multitool-daemonset.yaml` in the `kubernetes` directory, that will run an instance of the multitool on all hosts in the cluster using host networking. 181 | 182 | ``` 183 | $ kubectl apply -f kubernetes/multitool-daemonset.yaml 184 | ``` 185 | 186 | **Notes:** 187 | * You can pass additional parameter `--namespace=` to the above kubectl command. 188 | * Due to a possibility of something (some service) already listening on port 80 and 443 on the worker nodes, the `daemonset` is configured to run multitool on port `1180` and `11443`. You can change this in the YAML file if you want. 189 | 190 | 191 | # Configurable HTTP and HTTPS ports: 192 | There are times when one may want to join this (multitool) container to another container's IP namespace for troubleshooting, or on the host network. This is true for both Docker and Kubernetes platforms. During that time if the container in question is a web server (nginx, apache, etc), or a reverse-proxy (traefik, nginx, haproxy, etc), then network-multitool cannot join it in the same IP namespace on Docker, and similarly it cannot join the same pod on Kubernetes. This happens because network multitool also runs a web server on port 80 (and 443), and this results in port conflict on the same IP address. To help in this sort of troubleshooting, there are two environment variables **HTTP_PORT** and **HTTPS_PORT** , which you can use to provide the values of your choice instead of 80 and 443. When the container starts, it uses the values provided by you/user to listen for incoming connections. Below is an example: 193 | 194 | ``` 195 | $ docker run -e HTTP_PORT=1180 -e HTTPS_PORT=11443 \ 196 | -p 1180:1180 -p 11443:11443 -d local/network-multitool 197 | 4636efd4660c2436b3089ab1a979e5ce3ae23055f9ca5dc9ffbab508f28dfa2a 198 | 199 | 200 | $ docker ps 201 | CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 202 | 4636efd4660c local/network-multitool "/docker-entrypoint.…" 4 seconds ago Up 3 seconds 80/tcp, 0.0.0.0:1180->1180/tcp, 443/tcp, 0.0.0.0:11443->11443/tcp recursing_nobel 203 | 6e8b6ed8bfa6 nginx "nginx -g 'daemon of…" 56 minutes ago Up 56 minutes 80/tcp nginx 204 | 205 | 206 | $ curl http://localhost:1180 207 | Praqma Network MultiTool (with NGINX) - 4636efd4660c - 172.17.0.3/16 - HTTP: 1180 , HTTPS: 11443 208 | 209 | 210 | $ curl -k https://localhost:11443 211 | Praqma Network MultiTool (with NGINX) - 4636efd4660c - 172.17.0.3/16 - HTTP: 1180 , HTTPS: 11443 212 | ``` 213 | 214 | If these environment variables are absent/not-provided, the container will listen on normal/default ports 80 and 443. 215 | 216 | ------ 217 | 218 | # FAQs 219 | ## Why this multitool runs a web-server? 220 | Well, normally, if a container does not run a daemon/service, then running it (the container) involves using *creative ways / hacks* to keep it alive. If you don't want to suddenly start browsing the internet for "those creative ways", then it is best to run a small web server in the container - as the default process. 221 | 222 | This helps you when you are using Docker. You simply execute: 223 | ``` 224 | $ docker run -d wbitt/network-multitool 225 | ``` 226 | 227 | This also helps when you are using kubernetes. You simply execute: 228 | ``` 229 | $ kubectl run multitool --image=wbitt/network-multitool 230 | ``` 231 | 232 | 233 | The multitool container starts as web server - so it remains `UP`. Then, you simply connect to it using: 234 | ``` 235 | $ docker exec -it some-silly-container-name /bin/sh 236 | ``` 237 | 238 | Or, on Kubernetes: 239 | ``` 240 | $ kubectl exec -it multitool-3822887632-pwlr1 -- /bin/sh 241 | ``` 242 | 243 | This is why it is good to have a web-server in this tool. Hope this answers the question! Besides, I believe that having a web server in a multitool is like having yet another tool! Personally, I think this is cool! [Henrik](https://www.linkedin.com/in/henrikrenehoegh/) thinks the same! 244 | 245 | 246 | ## I can't find a tool I need for my use-case? 247 | We have tried to put in all the most commonly used tools, while keeping it small and practical. We can't have all the tools under the sun, otherwise it will end up as [something like this](https://www.amazon.ca/Wenger-16999-Swiss-Knife-Giant/dp/B001DZTJRQ). 248 | 249 | However, if you have a special need, for a special tool, for your special use-case, then I would recommend to simply build your own docker image using this one as base image, and expanding it with the tools you need. 250 | 251 | ## Why not use LetsEncrypt for SSL certificates instead of generating your own? 252 | There is absolutely no need to use LetsEncrypt. This is a testing tool, and validity of SSL certificates does not matter. 253 | 254 | ## Why use a daemonset when troubleshooting host networking on Kubernetes? 255 | One could argue that it is possible to simply install the tools on the hosts and get over with it. However, we should keep the infrastructure immutable and not install anything on the hosts. *Ideally* we should never `ssh` to our cluster worker nodes. Some of the reasons are: 256 | 257 | * It is generally cumbersome to install the tools since they might be needed on several hosts. 258 | * New packages may conflict with existing packages, and *may* break some functionality of the host. 259 | * Removing the tools and dependencies after use could be difficult, as it *may* break some functionality of the host. 260 | * By using a `daemonset`, it makes it easier to integrate with other resources. e.g. Use volumes for packet capture files, etc. 261 | * Using the `daemonset` provides a *'cloud native'* approach to provision debugging/testing tools. 262 | * You can `exec` into the `daemonset`, without needing to SSH into the node. 263 | 264 | 265 | ## How to contribute to this project? 266 | Contributions are welcome for packages/tools considered **"absolutely necessary"**, of **"core"** nature, are **"minimal"** in size, and **"have large number of use-cases"**. Remember, the goal is not to create yet another Linux distribution! :) 267 | 268 | -------------------------------------------------------------------------------- /blogpost.md: -------------------------------------------------------------------------------- 1 | # The Network Multitool container image 2 | 3 | ![](leatherman-wave.jpg) 4 | 5 | ## The itch 6 | There was a time ... actually ... many a times, when I needed more than just ping to reach a container running on a particular docker host on a particular docker / container network. 7 | 8 | We know that the idea behind a docker container in general is that it should have just enough software to make sure that the process/service it is supposed to run, is run without any problems; for example: a web server, a java application server, database server, etc. 9 | 10 | Just because these docker (container) images are very minimalistic, (in terms of size too), they have no other tools installed in them, making them very lean in nature. If a container is to run a single process all it's life, why bother filling it up with software which is never going to be used! Great! But since they are lean, troubleshooting them sometimes is difficult. 11 | 12 | Recently I was working on a Kubernetes cluster, and was not able to resolve services' names setup and provided by Kubernetes addon called SkyDNS. I had nginx running as a container, and being minimalistic in nature, it had no tools inside it except ping. It didn't even have nslookup! I installed nslookup in a running container, the usual `apt-get update` and `apt-get install dnsutils`. It is another story that nslookup alone did not prove to be much helpful in this particular case. It was just not giving me enough information about what was happening with name resolution. I was not until I decided to install `dig` that I figured out what was going on! It actually took me many container starts and many times going through the same `apt-get` update and install routines, until I reached a point to install dig instead of nslookup and things got very clear after that. 13 | 14 | This officially was a nasty (or very interesting) itch (depending on how you look at it), and I needed a solution for such situations. 15 | 16 | 17 | ## The solution 18 | Being a big fan and user of the multitools, such as the [Leatherman Wave](https://www.leatherman.com/wave-10.html) that I carry at all times with me as [EDC](https://en.wikipedia.org/wiki/Everyday_carry), I needed a container image which would have all the necessary tools installed in it, which I could use at will - without getting into the apt-* mess. I also wanted the container image to run as a standard (web service) pod too, so I could get two things out of it: 19 | 20 | * I will always have a web service to test my connections - nginx in this case; and, 21 | * I will just 'exec' and 'bash' into it and not have to remember complex kubectl commands to run it in interactive mode. 22 | 23 | So I went ahead and created [praqma/network-multitool](https://hub.docker.com/r/praqma/network-multitool/). I am a RedHat fan, so I based my image on CENTOS:7 . Intially I had Apache as web server, but later I replaced it with nginx - nginx being very light weight and very fast. 24 | 25 | ## Note about name/org change - Dec 2021: 26 | Earlier, I maintained this tool, as `praqma/network-multitool`. Praqma was bought by another company, and now the **"Praqma"** brand is being dismantled. This means the network-multitool's git and docker repositories must go. It was decided by the current representatives of the company to hand it over to me so I can continue maintaining it. So, except the small change in the repository name, nothing has changed. The docker repository to pull this image is now: [https://hub.docker.com/r/wbitt/network-multitool](https://hub.docker.com/r/wbitt/network-multitool) 27 | 28 | ## Example usage: 29 | This image can be used in any environment which allows you to run containers; docker, docker-swarm, kubernetes, etc. Here are few eamples on how you can run and use this image: 30 | 31 | ### On Docker host: 32 | 33 | First, pull the image, though not entirely necessary. 34 | ``` 35 | [kamran@kworkhorse ~]$ docker pull wbitt/network-multitool 36 | Using default tag: latest 37 | Trying to pull repository docker.io/wbitt/network-multitool ... 38 | sha256:970b1ef9c12f4368c67b1fdbcaaedf4030861dae8263a410701fe040d59d1317: Pulling from docker.io/wbitt/network-multitool 39 | 40 | 93857f76ae30: Already exists 41 | 6c1308705eea: Pull complete 42 | 82a705257117: Pull complete 43 | 1a824777af48: Pull complete 44 | dfe620fcbab4: Pull complete 45 | 417b019d6dbe: Pull complete 46 | 1d9e1b44b10a: Pull complete 47 | 4e922c058a8f: Pull complete 48 | Digest: sha256:970b1ef9c12f4368c67b1fdbcaaedf4030861dae8263a410701fe040d59d1317 49 | Status: Downloaded newer image for docker.io/wbitt/network-multitool:latest 50 | [kamran@kworkhorse ~]$ 51 | ``` 52 | 53 | **Interactive:** 54 | ``` 55 | [kamran@kworkhorse ~]$ docker run --rm -it wbitt/network-multitool bash 56 | 57 | [root@92288413e051 /]# nslookup yahoo.com 58 | Server: 192.168.100.1 59 | Address: 192.168.100.1#53 60 | 61 | Non-authoritative answer: 62 | Name: yahoo.com 63 | Address: 98.138.253.109 64 | Name: yahoo.com 65 | Address: 98.139.183.24 66 | Name: yahoo.com 67 | Address: 206.190.36.45 68 | 69 | [root@92288413e051 /]# 70 | ``` 71 | 72 | **Datached / Daemon mode:** 73 | ``` 74 | [kamran@kworkhorse ~]$ docker run -P -d wbitt/network-multitool 75 | a76d156c674f2b61c9b9fb10f87c645620c4fcbe88a13162546379abc9a87f14 76 | [kamran@kworkhorse ~]$ 77 | ``` 78 | 79 | ``` 80 | [kamran@kworkhorse ~]$ docker ps 81 | CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 82 | a76d156c674f wbitt/network-multitool "/start_nginx.sh" 31 seconds ago Up 30 seconds 0.0.0.0:32769->80/tcp, 0.0.0.0:32768->443/tcp silly_franklin 83 | [kamran@kworkhorse ~]$ 84 | ``` 85 | 86 | ``` 87 | [kamran@kworkhorse ~]$ docker exec -it silly_franklin bash 88 | 89 | [root@a76d156c674f /]# curl -I yahoo.com 90 | HTTP/1.1 301 Redirect 91 | Date: Sun, 16 Apr 2017 16:09:20 GMT 92 | Via: https/1.1 ir28.fp.ne1.yahoo.com (ApacheTrafficServer) 93 | Server: ATS 94 | Location: https://www.yahoo.com/ 95 | Content-Type: text/html 96 | Content-Language: en 97 | Cache-Control: no-store, no-cache 98 | Connection: keep-alive 99 | Content-Length: 304 100 | 101 | [root@a76d156c674f /]# 102 | ``` 103 | 104 | ### On Kubernetes cluster: 105 | First run the container image as **deployment (detached/daemon mode)**: 106 | 107 | ``` 108 | [kamran@kworkhorse ~]$ kubectl run multitool --image=wbitt/network-multitool 109 | deployment "multitool" created 110 | [kamran@kworkhorse ~]$ 111 | ``` 112 | 113 | Find the pod name and connect to it in **interactive mode**: 114 | ``` 115 | [kamran@kworkhorse ~]$ kubectl get pods 116 | NAME READY STATUS RESTARTS AGE 117 | multitool-2814616439-hd8p6 1/1 Running 0 1m 118 | [kamran@kworkhorse ~]$ 119 | ``` 120 | 121 | ``` 122 | [kamran@kworkhorse ~]$ kubectl exec -it multitool-2814616439-hd8p6 bash 123 | 124 | [root@multitool-2814616439-hd8p6 /]# traceroute google.com 125 | traceroute to google.com (64.233.184.102), 30 hops max, 60 byte packets 126 | 1 gateway (10.112.1.1) 0.044 ms 0.014 ms 0.009 ms 127 | 2 wa-in-f102.1e100.net (64.233.184.102) 0.716 ms 0.701 ms 0.896 ms 128 | [root@multitool-2814616439-hd8p6 /]# exit 129 | exit 130 | [kamran@kworkhorse ~]$ 131 | ``` 132 | 133 | ## Summary: 134 | Creating this network-multitool image has completely soothed this itch. Now, I use it to solve all sorts of problems. Packet capture, telnet , traceroute, mtr, dig, netstat, curl - you name it, it probably has it! I hope you will enjoy using this multitool as much as well as we do at Praqma. 135 | 136 | 137 | -------------------------------------------------------------------------------- /entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | 4 | # If the html directory is mounted, it means user has mounted some content in it. 5 | # In that case, we must not over-write the index.html file. 6 | 7 | WEB_ROOT=/usr/share/nginx/html 8 | MOUNT_CHECK=$(mount | grep ${WEB_ROOT}) 9 | HOSTNAME=$(hostname) 10 | 11 | if [ -z "${MOUNT_CHECK}" ] ; then 12 | echo "The directory ${WEB_ROOT} is not mounted." 13 | echo "Therefore, over-writing the default index.html file with some useful information:" 14 | 15 | # CONTAINER_IP=$(ip addr show eth0 | grep -w inet| awk '{print $2}') 16 | # Note: 17 | # CONTAINER IP cannot always be on device 'eth0'. 18 | # It could be something else too, as pointed by @arnaudveron . 19 | # The 'ip -j route' shows JSON output, 20 | # and always shows the default route as the first entry. 21 | # It also shows the correct device name as 'prefsrc', with correct IP address. 22 | CONTAINER_IP=$(ip -j route get 1 | jq -r '.[0] .prefsrc') 23 | 24 | # Reduced the information in just one line. It overwrites the default text. 25 | echo -e "WBITT Network MultiTool (with NGINX) - ${HOSTNAME} - ${CONTAINER_IP} - HTTP: ${HTTP_PORT:-80} , HTTPS: ${HTTPS_PORT:-443} . (Formerly praqma/network-multitool)" | tee ${WEB_ROOT}/index.html 26 | else 27 | echo "The directory ${WEB_ROOT} is a volume mount." 28 | echo "Therefore, will not over-write index.html" 29 | echo "Only logging the container characteristics:" 30 | echo -e "WBITT Network MultiTool (with NGINX) - ${HOSTNAME} - ${CONTAINER_IP} - HTTP: ${HTTP_PORT:-80} , HTTPS: ${HTTPS_PORT:-443} . (Formerly praqma/network-multitool)" 31 | 32 | fi 33 | 34 | # Custom/user-defined ports: 35 | # ------------------------- 36 | # If the env variables HTTP_PORT and HTTPS_PORT are set, then 37 | # modify/Replace default listening ports 80 and 443 to whatever the user wants. 38 | # If these variables are not defined, then the default ports 80 and 443 are used. 39 | 40 | if [ -n "${HTTP_PORT}" ]; then 41 | echo "Replacing default HTTP port (80) with the value specified by the user - (HTTP_PORT: ${HTTP_PORT})." 42 | sed -i "s/80/${HTTP_PORT}/g" /etc/nginx/nginx.conf 43 | fi 44 | 45 | if [ -n "${HTTPS_PORT}" ]; then 46 | echo "Replacing default HTTPS port (443) with the value specified by the user - (HTTPS_PORT: ${HTTPS_PORT})." 47 | sed -i "s/443/${HTTPS_PORT}/g" /etc/nginx/nginx.conf 48 | fi 49 | 50 | 51 | # Execute the command specified as CMD in Dockerfile: 52 | exec "$@" 53 | 54 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | WBITT Network MultiTool based on Alpine Linux 2 | -------------------------------------------------------------------------------- /kubernetes/README.md: -------------------------------------------------------------------------------- 1 | Running the multitool on Kubernetes using the POD network is as simple as: 2 | 3 | ```shell 4 | $ kubectl run nwtool --image wbitt/network-multitool 5 | ``` 6 | 7 | This allows you to use the utilities from the multitool to test on the POD 8 | network, however, sometimes you want to do similar testing using the host 9 | network. This can be achieved by running the multitool using host 10 | networking. The manifest file in this folder contain a daemonset definition that 11 | will run an instance of the multitool on all hosts in the cluster using host 12 | networking. 13 | 14 | Obviously one could simply install the tools on the hosts, however, there are 15 | several reasons why this is not ideal. 16 | 17 | - We should keep the infrastructure immutable and not install anything on the 18 | hosts. Ideally we should never ssh to our cluster hosts. 19 | 20 | - Its cumbersome to install the tools since they might be needed on several hosts 21 | 22 | - Removing the tools and dependencies after use could be difficult 23 | 24 | - Using the tools from a Kubernetes resource allows one to integrate with other 25 | resources like e.g. volumes for packet capture files. 26 | 27 | Using the daemonset provides a 'cloud native' approach to provision 28 | debugging/testing tools. 29 | -------------------------------------------------------------------------------- /kubernetes/multiool-daemonset.yml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: DaemonSet 3 | metadata: 4 | name: network-multitool 5 | labels: 6 | tier: node 7 | app: network-multitool 8 | spec: 9 | selector: 10 | matchLabels: 11 | tier: node 12 | app: network-multitool 13 | template: 14 | metadata: 15 | labels: 16 | tier: node 17 | app: network-multitool 18 | spec: 19 | hostNetwork: true 20 | tolerations: 21 | - operator: Exists 22 | effect: NoSchedule 23 | containers: 24 | - name: network-multitool 25 | image: wbitt/network-multitool 26 | env: 27 | - name: HTTP_PORT 28 | value: "1180" 29 | - name: HTTPS_PORT 30 | value: "11443" 31 | ports: 32 | - containerPort: 1180 33 | name: http-port 34 | - containerPort: 11443 35 | name: https-port 36 | resources: 37 | requests: 38 | cpu: "1m" 39 | memory: "20Mi" 40 | limits: 41 | cpu: "10m" 42 | memory: "20Mi" 43 | securityContext: 44 | runAsUser: 0 45 | capabilities: 46 | add: ["NET_ADMIN"] 47 | -------------------------------------------------------------------------------- /leatherman-wave.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wbitt/Network-MultiTool/f23a0853f77f723a309ee8cfb3b0b6951b63f148/leatherman-wave.jpg -------------------------------------------------------------------------------- /nginx.conf: -------------------------------------------------------------------------------- 1 | # This file is copied as /etc/nginx/nginx.conf inside the container image. 2 | 3 | user nginx; 4 | worker_processes 1; 5 | 6 | 7 | # Run in foreground by turning off daemon mode. 8 | # Either set it in this file: 9 | # daemon off; 10 | # 11 | # OR 12 | # 13 | # Pass it as an argument on command line. 14 | # CMD ["/usr/sbin/nginx", "-g", "daemon off;"] 15 | 16 | 17 | # Note: The PID directory needs to exist beforehand. 18 | # Also, the pid file is always created as/by user root. 19 | pid /var/run/nginx.pid; 20 | 21 | # Forward error logs to docker log collector, 22 | # by sending it to stderr instead of a log file. 23 | error_log /dev/stderr warn; 24 | 25 | events { 26 | worker_connections 32; 27 | } 28 | 29 | 30 | http { 31 | include /etc/nginx/mime.types; 32 | default_type application/octet-stream; 33 | 34 | log_format main '$remote_addr - $remote_user [$time_local] "$request" ' 35 | '$status $body_bytes_sent "$http_referer" ' 36 | '"$http_user_agent" "$http_x_forwarded_for"'; 37 | 38 | # Forward access logs to docker log collector, 39 | # by sending it to stdout instead of a log file. 40 | # This directive must be inside the nginx main 'http' section - not outside. 41 | access_log /dev/stdout main; 42 | 43 | sendfile on; 44 | 45 | keepalive_timeout 65; 46 | 47 | #gzip on; 48 | 49 | server { 50 | listen 80; 51 | server_name localhost; 52 | 53 | location / { 54 | root /usr/share/nginx/html; 55 | index index.html index.htm; 56 | } 57 | } 58 | 59 | server { 60 | listen 443 ssl; 61 | server_name localhost; 62 | 63 | location / { 64 | root /usr/share/nginx/html; 65 | index index.html index.htm; 66 | } 67 | 68 | ssl_certificate /certs/server.crt; 69 | ssl_certificate_key /certs/server.key; 70 | } 71 | } 72 | 73 | --------------------------------------------------------------------------------