From ca774c32498ac1b773011db5ad0cdd0109ec8203 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Tue, 25 Jan 2022 10:33:11 -0800 Subject: [PATCH] ipn/ipnserver: add TS_PERMIT_CERT_UID envknob to give webservers cert access So you can run Caddy etc as a non-root user and let it have access to get certs. Updates caddyserver/caddy#4541 Change-Id: Iecc5922274530e2b00ba107d4b536580f374109b Signed-off-by: Brad Fitzpatrick --- ipn/ipnserver/server.go | 22 ++++++++++++++++++++++ ipn/localapi/cert.go | 2 +- ipn/localapi/localapi.go | 7 +++++++ 3 files changed, 30 insertions(+), 1 deletion(-) diff --git a/ipn/ipnserver/server.go b/ipn/ipnserver/server.go index c963144987101..273500552c64f 100644 --- a/ipn/ipnserver/server.go +++ b/ipn/ipnserver/server.go @@ -32,6 +32,7 @@ import ( "inet.af/netaddr" "inet.af/peercred" "tailscale.com/control/controlclient" + "tailscale.com/envknob" "tailscale.com/ipn" "tailscale.com/ipn/ipnlocal" "tailscale.com/ipn/localapi" @@ -445,6 +446,26 @@ func (s *Server) localAPIPermissions(ci connIdentity) (read, write bool) { return false, false } +// connCanFetchCerts reports whether ci is allowed to fetch HTTPS +// certs from this server when it wouldn't otherwise be able to. +// +// That is, this reports whether ci should grant additional +// capabilities over what the conn would otherwise be able to do. +// +// For now this only returns true on Unix machines when +// TS_PERMIT_CERT_UID is set the to the userid of the peer +// connection. It's intended to give your non-root webserver access +// (www-data, caddy, nginx, etc) to certs. +func (s *Server) connCanFetchCerts(ci connIdentity) bool { + if ci.IsUnixSock && ci.Creds != nil { + connUID, ok := ci.Creds.UserID() + if ok && connUID == envknob.String("TS_PERMIT_CERT_UID") { + return true + } + } + return false +} + // registerDisconnectSub adds ch as a subscribe to connection disconnect // events. If add is false, the subscriber is removed. func (s *Server) registerDisconnectSub(ch chan<- struct{}, add bool) { @@ -1075,6 +1096,7 @@ func (psc *protoSwitchConn) Close() error { func (s *Server) localhostHandler(ci connIdentity) http.Handler { lah := localapi.NewHandler(s.b, s.logf, s.backendLogID) lah.PermitRead, lah.PermitWrite = s.localAPIPermissions(ci) + lah.PermitCert = s.connCanFetchCerts(ci) return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if strings.HasPrefix(r.URL.Path, "/localapi/") { diff --git a/ipn/localapi/cert.go b/ipn/localapi/cert.go index c0c54ae041801..07b397a7f2b04 100644 --- a/ipn/localapi/cert.go +++ b/ipn/localapi/cert.go @@ -66,7 +66,7 @@ func (h *Handler) certDir() (string, error) { var acmeDebug = envknob.Bool("TS_DEBUG_ACME") func (h *Handler) serveCert(w http.ResponseWriter, r *http.Request) { - if !h.PermitWrite { + if !h.PermitWrite && !h.PermitCert { http.Error(w, "cert access denied", http.StatusForbidden) return } diff --git a/ipn/localapi/localapi.go b/ipn/localapi/localapi.go index 713c13f00acc0..d1da06b74649e 100644 --- a/ipn/localapi/localapi.go +++ b/ipn/localapi/localapi.go @@ -52,8 +52,15 @@ type Handler struct { PermitRead bool // PermitWrite is whether mutating HTTP handlers are allowed. + // If PermitWrite is true, everything is allowed. + // It effectively means that the user is root or the admin + // (operator user). PermitWrite bool + // PermitCert is whether the client is additionally granted + // cert fetching access. + PermitCert bool + b *ipnlocal.LocalBackend logf logger.Logf backendLogID string