| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128 |
- // Copyright (c) Tailscale Inc & AUTHORS
- // SPDX-License-Identifier: BSD-3-Clause
- //go:build linux
- // Command nginx-auth is a tool that allows users to use Tailscale Whois
- // authentication with NGINX as a reverse proxy. This allows users that
- // already have a bunch of services hosted on an internal NGINX server
- // to point those domains to the Tailscale IP of the NGINX server and
- // then seamlessly use Tailscale for authentication.
- package main
- import (
- "flag"
- "log"
- "net"
- "net/http"
- "net/netip"
- "net/url"
- "os"
- "strings"
- "github.com/coreos/go-systemd/activation"
- "tailscale.com/client/tailscale"
- )
- var (
- sockPath = flag.String("sockpath", "", "the filesystem path for the unix socket this service exposes")
- )
- func main() {
- flag.Parse()
- mux := http.NewServeMux()
- mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
- remoteHost := r.Header.Get("Remote-Addr")
- remotePort := r.Header.Get("Remote-Port")
- if remoteHost == "" || remotePort == "" {
- w.WriteHeader(http.StatusBadRequest)
- log.Println("set Remote-Addr to $remote_addr and Remote-Port to $remote_port in your nginx config")
- return
- }
- remoteAddrStr := net.JoinHostPort(remoteHost, remotePort)
- remoteAddr, err := netip.ParseAddrPort(remoteAddrStr)
- if err != nil {
- w.WriteHeader(http.StatusUnauthorized)
- log.Printf("remote address and port are not valid: %v", err)
- return
- }
- info, err := tailscale.WhoIs(r.Context(), remoteAddr.String())
- if err != nil {
- w.WriteHeader(http.StatusUnauthorized)
- log.Printf("can't look up %s: %v", remoteAddr, err)
- return
- }
- if info.Node.IsTagged() {
- w.WriteHeader(http.StatusForbidden)
- log.Printf("node %s is tagged", info.Node.Hostinfo.Hostname())
- return
- }
- // tailnet of connected node. When accessing shared nodes, this
- // will be empty because the tailnet of the sharee is not exposed.
- var tailnet string
- if !info.Node.Hostinfo.ShareeNode() {
- var ok bool
- _, tailnet, ok = strings.Cut(info.Node.Name, info.Node.ComputedName+".")
- if !ok {
- w.WriteHeader(http.StatusUnauthorized)
- log.Printf("can't extract tailnet name from hostname %q", info.Node.Name)
- return
- }
- tailnet = strings.TrimSuffix(tailnet, ".beta.tailscale.net")
- }
- if expectedTailnet := r.Header.Get("Expected-Tailnet"); expectedTailnet != "" && expectedTailnet != tailnet {
- w.WriteHeader(http.StatusForbidden)
- log.Printf("user is part of tailnet %s, wanted: %s", tailnet, url.QueryEscape(expectedTailnet))
- return
- }
- h := w.Header()
- h.Set("Tailscale-Login", strings.Split(info.UserProfile.LoginName, "@")[0])
- h.Set("Tailscale-User", info.UserProfile.LoginName)
- h.Set("Tailscale-Name", info.UserProfile.DisplayName)
- h.Set("Tailscale-Profile-Picture", info.UserProfile.ProfilePicURL)
- h.Set("Tailscale-Tailnet", tailnet)
- w.WriteHeader(http.StatusNoContent)
- })
- if *sockPath != "" {
- _ = os.Remove(*sockPath) // ignore error, this file may not already exist
- ln, err := net.Listen("unix", *sockPath)
- if err != nil {
- log.Fatalf("can't listen on %s: %v", *sockPath, err)
- }
- defer ln.Close()
- log.Printf("listening on %s", *sockPath)
- log.Fatal(http.Serve(ln, mux))
- }
- listeners, err := activation.Listeners()
- if err != nil {
- log.Fatalf("no sockets passed to this service with systemd: %v", err)
- }
- // NOTE(Xe): normally you'd want to make a waitgroup here and then register
- // each listener with it. In this case I want this to blow up horribly if
- // any of the listeners stop working. systemd will restart it due to the
- // socket activation at play.
- //
- // TL;DR: Let it crash, it will come back
- for _, ln := range listeners {
- go func(ln net.Listener) {
- log.Printf("listening on %s", ln.Addr())
- log.Fatal(http.Serve(ln, mux))
- }(ln)
- }
- for {
- select {}
- }
- }
|