Monorepo for Tangled tangled.org
0

Configure Feed

Select the types of activity you want to include in your feed.

Labels

None yet.

Participants 1
AT URI
at://did:plc:dfl62fgb7wtjj3fcbb72naae/sh.tangled.repo.pull/3mrmmthi33x22
+114 -75
Diff #0
+6 -44
spindle/engines/microvm/networking.go
··· 6 6 _ "embed" 7 7 "fmt" 8 8 "log/slog" 9 - "net" 10 9 "os" 11 10 "os/exec" 12 11 "text/template" 13 - ) 14 12 15 - // https://www.iana.org/assignments/iana-ipv4-special-registry/iana-ipv4-special-registry.xhtml 16 - // https://www.iana.org/assignments/iana-ipv6-special-registry/iana-ipv6-special-registry.xhtml 17 - // https://datatracker.ietf.org/doc/rfc6890/ 18 - var blockedNamespaceRoutes = []string{ 19 - "0.0.0.0/8", // unspecified / "this network" addresses 20 - "10.0.0.0/8", // private network 21 - "100.64.0.0/10", // shared carrier-grade nat space 22 - "127.0.0.0/8", // loopback 23 - "169.254.0.0/16", // link-local / autoconfiguration 24 - "172.16.0.0/12", // private network 25 - "192.0.0.0/24", // ietf protocol assignments 26 - "192.0.2.0/24", // documentation / examples 27 - "192.88.99.0/24", // deprecated 6to4 relay anycast 28 - "192.168.0.0/16", // private network 29 - "198.18.0.0/15", // benchmarking / testing 30 - "198.51.100.0/24", // documentation / examples 31 - "203.0.113.0/24", // documentation / examples 32 - "224.0.0.0/4", // multicast 33 - "240.0.0.0/4", // reserved / future use, includes limited broadcast 34 - "::/128", // unspecified address 35 - "::1/128", // loopback 36 - "::ffff:0:0/96", // ipv4-mapped addresses 37 - "64:ff9b::/96", // ipv4/ipv6 translation prefix 38 - "100::/64", // discard-only prefix 39 - "2001::/23", // ietf protocol assignments 40 - "2001:db8::/32", // documentation / examples 41 - "2002::/16", // deprecated 6to4 addressing 42 - "fc00::/7", // unique local addresses 43 - "fe80::/10", // link-local unicast 44 - "ff00::/8", // multicast 45 - } 13 + "tangled.org/core/spindle/netguard" 14 + ) 46 15 47 - var blockedNamespaceNets = func() []*net.IPNet { 48 - nets := make([]*net.IPNet, 0, len(blockedNamespaceRoutes)) 49 - for _, route := range blockedNamespaceRoutes { 50 - _, ipnet, err := net.ParseCIDR(route) 51 - if err != nil { 52 - panic(fmt.Sprintf("parse blocked route %q: %v", route, err)) 53 - } 54 - nets = append(nets, ipnet) 55 - } 56 - return nets 57 - }() 16 + var ( 17 + blockedNamespaceRoutes = netguard.BlockedRoutes 18 + blockedNamespaceNets = netguard.BlockedNets 19 + ) 58 20 59 21 //go:embed netns_wrapper.sh.tmpl 60 22 var netnsWrapperTemplate string
+3 -29
spindle/engines/microvm/read_cache_proxy.go
··· 14 14 "net/url" 15 15 "strings" 16 16 "sync" 17 - "syscall" 18 17 "time" 19 18 20 19 "github.com/mdlayher/vsock" 20 + 21 + "tangled.org/core/spindle/netguard" 21 22 ) 22 23 23 24 const ( ··· 233 234 DialContext: (&net.Dialer{ 234 235 Timeout: 30 * time.Second, 235 236 KeepAlive: 30 * time.Second, 236 - Control: refuseSpecialPurposeAddrs, 237 + Control: netguard.RefuseSpecialPurposeAddrs, 237 238 }).DialContext, 238 239 ForceAttemptHTTP2: true, 239 240 MaxIdleConns: 100, ··· 242 243 ExpectContinueTimeout: 1 * time.Second, 243 244 } 244 245 245 - // this should run after dns resolution, so it should cover any rebinding tricks 246 - func refuseSpecialPurposeAddrs(network, address string, _ syscall.RawConn) error { 247 - host, _, err := net.SplitHostPort(address) 248 - if err != nil { 249 - return fmt.Errorf("split dial address %q: %w", address, err) 250 - } 251 - ip := net.ParseIP(host) 252 - if ip == nil { 253 - return fmt.Errorf("refusing to dial non-IP address %q", host) 254 - } 255 - bits := 128 256 - if ip4 := ip.To4(); ip4 != nil { 257 - ip = ip4 258 - bits = 32 259 - } 260 - for _, ipnet := range blockedNamespaceNets { 261 - _, blockedBits := ipnet.Mask.Size() 262 - if blockedBits != bits { 263 - continue 264 - } 265 - if ipnet.Contains(ip) { 266 - return fmt.Errorf("refusing to dial %s: %s is blocked for workflow caches", ip, ipnet) 267 - } 268 - } 269 - return nil 270 - } 271 - 272 246 // the proxy is the cache as far as the guest is concerned, so we answer 273 247 // /nix-cache-info ourselves instead of racing the upstreams for it. merging 274 248 // those also doesn't make any sense (none of the options make sense for
+3 -1
spindle/engines/microvm/read_cache_proxy_test.go
··· 8 8 "strings" 9 9 "testing" 10 10 "time" 11 + 12 + "tangled.org/core/spindle/netguard" 11 13 ) 12 14 13 15 func TestCacheProxyFallsBackOnNotFound(t *testing.T) { ··· 123 125 } 124 126 125 127 func TestCacheProxyGuardAllowsPublicIPv4(t *testing.T) { 126 - if err := refuseSpecialPurposeAddrs("tcp", "104.26.13.82:443", nil); err != nil { 128 + if err := netguard.RefuseSpecialPurposeAddrs("tcp", "104.26.13.82:443", nil); err != nil { 127 129 t.Fatalf("public IPv4 address was blocked: %v", err) 128 130 } 129 131 }
+82
spindle/netguard/netguard.go
··· 1 + // refuses outbound dials to special-purpose addresses, for anywhere 2 + // spindle fetches user-influenced urls (workflow caches, PDS blob 3 + // fetches) 4 + package netguard 5 + 6 + import ( 7 + "fmt" 8 + "net" 9 + "syscall" 10 + ) 11 + 12 + // https://www.iana.org/assignments/iana-ipv4-special-registry/iana-ipv4-special-registry.xhtml 13 + // https://www.iana.org/assignments/iana-ipv6-special-registry/iana-ipv6-special-registry.xhtml 14 + // https://datatracker.ietf.org/doc/rfc6890/ 15 + var BlockedRoutes = []string{ 16 + "0.0.0.0/8", // unspecified / "this network" addresses 17 + "10.0.0.0/8", // private network 18 + "100.64.0.0/10", // shared carrier-grade nat space 19 + "127.0.0.0/8", // loopback 20 + "169.254.0.0/16", // link-local / autoconfiguration 21 + "172.16.0.0/12", // private network 22 + "192.0.0.0/24", // ietf protocol assignments 23 + "192.0.2.0/24", // documentation / examples 24 + "192.88.99.0/24", // deprecated 6to4 relay anycast 25 + "192.168.0.0/16", // private network 26 + "198.18.0.0/15", // benchmarking / testing 27 + "198.51.100.0/24", // documentation / examples 28 + "203.0.113.0/24", // documentation / examples 29 + "224.0.0.0/4", // multicast 30 + "240.0.0.0/4", // reserved / future use, includes limited broadcast 31 + "::/128", // unspecified address 32 + "::1/128", // loopback 33 + "::ffff:0:0/96", // ipv4-mapped addresses 34 + "64:ff9b::/96", // ipv4/ipv6 translation prefix 35 + "100::/64", // discard-only prefix 36 + "2001::/23", // ietf protocol assignments 37 + "2001:db8::/32", // documentation / examples 38 + "2002::/16", // deprecated 6to4 addressing 39 + "fc00::/7", // unique local addresses 40 + "fe80::/10", // link-local unicast 41 + "ff00::/8", // multicast 42 + } 43 + 44 + var BlockedNets = func() []*net.IPNet { 45 + nets := make([]*net.IPNet, 0, len(BlockedRoutes)) 46 + for _, route := range BlockedRoutes { 47 + _, ipnet, err := net.ParseCIDR(route) 48 + if err != nil { 49 + panic(fmt.Sprintf("parse blocked route %q: %v", route, err)) 50 + } 51 + nets = append(nets, ipnet) 52 + } 53 + return nets 54 + }() 55 + 56 + // net.Dialer Control func rejecting blocked special-purpose addresses. 57 + // this should run after dns resolution, so it should cover any rebinding tricks 58 + func RefuseSpecialPurposeAddrs(network, address string, _ syscall.RawConn) error { 59 + host, _, err := net.SplitHostPort(address) 60 + if err != nil { 61 + return fmt.Errorf("split dial address %q: %w", address, err) 62 + } 63 + ip := net.ParseIP(host) 64 + if ip == nil { 65 + return fmt.Errorf("refusing to dial non-IP address %q", host) 66 + } 67 + bits := 128 68 + if ip4 := ip.To4(); ip4 != nil { 69 + ip = ip4 70 + bits = 32 71 + } 72 + for _, ipnet := range BlockedNets { 73 + _, blockedBits := ipnet.Mask.Size() 74 + if blockedBits != bits { 75 + continue 76 + } 77 + if ipnet.Contains(ip) { 78 + return fmt.Errorf("refusing to dial %s: %s is blocked", ip, ipnet) 79 + } 80 + } 81 + return nil 82 + }
+20 -1
spindle/tapclient.go
··· 7 7 "errors" 8 8 "fmt" 9 9 "log/slog" 10 + "net" 10 11 "net/http" 11 12 "net/url" 12 13 "sync" ··· 22 23 "tangled.org/core/spindle/db" 23 24 "tangled.org/core/spindle/git" 24 25 "tangled.org/core/spindle/models" 26 + "tangled.org/core/spindle/netguard" 25 27 "tangled.org/core/tapc" 26 28 "tangled.org/core/tid" 27 29 "tangled.org/core/workflow" ··· 32 34 pendingCollabTTL = 10 * time.Minute 33 35 ) 34 36 37 + // blobs are fetched from user controlled PDSes so protect our transport 38 + // from dialing internal addresses 39 + var guardedBlobClient = &http.Client{ 40 + Transport: &http.Transport{ 41 + DialContext: (&net.Dialer{ 42 + Timeout: 30 * time.Second, 43 + KeepAlive: 30 * time.Second, 44 + Control: netguard.RefuseSpecialPurposeAddrs, 45 + }).DialContext, 46 + ForceAttemptHTTP2: true, 47 + MaxIdleConns: 100, 48 + IdleConnTimeout: 90 * time.Second, 49 + TLSHandshakeTimeout: 10 * time.Second, 50 + ExpectContinueTimeout: 1 * time.Second, 51 + }, 52 + } 53 + 35 54 type pendingCollabEvent struct { 36 55 evt *tapc.RecordEventData 37 56 at time.Time ··· 576 595 } 577 596 req.Header.Set("Content-Type", "application/json") 578 597 579 - blobResp, err := http.DefaultClient.Do(req) 598 + blobResp, err := guardedBlobClient.Do(req) 580 599 if err != nil { 581 600 return nil, fmt.Errorf("failed to fetch blob: %w", err) 582 601 }

History

1 round 0 comments
Sign up or Login to add to the discussion
ptr.pet submitted #0
1 commit
Expand
spindle/netguard: refuse to dial internal addresses on user-controlled fetches
3/3 success
Expand
Checking mergeability…
Expand 0 comments