Skip to content

Commit

Permalink
ipn/ipnserver: add TS_PERMIT_CERT_UID envknob to give webservers cert…
Browse files Browse the repository at this point in the history
… 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>
  • Loading branch information
bradfitz committed Jan 25, 2022
1 parent 508f332 commit ca774c3
Show file tree
Hide file tree
Showing 3 changed files with 30 additions and 1 deletion.
22 changes: 22 additions & 0 deletions ipn/ipnserver/server.go
Expand Up @@ -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"
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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/") {
Expand Down
2 changes: 1 addition & 1 deletion ipn/localapi/cert.go
Expand Up @@ -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
}
Expand Down
7 changes: 7 additions & 0 deletions ipn/localapi/localapi.go
Expand Up @@ -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
Expand Down

0 comments on commit ca774c3

Please sign in to comment.