Skip to content

Commit

Permalink
Use tsnet, build docker image with GHCR (#4)
Browse files Browse the repository at this point in the history
  • Loading branch information
kotx authored Nov 2, 2024
1 parent 858195a commit f3b2c47
Show file tree
Hide file tree
Showing 9 changed files with 407 additions and 111 deletions.
51 changes: 51 additions & 0 deletions .github/workflows/docker-push.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# Adapted from https://github.com/Erisa/Cliptok/blob/14460d6c097de7c2ebd939b4ceadebc9c99ead04/.github/workflows/docker-push.yml
# under MIT license.

name: docker-push

on:
push:
workflow_dispatch:

jobs:
proxy-build-publish:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Cache Docker layers
uses: actions/cache@v4
with:
path: /tmp/.buildx-cache
key: ${{ runner.os }}-buildx-iteration3-${{ github.sha }}
restore-keys: |
${{ runner.os }}-buildx-iteration3-
- name: Login to Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Generate Docker metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ghcr.io/kotx/dex-tailscale
tags: |
type=raw,value=latest,enable=${{ github.ref == 'refs/heads/master' }}
type=raw,value=${{ github.sha }}
type=ref,event=branch
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.meta.outputs.tags }}
cache-from: type=local,src=/tmp/.buildx-cache
cache-to: type=local,dest=/tmp/.buildx-cache
4 changes: 1 addition & 3 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1 @@
.env
dex.env
tailscale.env
*.env
13 changes: 10 additions & 3 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
FROM golang:1.23 AS build
FROM --platform=${BUILDPLATFORM} \
golang:1.23 AS build

WORKDIR /go/src/app
COPY go.mod .

COPY go.mod go.sum .
RUN go mod download && go mod verify

COPY ./cmd/proxy ./cmd/proxy
RUN go vet -v ./cmd/proxy

RUN CGO_ENABLED=0 go build -o /go/bin/proxy ./cmd/proxy
ARG TARGETARCH
RUN CGO_ENABLED=0 GOARCH=${TARGETARCH} \
go build -o /go/bin/proxy ./cmd/proxy

FROM gcr.io/distroless/static-debian12

Expand Down
139 changes: 79 additions & 60 deletions cmd/proxy/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,13 @@ import (
"net/url"
"strings"
"time"

"tailscale.com/client/tailscale"
"tailscale.com/tsnet"
)

var endpoint = flag.String("endpoint", "http://dex:5556", "the host to proxy requests to")
var tsHost = flag.String("hostname", "dex", "hostname to use in the tailnet")
var endpoint = flag.String("endpoint", "http://dex:5556", "the Dex host to proxy requests to")

func main() {
flag.Parse()
Expand All @@ -25,6 +29,20 @@ func main() {
log.Fatalf("endpoint must be a valid url: %v", err)
}

serve := new(tsnet.Server)
serve.Hostname = *tsHost
defer serve.Close()

ln, err := serve.ListenFunnel("tcp", ":443")
if err != nil {
log.Fatalf("error listening on funnel: %v", err)
}

lc, err := serve.LocalClient()
if err != nil {
log.Fatalf("error creating tailscale local client: %v", err)
}

log.Printf("proxying requests to %s", endpointUrl)

httpClient := &http.Client{
Expand All @@ -34,63 +52,64 @@ func main() {
Timeout: time.Minute,
}

http.HandleFunc("/", func(writer http.ResponseWriter, req *http.Request) {
req.Host = endpointUrl.Host
req.URL.Host = endpointUrl.Host
req.URL.Scheme = endpointUrl.Scheme
req.RequestURI = ""

slog.Info("proxying", slog.Group("request",
"method", req.Method,
"url", req.URL,
"remote_addr", req.RemoteAddr,
))

for key, value := range req.Header {
if strings.HasPrefix(http.CanonicalHeaderKey("X-Remote-"), key) {
slog.Info("removing spoofed header", "key", key, "value", value)
req.Header.Del(key)
}

switch key {
case http.CanonicalHeaderKey("Tailscale-User-Login"):
slog.Debug("header", "tailscale-user-login", value)

req.Header["X-Remote-User-Id"] = value
req.Header["X-Remote-User-Email"] = value
case http.CanonicalHeaderKey("Tailscale-User-Name"):
slog.Debug("header", "tailscale-user-name", value)

req.Header["X-Remote-User"] = value
default:
slog.Debug("allowing header", "key", key)
}
}

res, err := httpClient.Do(req)
if err != nil {
var urlError *url.Error
if errors.As(err, &urlError) && urlError.Timeout() {
writer.WriteHeader(http.StatusGatewayTimeout)
} else {
writer.WriteHeader(http.StatusBadGateway)
}

_, _ = fmt.Fprint(writer, err)
slog.Error("proxying request", "err", err)
return
}

for key, value := range res.Header {
writer.Header()[key] = value
}
writer.WriteHeader(res.StatusCode)

_, err = io.Copy(writer, res.Body)
defer res.Body.Close()
if err != nil {
slog.Error("reading response body", "err", err)
}
})
http.ListenAndServe(":8080", nil)
log.Fatal(http.Serve(ln,
http.HandlerFunc(
func(writer http.ResponseWriter, req *http.Request) {
slog.Info("proxying", slog.Group("request",
"method", req.Method,
"url", req.URL,
"remote_addr", req.RemoteAddr,
))

who, err := lc.WhoIs(req.Context(), req.RemoteAddr)
if err != nil && err != tailscale.ErrPeerNotFound {
slog.Error("tailscale whois", "err", err)
http.Error(writer, err.Error(), 500)
return
}
slog.Debug("tailscale", "whois", who)

req.Host = endpointUrl.Host
req.URL.Host = endpointUrl.Host
req.URL.Scheme = endpointUrl.Scheme
req.RequestURI = ""

for key, value := range req.Header {
if strings.HasPrefix(http.CanonicalHeaderKey("X-Remote-"), key) {
slog.Info("removing spoofed header", "key", key, "value", value)
req.Header.Del(key)
}
}

if who != nil {
req.Header.Set("X-Remote-User-Email", who.UserProfile.LoginName)
req.Header.Set("X-Remote-User", who.UserProfile.DisplayName)
req.Header.Set("X-Remote-User-Id", who.UserProfile.ID.String())
}

res, err := httpClient.Do(req)
if err != nil {
var urlError *url.Error
if errors.As(err, &urlError) && urlError.Timeout() {
writer.WriteHeader(http.StatusGatewayTimeout)
} else {
writer.WriteHeader(http.StatusBadGateway)
}

_, _ = fmt.Fprint(writer, err)
slog.Error("proxying request", "err", err)
return
}

for key, value := range res.Header {
writer.Header()[key] = value
}
writer.WriteHeader(res.StatusCode)

_, err = io.Copy(writer, res.Body)
defer res.Body.Close()
if err != nil {
slog.Error("reading response body", "err", err)
}
})))
}
30 changes: 5 additions & 25 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,33 +9,13 @@ services:
- 5556
restart: unless-stopped

tailscale:
image: tailscale/tailscale:latest
env_file: tailscale.env
environment:
- TS_STATE_DIR=/var/lib/tailscale
- TS_USERSPACE=false
- TS_HOSTNAME=dex
- TS_SERVE_CONFIG=/config/funnel.json
volumes:
- tailscale_state:/var/lib/tailscale
- ${PWD}/funnel.json:/config/funnel.json
- /dev/net/tun:/dev/net/tun
cap_add:
- net_admin
- sys_module
restart: unless-stopped

proxy:
build:
context: .
dockerfile: ./Dockerfile
depends_on:
- tailscale
expose:
- 8080
image: ghcr.io/kotx/dex-tailscale:latest
# build:
# context: .
# dockerfile: ./Dockerfile
env_file: tailscale.env
restart: unless-stopped

volumes:
dex_data:
tailscale_state:
19 changes: 0 additions & 19 deletions funnel.json

This file was deleted.

83 changes: 83 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,3 +1,86 @@
module github.com/kotx/dex-tailscale

go 1.23.2

require (
filippo.io/edwards25519 v1.1.0 // indirect
github.com/akutz/memconn v0.1.0 // indirect
github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa // indirect
github.com/aws/aws-sdk-go-v2 v1.24.1 // indirect
github.com/aws/aws-sdk-go-v2/config v1.26.5 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.16.16 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.11 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.10 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.10 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.7.2 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.10 // indirect
github.com/aws/aws-sdk-go-v2/service/ssm v1.44.7 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.18.7 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.7 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.26.7 // indirect
github.com/aws/smithy-go v1.19.0 // indirect
github.com/bits-and-blooms/bitset v1.13.0 // indirect
github.com/coder/websocket v1.8.12 // indirect
github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6 // indirect
github.com/dblohm7/wingoes v0.0.0-20240119213807-a09d6be7affa // indirect
github.com/digitalocean/go-smbios v0.0.0-20180907143718-390a4f403a8e // indirect
github.com/fxamacker/cbor/v2 v2.6.0 // indirect
github.com/gaissmai/bart v0.11.1 // indirect
github.com/go-json-experiment/json v0.0.0-20231102232822-2e55bd4e08b0 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect
github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/google/btree v1.1.2 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/google/nftables v0.2.1-0.20240414091927-5e242ec57806 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/gorilla/csrf v1.7.2 // indirect
github.com/gorilla/securecookie v1.1.2 // indirect
github.com/hdevalence/ed25519consensus v0.2.0 // indirect
github.com/illarion/gonotify/v2 v2.0.3 // indirect
github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/josharian/native v1.1.1-0.20230202152459-5c7d0dd6ab86 // indirect
github.com/jsimonetti/rtnetlink v1.4.0 // indirect
github.com/klauspost/compress v1.17.4 // indirect
github.com/kortschak/wol v0.0.0-20200729010619-da482cc4850a // indirect
github.com/mdlayher/genetlink v1.3.2 // indirect
github.com/mdlayher/netlink v1.7.2 // indirect
github.com/mdlayher/sdnotify v1.0.0 // indirect
github.com/mdlayher/socket v0.5.0 // indirect
github.com/miekg/dns v1.1.58 // indirect
github.com/mitchellh/go-ps v1.0.0 // indirect
github.com/pierrec/lz4/v4 v4.1.21 // indirect
github.com/prometheus-community/pro-bing v0.4.0 // indirect
github.com/safchain/ethtool v0.3.0 // indirect
github.com/tailscale/certstore v0.1.1-0.20231202035212-d3fa0460f47e // indirect
github.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55 // indirect
github.com/tailscale/golang-x-crypto v0.0.0-20240604161659-3fde5e568aa4 // indirect
github.com/tailscale/goupnp v1.0.1-0.20210804011211-c64d0f06ea05 // indirect
github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a // indirect
github.com/tailscale/netlink v1.1.1-0.20240822203006-4d49adab4de7 // indirect
github.com/tailscale/peercred v0.0.0-20240214030740-b535050b2aa4 // indirect
github.com/tailscale/web-client-prebuilt v0.0.0-20240226180453-5db17b287bf1 // indirect
github.com/tailscale/wireguard-go v0.0.0-20240905161824-799c1978fafc // indirect
github.com/tcnksm/go-httpstat v0.2.0 // indirect
github.com/u-root/uio v0.0.0-20240118234441-a3c409a6018e // indirect
github.com/vishvananda/netns v0.0.4 // indirect
github.com/x448/float16 v0.8.4 // indirect
go4.org/mem v0.0.0-20220726221520-4f986261bf13 // indirect
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect
golang.org/x/crypto v0.25.0 // indirect
golang.org/x/exp v0.0.0-20240119083558-1b970713d09a // indirect
golang.org/x/mod v0.19.0 // indirect
golang.org/x/net v0.27.0 // indirect
golang.org/x/sync v0.7.0 // indirect
golang.org/x/sys v0.22.0 // indirect
golang.org/x/term v0.22.0 // indirect
golang.org/x/text v0.16.0 // indirect
golang.org/x/time v0.5.0 // indirect
golang.org/x/tools v0.23.0 // indirect
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
golang.zx2c4.com/wireguard/windows v0.5.3 // indirect
gvisor.dev/gvisor v0.0.0-20240722211153-64c016c92987 // indirect
tailscale.com v1.76.3 // indirect
)
Loading

0 comments on commit f3b2c47

Please sign in to comment.