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 <bradfitz@tailscale.com>
pull/3813/head
Brad Fitzpatrick 3 years ago committed by Brad Fitzpatrick
parent 508f332bb2
commit ca774c3249

@ -32,6 +32,7 @@ import (
"inet.af/netaddr" "inet.af/netaddr"
"inet.af/peercred" "inet.af/peercred"
"tailscale.com/control/controlclient" "tailscale.com/control/controlclient"
"tailscale.com/envknob"
"tailscale.com/ipn" "tailscale.com/ipn"
"tailscale.com/ipn/ipnlocal" "tailscale.com/ipn/ipnlocal"
"tailscale.com/ipn/localapi" "tailscale.com/ipn/localapi"
@ -445,6 +446,26 @@ func (s *Server) localAPIPermissions(ci connIdentity) (read, write bool) {
return false, false 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 // registerDisconnectSub adds ch as a subscribe to connection disconnect
// events. If add is false, the subscriber is removed. // events. If add is false, the subscriber is removed.
func (s *Server) registerDisconnectSub(ch chan<- struct{}, add bool) { 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 { func (s *Server) localhostHandler(ci connIdentity) http.Handler {
lah := localapi.NewHandler(s.b, s.logf, s.backendLogID) lah := localapi.NewHandler(s.b, s.logf, s.backendLogID)
lah.PermitRead, lah.PermitWrite = s.localAPIPermissions(ci) lah.PermitRead, lah.PermitWrite = s.localAPIPermissions(ci)
lah.PermitCert = s.connCanFetchCerts(ci)
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if strings.HasPrefix(r.URL.Path, "/localapi/") { if strings.HasPrefix(r.URL.Path, "/localapi/") {

@ -66,7 +66,7 @@ func (h *Handler) certDir() (string, error) {
var acmeDebug = envknob.Bool("TS_DEBUG_ACME") var acmeDebug = envknob.Bool("TS_DEBUG_ACME")
func (h *Handler) serveCert(w http.ResponseWriter, r *http.Request) { 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) http.Error(w, "cert access denied", http.StatusForbidden)
return return
} }

@ -52,8 +52,15 @@ type Handler struct {
PermitRead bool PermitRead bool
// PermitWrite is whether mutating HTTP handlers are allowed. // 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 PermitWrite bool
// PermitCert is whether the client is additionally granted
// cert fetching access.
PermitCert bool
b *ipnlocal.LocalBackend b *ipnlocal.LocalBackend
logf logger.Logf logf logger.Logf
backendLogID string backendLogID string

Loading…
Cancel
Save