Skip to content

Instantly share code, notes, and snippets.

@bmizerany
Last active April 19, 2022 17:22
Show Gist options
  • Save bmizerany/f032a653cbfa47d0fd86f44a0b0f6940 to your computer and use it in GitHub Desktop.
Save bmizerany/f032a653cbfa47d0fd86f44a0b0f6940 to your computer and use it in GitHub Desktop.
package main
import (
"context"
"fmt"
"log"
"net/http"
"time"
"tailscale.com/client/tailscale"
"tailscale.com/tailcfg"
"tailscale.com/tsnet"
)
func main() {
h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
user, err := getTailscaleUser(r.Context(), r.RemoteAddr)
if err != nil {
fmt.Fprintf(w, "Hey! You're not on Tailscale! You can only see public things.\n")
} else {
fmt.Fprintf(w, "Hello, %v! You can access internal routes.\n", user.LoginName)
}
})
// Server internal VPN traffic.
go listenAndServeTailscaleTLS(h)
// Serve public traffic from Fly's edge.
log.Fatal(http.ListenAndServe(":8080", h))
}
func getTailscaleUser(ctx context.Context, ipPort string) (*tailcfg.UserProfile, error) {
whois, err := tailscale.WhoIs(ctx, ipPort)
if err != nil {
return nil, fmt.Errorf("failed to identify remote host: %w", err)
}
if len(whois.Node.Tags) != 0 {
return nil, fmt.Errorf("tagged nodes are not users")
}
if whois.UserProfile == nil || whois.UserProfile.LoginName == "" {
return nil, fmt.Errorf("failed to identify remote user")
}
return whois.UserProfile, nil
}
// listenAndServeTailscaleTLS listens on the tailnet associated with TS_AUTHKEY
// on ports 443 and 80. Port 80 redirects all requests to 443.
func listenAndServeTailscaleTLS(h http.Handler) {
ts := &tsnet.Server{
Ephemeral: true,
Hostname: "tierdemo",
}
go forceTLS(ts)
ln, err := ts.Listen("tcp", ":443")
if err != nil {
log.Fatal(err)
}
log.Fatal(http.Serve(ln, h))
}
func forceTLS(ts *tsnet.Server) {
// wait for tailscale to start before trying to fetch cert names
for i := 0; i < 60; i++ {
st, err := tailscale.Status(context.Background())
if err != nil {
log.Fatal(err)
}
log.Printf("tailscale status: %v", st.BackendState)
if st.BackendState == "Running" {
break
}
time.Sleep(time.Second)
}
l80, err := ts.Listen("tcp", ":80")
if err != nil {
log.Fatal(err)
}
name, ok := tailscale.ExpandSNIName(context.Background(), ts.Hostname)
if !ok {
log.Fatalf("can't get hostname for https redirect")
}
if err := http.Serve(l80, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, fmt.Sprintf("https://%s", name), http.StatusMovedPermanently)
})); err != nil {
log.Fatal(err)
}
}
set -x
set -e
GOOS=linux go build -o demo
flyctl deploy
FROM scratch
COPY demo /
CMD ["/demo"]
fly.io.demo % ./deploy.sh
+ set -e
+ GOOS=linux
+ go build -o demo
+ flyctl deploy
==> Verifying app config
--> Verified app config
==> Building image
==> Creating build context
--> Creating build context done
==> Building image with Docker
--> docker host: 20.10.11 linux aarch64
[+] Building 0.3s (0/1)
[+] Building 0.2s (5/5) FINISHED
=> [internal] load remote build context 0.0s
=> copy /context / 0.1s
=> CACHED copy /context / 0.0s
=> [1/1] COPY demo / 0.0s
=> exporting to image 0.0s
=> => exporting layers 0.0s
=> => writing image sha256:4616a340367b3af822b1d1d9a4aace67e84855c12c331e03b41b49bfe01b9322 0.0s
=> => naming to registry.fly.io/wandering-night-9237:deployment-1650388771 0.0s
--> Building image done
==> Pushing image to fly
The push refers to repository [registry.fly.io/wandering-night-9237]
8e88445fefd1: Pushed
deployment-1650388771: digest: sha256:bfc3ac6cab4ece6a827fa53e6d3ca9d0b6ea94cde20d56e713a6bc357a6949ae size: 527
--> Pushing image done
image: registry.fly.io/wandering-night-9237:deployment-1650388771
image size: 18 MB
==> Creating release
--> release v28 created
--> You can detach the terminal anytime without stopping the deployment
==> Monitoring deployment
1 desired, 1 placed, 0 healthy, 1 unhealthy [restarts: 2] [health checks: 1 total]
Failed Instances
Failure #1
Instance
ID PROCESS VERSION REGION DESIRED STATUS HEALTH CHECKS RESTARTS CREATED
d60f3801 28 sjc run pending 1 total 2 26s ago
Recent Events
TIMESTAMP TYPE MESSAGE
2022-04-19T17:19:51Z Received Task received by client
2022-04-19T17:19:51Z Task Setup Building Task Directory
2022-04-19T17:19:57Z Started Task started by client
2022-04-19T17:20:01Z Terminated Exit Code: 1
2022-04-19T17:20:01Z Restarting Task restarting in 1.180340653s
2022-04-19T17:20:08Z Started Task started by client
2022-04-19T17:20:12Z Terminated Exit Code: 1
2022-04-19T17:20:12Z Restarting Task restarting in 1.187884191s
2022-04-19T17:20:20Z [info]2022/04/19 17:20:20 [v2] Routine: receive incoming v6 - started
2022-04-19T17:20:20Z [info]2022/04/19 17:20:20 [v2] Routine: receive incoming receiveDERP - started
2022-04-19T17:20:20Z [info]2022/04/19 17:20:20 [v1] got initial portlist info in 3ms
2022-04-19T17:20:20Z [info]2022/04/19 17:20:20 control: [v1] HostInfo: {"IPNVersion":"1.22.2-date.20220107","BackendLogID":"tslib-TODO","OS":"linux","OSVersion":"Other; kernel=5.12.2; env=fly","Hostname":"tierdemo","GoArch":"amd64","Services":[{"Proto":"tcp","Port":22,"Description":"hallpass"},{"Proto":"tcp","Port":8080,"Description":"demo"}]}
2022-04-19T17:20:20Z [info]2022/04/19 17:20:20 Backend: logs: be:tslib-TODO fe:
2022-04-19T17:20:20Z [info]2022/04/19 17:20:20 Switching ipn state NoState -> NeedsLogin (WantRunning=true, nm=false)
2022-04-19T17:20:20Z [info]2022/04/19 17:20:20 blockEngineUpdates(true)
2022-04-19T17:20:20Z [info]2022/04/19 17:20:20 wgengine: Reconfig: configuring userspace wireguard config (with 0/0 peers)
2022-04-19T17:20:20Z [info]2022/04/19 17:20:20 wgengine: Reconfig: configuring router
2022-04-19T17:20:20Z [info]2022/04/19 17:20:20 [v1] warning: fakeRouter.Set: not implemented.
2022-04-19T17:20:20Z [info]2022/04/19 17:20:20 wgengine: Reconfig: configuring DNS
2022-04-19T17:20:20Z [info]2022/04/19 17:20:20 dns: Set: {DefaultResolvers:[] Routes:{} SearchDomains:[] Hosts:0}
2022-04-19T17:20:20Z [info]2022/04/19 17:20:20 dns: Resolvercfg: {Routes:{} Hosts:0 LocalDomains:[]}
2022-04-19T17:20:20Z [info]2022/04/19 17:20:20 dns: OScfg: {Nameservers:[] SearchDomains:[] MatchDomains:[]}
2022-04-19T17:20:20Z [info]2022/04/19 17:20:20 [v1] wgengine: Reconfig done
2022-04-19T17:20:20Z [info]2022/04/19 17:20:20 StartLoginInteractive: url=false
2022-04-19T17:20:20Z [info]2022/04/19 17:20:20 control: client.Login(false, 6)
2022-04-19T17:20:20Z [info]2022/04/19 17:20:20 control: [v1] authRoutine: state:new; wantLoggedIn=true
2022-04-19T17:20:20Z [info]2022/04/19 17:20:20 control: [v1] direct.TryLogin(token=false, flags=6)
2022-04-19T17:20:20Z [info]2022/04/19 17:20:20 control: LoginInteractive -> regen=true
2022-04-19T17:20:20Z [info]2022/04/19 17:20:20 control: doLogin(regen=true, hasUrl=false)
2022-04-19T17:20:20Z [info]2022/04/19 17:20:20 control: [v1] mapRoutine: state:authenticating
2022-04-19T17:20:20Z [info]2022/04/19 17:20:20 health("overall"): error: state=NeedsLogin, wantRunning=true
2022-04-19T17:20:21Z [info]2022/04/19 17:20:21 [v1] netmap packet filter: (not ready yet)
2022-04-19T17:20:21Z [info]2022/04/19 17:20:21 [v1] LinkChange: minor
2022-04-19T17:20:21Z [info]2022/04/19 17:20:21 [v1] magicsock: starting endpoint update (link-change-minor)
2022-04-19T17:20:21Z [info]2022/04/19 17:20:21 [v1] magicsock: ignoring pre-DERP map, STUN-less endpoint update: [{172.19.3.98:60020 local} {172.19.3.99:60020 local} {[2604:1380:45e1:3002:0:d60f:3801:1]:60020 local}]
2022-04-19T17:20:22Z [info]2022/04/19 17:20:22 Failed to connect to local Tailscale daemon for /localapi/v0/status; not running? Error: dial unix tailscaled.sock: connect: no such file or directory
2022-04-19T17:20:23Z [info]Main child exited normally with code: 1
2022-04-19T17:20:23Z [info]Starting clean up.
--> v28 failed - Failed due to unhealthy allocations - rolling back to job version 27 and deploying as v29
--> Troubleshooting guide at https://fly.io/docs/getting-started/troubleshooting/
Error abort
# fly.toml file generated for wandering-night-9237 on 2022-04-18T20:45:41-07:00
app = "wandering-night-9237"
kill_signal = "SIGINT"
kill_timeout = 5
processes = []
[env]
TAILSCALE_USE_WIP_CODE = 1
[experimental]
allowed_public_ports = []
auto_rollback = true
[[services]]
http_checks = []
internal_port = 8080
processes = ["app"]
protocol = "tcp"
script_checks = []
[services.concurrency]
hard_limit = 25
soft_limit = 20
type = "connections"
[[services.ports]]
force_https = true
handlers = ["http"]
port = 80
[[services.ports]]
handlers = ["tls", "http"]
port = 443
[[services.tcp_checks]]
grace_period = "1s"
interval = "15s"
restart_limit = 0
timeout = "2s"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment