Compare commits

..

2 Commits

Author SHA1 Message Date
Xe
1444e34521 cmd/gitops-pusher: fix minor bug with ACL tests
Signed-off-by: Xe <xe@tailscale.com>
2022-07-22 12:38:24 -04:00
Xe
707df2efb2 cmd/gitops-pusher: port to use ffcli
Signed-off-by: Xe <xe@tailscale.com>
2022-07-21 12:26:02 -04:00
17 changed files with 64 additions and 245 deletions

View File

@@ -72,4 +72,3 @@ FROM alpine:3.16
RUN apk add --no-cache ca-certificates iptables iproute2 ip6tables
COPY --from=build-env /go/bin/* /usr/local/bin/
COPY --from=build-env /go/src/tailscale/docs/k8s/run.sh /usr/local/bin/

2
api.md
View File

@@ -1120,7 +1120,7 @@ Replaces the list of searchpaths with the list supplied by the user and returns
`searchPaths` - A list of searchpaths in JSON.
```
{
"searchPaths": ["user1.example.com", "user2.example.com"]
"searchPaths: ["user1.example.com", "user2.example.com"]
}
```

View File

@@ -1 +0,0 @@
version-cache.json

View File

@@ -1,67 +0,0 @@
// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
import (
"encoding/json"
"os"
)
// Cache contains cached information about the last time this tool was run.
//
// This is serialized to a JSON file that should NOT be checked into git.
// It should be managed with either CI cache tools or stored locally somehow. The
// exact mechanism is irrelevant as long as it is consistent.
//
// This allows gitops-pusher to detect external ACL changes. I'm not sure what to
// call this problem, so I've been calling it the "three version problem" in my
// notes. The basic problem is that at any given time we only have two versions
// of the ACL file at any given point. In order to check if there has been
// tampering of the ACL files in the admin panel, we need to have a _third_ version
// to compare against.
//
// In this case I am not storing the old ACL entirely (though that could be a
// reasonable thing to add in the future), but only its sha256sum. This allows
// us to detect if the shasum in control matches the shasum we expect, and if that
// expectation fails, then we can react accordingly.
type Cache struct {
PrevETag string // Stores the previous ETag of the ACL to allow
}
// Save persists the cache to a given file.
func (c *Cache) Save(fname string) error {
os.Remove(fname)
fout, err := os.Create(fname)
if err != nil {
return err
}
defer fout.Close()
return json.NewEncoder(fout).Encode(c)
}
// LoadCache loads the cache from a given file.
func LoadCache(fname string) (*Cache, error) {
var result Cache
fin, err := os.Open(fname)
if err != nil {
return nil, err
}
defer fin.Close()
err = json.NewDecoder(fin).Decode(&result)
if err != nil {
return nil, err
}
return &result, nil
}
// Shuck removes the first and last character of a string, analogous to
// shucking off the husk of an ear of corn.
func Shuck(s string) string {
return s[1 : len(s)-1]
}

View File

@@ -15,6 +15,7 @@ import (
"fmt"
"log"
"net/http"
"net/http/httputil"
"os"
"regexp"
"strings"
@@ -27,23 +28,11 @@ import (
var (
rootFlagSet = flag.NewFlagSet("gitops-pusher", flag.ExitOnError)
policyFname = rootFlagSet.String("policy-file", "./policy.hujson", "filename for policy file")
cacheFname = rootFlagSet.String("cache-file", "./version-cache.json", "filename for the previous known version hash")
timeout = rootFlagSet.Duration("timeout", 5*time.Minute, "timeout for the entire CI run")
githubSyntax = rootFlagSet.Bool("github-syntax", true, "use GitHub Action error syntax (https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#setting-an-error-message)")
modifiedExternallyFailure = make(chan struct{}, 1)
)
func modifiedExternallyError() {
if *githubSyntax {
fmt.Printf("::error file=%s,line=1,col=1,title=Policy File Modified Externally::The policy file was modified externally in the admin console.\n", *policyFname)
} else {
fmt.Printf("The policy file was modified externally in the admin console.\n")
}
modifiedExternallyFailure <- struct{}{}
}
func apply(cache *Cache, tailnet, apiKey string) func(context.Context, []string) error {
func apply(tailnet, apiKey string) func(context.Context, []string) error {
return func(ctx context.Context, args []string) error {
controlEtag, err := getACLETag(ctx, tailnet, apiKey)
if err != nil {
@@ -55,21 +44,10 @@ func apply(cache *Cache, tailnet, apiKey string) func(context.Context, []string)
return err
}
if cache.PrevETag == "" {
log.Println("no previous etag found, assuming local file is correct and recording that")
cache.PrevETag = localEtag
}
log.Printf("control: %s", controlEtag)
log.Printf("local: %s", localEtag)
log.Printf("cache: %s", cache.PrevETag)
if cache.PrevETag != controlEtag {
modifiedExternallyError()
}
if controlEtag == localEtag {
cache.PrevETag = localEtag
log.Println("no update needed, doing nothing")
return nil
}
@@ -78,13 +56,11 @@ func apply(cache *Cache, tailnet, apiKey string) func(context.Context, []string)
return err
}
cache.PrevETag = localEtag
return nil
}
}
func test(cache *Cache, tailnet, apiKey string) func(context.Context, []string) error {
func test(tailnet, apiKey string) func(context.Context, []string) error {
return func(ctx context.Context, args []string) error {
controlEtag, err := getACLETag(ctx, tailnet, apiKey)
if err != nil {
@@ -96,18 +72,8 @@ func test(cache *Cache, tailnet, apiKey string) func(context.Context, []string)
return err
}
if cache.PrevETag == "" {
log.Println("no previous etag found, assuming local file is correct and recording that")
cache.PrevETag = localEtag
}
log.Printf("control: %s", controlEtag)
log.Printf("local: %s", localEtag)
log.Printf("cache: %s", cache.PrevETag)
if cache.PrevETag != controlEtag {
modifiedExternallyError()
}
if controlEtag == localEtag {
log.Println("no updates found, doing nothing")
@@ -121,7 +87,7 @@ func test(cache *Cache, tailnet, apiKey string) func(context.Context, []string)
}
}
func getChecksums(cache *Cache, tailnet, apiKey string) func(context.Context, []string) error {
func getChecksums(tailnet, apiKey string) func(context.Context, []string) error {
return func(ctx context.Context, args []string) error {
controlEtag, err := getACLETag(ctx, tailnet, apiKey)
if err != nil {
@@ -133,14 +99,8 @@ func getChecksums(cache *Cache, tailnet, apiKey string) func(context.Context, []
return err
}
if cache.PrevETag == "" {
log.Println("no previous etag found, assuming local file is correct and recording that")
cache.PrevETag = Shuck(localEtag)
}
log.Printf("control: %s", controlEtag)
log.Printf("local: %s", localEtag)
log.Printf("cache: %s", cache.PrevETag)
return nil
}
@@ -155,22 +115,13 @@ func main() {
if !ok {
log.Fatal("set envvar TS_API_KEY to your Tailscale API key")
}
cache, err := LoadCache(*cacheFname)
if err != nil {
if os.IsNotExist(err) {
cache = &Cache{}
} else {
log.Fatalf("error loading cache: %v", err)
}
}
defer cache.Save(*cacheFname)
applyCmd := &ffcli.Command{
Name: "apply",
ShortUsage: "gitops-pusher [options] apply",
ShortHelp: "Pushes changes to CONTROL",
LongHelp: `Pushes changes to CONTROL`,
Exec: apply(cache, tailnet, apiKey),
Exec: apply(tailnet, apiKey),
}
testCmd := &ffcli.Command{
@@ -178,7 +129,7 @@ func main() {
ShortUsage: "gitops-pusher [options] test",
ShortHelp: "Tests ACL changes",
LongHelp: "Tests ACL changes",
Exec: test(cache, tailnet, apiKey),
Exec: test(tailnet, apiKey),
}
cksumCmd := &ffcli.Command{
@@ -186,7 +137,7 @@ func main() {
ShortUsage: "Shows checksums of ACL files",
ShortHelp: "Fetch checksum of CONTROL's ACL and the local ACL for comparison",
LongHelp: "Fetch checksum of CONTROL's ACL and the local ACL for comparison",
Exec: getChecksums(cache, tailnet, apiKey),
Exec: getChecksums(tailnet, apiKey),
}
root := &ffcli.Command{
@@ -207,10 +158,6 @@ func main() {
fmt.Println(err)
os.Exit(1)
}
if len(modifiedExternallyFailure) != 0 {
os.Exit(1)
}
}
func sumFile(fname string) (string, error) {
@@ -230,7 +177,7 @@ func sumFile(fname string) (string, error) {
return "", err
}
return fmt.Sprintf("%x", h.Sum(nil)), nil
return fmt.Sprintf("\"%x\"", h.Sum(nil)), nil
}
func applyNewACL(ctx context.Context, tailnet, apiKey, policyFname, oldEtag string) error {
@@ -247,7 +194,7 @@ func applyNewACL(ctx context.Context, tailnet, apiKey, policyFname, oldEtag stri
req.SetBasicAuth(apiKey, "")
req.Header.Set("Content-Type", "application/hujson")
req.Header.Set("If-Match", `"`+oldEtag+`"`)
req.Header.Set("If-Match", oldEtag)
resp, err := http.DefaultClient.Do(req)
if err != nil {
@@ -291,12 +238,6 @@ func testNewACLs(ctx context.Context, tailnet, apiKey, policyFname string) error
}
defer resp.Body.Close()
got := resp.StatusCode
want := http.StatusOK
if got != want {
return fmt.Errorf("wanted HTTP status code %d but got %d", want, got)
}
var ate ACLTestError
err = json.NewDecoder(resp.Body).Decode(&ate)
if err != nil {
@@ -307,10 +248,18 @@ func testNewACLs(ctx context.Context, tailnet, apiKey, policyFname string) error
return ate
}
got := resp.StatusCode
want := http.StatusOK
if got != want {
data, _ := httputil.DumpResponse(resp, true)
os.Stderr.Write(data)
return fmt.Errorf("wanted HTTP status code %d but got %d", want, got)
}
return nil
}
var lineColMessageSplit = regexp.MustCompile(`line ([0-9]+), column ([0-9]+): (.*)$`)
var lineColMessageSplit = regexp.MustCompile(`^line ([0-9]+), column ([0-9]+): (.*)$`)
type ACLTestError struct {
Message string `json:"message"`
@@ -369,5 +318,5 @@ func getACLETag(ctx context.Context, tailnet, apiKey string) (string, error) {
return "", fmt.Errorf("wanted HTTP status code %d but got %d", want, got)
}
return Shuck(resp.Header.Get("ETag")), nil
return resp.Header.Get("ETag"), nil
}

View File

@@ -129,8 +129,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
💣 golang.zx2c4.com/wireguard/tun from golang.zx2c4.com/wireguard/device+
W 💣 golang.zx2c4.com/wireguard/windows/tunnel/winipcfg from tailscale.com/cmd/tailscaled+
gvisor.dev/gvisor/pkg/atomicbitops from gvisor.dev/gvisor/pkg/tcpip+
gvisor.dev/gvisor/pkg/bits from gvisor.dev/gvisor/pkg/bufferv2
💣 gvisor.dev/gvisor/pkg/bufferv2 from gvisor.dev/gvisor/pkg/tcpip+
💣 gvisor.dev/gvisor/pkg/buffer from gvisor.dev/gvisor/pkg/tcpip/stack
gvisor.dev/gvisor/pkg/context from gvisor.dev/gvisor/pkg/refs+
💣 gvisor.dev/gvisor/pkg/gohacks from gvisor.dev/gvisor/pkg/state/wire+
gvisor.dev/gvisor/pkg/linewriter from gvisor.dev/gvisor/pkg/log
@@ -144,6 +143,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
💣 gvisor.dev/gvisor/pkg/sync from gvisor.dev/gvisor/pkg/linewriter+
gvisor.dev/gvisor/pkg/tcpip from gvisor.dev/gvisor/pkg/tcpip/header+
gvisor.dev/gvisor/pkg/tcpip/adapters/gonet from tailscale.com/wgengine/netstack
💣 gvisor.dev/gvisor/pkg/tcpip/buffer from gvisor.dev/gvisor/pkg/tcpip/header+
gvisor.dev/gvisor/pkg/tcpip/hash/jenkins from gvisor.dev/gvisor/pkg/tcpip/stack+
gvisor.dev/gvisor/pkg/tcpip/header from gvisor.dev/gvisor/pkg/tcpip/header/parse+
gvisor.dev/gvisor/pkg/tcpip/header/parse from gvisor.dev/gvisor/pkg/tcpip/network/ipv4+
@@ -152,7 +152,6 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
gvisor.dev/gvisor/pkg/tcpip/network/hash from gvisor.dev/gvisor/pkg/tcpip/network/ipv4+
gvisor.dev/gvisor/pkg/tcpip/network/internal/fragmentation from gvisor.dev/gvisor/pkg/tcpip/network/ipv4+
gvisor.dev/gvisor/pkg/tcpip/network/internal/ip from gvisor.dev/gvisor/pkg/tcpip/network/ipv4+
gvisor.dev/gvisor/pkg/tcpip/network/internal/multicast from gvisor.dev/gvisor/pkg/tcpip/network/ipv4+
gvisor.dev/gvisor/pkg/tcpip/network/ipv4 from tailscale.com/net/tstun+
gvisor.dev/gvisor/pkg/tcpip/network/ipv6 from tailscale.com/wgengine/netstack
gvisor.dev/gvisor/pkg/tcpip/ports from gvisor.dev/gvisor/pkg/tcpip/stack+

View File

@@ -4,8 +4,6 @@
#! /bin/sh
set -m # enable job control
export PATH=$PATH:/tailscale/bin
TS_AUTH_KEY="${TS_AUTH_KEY:-}"
@@ -62,6 +60,7 @@ fi
echo "Starting tailscaled"
tailscaled ${TAILSCALED_ARGS} &
PID=$!
UP_ARGS="--accept-dns=${TS_ACCEPT_DNS}"
if [[ ! -z "${TS_ROUTES}" ]]; then
@@ -82,4 +81,4 @@ if [[ ! -z "${TS_DEST_IP}" ]]; then
iptables -t nat -I PREROUTING -d "$(tailscale --socket=/tmp/tailscaled.sock ip -4)" -j DNAT --to-destination "${TS_DEST_IP}"
fi
fg
wait ${PID}

6
go.mod
View File

@@ -19,14 +19,12 @@ require (
github.com/dave/jennifer v1.4.1
github.com/evanw/esbuild v0.14.39
github.com/frankban/quicktest v1.14.0
github.com/fxamacker/cbor/v2 v2.4.0
github.com/go-ole/go-ole v1.2.6
github.com/godbus/dbus/v5 v5.0.6
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da
github.com/google/go-cmp v0.5.8
github.com/google/uuid v1.3.0
github.com/goreleaser/nfpm v1.10.3
github.com/hdevalence/ed25519consensus v0.0.0-20220222234857-c00d1f31bab3
github.com/iancoleman/strcase v0.2.0
github.com/insomniacslk/dhcp v0.0.0-20211209223715-7d93572ebe8e
github.com/jsimonetti/rtnetlink v1.1.2-0.20220408201609-d380b505068b
@@ -63,7 +61,7 @@ require (
golang.org/x/tools v0.1.11
golang.zx2c4.com/wireguard v0.0.0-20220703234212-c31a7b1ab478
golang.zx2c4.com/wireguard/windows v0.4.10
gvisor.dev/gvisor v0.0.0-20220721202624-0b2c11c2773c
gvisor.dev/gvisor v0.0.0-20220407223209-21871174d445
honnef.co/go/tools v0.4.0-0.dev.0.20220404092545-59d7a2877f83
inet.af/netaddr v0.0.0-20220617031823-097006376321
inet.af/peercred v0.0.0-20210906144145-0893ea02156a
@@ -126,6 +124,7 @@ require (
github.com/fatih/color v1.13.0 // indirect
github.com/fatih/structtag v1.2.0 // indirect
github.com/fsnotify/fsnotify v1.5.1 // indirect
github.com/fxamacker/cbor/v2 v2.4.0 // indirect
github.com/fzipp/gocyclo v0.3.1 // indirect
github.com/gliderlabs/ssh v0.3.3 // indirect
github.com/go-critic/go-critic v0.6.1 // indirect
@@ -167,6 +166,7 @@ require (
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/hdevalence/ed25519consensus v0.0.0-20220222234857-c00d1f31bab3 // indirect
github.com/huandu/xstrings v1.3.2 // indirect
github.com/imdario/mergo v0.3.12 // indirect
github.com/inconshreveable/mousetrap v1.0.0 // indirect

6
go.sum
View File

@@ -1110,6 +1110,8 @@ github.com/tailscale/golang-x-crypto v0.0.0-20220428210705-0b941c09a5e1 h1:vsFV6
github.com/tailscale/golang-x-crypto v0.0.0-20220428210705-0b941c09a5e1/go.mod h1:95n9fbUCixVSI4QXLEvdKJjnYK2eUlkTx9+QwLPXFKU=
github.com/tailscale/goupnp v1.0.1-0.20210804011211-c64d0f06ea05 h1:4chzWmimtJPxRs2O36yuGRW3f9SYV+bMTTvMBI0EKio=
github.com/tailscale/goupnp v1.0.1-0.20210804011211-c64d0f06ea05/go.mod h1:PdCqy9JzfWMJf1H5UJW2ip33/d4YkoKN0r67yKH1mG8=
github.com/tailscale/hujson v0.0.0-20220506202205-92b4b88a9e17 h1:QaQrUggZ7U2lE3HhoPx6bDK7fO385FR7pHRYSPEv70Q=
github.com/tailscale/hujson v0.0.0-20220506202205-92b4b88a9e17/go.mod h1:DFSS3NAGHthKo1gTlmEcSBiZrRJXi28rLNd/1udP1c8=
github.com/tailscale/hujson v0.0.0-20220630195928-54599719472f h1:n4r/sJ92cBSBHK8n9lR1XLFr0OiTVeGfN5TR+9LaN7E=
github.com/tailscale/hujson v0.0.0-20220630195928-54599719472f/go.mod h1:DFSS3NAGHthKo1gTlmEcSBiZrRJXi28rLNd/1udP1c8=
github.com/tailscale/mkctr v0.0.0-20220601142259-c0b937af2e89 h1:7xU7AFQE83h0wz/dIMvD0t77g0FxFfZIQjghDQxyG2U=
@@ -1857,8 +1859,8 @@ gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0=
gvisor.dev/gvisor v0.0.0-20220721202624-0b2c11c2773c h1:frrINYSQqhraHqy23/dWqdNt7mRlsGJJBwGHvI3Q+/c=
gvisor.dev/gvisor v0.0.0-20220721202624-0b2c11c2773c/go.mod h1:TIvkJD0sxe8pIob3p6T8IzxXunlp6yfgktvTNp+DGNM=
gvisor.dev/gvisor v0.0.0-20220407223209-21871174d445 h1:pLNQCtMzh4O6rdhoUeWHuutt4yMft+B9Cgw/bezWchE=
gvisor.dev/gvisor v0.0.0-20220407223209-21871174d445/go.mod h1:tWwEcFvJavs154OdjFCw78axNrsDlz4Zh8jvPqwcpGI=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

View File

@@ -117,8 +117,7 @@ func (b *LocalBackend) hostKeyFileOrCreate(keyDir, typ string) ([]byte, error) {
func (b *LocalBackend) getSystemSSH_HostKeys() (ret []ssh.Signer, err error) {
// TODO(bradfitz): cache this?
for _, typ := range keyTypes {
filename := "/etc/ssh/ssh_host_" + typ + "_key"
hostKey, err := ioutil.ReadFile(filename)
hostKey, err := ioutil.ReadFile("/etc/ssh/ssh_host_" + typ + "_key")
if os.IsNotExist(err) {
continue
}
@@ -127,7 +126,7 @@ func (b *LocalBackend) getSystemSSH_HostKeys() (ret []ssh.Signer, err error) {
}
signer, err := ssh.ParsePrivateKey(hostKey)
if err != nil {
return nil, fmt.Errorf("error reading private key %s: %w", filename, err)
return nil, err
}
ret = append(ret, signer)
}

View File

@@ -14,6 +14,7 @@ import (
"golang.org/x/sys/unix"
"golang.zx2c4.com/wireguard/tun"
"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/buffer"
"gvisor.dev/gvisor/pkg/tcpip/header"
"gvisor.dev/gvisor/pkg/tcpip/network/ipv4"
"gvisor.dev/gvisor/pkg/tcpip/transport/udp"
@@ -289,7 +290,7 @@ func (t *Wrapper) handleDHCPRequest(ethBuf []byte) bool {
}
func packLayer2UDP(payload []byte, srcMAC, dstMAC net.HardwareAddr, src, dst netaddr.IPPort) []byte {
buf := make([]byte, header.EthernetMinimumSize+header.UDPMinimumSize+header.IPv4MinimumSize+len(payload))
buf := buffer.NewView(header.EthernetMinimumSize + header.UDPMinimumSize + header.IPv4MinimumSize + len(payload))
payloadStart := len(buf) - len(payload)
copy(buf[payloadStart:], payload)
srcB := src.IP().As4()

View File

@@ -524,10 +524,9 @@ func (t *Wrapper) Read(buf []byte, offset int) (int, error) {
var n int
if res.packet != nil {
n = copy(buf[offset:], res.packet.NetworkHeader().Slice())
n += copy(buf[offset+n:], res.packet.TransportHeader().Slice())
n += copy(buf[offset+n:], res.packet.Data().AsRange().ToSlice())
n = copy(buf[offset:], res.packet.NetworkHeader().View())
n += copy(buf[offset+n:], res.packet.TransportHeader().View())
n += copy(buf[offset+n:], res.packet.Data().AsRange().AsView())
res.packet.DecRef()
} else {
@@ -716,9 +715,9 @@ func (t *Wrapper) SetFilter(filt *filter.Filter) {
func (t *Wrapper) InjectInboundPacketBuffer(pkt *stack.PacketBuffer) error {
buf := make([]byte, PacketStartOffset+pkt.Size())
n := copy(buf[PacketStartOffset:], pkt.NetworkHeader().Slice())
n += copy(buf[PacketStartOffset+n:], pkt.TransportHeader().Slice())
n += copy(buf[PacketStartOffset+n:], pkt.Data().AsRange().ToSlice())
n := copy(buf[PacketStartOffset:], pkt.NetworkHeader().View())
n += copy(buf[PacketStartOffset+n:], pkt.TransportHeader().View())
n += copy(buf[PacketStartOffset+n:], pkt.Data().AsRange().AsView())
if n != pkt.Size() {
panic("unexpected packet size after copy")
}

View File

@@ -86,11 +86,8 @@ func (ss *sshSession) newIncubatorCommand() *exec.Cmd {
// TODO(maisem): this doesn't work with sftp
return exec.CommandContext(ss.ctx, name, args...)
}
ss.conn.mu.Lock()
lu := ss.conn.localUser
ci := ss.conn.info
gids := strings.Join(ss.conn.userGroupIDs, ",")
ss.conn.mu.Unlock()
remoteUser := ci.uprof.LoginName
if len(ci.node.Tags) > 0 {
remoteUser = strings.Join(ci.node.Tags, ",")
@@ -101,7 +98,7 @@ func (ss *sshSession) newIncubatorCommand() *exec.Cmd {
"ssh",
"--uid=" + lu.Uid,
"--gid=" + lu.Gid,
"--groups=" + gids,
"--groups=" + strings.Join(ss.conn.userGroupIDs, ","),
"--local-user=" + lu.Username,
"--remote-user=" + remoteUser,
"--remote-ip=" + ci.src.IP().String(),

View File

@@ -141,14 +141,6 @@ func (srv *server) OnPolicyChange() {
srv.mu.Lock()
defer srv.mu.Unlock()
for c := range srv.activeConns {
c.mu.Lock()
ci := c.info
c.mu.Unlock()
if ci == nil {
// c.info is nil when the connection hasn't been authenticated yet.
// In that case, the connection will be terminated when it is.
continue
}
go c.checkStillValid()
}
}
@@ -160,14 +152,14 @@ type conn struct {
insecureSkipTailscaleAuth bool // used by tests.
connID string // ID that's shared with control
action0 *tailcfg.SSHAction // first matching action
srv *server
mu sync.Mutex // protects the following
connID string // ID that's shared with control
action0 *tailcfg.SSHAction // first matching action
srv *server
info *sshConnInfo // set by setInfo
localUser *user.User // set by checkAuth
userGroupIDs []string // set by checkAuth
info *sshConnInfo // set by setInfo
mu sync.Mutex // protects the following
// idH is the RFC4253 sec8 hash H. It is used to identify the connection,
// and is shared among all sessions. It should not be shared outside
// process. It is confusingly referred to as SessionID by the gliderlabs/ssh
@@ -187,13 +179,9 @@ func (c *conn) logf(format string, args ...any) {
// PublicKeyHandler implements ssh.PublicKeyHandler is called by the the
// ssh.Server when the client presents a public key.
func (c *conn) PublicKeyHandler(ctx ssh.Context, pubKey ssh.PublicKey) error {
c.mu.Lock()
ci := c.info
c.mu.Unlock()
if ci == nil {
if c.info == nil {
return gossh.ErrDenied
}
if err := c.checkAuth(pubKey); err != nil {
// TODO(maisem/bradfitz): surface the error here.
c.logf("rejecting SSH public key %s: %v", bytes.TrimSpace(gossh.MarshalAuthorizedKey(pubKey)), err)
@@ -229,7 +217,7 @@ func (c *conn) NoClientAuthCallback(cm gossh.ConnMetadata) (*gossh.Permissions,
func (c *conn) checkAuth(pubKey ssh.PublicKey) error {
a, localUser, err := c.evaluatePolicy(pubKey)
if err != nil {
if pubKey == nil && c.havePubKeyPolicy() {
if pubKey == nil && c.havePubKeyPolicy(c.info) {
return errPubKeyRequired
}
return fmt.Errorf("%w: %v", gossh.ErrDenied, err)
@@ -248,8 +236,6 @@ func (c *conn) checkAuth(pubKey ssh.PublicKey) error {
if err != nil {
return err
}
c.mu.Lock()
defer c.mu.Unlock()
c.userGroupIDs = gids
c.localUser = lu
return nil
@@ -290,7 +276,7 @@ func (srv *server) newConn() (*conn, error) {
srv.mu.Unlock()
c := &conn{srv: srv}
now := srv.now()
c.connID = fmt.Sprintf("ssh-conn-%s-%02x", now.UTC().Format("20060102T150405"), randBytes(5))
c.connID = fmt.Sprintf("conn-%s-%02x", now.UTC().Format("20060102T150405"), randBytes(5))
c.Server = &ssh.Server{
Version: "Tailscale",
Handler: c.handleSessionPostSSHAuth,
@@ -343,13 +329,7 @@ func (c *conn) mayForwardLocalPortTo(ctx ssh.Context, destinationHost string, de
// havePubKeyPolicy reports whether any policy rule may provide access by means
// of a ssh.PublicKey.
func (c *conn) havePubKeyPolicy() bool {
c.mu.Lock()
ci := c.info
c.mu.Unlock()
if ci == nil {
panic("havePubKeyPolicy called before setInfo")
}
func (c *conn) havePubKeyPolicy(ci *sshConnInfo) bool {
// Is there any rule that looks like it'd require a public key for this
// sshUser?
pol, ok := c.sshPolicy()
@@ -434,8 +414,6 @@ func (c *conn) setInfo(cm gossh.ConnMetadata) error {
if !ok {
return fmt.Errorf("unknown Tailscale identity from src %v", ci.src)
}
c.mu.Lock()
defer c.mu.Unlock()
ci.node = node
ci.uprof = &uprof
@@ -611,10 +589,8 @@ func (c *conn) handleSessionPostSSHAuth(s ssh.Session) {
}
ss := c.newSSHSession(s)
c.mu.Lock()
ss.logf("handling new SSH connection from %v (%v) to ssh-user %q", c.info.uprof.LoginName, c.info.src.IP(), c.localUser.Username)
ss.logf("access granted to %v as ssh-user %q", c.info.uprof.LoginName, c.localUser.Username)
c.mu.Unlock()
ss.run()
}
@@ -712,10 +688,7 @@ func (c *conn) resolveTerminalActionLocked(s ssh.Session, cr *contextReader) (ac
func (c *conn) expandDelegateURL(actionURL string) string {
nm := c.srv.lb.NetMap()
c.mu.Lock()
ci := c.info
lu := c.localUser
c.mu.Unlock()
var dstNodeID string
if nm != nil {
dstNodeID = fmt.Sprint(int64(nm.SelfNode.ID))
@@ -726,7 +699,7 @@ func (c *conn) expandDelegateURL(actionURL string) string {
"$DST_NODE_IP", url.QueryEscape(ci.dst.IP().String()),
"$DST_NODE_ID", dstNodeID,
"$SSH_USER", url.QueryEscape(ci.sshUser),
"$LOCAL_USER", url.QueryEscape(lu.Username),
"$LOCAL_USER", url.QueryEscape(c.localUser.Username),
).Replace(actionURL)
}
@@ -736,12 +709,10 @@ func (c *conn) expandPublicKeyURL(pubKeyURL string) string {
}
var localPart string
var loginName string
c.mu.Lock()
if c.info.uprof != nil {
loginName = c.info.uprof.LoginName
localPart, _, _ = strings.Cut(loginName, "@")
}
c.mu.Unlock()
return strings.NewReplacer(
"$LOGINNAME_EMAIL", loginName,
"$LOGINNAME_LOCALPART", localPart,
@@ -797,8 +768,6 @@ func (c *conn) isStillValid() bool {
if !a.Accept && a.HoldAndDelegate == "" {
return false
}
c.mu.Lock()
defer c.mu.Unlock()
return c.localUser.Username == localUser
}
@@ -975,8 +944,6 @@ func (ss *sshSession) run() {
return
}
ss.conn.startSessionLocked(ss)
lu := ss.conn.localUser
localUser := lu.Username
srv.mu.Unlock()
defer ss.conn.endSession(ss)
@@ -992,6 +959,8 @@ func (ss *sshSession) run() {
}
logf := ss.logf
lu := ss.conn.localUser
localUser := lu.Username
if euid := os.Geteuid(); euid != 0 {
if lu.Uid != fmt.Sprint(euid) {
@@ -1141,20 +1110,9 @@ var (
errRuleExpired = errors.New("rule expired")
errPrincipalMatch = errors.New("principal didn't match")
errUserMatch = errors.New("user didn't match")
errInvalidConn = errors.New("invalid connection state")
)
func (c *conn) matchRule(r *tailcfg.SSHRule, pubKey gossh.PublicKey) (a *tailcfg.SSHAction, localUser string, err error) {
if c == nil {
return nil, "", errInvalidConn
}
c.mu.Lock()
ci := c.info
c.mu.Unlock()
if ci == nil {
c.logf("invalid connection state")
return nil, "", errInvalidConn
}
if r == nil {
return nil, "", errNilRule
}
@@ -1168,7 +1126,7 @@ func (c *conn) matchRule(r *tailcfg.SSHRule, pubKey gossh.PublicKey) (a *tailcfg
// For all but Reject rules, SSHUsers is required.
// If SSHUsers is nil or empty, mapLocalUser will return an
// empty string anyway.
localUser = mapLocalUser(r.SSHUsers, ci.sshUser)
localUser = mapLocalUser(r.SSHUsers, c.info.sshUser)
if localUser == "" {
return nil, "", errUserMatch
}
@@ -1217,9 +1175,7 @@ func (c *conn) principalMatches(p *tailcfg.SSHPrincipal, pubKey gossh.PublicKey)
// that match the Tailscale identity match (Node, NodeIP, UserLogin, Any).
// This function does not consider PubKeys.
func (c *conn) principalMatchesTailscaleIdentity(p *tailcfg.SSHPrincipal) bool {
c.mu.Lock()
ci := c.info
c.mu.Unlock()
if p.Any {
return true
}

View File

@@ -47,26 +47,13 @@ func TestMatchRule(t *testing.T) {
wantErr error
wantUser string
}{
{
name: "invalid-conn",
rule: &tailcfg.SSHRule{
Action: someAction,
Principals: []*tailcfg.SSHPrincipal{{Any: true}},
SSHUsers: map[string]string{
"*": "ubuntu",
},
},
wantErr: errInvalidConn,
},
{
name: "nil-rule",
ci: &sshConnInfo{},
rule: nil,
wantErr: errNilRule,
},
{
name: "nil-action",
ci: &sshConnInfo{},
rule: &tailcfg.SSHRule{},
wantErr: errNilAction,
},
@@ -193,7 +180,6 @@ func TestMatchRule(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
c := &conn{
info: tt.ci,
srv: &server{logf: t.Logf},
}
got, gotUser, err := c.matchRule(tt.rule, nil)
if err != tt.wantErr {

View File

@@ -14,9 +14,9 @@ import (
// tests netstack's AlignedAtomicInt64.
func TestAlignedAtomicInt64(t *testing.T) {
type T struct {
A atomicbitops.Int64
A atomicbitops.AlignedAtomicInt64
x int32
B atomicbitops.Int64
B atomicbitops.AlignedAtomicInt64
}
t.Logf("I am %v/%v\n", runtime.GOOS, runtime.GOARCH)

View File

@@ -21,10 +21,10 @@ import (
"sync/atomic"
"time"
"gvisor.dev/gvisor/pkg/bufferv2"
"gvisor.dev/gvisor/pkg/refs"
"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/adapters/gonet"
"gvisor.dev/gvisor/pkg/tcpip/buffer"
"gvisor.dev/gvisor/pkg/tcpip/header"
"gvisor.dev/gvisor/pkg/tcpip/link/channel"
"gvisor.dev/gvisor/pkg/tcpip/network/ipv4"
@@ -402,9 +402,9 @@ func (ns *Impl) handleLocalPackets(p *packet.Parsed, t *tstun.Wrapper) filter.Re
if debugPackets {
ns.logf("[v2] service packet in (from %v): % x", p.Src, p.Buffer())
}
vv := buffer.View(append([]byte(nil), p.Buffer()...)).ToVectorisedView()
packetBuf := stack.NewPacketBuffer(stack.PacketBufferOptions{
Payload: bufferv2.MakeWithData(append([]byte(nil), p.Buffer()...)),
Data: vv,
})
ns.linkEP.InjectInbound(pn, packetBuf)
packetBuf.DecRef()
@@ -477,7 +477,7 @@ func (ns *Impl) inject() {
// TODO(tom): Figure out if its safe to modify packet.Parsed to fill in
// the IP src/dest even if its missing the rest of the pkt.
// That way we dont have to do this twitchy-af byte-yeeting.
if b := pkt.NetworkHeader().Slice(); len(b) >= 20 { // min ipv4 header
if b := pkt.NetworkHeader().View(); len(b) >= 20 { // min ipv4 header
switch b[0] >> 4 { // ip proto field
case 4:
if srcIP := netaddr.IPv4(b[12], b[13], b[14], b[15]); magicDNSIP == srcIP {
@@ -687,8 +687,9 @@ func (ns *Impl) injectInbound(p *packet.Parsed, t *tstun.Wrapper) filter.Respons
if debugPackets {
ns.logf("[v2] packet in (from %v): % x", p.Src, p.Buffer())
}
vv := buffer.View(append([]byte(nil), p.Buffer()...)).ToVectorisedView()
packetBuf := stack.NewPacketBuffer(stack.PacketBufferOptions{
Payload: bufferv2.MakeWithData(append([]byte(nil), p.Buffer()...)),
Data: vv,
})
ns.linkEP.InjectInbound(pn, packetBuf)
packetBuf.DecRef()