diff --git a/cmd/derper/depaware.txt b/cmd/derper/depaware.txt index 3a1ca6a8d..88ccf3d20 100644 --- a/cmd/derper/depaware.txt +++ b/cmd/derper/depaware.txt @@ -2,11 +2,6 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa filippo.io/edwards25519 from github.com/hdevalence/ed25519consensus filippo.io/edwards25519/field from filippo.io/edwards25519 - W 💣 github.com/Microsoft/go-winio from tailscale.com/safesocket - W 💣 github.com/Microsoft/go-winio/internal/fs from github.com/Microsoft/go-winio - W 💣 github.com/Microsoft/go-winio/internal/socket from github.com/Microsoft/go-winio - W github.com/Microsoft/go-winio/internal/stringbuffer from github.com/Microsoft/go-winio/internal/fs - W github.com/Microsoft/go-winio/pkg/guid from github.com/Microsoft/go-winio+ W 💣 github.com/alexbrainman/sspi from github.com/alexbrainman/sspi/internal/common+ W github.com/alexbrainman/sspi/internal/common from github.com/alexbrainman/sspi/negotiate W 💣 github.com/alexbrainman/sspi/negotiate from tailscale.com/net/tshttpproxy @@ -34,6 +29,11 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa LD github.com/prometheus/procfs from github.com/prometheus/client_golang/prometheus LD github.com/prometheus/procfs/internal/fs from github.com/prometheus/procfs LD github.com/prometheus/procfs/internal/util from github.com/prometheus/procfs + W 💣 github.com/tailscale/go-winio from tailscale.com/safesocket + W 💣 github.com/tailscale/go-winio/internal/fs from github.com/tailscale/go-winio + W 💣 github.com/tailscale/go-winio/internal/socket from github.com/tailscale/go-winio + W github.com/tailscale/go-winio/internal/stringbuffer from github.com/tailscale/go-winio/internal/fs + W github.com/tailscale/go-winio/pkg/guid from github.com/tailscale/go-winio+ github.com/x448/float16 from github.com/fxamacker/cbor/v2 💣 go4.org/mem from tailscale.com/client/tailscale+ go4.org/netipx from tailscale.com/wgengine/filter @@ -98,7 +98,7 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa 💣 tailscale.com/net/tshttpproxy from tailscale.com/derp/derphttp+ tailscale.com/net/wsconn from tailscale.com/cmd/derper+ tailscale.com/paths from tailscale.com/client/tailscale - tailscale.com/safesocket from tailscale.com/client/tailscale + 💣 tailscale.com/safesocket from tailscale.com/client/tailscale tailscale.com/syncs from tailscale.com/cmd/derper+ tailscale.com/tailcfg from tailscale.com/client/tailscale+ tailscale.com/tka from tailscale.com/client/tailscale+ @@ -180,7 +180,6 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa bytes from bufio+ compress/flate from compress/gzip+ compress/gzip from internal/profile+ - L compress/zlib from debug/elf container/list from crypto/tls+ context from crypto/tls+ crypto from crypto/ecdsa+ @@ -204,8 +203,6 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa crypto/tls from golang.org/x/crypto/acme+ crypto/x509 from crypto/tls+ crypto/x509/pkix from crypto/x509+ - L debug/dwarf from debug/elf - L debug/elf from golang.org/x/sys/unix embed from crypto/internal/nistec+ encoding from encoding/json+ encoding/asn1 from crypto/x509+ @@ -221,7 +218,6 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa fmt from compress/flate+ go/token from google.golang.org/protobuf/internal/strs hash from crypto+ - L hash/adler32 from compress/zlib hash/crc32 from compress/gzip+ hash/fnv from google.golang.org/protobuf/internal/detrand hash/maphash from go4.org/mem diff --git a/cmd/tailscale/depaware.txt b/cmd/tailscale/depaware.txt index 3b5eb15b4..1f0682f27 100644 --- a/cmd/tailscale/depaware.txt +++ b/cmd/tailscale/depaware.txt @@ -2,11 +2,6 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep filippo.io/edwards25519 from github.com/hdevalence/ed25519consensus filippo.io/edwards25519/field from filippo.io/edwards25519 - W 💣 github.com/Microsoft/go-winio from tailscale.com/safesocket - W 💣 github.com/Microsoft/go-winio/internal/fs from github.com/Microsoft/go-winio - W 💣 github.com/Microsoft/go-winio/internal/socket from github.com/Microsoft/go-winio - W github.com/Microsoft/go-winio/internal/stringbuffer from github.com/Microsoft/go-winio/internal/fs - W github.com/Microsoft/go-winio/pkg/guid from github.com/Microsoft/go-winio+ W 💣 github.com/alexbrainman/sspi from github.com/alexbrainman/sspi/negotiate+ W github.com/alexbrainman/sspi/internal/common from github.com/alexbrainman/sspi/negotiate W 💣 github.com/alexbrainman/sspi/negotiate from tailscale.com/net/tshttpproxy @@ -30,6 +25,11 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep github.com/skip2/go-qrcode from tailscale.com/cmd/tailscale/cli github.com/skip2/go-qrcode/bitset from github.com/skip2/go-qrcode+ github.com/skip2/go-qrcode/reedsolomon from github.com/skip2/go-qrcode + W 💣 github.com/tailscale/go-winio from tailscale.com/safesocket + W 💣 github.com/tailscale/go-winio/internal/fs from github.com/tailscale/go-winio + W 💣 github.com/tailscale/go-winio/internal/socket from github.com/tailscale/go-winio + W github.com/tailscale/go-winio/internal/stringbuffer from github.com/tailscale/go-winio/internal/fs + W github.com/tailscale/go-winio/pkg/guid from github.com/tailscale/go-winio+ github.com/tailscale/goupnp from github.com/tailscale/goupnp/dcps/internetgateway2+ github.com/tailscale/goupnp/dcps/internetgateway2 from tailscale.com/net/portmapper github.com/tailscale/goupnp/httpu from github.com/tailscale/goupnp+ @@ -89,7 +89,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep 💣 tailscale.com/net/tshttpproxy from tailscale.com/derp/derphttp+ tailscale.com/net/wsconn from tailscale.com/control/controlhttp+ tailscale.com/paths from tailscale.com/cmd/tailscale/cli+ - tailscale.com/safesocket from tailscale.com/cmd/tailscale/cli+ + 💣 tailscale.com/safesocket from tailscale.com/cmd/tailscale/cli+ tailscale.com/syncs from tailscale.com/net/netcheck+ tailscale.com/tailcfg from tailscale.com/cmd/tailscale/cli+ tailscale.com/tka from tailscale.com/client/tailscale+ @@ -177,7 +177,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep bytes from bufio+ compress/flate from compress/gzip+ compress/gzip from net/http - compress/zlib from image/png+ + compress/zlib from image/png container/list from crypto/tls+ context from crypto/tls+ crypto from crypto/ecdsa+ @@ -202,8 +202,6 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep crypto/x509 from crypto/tls+ crypto/x509/pkix from crypto/x509+ database/sql/driver from github.com/google/uuid - L debug/dwarf from debug/elf - L debug/elf from golang.org/x/sys/unix embed from tailscale.com/cmd/tailscale/cli+ encoding from encoding/json+ encoding/asn1 from crypto/x509+ diff --git a/cmd/tailscaled/depaware.txt b/cmd/tailscaled/depaware.txt index 615d23eef..87f619e80 100644 --- a/cmd/tailscaled/depaware.txt +++ b/cmd/tailscaled/depaware.txt @@ -2,11 +2,6 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de filippo.io/edwards25519 from github.com/hdevalence/ed25519consensus filippo.io/edwards25519/field from filippo.io/edwards25519 - W 💣 github.com/Microsoft/go-winio from tailscale.com/safesocket - W 💣 github.com/Microsoft/go-winio/internal/fs from github.com/Microsoft/go-winio - W 💣 github.com/Microsoft/go-winio/internal/socket from github.com/Microsoft/go-winio - W github.com/Microsoft/go-winio/internal/stringbuffer from github.com/Microsoft/go-winio/internal/fs - W github.com/Microsoft/go-winio/pkg/guid from github.com/Microsoft/go-winio+ W 💣 github.com/alexbrainman/sspi from github.com/alexbrainman/sspi/internal/common+ W github.com/alexbrainman/sspi/internal/common from github.com/alexbrainman/sspi/negotiate W 💣 github.com/alexbrainman/sspi/negotiate from tailscale.com/net/tshttpproxy @@ -121,6 +116,11 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de LD github.com/pkg/sftp from tailscale.com/ssh/tailssh LD github.com/pkg/sftp/internal/encoding/ssh/filexfer from github.com/pkg/sftp W 💣 github.com/tailscale/certstore from tailscale.com/control/controlclient + W 💣 github.com/tailscale/go-winio from tailscale.com/safesocket + W 💣 github.com/tailscale/go-winio/internal/fs from github.com/tailscale/go-winio + W 💣 github.com/tailscale/go-winio/internal/socket from github.com/tailscale/go-winio + W github.com/tailscale/go-winio/internal/stringbuffer from github.com/tailscale/go-winio/internal/fs + W github.com/tailscale/go-winio/pkg/guid from github.com/tailscale/go-winio+ LD github.com/tailscale/golang-x-crypto/chacha20 from github.com/tailscale/golang-x-crypto/ssh LD 💣 github.com/tailscale/golang-x-crypto/internal/alias from github.com/tailscale/golang-x-crypto/chacha20 LD github.com/tailscale/golang-x-crypto/ssh from tailscale.com/ipn/ipnlocal+ @@ -273,7 +273,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de tailscale.com/net/wsconn from tailscale.com/control/controlhttp+ tailscale.com/paths from tailscale.com/ipn/ipnlocal+ 💣 tailscale.com/portlist from tailscale.com/ipn/ipnlocal - tailscale.com/safesocket from tailscale.com/client/tailscale+ + 💣 tailscale.com/safesocket from tailscale.com/client/tailscale+ tailscale.com/smallzstd from tailscale.com/cmd/tailscaled+ LD 💣 tailscale.com/ssh/tailssh from tailscale.com/cmd/tailscaled tailscale.com/syncs from tailscale.com/net/netcheck+ @@ -321,7 +321,6 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de tailscale.com/util/multierr from tailscale.com/control/controlclient+ tailscale.com/util/must from tailscale.com/logpolicy tailscale.com/util/osshare from tailscale.com/ipn/ipnlocal+ - W tailscale.com/util/pidowner from tailscale.com/ipn/ipnauth tailscale.com/util/racebuild from tailscale.com/logpolicy tailscale.com/util/ringbuffer from tailscale.com/wgengine/magicsock tailscale.com/util/set from tailscale.com/health+ @@ -365,7 +364,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de golang.org/x/crypto/salsa20/salsa from golang.org/x/crypto/nacl/box+ LD golang.org/x/crypto/ssh from tailscale.com/ssh/tailssh+ golang.org/x/exp/constraints from golang.org/x/exp/slices+ - golang.org/x/exp/maps from tailscale.com/wgengine + golang.org/x/exp/maps from tailscale.com/wgengine+ golang.org/x/exp/slices from tailscale.com/ipn/ipnlocal+ golang.org/x/net/bpf from github.com/mdlayher/genetlink+ golang.org/x/net/dns/dnsmessage from net+ @@ -398,7 +397,6 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de bytes from bufio+ compress/flate from compress/gzip+ compress/gzip from golang.org/x/net/http2+ - L compress/zlib from debug/elf container/heap from gvisor.dev/gvisor/pkg/tcpip/transport/tcp container/list from crypto/tls+ context from crypto/tls+ @@ -423,8 +421,6 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de crypto/tls from github.com/tcnksm/go-httpstat+ crypto/x509 from crypto/tls+ crypto/x509/pkix from crypto/x509+ - L debug/dwarf from debug/elf - L debug/elf from golang.org/x/sys/unix embed from tailscale.com+ encoding from encoding/json+ encoding/asn1 from crypto/x509+ @@ -440,7 +436,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de flag from net/http/httptest+ fmt from compress/flate+ hash from crypto+ - hash/adler32 from tailscale.com/ipn/ipnlocal+ + hash/adler32 from tailscale.com/ipn/ipnlocal hash/crc32 from compress/gzip+ hash/fnv from tailscale.com/wgengine/magicsock+ hash/maphash from go4.org/mem diff --git a/go.mod b/go.mod index 92613bc6b..19e5ddac9 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,6 @@ go 1.20 require ( filippo.io/mkcert v1.4.4 - github.com/Microsoft/go-winio v0.6.1 github.com/akutz/memconn v0.1.0 github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74 github.com/andybalholm/brotli v1.0.5 @@ -73,16 +72,16 @@ require ( go.uber.org/zap v1.24.0 go4.org/mem v0.0.0-20220726221520-4f986261bf13 go4.org/netipx v0.0.0-20230303233057-f1b76eb4bb35 - golang.org/x/crypto v0.8.0 + golang.org/x/crypto v0.11.0 golang.org/x/exp v0.0.0-20230425010034-47ecfdc1ba53 - golang.org/x/mod v0.10.0 - golang.org/x/net v0.10.0 + golang.org/x/mod v0.12.0 + golang.org/x/net v0.12.0 golang.org/x/oauth2 v0.7.0 - golang.org/x/sync v0.2.0 - golang.org/x/sys v0.8.1-0.20230609144347-5059a07aa46a - golang.org/x/term v0.8.0 + golang.org/x/sync v0.3.0 + golang.org/x/sys v0.10.0 + golang.org/x/term v0.10.0 golang.org/x/time v0.3.0 - golang.org/x/tools v0.9.1 + golang.org/x/tools v0.11.0 golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 golang.zx2c4.com/wireguard/windows v0.5.3 gvisor.dev/gvisor v0.0.0-20230504175454-7b0a1988a28f @@ -99,6 +98,8 @@ require ( software.sslmate.com/src/go-pkcs12 v0.2.0 ) +require github.com/Microsoft/go-winio v0.6.0 // indirect + require ( 4d63.com/gocheckcompilerdirectives v1.2.1 // indirect 4d63.com/gochecknoglobals v0.2.1 // indirect @@ -291,7 +292,7 @@ require ( github.com/securego/gosec/v2 v2.15.0 // indirect github.com/sergi/go-diff v1.3.1 // indirect github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c // indirect - github.com/sirupsen/logrus v1.9.0 // indirect + github.com/sirupsen/logrus v1.9.3 // indirect github.com/sivchari/containedctx v1.0.3 // indirect github.com/sivchari/nosnakecase v1.7.0 // indirect github.com/sivchari/tenv v1.7.1 // indirect @@ -310,6 +311,7 @@ require ( github.com/stretchr/testify v1.8.2 // indirect github.com/subosito/gotenv v1.4.2 // indirect github.com/t-yuki/gocover-cobertura v0.0.0-20180217150009-aaee18c8195c // indirect + github.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55 github.com/tdakkota/asciicheck v0.2.0 // indirect github.com/tetafro/godot v1.4.11 // indirect github.com/timakin/bodyclose v0.0.0-20230421092635-574207250966 // indirect @@ -333,7 +335,7 @@ require ( go.uber.org/multierr v1.11.0 // indirect golang.org/x/exp/typeparams v0.0.0-20230425010034-47ecfdc1ba53 // indirect golang.org/x/image v0.7.0 // indirect - golang.org/x/text v0.9.0 // indirect + golang.org/x/text v0.11.0 // indirect gomodules.xyz/jsonpatch/v2 v2.3.0 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/protobuf v1.30.0 // indirect diff --git a/go.sum b/go.sum index 1848ea67e..24bac85b3 100644 --- a/go.sum +++ b/go.sum @@ -78,8 +78,8 @@ github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jB github.com/Microsoft/go-winio v0.4.15/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw= github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= -github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= -github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= +github.com/Microsoft/go-winio v0.6.0 h1:slsWYD/zyx7lCXoZVlvQrj0hPTM1HI4+v1sIda2yDvg= +github.com/Microsoft/go-winio v0.6.0/go.mod h1:cTAf44im0RAYeL23bpB+fzCyDH2MJiz2BO69KH/soAE= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/OpenPeeDeeP/depguard v1.0.1/go.mod h1:xsIw86fROiiwelg+jB2uM9PiKihMMmUx/1V+TNhjQvM= github.com/OpenPeeDeeP/depguard v1.1.1 h1:TSUznLjvp/4IUP+OQ0t/4jF4QUyxIcVX8YnghZdunyA= @@ -973,8 +973,8 @@ github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMB github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= -github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= -github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sivchari/containedctx v1.0.3 h1:x+etemjbsh2fB5ewm5FeLNi5bUjK0V8n0RB+Wwfd0XE= github.com/sivchari/containedctx v1.0.3/go.mod h1:c1RDvCbnJLtH4lLcYD/GqwiBSSf4F5Qk0xld2rBqzJ4= github.com/sivchari/nosnakecase v1.7.0 h1:7QkpWIRMe8x25gckkFd2A5Pi6Ymo0qgr4JrhGt95do8= @@ -1052,6 +1052,8 @@ github.com/tailscale/certstore v0.1.1-0.20220316223106-78d6e1c49d8d h1:K3j02b5j2 github.com/tailscale/certstore v0.1.1-0.20220316223106-78d6e1c49d8d/go.mod h1:2P+hpOwd53e7JMX/L4f3VXkv1G+33ES6IWZSrkIeWNs= github.com/tailscale/depaware v0.0.0-20210622194025-720c4b409502 h1:34icjjmqJ2HPjrSuJYEkdZ+0ItmGQAQ75cRHIiftIyE= github.com/tailscale/depaware v0.0.0-20210622194025-720c4b409502/go.mod h1:p9lPsd+cx33L3H9nNoecRRxPssFKUwwI50I3pZ0yT+8= +github.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55 h1:Gzfnfk2TWrk8Jj4P4c1a3CtQyMaTVCznlkLZI++hok4= +github.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55/go.mod h1:4k4QO+dQ3R5FofL+SanAUZe+/QfeK0+OIuwDIRu2vSg= github.com/tailscale/goexpect v0.0.0-20210902213824-6e8c725cea41 h1:/V2rCMMWcsjYaYO2MeovLw+ClP63OtXgCF2Y1eb8+Ns= github.com/tailscale/goexpect v0.0.0-20210902213824-6e8c725cea41/go.mod h1:/roCdA6gg6lQyw/Oz6gIIGu3ggJKYhF+WC/AQReE5XQ= github.com/tailscale/golang-x-crypto v0.0.0-20221115211329-17a3db2c30d2 h1:pBpqbsyX9H8c26oPYC2H+232HOdp1gDnCztoKmKWKDA= @@ -1210,8 +1212,8 @@ golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d/go.mod h1:IxCIyHEi3zRg3s0 golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= -golang.org/x/crypto v0.8.0 h1:pd9TJtTueMTVQXzk8E2XESSMQDj/U7OUu0PqJqPXQjQ= -golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE= +golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA= +golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -1261,8 +1263,8 @@ golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91 golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI= golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk= -golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1316,8 +1318,8 @@ golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= -golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= -golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50= +golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1343,8 +1345,8 @@ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI= -golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= +golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1432,8 +1434,8 @@ golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.4.1-0.20230131160137-e7d7f63158de/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.8.1-0.20230609144347-5059a07aa46a h1:qMsju+PNttu/NMbq8bQ9waDdxgJMu9QNoUDuhnBaYt0= -golang.org/x/sys v0.8.1-0.20230609144347-5059a07aa46a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= +golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -1444,8 +1446,8 @@ golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= -golang.org/x/term v0.8.0 h1:n5xxQn2i3PC0yLAbjTpNT85q/Kgzcr2gIoX9OrJUols= -golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.10.0 h1:3R7pNqamzBraeqj/Tj8qt1aQ2HpmlC+Cx/qL/7hn4/c= +golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1460,8 +1462,9 @@ golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4= +golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -1566,8 +1569,8 @@ golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k= golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ= golang.org/x/tools v0.5.0/go.mod h1:N+Kgy78s5I24c24dU8OfWNEotWjutIs8SnJvn5IDq+k= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.9.1 h1:8WMNJAz3zrtPmnYC7ISf5dEn3MT0gY7jBJfw27yrrLo= -golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc= +golang.org/x/tools v0.11.0 h1:EMCa6U9S2LtZXLAMoWiR/R8dAQFRqbAitmbJ2UKhoi8= +golang.org/x/tools v0.11.0/go.mod h1:anzJrxPjNtfgiYQYirP2CPGzGLxrH2u2QBhn6Bf3qY8= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/ipn/ipnauth/ipnauth.go b/ipn/ipnauth/ipnauth.go index a83ca459b..5dc2e2768 100644 --- a/ipn/ipnauth/ipnauth.go +++ b/ipn/ipnauth/ipnauth.go @@ -5,7 +5,9 @@ package ipnauth import ( + "errors" "fmt" + "io" "net" "net/netip" "os" @@ -25,6 +27,35 @@ import ( "tailscale.com/version/distro" ) +// ErrNotImplemented is returned by ConnIdentity.WindowsToken when it is not +// implemented for the current GOOS. +var ErrNotImplemented = errors.New("not implemented for GOOS=" + runtime.GOOS) + +// WindowsToken represents the current security context of a Windows user. +type WindowsToken interface { + io.Closer + // EqualUIDs reports whether other refers to the same user ID as the receiver. + EqualUIDs(other WindowsToken) bool + // IsAdministrator reports whether the receiver is a member of the built-in + // Administrators group, or else an error. Use IsElevated to determine whether + // the receiver is actually utilizing administrative rights. + IsAdministrator() (bool, error) + // IsUID reports whether the receiver's user ID matches uid. + IsUID(uid ipn.WindowsUserID) bool + // UID returns the ipn.WindowsUserID associated with the receiver, or else + // an error. + UID() (ipn.WindowsUserID, error) + // IsElevated reports whether the receiver is currently executing as an + // elevated administrative user. + IsElevated() bool + // UserDir returns the special directory identified by folderID as associated + // with the receiver. folderID must be one of the KNOWNFOLDERID values from + // the x/sys/windows package, serialized as a stringified GUID. + UserDir(folderID string) (string, error) + // Username returns the user name associated with the receiver. + Username() (string, error) +} + // ConnIdentity represents the owner of a localhost TCP or unix socket connection // connecting to the LocalAPI. type ConnIdentity struct { @@ -38,9 +69,7 @@ type ConnIdentity struct { // Used on Windows: // TODO(bradfitz): merge these into the peercreds package and // use that for all. - pid int - userID ipn.WindowsUserID - user *user.User + pid int } // WindowsUserID returns the local machine's userid of the connection @@ -52,8 +81,11 @@ func (ci *ConnIdentity) WindowsUserID() ipn.WindowsUserID { if envknob.GOOS() != "windows" { return "" } - if ci.userID != "" { - return ci.userID + if tok, err := ci.WindowsToken(); err == nil { + defer tok.Close() + if uid, err := tok.UID(); err == nil { + return uid + } } // For Linux tests running as Windows: const isBroken = true // TODO(bradfitz,maisem): fix tests; this doesn't work yet @@ -65,7 +97,6 @@ func (ci *ConnIdentity) WindowsUserID() ipn.WindowsUserID { return "" } -func (ci *ConnIdentity) User() *user.User { return ci.user } func (ci *ConnIdentity) Pid() int { return ci.pid } func (ci *ConnIdentity) IsUnixSock() bool { return ci.isUnixSock } func (ci *ConnIdentity) Creds() *peercred.Creds { return ci.creds } diff --git a/ipn/ipnauth/ipnauth_notwindows.go b/ipn/ipnauth/ipnauth_notwindows.go index 0a6275e65..135ab3674 100644 --- a/ipn/ipnauth/ipnauth_notwindows.go +++ b/ipn/ipnauth/ipnauth_notwindows.go @@ -21,3 +21,9 @@ func GetConnIdentity(_ logger.Logf, c net.Conn) (ci *ConnIdentity, err error) { ci.creds, _ = peercred.Get(c) return ci, nil } + +// WindowsToken is unsupported when GOOS != windows and always returns +// ErrNotImplemented. +func (ci *ConnIdentity) WindowsToken() (WindowsToken, error) { + return nil, ErrNotImplemented +} diff --git a/ipn/ipnauth/ipnauth_windows.go b/ipn/ipnauth/ipnauth_windows.go index abf795832..86cfd7969 100644 --- a/ipn/ipnauth/ipnauth_windows.go +++ b/ipn/ipnauth/ipnauth_windows.go @@ -6,53 +6,157 @@ package ipnauth import ( "fmt" "net" - "syscall" + "runtime" "unsafe" "golang.org/x/sys/windows" "tailscale.com/ipn" + "tailscale.com/safesocket" "tailscale.com/types/logger" - "tailscale.com/util/pidowner" ) -var ( - kernel32 = syscall.NewLazyDLL("kernel32.dll") - procGetNamedPipeClientProcessId = kernel32.NewProc("GetNamedPipeClientProcessId") -) - -func getNamedPipeClientProcessId(h windows.Handle) (pid uint32, err error) { - r1, _, err := procGetNamedPipeClientProcessId.Call(uintptr(h), uintptr(unsafe.Pointer(&pid))) - if r1 > 0 { - return pid, nil - } - return 0, err -} - // GetConnIdentity extracts the identity information from the connection // based on the user who owns the other end of the connection. // If c is not backed by a named pipe, an error is returned. func GetConnIdentity(logf logger.Logf, c net.Conn) (ci *ConnIdentity, err error) { ci = &ConnIdentity{conn: c} - h, ok := c.(interface { - Fd() uintptr - }) + wcc, ok := c.(*safesocket.WindowsClientConn) if !ok { - return ci, fmt.Errorf("not a windows handle: %T", c) + return nil, fmt.Errorf("not a WindowsClientConn: %T", c) } - pid, err := getNamedPipeClientProcessId(windows.Handle(h.Fd())) + ci.pid, err = wcc.ClientPID() if err != nil { - return ci, fmt.Errorf("getNamedPipeClientProcessId: %v", err) + return nil, err } - ci.pid = int(pid) - uid, err := pidowner.OwnerOfPID(ci.pid) + return ci, nil +} + +type token struct { + t windows.Token +} + +func (t *token) UID() (ipn.WindowsUserID, error) { + sid, err := t.uid() if err != nil { - return ci, fmt.Errorf("failed to map connection's pid to a user (WSL?): %w", err) + return "", fmt.Errorf("failed to look up user from token: %w", err) } - ci.userID = ipn.WindowsUserID(uid) - u, err := LookupUserFromID(logf, uid) + + return ipn.WindowsUserID(sid.String()), nil +} + +func (t *token) Username() (string, error) { + sid, err := t.uid() if err != nil { - return ci, fmt.Errorf("failed to look up user from userid: %w", err) + return "", fmt.Errorf("failed to look up user from token: %w", err) } - ci.user = u - return ci, nil + + username, domain, _, err := sid.LookupAccount("") + if err != nil { + return "", fmt.Errorf("failed to look up username from SID: %w", err) + } + + return fmt.Sprintf(`%s\%s`, domain, username), nil +} + +func (t *token) IsAdministrator() (bool, error) { + baSID, err := windows.CreateWellKnownSid(windows.WinBuiltinAdministratorsSid) + if err != nil { + return false, err + } + + return t.t.IsMember(baSID) +} + +func (t *token) IsElevated() bool { + return t.t.IsElevated() +} + +func (t *token) UserDir(folderID string) (string, error) { + guid, err := windows.GUIDFromString(folderID) + if err != nil { + return "", err + } + + return t.t.KnownFolderPath((*windows.KNOWNFOLDERID)(unsafe.Pointer(&guid)), 0) +} + +func (t *token) Close() error { + if t.t == 0 { + return nil + } + if err := t.t.Close(); err != nil { + return err + } + t.t = 0 + runtime.SetFinalizer(t, nil) + return nil +} + +func (t *token) EqualUIDs(other WindowsToken) bool { + if t != nil && other == nil || t == nil && other != nil { + return false + } + ot, ok := other.(*token) + if !ok { + return false + } + if t == ot { + return true + } + uid, err := t.uid() + if err != nil { + return false + } + oUID, err := ot.uid() + if err != nil { + return false + } + return uid.Equals(oUID) +} + +func (t *token) uid() (*windows.SID, error) { + tu, err := t.t.GetTokenUser() + if err != nil { + return nil, err + } + + return tu.User.Sid, nil +} + +func (t *token) IsUID(uid ipn.WindowsUserID) bool { + tUID, err := t.UID() + if err != nil { + return false + } + + return tUID == uid +} + +// WindowsToken returns the WindowsToken representing the security context +// of the connection's client. +func (ci *ConnIdentity) WindowsToken() (WindowsToken, error) { + var wcc *safesocket.WindowsClientConn + var ok bool + if wcc, ok = ci.conn.(*safesocket.WindowsClientConn); !ok { + return nil, fmt.Errorf("not a WindowsClientConn: %T", ci.conn) + } + + // We duplicate the token's handle so that the WindowsToken we return may have + // a lifetime independent from the original connection. + var h windows.Handle + if err := windows.DuplicateHandle( + windows.CurrentProcess(), + windows.Handle(wcc.Token()), + windows.CurrentProcess(), + &h, + 0, + false, + windows.DUPLICATE_SAME_ACCESS, + ); err != nil { + return nil, err + } + + result := &token{t: windows.Token(h)} + runtime.SetFinalizer(result, func(t *token) { t.Close() }) + return result, nil } diff --git a/ipn/ipnlocal/local.go b/ipn/ipnlocal/local.go index 762dfba9b..d0169adcd 100644 --- a/ipn/ipnlocal/local.go +++ b/ipn/ipnlocal/local.go @@ -235,6 +235,7 @@ type LocalBackend struct { directFileRoot string directFileDoFinalRename bool // false on macOS, true on several NAS platforms componentLogUntil map[string]componentLogState + currentUser ipnauth.WindowsToken // ServeConfig fields. (also guarded by mu) lastServeConfJSON mem.RO // last JSON that was parsed into serveConfig @@ -2513,7 +2514,7 @@ func (b *LocalBackend) shouldUploadServices() bool { return !p.ShieldsUp() && b.netMap.CollectServices } -// SetCurrentUserID is used to implement support for multi-user systems (only +// SetCurrentUser is used to implement support for multi-user systems (only // Windows 2022-11-25). On such systems, the uid is used to determine which // user's state should be used. The current user is maintained by active // connections open to the backend. @@ -2528,18 +2529,35 @@ func (b *LocalBackend) shouldUploadServices() bool { // unattended mode. The user must disable unattended mode before the user can be // changed. // -// On non-multi-user systems, the uid should be set to empty string. -func (b *LocalBackend) SetCurrentUserID(uid ipn.WindowsUserID) { +// On non-multi-user systems, the token should be set to nil. +// +// SetCurrentUser returns the ipn.WindowsUserID associated with token +// when successful. +func (b *LocalBackend) SetCurrentUser(token ipnauth.WindowsToken) (ipn.WindowsUserID, error) { + var uid ipn.WindowsUserID + if token != nil { + var err error + uid, err = token.UID() + if err != nil { + return "", err + } + } + b.mu.Lock() if b.pm.CurrentUserID() == uid { b.mu.Unlock() - return + return uid, nil } if err := b.pm.SetCurrentUserID(uid); err != nil { b.mu.Unlock() - return + return uid, nil } + if b.currentUser != nil { + b.currentUser.Close() + } + b.currentUser = token b.resetForProfileChangeLockedOnEntry() + return uid, nil } func (b *LocalBackend) CheckPrefs(p *ipn.Prefs) error { @@ -3851,6 +3869,10 @@ func (b *LocalBackend) ResetForClientDisconnect() { b.resetControlClientLockedAsync() b.setNetMapLocked(nil) b.pm.Reset() + if b.currentUser != nil { + b.currentUser.Close() + b.currentUser = nil + } b.keyExpired = false b.authURL = "" b.authURLSticky = "" diff --git a/ipn/ipnserver/server.go b/ipn/ipnserver/server.go index 705c01016..755919275 100644 --- a/ipn/ipnserver/server.go +++ b/ipn/ipnserver/server.go @@ -202,6 +202,7 @@ func (s *Server) serveHTTP(w http.ResponseWriter, r *http.Request) { lah := localapi.NewHandler(lb, s.logf, s.netMon, s.backendLogID) lah.PermitRead, lah.PermitWrite = s.localAPIPermissions(ci) lah.PermitCert = s.connCanFetchCerts(ci) + lah.CallerIsLocalAdmin = s.connIsLocalAdmin(ci) lah.ServeHTTP(w, r) return } @@ -242,8 +243,30 @@ func (s *Server) checkConnIdentityLocked(ci *ipnauth.ConnIdentity) error { for _, active = range s.activeReqs { break } - if active != nil && ci.WindowsUserID() != active.WindowsUserID() { - return inUseOtherUserError{fmt.Errorf("Tailscale already in use by %s, pid %d", active.User().Username, active.Pid())} + if active != nil { + chkTok, err := ci.WindowsToken() + if err == nil { + defer chkTok.Close() + } else if !errors.Is(err, ipnauth.ErrNotImplemented) { + return err + } + + activeTok, err := active.WindowsToken() + if err == nil { + defer activeTok.Close() + } else if !errors.Is(err, ipnauth.ErrNotImplemented) { + return err + } + + if chkTok != nil && !chkTok.EqualUIDs(activeTok) { + var b strings.Builder + b.WriteString("Tailscale already in use") + if username, err := activeTok.Username(); err == nil { + fmt.Fprintf(&b, " by %s", username) + } + fmt.Fprintf(&b, ", pid %d", active.Pid()) + return inUseOtherUserError{errors.New(b.String())} + } } } if err := s.mustBackend().CheckIPNConnectionAllowed(ci); err != nil { @@ -341,6 +364,31 @@ func (s *Server) connCanFetchCerts(ci *ipnauth.ConnIdentity) bool { return false } +// connIsLocalAdmin reports whether ci has administrative access to the local +// machine, for whatever that means with respect to the current OS. +// +// This returns true only on Windows machines when the client user is a +// member of the built-in Administrators group (but not necessarily elevated). +// This is useful because, on Windows, tailscaled itself always runs with +// elevated rights: we want to avoid privilege escalation for certain mutative operations. +func (s *Server) connIsLocalAdmin(ci *ipnauth.ConnIdentity) bool { + tok, err := ci.WindowsToken() + if err != nil { + if !errors.Is(err, ipnauth.ErrNotImplemented) { + s.logf("ipnauth.ConnIdentity.WindowsToken() error: %v", err) + } + return false + } + defer tok.Close() + + isAdmin, err := tok.IsAdministrator() + if err != nil { + s.logf("ipnauth.WindowsToken.IsAdministrator() error: %v", err) + return false + } + return isAdmin +} + // addActiveHTTPRequest adds c to the server's list of active HTTP requests. // // If the returned error may be of type inUseOtherUserError. @@ -372,14 +420,25 @@ func (s *Server) addActiveHTTPRequest(req *http.Request, ci *ipnauth.ConnIdentit mak.Set(&s.activeReqs, req, ci) - if uid := ci.WindowsUserID(); uid != "" && len(s.activeReqs) == 1 { - // Tell the LocalBackend about the identity we're now running as. - lb.SetCurrentUserID(uid) - if s.lastUserID != uid { - if s.lastUserID != "" { - doReset = true + if len(s.activeReqs) == 1 { + token, err := ci.WindowsToken() + if err != nil { + if !errors.Is(err, ipnauth.ErrNotImplemented) { + s.logf("error obtaining access token: %v", err) + } + } else { + // Tell the LocalBackend about the identity we're now running as. + uid, err := lb.SetCurrentUser(token) + if err != nil { + token.Close() + return nil, err + } + if s.lastUserID != uid { + if s.lastUserID != "" { + doReset = true + } + s.lastUserID = uid } - s.lastUserID = uid } } diff --git a/ipn/localapi/localapi.go b/ipn/localapi/localapi.go index d99dcad88..86fc60f4e 100644 --- a/ipn/localapi/localapi.go +++ b/ipn/localapi/localapi.go @@ -151,6 +151,17 @@ type Handler struct { // cert fetching access. PermitCert bool + // CallerIsLocalAdmin is whether the this handler is being invoked as a + // result of a LocalAPI call from a user who is a local admin of the current + // machine. + // + // As of 2023-10-26 it is only populated on Windows. + // + // It can be used to to restrict some LocalAPI operations which should only + // be run by an admin and not unprivileged users in a computing environment + // managed by IT admins. + CallerIsLocalAdmin bool + b *ipnlocal.LocalBackend logf logger.Logf netMon *netmon.Monitor // optional; nil means interfaces will be looked up on-demand @@ -832,6 +843,13 @@ func (h *Handler) serveServeConfig(w http.ResponseWriter, r *http.Request) { writeErrorJSON(w, fmt.Errorf("decoding config: %w", err)) return } + // require a local admin when setting a path handler + // TODO: roll-up this Windows-specific check into either PermitWrite + // or a global admin escalation check. + if shouldDenyServeConfigForGOOSAndUserContext(runtime.GOOS, configIn, h) { + http.Error(w, "must be a Windows local admin to serve a path", http.StatusUnauthorized) + return + } if err := h.b.SetServeConfig(configIn); err != nil { writeErrorJSON(w, fmt.Errorf("updating config: %w", err)) return @@ -842,6 +860,16 @@ func (h *Handler) serveServeConfig(w http.ResponseWriter, r *http.Request) { } } +func shouldDenyServeConfigForGOOSAndUserContext(goos string, configIn *ipn.ServeConfig, h *Handler) bool { + if goos != "windows" { + return false + } + if !configIn.HasPathHandler() { + return false + } + return !h.CallerIsLocalAdmin +} + func (h *Handler) serveCheckIPForwarding(w http.ResponseWriter, r *http.Request) { if !h.PermitRead { http.Error(w, "IP forwarding check access denied", http.StatusForbidden) diff --git a/ipn/localapi/localapi_test.go b/ipn/localapi/localapi_test.go index 057da9039..7a60a1f25 100644 --- a/ipn/localapi/localapi_test.go +++ b/ipn/localapi/localapi_test.go @@ -13,6 +13,7 @@ import ( "tailscale.com/client/tailscale/apitype" "tailscale.com/hostinfo" + "tailscale.com/ipn" "tailscale.com/ipn/ipnlocal" "tailscale.com/tstest" ) @@ -77,3 +78,69 @@ func TestSetPushDeviceToken(t *testing.T) { t.Errorf("hostinfo.PushDeviceToken=%q, want %q", got, want) } } + +func TestShouldDenyServeConfigForGOOSAndUserContext(t *testing.T) { + tests := []struct { + name string + goos string + configIn *ipn.ServeConfig + h *Handler + want bool + }{ + { + name: "linux", + goos: "linux", + configIn: &ipn.ServeConfig{}, + h: &Handler{CallerIsLocalAdmin: false}, + want: false, + }, + { + name: "windows-not-path-handler", + goos: "windows", + configIn: &ipn.ServeConfig{ + Web: map[ipn.HostPort]*ipn.WebServerConfig{ + "foo.test.ts.net:443": {Handlers: map[string]*ipn.HTTPHandler{ + "/": {Proxy: "http://127.0.0.1:3000"}, + }}, + }, + }, + h: &Handler{CallerIsLocalAdmin: false}, + want: false, + }, + { + name: "windows-path-handler-admin", + goos: "windows", + configIn: &ipn.ServeConfig{ + Web: map[ipn.HostPort]*ipn.WebServerConfig{ + "foo.test.ts.net:443": {Handlers: map[string]*ipn.HTTPHandler{ + "/": {Path: "/tmp"}, + }}, + }, + }, + h: &Handler{CallerIsLocalAdmin: true}, + want: false, + }, + { + name: "windows-path-handler-not-admin", + goos: "windows", + configIn: &ipn.ServeConfig{ + Web: map[ipn.HostPort]*ipn.WebServerConfig{ + "foo.test.ts.net:443": {Handlers: map[string]*ipn.HTTPHandler{ + "/": {Path: "/tmp"}, + }}, + }, + }, + h: &Handler{CallerIsLocalAdmin: false}, + want: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := shouldDenyServeConfigForGOOSAndUserContext(tt.goos, tt.configIn, tt.h) + if got != tt.want { + t.Errorf("shouldDenyServeConfigForGOOSAndUserContext() got = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/ipn/serve.go b/ipn/serve.go index 48e3343a1..75c710379 100644 --- a/ipn/serve.go +++ b/ipn/serve.go @@ -134,6 +134,22 @@ func (sc *ServeConfig) GetTCPPortHandler(port uint16) *TCPPortHandler { return sc.TCP[port] } +// HasPathHandler reports whether if ServeConfig has at least +// one path handler, including foreground configs. +func (sc *ServeConfig) HasPathHandler() bool { + if sc.Web != nil { + for _, webServerConfig := range sc.Web { + for _, httpHandler := range webServerConfig.Handlers { + if httpHandler.Path != "" { + return true + } + } + } + } + + return false +} + // IsTCPForwardingAny reports whether ServeConfig is currently forwarding in // TCPForward mode on any port. This is exclusive of Web/HTTPS serving. func (sc *ServeConfig) IsTCPForwardingAny() bool { diff --git a/ipn/serve_test.go b/ipn/serve_test.go index d08aba047..974ac4ba8 100644 --- a/ipn/serve_test.go +++ b/ipn/serve_test.go @@ -38,3 +38,50 @@ func TestCheckFunnelAccess(t *testing.T) { } } } + +func TestHasPathHandler(t *testing.T) { + tests := []struct { + name string + cfg ServeConfig + want bool + }{ + { + name: "empty-config", + cfg: ServeConfig{}, + want: false, + }, + { + name: "with-bg-path-handler", + cfg: ServeConfig{ + TCP: map[uint16]*TCPPortHandler{80: {HTTP: true}}, + Web: map[HostPort]*WebServerConfig{ + "foo.test.ts.net:80": {Handlers: map[string]*HTTPHandler{ + "/": {Path: "/tmp"}, + }}, + }, + }, + want: true, + }, + { + name: "with-no-bg-path-handler", + cfg: ServeConfig{ + TCP: map[uint16]*TCPPortHandler{443: {HTTPS: true}}, + Web: map[HostPort]*WebServerConfig{ + "foo.test.ts.net:443": {Handlers: map[string]*HTTPHandler{ + "/": {Proxy: "http://127.0.0.1:3000"}, + }}, + }, + AllowFunnel: map[HostPort]bool{"foo.test.ts.net:443": true}, + }, + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := tt.cfg.HasPathHandler() + if tt.want != got { + t.Errorf("HasPathHandler() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/safesocket/pipe_windows.go b/safesocket/pipe_windows.go index a110f5f2b..999929120 100644 --- a/safesocket/pipe_windows.go +++ b/safesocket/pipe_windows.go @@ -3,16 +3,27 @@ package safesocket +//go:generate go run golang.org/x/sys/windows/mkwinsyscall -output zsyscall_windows.go pipe_windows.go + import ( + "context" "fmt" "net" + "runtime" "syscall" + "time" - "github.com/Microsoft/go-winio" + "github.com/tailscale/go-winio" + "golang.org/x/sys/windows" ) func connect(s *ConnectionStrategy) (net.Conn, error) { - return winio.DialPipe(s.path, nil) + dl := time.Now().Add(20 * time.Second) + ctx, cancel := context.WithDeadline(context.Background(), dl) + defer cancel() + // We use the identification impersonation level so that tailscaled may + // obtain information about our token for access control purposes. + return winio.DialPipeAccessImpLevel(ctx, s.path, windows.GENERIC_READ|windows.GENERIC_WRITE, winio.PipeImpLevelIdentification) } func setFlags(network, address string, c syscall.RawConn) error { @@ -39,5 +50,109 @@ func listen(path string) (net.Listener, error) { if err != nil { return nil, fmt.Errorf("namedpipe.Listen: %w", err) } - return lc, nil + return &winIOPipeListener{Listener: lc}, nil +} + +// WindowsClientConn is an implementation of net.Conn that permits retrieval of +// the Windows access token associated with the connection's client. The +// embedded net.Conn must be a go-winio PipeConn. +type WindowsClientConn struct { + net.Conn + token windows.Token +} + +// winioPipeHandle is fulfilled by the underlying code implementing go-winio's +// PipeConn interface. +type winioPipeHandle interface { + // Fd returns the Windows handle associated with the connection. + Fd() uintptr +} + +func resolvePipeHandle(c net.Conn) windows.Handle { + wph, ok := c.(winioPipeHandle) + if !ok { + return 0 + } + return windows.Handle(wph.Fd()) +} + +func (conn *WindowsClientConn) handle() windows.Handle { + return resolvePipeHandle(conn.Conn) +} + +// ClientPID returns the pid of conn's client, or else an error. +func (conn *WindowsClientConn) ClientPID() (int, error) { + var pid uint32 + if err := getNamedPipeClientProcessId(conn.handle(), &pid); err != nil { + return -1, fmt.Errorf("GetNamedPipeClientProcessId: %w", err) + } + return int(pid), nil +} + +// Token returns the Windows access token of the client user. +func (conn *WindowsClientConn) Token() windows.Token { + return conn.token +} + +func (conn *WindowsClientConn) Close() error { + if conn.token != 0 { + conn.token.Close() + conn.token = 0 + } + return conn.Conn.Close() +} + +type winIOPipeListener struct { + net.Listener +} + +func (lw *winIOPipeListener) Accept() (net.Conn, error) { + conn, err := lw.Listener.Accept() + if err != nil { + return nil, err + } + + token, err := clientUserAccessToken(conn) + if err != nil { + conn.Close() + return nil, err + } + + return &WindowsClientConn{ + Conn: conn, + token: token, + }, nil } + +func clientUserAccessToken(c net.Conn) (windows.Token, error) { + h := resolvePipeHandle(c) + if h == 0 { + return 0, fmt.Errorf("not a windows handle: %T", c) + } + + // Impersonation touches thread-local state, so we need to lock until the + // client access token has been extracted. + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + if err := impersonateNamedPipeClient(h); err != nil { + return 0, err + } + defer func() { + // Revert the current thread's impersonation. + if err := windows.RevertToSelf(); err != nil { + panic(fmt.Errorf("could not revert impersonation: %w", err)) + } + }() + + // Extract the client's access token from the thread-local state. + var token windows.Token + if err := windows.OpenThreadToken(windows.CurrentThread(), windows.TOKEN_DUPLICATE|windows.TOKEN_QUERY, true, &token); err != nil { + return 0, err + } + + return token, nil +} + +//sys getNamedPipeClientProcessId(h windows.Handle, clientPid *uint32) (err error) [int32(failretval)==0] = kernel32.GetNamedPipeClientProcessId +//sys impersonateNamedPipeClient(h windows.Handle) (err error) [int32(failretval)==0] = advapi32.ImpersonateNamedPipeClient diff --git a/safesocket/zsyscall_windows.go b/safesocket/zsyscall_windows.go new file mode 100644 index 000000000..db22d7386 --- /dev/null +++ b/safesocket/zsyscall_windows.go @@ -0,0 +1,62 @@ +// Code generated by 'go generate'; DO NOT EDIT. + +package safesocket + +import ( + "syscall" + "unsafe" + + "golang.org/x/sys/windows" +) + +var _ unsafe.Pointer + +// Do the interface allocations only once for common +// Errno values. +const ( + errnoERROR_IO_PENDING = 997 +) + +var ( + errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING) + errERROR_EINVAL error = syscall.EINVAL +) + +// errnoErr returns common boxed Errno values, to prevent +// allocations at runtime. +func errnoErr(e syscall.Errno) error { + switch e { + case 0: + return errERROR_EINVAL + case errnoERROR_IO_PENDING: + return errERROR_IO_PENDING + } + // TODO: add more here, after collecting data on the common + // error values see on Windows. (perhaps when running + // all.bat?) + return e +} + +var ( + modadvapi32 = windows.NewLazySystemDLL("advapi32.dll") + modkernel32 = windows.NewLazySystemDLL("kernel32.dll") + + procImpersonateNamedPipeClient = modadvapi32.NewProc("ImpersonateNamedPipeClient") + procGetNamedPipeClientProcessId = modkernel32.NewProc("GetNamedPipeClientProcessId") +) + +func impersonateNamedPipeClient(h windows.Handle) (err error) { + r1, _, e1 := syscall.Syscall(procImpersonateNamedPipeClient.Addr(), 1, uintptr(h), 0, 0) + if int32(r1) == 0 { + err = errnoErr(e1) + } + return +} + +func getNamedPipeClientProcessId(h windows.Handle, clientPid *uint32) (err error) { + r1, _, e1 := syscall.Syscall(procGetNamedPipeClientProcessId.Addr(), 2, uintptr(h), uintptr(unsafe.Pointer(clientPid)), 0) + if int32(r1) == 0 { + err = errnoErr(e1) + } + return +}