-
Notifications
You must be signed in to change notification settings - Fork 43
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This includes: - Move TailscaleAuth logic into auth.go - Move all TSApp logic into app.go (including caddyfile parsing) - Rename "server" to "node" throughout. This aligns better with Tailscale terminology, and is reflective of the fact that nodes can also just be used as proxy transports, in which case they are not acting as servers at all. - Generally prefer referring to a node's "name" than "host". While this name is still used as the default hostname for the node, I would expect that to change with a future iteration of #18. - add godocs throughout Signed-off-by: Will Norris <will@tailscale.com>
- Loading branch information
1 parent
af99185
commit a6ab449
Showing
7 changed files
with
281 additions
and
241 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
package tscaddy | ||
|
||
// auth.go contains the TailscaleAuth module and supporting logic. | ||
|
||
import ( | ||
"fmt" | ||
"net/http" | ||
"strings" | ||
|
||
"github.com/caddyserver/caddy/v2" | ||
"github.com/caddyserver/caddy/v2/caddyconfig" | ||
"github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile" | ||
"github.com/caddyserver/caddy/v2/modules/caddyhttp" | ||
"github.com/caddyserver/caddy/v2/modules/caddyhttp/caddyauth" | ||
"tailscale.com/client/tailscale" | ||
"tailscale.com/tsnet" | ||
) | ||
|
||
func init() { | ||
caddy.RegisterModule(TailscaleAuth{}) | ||
httpcaddyfile.RegisterHandlerDirective("tailscale_auth", parseAuthConfig) | ||
} | ||
|
||
// TailscaleAuth is an HTTP authentication provider that authenticates users based on their Tailscale identity. | ||
// If configured on a caddy site that is listening on a tailscale node, | ||
// that node will be used to identify the user information for inbound requests. | ||
// Otherwise, it will attempt to find and use the local tailscaled daemon running on the system. | ||
type TailscaleAuth struct { | ||
localclient *tailscale.LocalClient | ||
} | ||
|
||
func (TailscaleAuth) CaddyModule() caddy.ModuleInfo { | ||
return caddy.ModuleInfo{ | ||
ID: "http.authentication.providers.tailscale", | ||
New: func() caddy.Module { return new(TailscaleAuth) }, | ||
} | ||
} | ||
|
||
// client returns the tailscale LocalClient for the TailscaleAuth module. | ||
// If the LocalClient has not already been configured, the provided request will be used to | ||
// lookup the tailscale node that serviced the request, and get the associated LocalClient. | ||
func (ta *TailscaleAuth) client(r *http.Request) (*tailscale.LocalClient, error) { | ||
if ta.localclient != nil { | ||
return ta.localclient, nil | ||
} | ||
|
||
// if request was made through a tsnet listener, set up the client for the associated tsnet | ||
// server. | ||
server := r.Context().Value(caddyhttp.ServerCtxKey).(*caddyhttp.Server) | ||
for _, listener := range server.Listeners() { | ||
if tsl, ok := listener.(tsnetListener); ok { | ||
var err error | ||
ta.localclient, err = tsl.Server().LocalClient() | ||
if err != nil { | ||
return nil, err | ||
} | ||
} | ||
} | ||
|
||
if ta.localclient == nil { | ||
// default to empty client that will talk to local tailscaled | ||
ta.localclient = new(tailscale.LocalClient) | ||
} | ||
|
||
return ta.localclient, nil | ||
} | ||
|
||
// tsnetListener is an interface that is implemented by [tsnet.Listener]. | ||
type tsnetListener interface { | ||
Server() *tsnet.Server | ||
} | ||
|
||
// Authenticate authenticates the request and sets Tailscale user data on the caddy User object. | ||
// | ||
// This method will set the following user metadata: | ||
// - tailscale_login: the user's login name without the domain | ||
// - tailscale_user: the user's full login name | ||
// - tailscale_name: the user's display name | ||
// - tailscale_profile_picture: the user's profile picture URL | ||
// - tailscale_tailnet: the user's tailnet name (if the user is not connecting to a shared node) | ||
func (ta TailscaleAuth) Authenticate(w http.ResponseWriter, r *http.Request) (caddyauth.User, bool, error) { | ||
user := caddyauth.User{} | ||
|
||
client, err := ta.client(r) | ||
if err != nil { | ||
return user, false, err | ||
} | ||
|
||
info, err := client.WhoIs(r.Context(), r.RemoteAddr) | ||
if err != nil { | ||
return user, false, err | ||
} | ||
|
||
if len(info.Node.Tags) != 0 { | ||
return user, false, fmt.Errorf("node %s has tags", info.Node.Hostinfo.Hostname()) | ||
} | ||
|
||
var tailnet string | ||
if !info.Node.Hostinfo.ShareeNode() { | ||
if s, found := strings.CutPrefix(info.Node.Name, info.Node.ComputedName+"."); found { | ||
// TODO(will): Update this for current ts.net magicdns hostnames. | ||
if s, found := strings.CutSuffix(s, ".beta.tailscale.net."); found { | ||
tailnet = s | ||
} | ||
} | ||
} | ||
|
||
user.ID = info.UserProfile.LoginName | ||
user.Metadata = map[string]string{ | ||
"tailscale_login": strings.Split(info.UserProfile.LoginName, "@")[0], | ||
"tailscale_user": info.UserProfile.LoginName, | ||
"tailscale_name": info.UserProfile.DisplayName, | ||
"tailscale_profile_picture": info.UserProfile.ProfilePicURL, | ||
"tailscale_tailnet": tailnet, | ||
} | ||
return user, true, nil | ||
} | ||
|
||
func parseAuthConfig(_ httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) { | ||
var ta TailscaleAuth | ||
|
||
return caddyauth.Authentication{ | ||
ProvidersRaw: caddy.ModuleMap{ | ||
"tailscale": caddyconfig.JSON(ta, nil), | ||
}, | ||
}, nil | ||
} |
This file was deleted.
Oops, something went wrong.
Oops, something went wrong.