Skip to content

Commit 1f9ecad

Browse files
author
Nigel Sotelo
committed
Initial commit
0 parents  commit 1f9ecad

16 files changed

+560
-0
lines changed

.dockerignore

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
./Makefile
2+
./bin
3+
./python-client

.gitignore

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
*.egg-info
2+
.venv
3+
__pycache__
4+
bin
5+
ninja-proxy

Dockerfile

+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
############################
2+
# STEP 1 build executable binary
3+
############################
4+
# golang alpine 1.15.1
5+
FROM golang@sha256:bc6f2b94340ed84ec22dab364505b0d1a339ecff99b583a63372ecf966129be2 as builder
6+
7+
# Install git + SSL ca certificates.
8+
# Git is required for fetching the dependencies.
9+
# Ca-certificates is required to call HTTPS endpoints.
10+
RUN apk update && apk add --no-cache git ca-certificates tzdata && update-ca-certificates
11+
12+
# Create appuser
13+
ENV USER=appuser
14+
ENV UID=10001
15+
16+
# See https://stackoverflow.com/a/55757473/12429735
17+
RUN adduser \
18+
--disabled-password \
19+
--gecos "" \
20+
--home "/nonexistent" \
21+
--shell "/sbin/nologin" \
22+
--no-create-home \
23+
--uid "${UID}" \
24+
"${USER}"
25+
WORKDIR $GOPATH/src/mypackage/myapp/
26+
COPY server-src/ .
27+
28+
# Fetch dependencies.
29+
RUN go get -d -v
30+
31+
# Build the binary
32+
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build \
33+
-ldflags='-w -s -extldflags "-static"' -a \
34+
-o /go/bin/ninja-proxy .
35+
36+
############################
37+
# STEP 2 build a small image
38+
############################
39+
FROM scratch
40+
41+
# Import from builder.
42+
COPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo
43+
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
44+
COPY --from=builder /etc/passwd /etc/passwd
45+
COPY --from=builder /etc/group /etc/group
46+
47+
# Copy our static executable
48+
COPY --from=builder /go/bin/ninja-proxy /go/bin/ninja-proxy
49+
50+
# Use an unprivileged user.
51+
USER appuser:appuser
52+
53+
EXPOSE 7777
54+
55+
# Run the binary.
56+
ENTRYPOINT ["/go/bin/ninja-proxy"]

Makefile

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
.DEFAULT_GOAL := help
2+
SHELL := /bin/bash
3+
4+
.PHONY: help
5+
help: ## Print documentation
6+
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
7+
8+
.PHONY: binary
9+
binary: ## Build the go binary
10+
mkdir -p bin
11+
cd server-src && go build -o ../bin/ninja-proxy
12+
13+
.PHONY: docker
14+
docker: ## Build the docker image
15+
docker build -t "ninja-proxy:latest" .

README.md

+76
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
# Ninja proxy
2+
<p align="center">
3+
<img alt="ninja cat image" src="https://i.ibb.co/S54DPbc/ninja.png" />
4+
</p>
5+
6+
## Generate temporary URLs with a stateless service
7+
This project grew out of the need to share authenticated proxies with third-party services, so that they can all operate with the same IP. Several proxy-providers provide mechanisms for this, but they usually require sending your username and password to the third-party and this can be very risky. Ideally you want some temporary URL that opaquely routes the traffic from the third-party to your proxy provider, and that is exactly what this project provides.
8+
9+
### Quick start
10+
Clone this repo and build the project. Assuming that golang is installed, you build the server using
11+
12+
`make binary`
13+
14+
that will put it into `./bin/ninja-proxy`.
15+
16+
You can install the helper utilities by running
17+
18+
```bash
19+
source activate.sh
20+
```
21+
22+
and load a key into your terminal with
23+
24+
```bash
25+
KEY=$(ninja-key)
26+
```
27+
28+
Now you can run the service (in the background) using
29+
30+
```bash
31+
bin/ninja-proxy -key $KEY &
32+
```
33+
which by default will run on 0.0.0.0:7777.
34+
35+
To generate a link for the service call
36+
37+
```bash
38+
link=$(ninja-link 120 $KEY http://username:[email protected]:80 --headers test-header=foo another=bar)
39+
```
40+
41+
```bash
42+
$ curl $link/headers
43+
{
44+
"headers": {
45+
"Accept": "*/*",
46+
"Another": "bar",
47+
"Authorization": "Basic dXNlcm5hbWU6cGFzc3dvcmQ=",
48+
"Host": "httpbin.org",
49+
"Test-Header": "foo",
50+
"User-Agent": "curl/7.72.0",
51+
"X-Amzn-Trace-Id": "Root=1-5f56788f-d9513fb01690f9080adeb528"
52+
}
53+
}
54+
```
55+
Notice how the username and password are automatically added to the `Authorization` header, you can do the same thing with a proxy URL and it will just work.
56+
57+
The generated link contains a username and header field with all of this information encrypted so that it can't be read. It looks quite messy, but it works.
58+
59+
#### Proxies
60+
There's not very much else to say, use exactly the same procedure as above to wrap your proxy URL and use it as normal
61+
62+
```bash
63+
proxy_link=$(ninja-link 120 $KEY "http://$PROXY_USER:$PROXY_PASS@$PROXY_HOST:$PROXY_PORT")
64+
curl -x $proxy_link -k http://httpbin.org/ip
65+
```
66+
67+
### Docker image
68+
A dockerfile has been included with this repository and can be built using `make docker`. Then you can run this using
69+
70+
```
71+
NINJA_PORT=7777
72+
docker run --rm -d -p $NINJA_PORT:7777 ninja-proxy
73+
```
74+
75+
### Limitations
76+
This is a young project created for a specific problem at hand. You can use this to proxy both HTTP and HTTPS connections, but right now it only supports HTTP proxies or URLs using HTTP/1.x.

activate.sh

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
#! /usr/bin/env bash
2+
3+
python3 -m venv .venv
4+
source .venv/bin/activate
5+
pip install ./utilities/
6+
7+
echo -e "Run \e[32m\e[1mninja-key\e[0m to get a new key."
8+
echo -e "Run \e[32m\e[1mninja-link\e[0m to generate a temporary link (use -h for more information)"

server-src/encryption.go

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package main
2+
3+
import (
4+
"crypto/aes"
5+
"crypto/cipher"
6+
b64 "encoding/base64"
7+
"encoding/json"
8+
"net/url"
9+
"strings"
10+
"time"
11+
)
12+
13+
func decrypt(key []byte, nonceString, encryptedString string) (url url.URL, expiry time.Time, headers map[string]string) {
14+
nonce, _ := b64.URLEncoding.DecodeString(nonceString)
15+
ciphertext, _ := b64.URLEncoding.DecodeString(encryptedString)
16+
17+
//Create a new Cipher Block from the key
18+
block, err := aes.NewCipher(key)
19+
checkError(err)
20+
21+
//Create a new GCM
22+
aesGCM, err := cipher.NewGCMWithNonceSize(block, len(nonce))
23+
checkError(err)
24+
25+
//Decrypt the data
26+
plaintext, err := aesGCM.Open(nil, nonce, ciphertext, nil)
27+
checkError(err)
28+
29+
//Parse the URL and expiry
30+
payload := strings.Split(string(plaintext), ";")
31+
layout := "2006-01-02T15:04:05.000000"
32+
t, err := time.Parse(layout, payload[1])
33+
checkError(err)
34+
35+
var headerJson map[string]string
36+
json.Unmarshal([]byte(payload[2]), &headerJson)
37+
38+
u, err := url.Parse(payload[0])
39+
checkError(err)
40+
41+
return *u, t, headerJson
42+
}

server-src/errors.go

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package main
2+
3+
import "log"
4+
5+
func checkError(err error) {
6+
if err != nil {
7+
panic(err)
8+
}
9+
}
10+
11+
func checkKey(key []byte) {
12+
for _, size := range []int{16, 24, 32} {
13+
if len(key) == size {
14+
return
15+
}
16+
}
17+
log.Fatal("The encryption key has to be 16, 24 or 32 characters in length.")
18+
}
19+
20+
func trapError() {
21+
r := recover()
22+
if r != nil {
23+
log.Printf("Warning: %s", r)
24+
}
25+
}

0 commit comments

Comments
 (0)