mirror of
https://github.com/slackhq/nebula.git
synced 2026-08-15 11:37:02 +02:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8bb6090ffd |
@@ -30,9 +30,6 @@ jobs:
|
|||||||
- name: add hashicorp source
|
- name: add hashicorp source
|
||||||
run: wget -O- https://apt.releases.hashicorp.com/gpg | gpg --dearmor | sudo tee /usr/share/keyrings/hashicorp-archive-keyring.gpg && echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list
|
run: wget -O- https://apt.releases.hashicorp.com/gpg | gpg --dearmor | sudo tee /usr/share/keyrings/hashicorp-archive-keyring.gpg && echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list
|
||||||
|
|
||||||
- name: workaround AMD-V issue # https://github.com/cri-o/packaging/pull/306
|
|
||||||
run: sudo rmmod kvm_amd
|
|
||||||
|
|
||||||
- name: install vagrant
|
- name: install vagrant
|
||||||
run: sudo apt-get update && sudo apt-get install -y vagrant virtualbox
|
run: sudo apt-get update && sudo apt-get install -y vagrant virtualbox
|
||||||
|
|
||||||
|
|||||||
@@ -37,18 +37,17 @@ docker run --name host4 --device /dev/net/tun:/dev/net/tun --cap-add NET_ADMIN -
|
|||||||
sleep 1
|
sleep 1
|
||||||
|
|
||||||
# grab tcpdump pcaps for debugging
|
# grab tcpdump pcaps for debugging
|
||||||
docker exec lighthouse1 tcpdump -i tun0 -q -w - -U 2>logs/lighthouse1.inside.log >logs/lighthouse1.inside.pcap &
|
docker exec lighthouse1 tcpdump -i nebula1 -q -w - -U 2>logs/lighthouse1.inside.log >logs/lighthouse1.inside.pcap &
|
||||||
docker exec lighthouse1 tcpdump -i eth0 -q -w - -U 2>logs/lighthouse1.outside.log >logs/lighthouse1.outside.pcap &
|
docker exec lighthouse1 tcpdump -i eth0 -q -w - -U 2>logs/lighthouse1.outside.log >logs/lighthouse1.outside.pcap &
|
||||||
docker exec host2 tcpdump -i tun0 -q -w - -U 2>logs/host2.inside.log >logs/host2.inside.pcap &
|
docker exec host2 tcpdump -i nebula1 -q -w - -U 2>logs/host2.inside.log >logs/host2.inside.pcap &
|
||||||
docker exec host2 tcpdump -i eth0 -q -w - -U 2>logs/host2.outside.log >logs/host2.outside.pcap &
|
docker exec host2 tcpdump -i eth0 -q -w - -U 2>logs/host2.outside.log >logs/host2.outside.pcap &
|
||||||
docker exec host3 tcpdump -i tun0 -q -w - -U 2>logs/host3.inside.log >logs/host3.inside.pcap &
|
docker exec host3 tcpdump -i nebula1 -q -w - -U 2>logs/host3.inside.log >logs/host3.inside.pcap &
|
||||||
docker exec host3 tcpdump -i eth0 -q -w - -U 2>logs/host3.outside.log >logs/host3.outside.pcap &
|
docker exec host3 tcpdump -i eth0 -q -w - -U 2>logs/host3.outside.log >logs/host3.outside.pcap &
|
||||||
docker exec host4 tcpdump -i tun0 -q -w - -U 2>logs/host4.inside.log >logs/host4.inside.pcap &
|
docker exec host4 tcpdump -i nebula1 -q -w - -U 2>logs/host4.inside.log >logs/host4.inside.pcap &
|
||||||
docker exec host4 tcpdump -i eth0 -q -w - -U 2>logs/host4.outside.log >logs/host4.outside.pcap &
|
docker exec host4 tcpdump -i eth0 -q -w - -U 2>logs/host4.outside.log >logs/host4.outside.pcap &
|
||||||
|
|
||||||
docker exec host2 ncat -nklv 0.0.0.0 2000 &
|
docker exec host2 ncat -nklv 0.0.0.0 2000 &
|
||||||
docker exec host3 ncat -nklv 0.0.0.0 2000 &
|
docker exec host3 ncat -nklv 0.0.0.0 2000 &
|
||||||
docker exec host4 ncat -nkluv 0.0.0.0 4000 &
|
|
||||||
docker exec host2 ncat -e '/usr/bin/echo host2' -nkluv 0.0.0.0 3000 &
|
docker exec host2 ncat -e '/usr/bin/echo host2' -nkluv 0.0.0.0 3000 &
|
||||||
docker exec host3 ncat -e '/usr/bin/echo host3' -nkluv 0.0.0.0 3000 &
|
docker exec host3 ncat -e '/usr/bin/echo host3' -nkluv 0.0.0.0 3000 &
|
||||||
|
|
||||||
@@ -120,11 +119,11 @@ echo
|
|||||||
echo " *** Testing conntrack"
|
echo " *** Testing conntrack"
|
||||||
echo
|
echo
|
||||||
set -x
|
set -x
|
||||||
|
# host2 can ping host3 now that host3 pinged it first
|
||||||
# host2 speaking to host4 on UDP 4000 should allow it to reply, when firewall rules would normally not permit this
|
docker exec host2 ping -c1 192.168.100.3
|
||||||
docker exec host2 sh -c "/usr/bin/echo host2 | ncat -nuv 192.168.100.4 4000"
|
# host4 can ping host2 once conntrack established
|
||||||
docker exec host2 ncat -e '/usr/bin/echo helloagainfromhost2' -nkluv 0.0.0.0 4000 &
|
docker exec host2 ping -c1 192.168.100.4
|
||||||
docker exec host4 sh -c "/usr/bin/echo host4 | ncat -nuv 192.168.100.2 4000"
|
docker exec host4 ping -c1 192.168.100.2
|
||||||
|
|
||||||
docker exec host4 sh -c 'kill 1'
|
docker exec host4 sh -c 'kill 1'
|
||||||
docker exec host3 sh -c 'kill 1'
|
docker exec host3 sh -c 'kill 1'
|
||||||
|
|||||||
+1
-15
@@ -7,19 +7,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
## [1.10.3] - 2026-02-06
|
|
||||||
|
|
||||||
### Security
|
|
||||||
|
|
||||||
- Fix an issue where blocklist bypass is possible when using curve P256 since the signature can have 2 valid representations.
|
|
||||||
Both fingerprint representations will be tested against the blocklist.
|
|
||||||
Any newly issued P256 based certificates will have their signature clamped to the low-s form.
|
|
||||||
Nebula will assert the low-s signature form when validating certificates in a future version. [GHSA-69x3-g4r3-p962](https://github.com/slackhq/nebula/security/advisories/GHSA-69x3-g4r3-p962)
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
|
|
||||||
- Improve error reporting if nebula fails to start due to a tun device naming issue. (#1588)
|
|
||||||
|
|
||||||
## [1.10.2] - 2026-01-21
|
## [1.10.2] - 2026-01-21
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
@@ -788,8 +775,7 @@ created.)
|
|||||||
|
|
||||||
- Initial public release.
|
- Initial public release.
|
||||||
|
|
||||||
[Unreleased]: https://github.com/slackhq/nebula/compare/v1.10.3...HEAD
|
[Unreleased]: https://github.com/slackhq/nebula/compare/v1.10.2...HEAD
|
||||||
[1.10.3]: https://github.com/slackhq/nebula/releases/tag/v1.10.3
|
|
||||||
[1.10.2]: https://github.com/slackhq/nebula/releases/tag/v1.10.2
|
[1.10.2]: https://github.com/slackhq/nebula/releases/tag/v1.10.2
|
||||||
[1.10.1]: https://github.com/slackhq/nebula/releases/tag/v1.10.1
|
[1.10.1]: https://github.com/slackhq/nebula/releases/tag/v1.10.1
|
||||||
[1.10.0]: https://github.com/slackhq/nebula/releases/tag/v1.10.0
|
[1.10.0]: https://github.com/slackhq/nebula/releases/tag/v1.10.0
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
#ECCN:Open Source
|
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
//go:build boringcrypto
|
//go:build boringcrypto
|
||||||
|
// +build boringcrypto
|
||||||
|
|
||||||
package nebula
|
package nebula
|
||||||
|
|
||||||
|
|||||||
@@ -141,23 +141,10 @@ func (ncp *CAPool) VerifyCertificate(now time.Time, c Certificate) (*CachedCerti
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pre nebula v1.10.3 could generate signatures in either high or low s form and validation
|
|
||||||
// of signatures allowed for either. Nebula v1.10.3 and beyond clamps signature generation to low-s form
|
|
||||||
// but validation still allows for either. Since a change in the signature bytes affects the fingerprint, we
|
|
||||||
// need to test both forms until such a time comes that we enforce low-s form on signature validation.
|
|
||||||
fp2, err := CalculateAlternateFingerprint(c)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("could not calculate alternate fingerprint to verify: %w", err)
|
|
||||||
}
|
|
||||||
if fp2 != "" && ncp.IsBlocklisted(fp2) {
|
|
||||||
return nil, ErrBlockListed
|
|
||||||
}
|
|
||||||
|
|
||||||
cc := CachedCertificate{
|
cc := CachedCertificate{
|
||||||
Certificate: c,
|
Certificate: c,
|
||||||
InvertedGroups: make(map[string]struct{}),
|
InvertedGroups: make(map[string]struct{}),
|
||||||
Fingerprint: fp,
|
Fingerprint: fp,
|
||||||
fingerprint2: fp2,
|
|
||||||
signerFingerprint: signer.Fingerprint,
|
signerFingerprint: signer.Fingerprint,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -171,11 +158,6 @@ func (ncp *CAPool) VerifyCertificate(now time.Time, c Certificate) (*CachedCerti
|
|||||||
// VerifyCachedCertificate is the same as VerifyCertificate other than it operates on a pre-verified structure and
|
// VerifyCachedCertificate is the same as VerifyCertificate other than it operates on a pre-verified structure and
|
||||||
// is a cheaper operation to perform as a result.
|
// is a cheaper operation to perform as a result.
|
||||||
func (ncp *CAPool) VerifyCachedCertificate(now time.Time, c *CachedCertificate) error {
|
func (ncp *CAPool) VerifyCachedCertificate(now time.Time, c *CachedCertificate) error {
|
||||||
// Check any available alternate fingerprint forms for this certificate, re P256 high-s/low-s
|
|
||||||
if c.fingerprint2 != "" && ncp.IsBlocklisted(c.fingerprint2) {
|
|
||||||
return ErrBlockListed
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err := ncp.verify(c.Certificate, now, c.Fingerprint, c.signerFingerprint)
|
_, err := ncp.verify(c.Certificate, now, c.Fingerprint, c.signerFingerprint)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-43
@@ -5,7 +5,6 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/slackhq/nebula/cert/p256"
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
@@ -171,15 +170,6 @@ func TestCertificateV1_VerifyP256(t *testing.T) {
|
|||||||
_, err = caPool.VerifyCertificate(time.Now(), c)
|
_, err = caPool.VerifyCertificate(time.Now(), c)
|
||||||
require.EqualError(t, err, "certificate is in the block list")
|
require.EqualError(t, err, "certificate is in the block list")
|
||||||
|
|
||||||
// Create a copy of the cert and swap to the alternate form for the signature
|
|
||||||
nc := c.Copy()
|
|
||||||
b, err := p256.Swap(c.Signature())
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.NoError(t, nc.(*certificateV1).setSignature(b))
|
|
||||||
|
|
||||||
_, err = caPool.VerifyCertificate(time.Now(), nc)
|
|
||||||
require.EqualError(t, err, "certificate is in the block list")
|
|
||||||
|
|
||||||
caPool.ResetCertBlocklist()
|
caPool.ResetCertBlocklist()
|
||||||
_, err = caPool.VerifyCertificate(time.Now(), c)
|
_, err = caPool.VerifyCertificate(time.Now(), c)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@@ -197,7 +187,7 @@ func TestCertificateV1_VerifyP256(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
caPool = NewCAPool()
|
caPool = NewCAPool()
|
||||||
b, err = caPool.AddCAFromPEM(caPem)
|
b, err := caPool.AddCAFromPEM(caPem)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Empty(t, b)
|
assert.Empty(t, b)
|
||||||
|
|
||||||
@@ -206,17 +196,7 @@ func TestCertificateV1_VerifyP256(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
c, _, _, _ = NewTestCert(Version1, Curve_P256, ca, caKey, "test", time.Now(), time.Now().Add(5*time.Minute), nil, nil, []string{"test1"})
|
c, _, _, _ = NewTestCert(Version1, Curve_P256, ca, caKey, "test", time.Now(), time.Now().Add(5*time.Minute), nil, nil, []string{"test1"})
|
||||||
cc, err := caPool.VerifyCertificate(time.Now(), c)
|
_, err = caPool.VerifyCertificate(time.Now(), c)
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
// Reset the blocklist and block the alternate form fingerprint
|
|
||||||
caPool.ResetCertBlocklist()
|
|
||||||
caPool.BlocklistFingerprint(cc.fingerprint2)
|
|
||||||
err = caPool.VerifyCachedCertificate(time.Now(), cc)
|
|
||||||
require.EqualError(t, err, "certificate is in the block list")
|
|
||||||
|
|
||||||
caPool.ResetCertBlocklist()
|
|
||||||
err = caPool.VerifyCachedCertificate(time.Now(), cc)
|
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -414,15 +394,6 @@ func TestCertificateV2_VerifyP256(t *testing.T) {
|
|||||||
_, err = caPool.VerifyCertificate(time.Now(), c)
|
_, err = caPool.VerifyCertificate(time.Now(), c)
|
||||||
require.EqualError(t, err, "certificate is in the block list")
|
require.EqualError(t, err, "certificate is in the block list")
|
||||||
|
|
||||||
// Create a copy of the cert and swap to the alternate form for the signature
|
|
||||||
nc := c.Copy()
|
|
||||||
b, err := p256.Swap(c.Signature())
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.NoError(t, nc.(*certificateV2).setSignature(b))
|
|
||||||
|
|
||||||
_, err = caPool.VerifyCertificate(time.Now(), nc)
|
|
||||||
require.EqualError(t, err, "certificate is in the block list")
|
|
||||||
|
|
||||||
caPool.ResetCertBlocklist()
|
caPool.ResetCertBlocklist()
|
||||||
_, err = caPool.VerifyCertificate(time.Now(), c)
|
_, err = caPool.VerifyCertificate(time.Now(), c)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@@ -440,7 +411,7 @@ func TestCertificateV2_VerifyP256(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
caPool = NewCAPool()
|
caPool = NewCAPool()
|
||||||
b, err = caPool.AddCAFromPEM(caPem)
|
b, err := caPool.AddCAFromPEM(caPem)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Empty(t, b)
|
assert.Empty(t, b)
|
||||||
|
|
||||||
@@ -449,17 +420,7 @@ func TestCertificateV2_VerifyP256(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
c, _, _, _ = NewTestCert(Version2, Curve_P256, ca, caKey, "test", time.Now(), time.Now().Add(5*time.Minute), nil, nil, []string{"test1"})
|
c, _, _, _ = NewTestCert(Version2, Curve_P256, ca, caKey, "test", time.Now(), time.Now().Add(5*time.Minute), nil, nil, []string{"test1"})
|
||||||
cc, err := caPool.VerifyCertificate(time.Now(), c)
|
_, err = caPool.VerifyCertificate(time.Now(), c)
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
// Reset the blocklist and block the alternate form fingerprint
|
|
||||||
caPool.ResetCertBlocklist()
|
|
||||||
caPool.BlocklistFingerprint(cc.fingerprint2)
|
|
||||||
err = caPool.VerifyCachedCertificate(time.Now(), cc)
|
|
||||||
require.EqualError(t, err, "certificate is in the block list")
|
|
||||||
|
|
||||||
caPool.ResetCertBlocklist()
|
|
||||||
err = caPool.VerifyCachedCertificate(time.Now(), cc)
|
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,8 +4,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/slackhq/nebula/cert/p256"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Version uint8
|
type Version uint8
|
||||||
@@ -112,9 +110,6 @@ type CachedCertificate struct {
|
|||||||
InvertedGroups map[string]struct{}
|
InvertedGroups map[string]struct{}
|
||||||
Fingerprint string
|
Fingerprint string
|
||||||
signerFingerprint string
|
signerFingerprint string
|
||||||
|
|
||||||
// A place to store a 2nd fingerprint if the certificate could have one, such as with P256
|
|
||||||
fingerprint2 string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cc *CachedCertificate) String() string {
|
func (cc *CachedCertificate) String() string {
|
||||||
@@ -157,31 +152,3 @@ func Recombine(v Version, rawCertBytes, publicKey []byte, curve Curve) (Certific
|
|||||||
|
|
||||||
return c, nil
|
return c, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// CalculateAlternateFingerprint calculates a 2nd fingerprint representation for P256 certificates
|
|
||||||
// CAPool blocklist testing through `VerifyCertificate` and `VerifyCachedCertificate` automatically performs this step.
|
|
||||||
func CalculateAlternateFingerprint(c Certificate) (string, error) {
|
|
||||||
if c.Curve() != Curve_P256 {
|
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
nc := c.Copy()
|
|
||||||
b, err := p256.Swap(nc.Signature())
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
switch v := nc.(type) {
|
|
||||||
case *certificateV1:
|
|
||||||
err = v.setSignature(b)
|
|
||||||
case *certificateV2:
|
|
||||||
err = v.setSignature(b)
|
|
||||||
default:
|
|
||||||
return "", ErrUnknownVersion
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return nc.Fingerprint()
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,122 +0,0 @@
|
|||||||
package p256
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/elliptic"
|
|
||||||
"errors"
|
|
||||||
"math/big"
|
|
||||||
|
|
||||||
"filippo.io/bigmod"
|
|
||||||
|
|
||||||
"golang.org/x/crypto/cryptobyte"
|
|
||||||
"golang.org/x/crypto/cryptobyte/asn1"
|
|
||||||
)
|
|
||||||
|
|
||||||
var halfN = new(big.Int).Rsh(elliptic.P256().Params().N, 1)
|
|
||||||
var nMod *bigmod.Modulus
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
n, err := bigmod.NewModulus(elliptic.P256().Params().N.Bytes())
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
nMod = n
|
|
||||||
}
|
|
||||||
|
|
||||||
func IsNormalized(sig []byte) (bool, error) {
|
|
||||||
r, s, err := parseSignature(sig)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
return checkLowS(r, s), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkLowS(_, s []byte) bool {
|
|
||||||
bigS := new(big.Int).SetBytes(s)
|
|
||||||
// Check if S <= (N/2), because we want to include the midpoint in the set of low-s
|
|
||||||
return bigS.Cmp(halfN) <= 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func swap(r, s []byte) ([]byte, []byte, error) {
|
|
||||||
var err error
|
|
||||||
bigS, err := bigmod.NewNat().SetBytes(s, nMod)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
sNormalized := nMod.Nat().Sub(bigS, nMod)
|
|
||||||
|
|
||||||
return r, sNormalized.Bytes(nMod), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func Normalize(sig []byte) ([]byte, error) {
|
|
||||||
r, s, err := parseSignature(sig)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if checkLowS(r, s) {
|
|
||||||
return sig, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
newR, newS, err := swap(r, s)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return encodeSignature(newR, newS)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Swap will change sig between its current form to the opposite high or low form.
|
|
||||||
func Swap(sig []byte) ([]byte, error) {
|
|
||||||
r, s, err := parseSignature(sig)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
newR, newS, err := swap(r, s)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return encodeSignature(newR, newS)
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseSignature taken exactly from crypto/ecdsa/ecdsa.go
|
|
||||||
func parseSignature(sig []byte) (r, s []byte, err error) {
|
|
||||||
var inner cryptobyte.String
|
|
||||||
input := cryptobyte.String(sig)
|
|
||||||
if !input.ReadASN1(&inner, asn1.SEQUENCE) ||
|
|
||||||
!input.Empty() ||
|
|
||||||
!inner.ReadASN1Integer(&r) ||
|
|
||||||
!inner.ReadASN1Integer(&s) ||
|
|
||||||
!inner.Empty() {
|
|
||||||
return nil, nil, errors.New("invalid ASN.1")
|
|
||||||
}
|
|
||||||
return r, s, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func encodeSignature(r, s []byte) ([]byte, error) {
|
|
||||||
var b cryptobyte.Builder
|
|
||||||
b.AddASN1(asn1.SEQUENCE, func(b *cryptobyte.Builder) {
|
|
||||||
addASN1IntBytes(b, r)
|
|
||||||
addASN1IntBytes(b, s)
|
|
||||||
})
|
|
||||||
return b.Bytes()
|
|
||||||
}
|
|
||||||
|
|
||||||
// addASN1IntBytes encodes in ASN.1 a positive integer represented as
|
|
||||||
// a big-endian byte slice with zero or more leading zeroes.
|
|
||||||
func addASN1IntBytes(b *cryptobyte.Builder, bytes []byte) {
|
|
||||||
for len(bytes) > 0 && bytes[0] == 0 {
|
|
||||||
bytes = bytes[1:]
|
|
||||||
}
|
|
||||||
if len(bytes) == 0 {
|
|
||||||
b.SetError(errors.New("invalid integer"))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
b.AddASN1(asn1.INTEGER, func(c *cryptobyte.Builder) {
|
|
||||||
if bytes[0]&0x80 != 0 {
|
|
||||||
c.AddUint8(0)
|
|
||||||
}
|
|
||||||
c.AddBytes(bytes)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
package p256
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/ecdsa"
|
|
||||||
"crypto/elliptic"
|
|
||||||
"crypto/rand"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestFlipping(t *testing.T) {
|
|
||||||
priv, err1 := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
|
||||||
require.NoError(t, err1)
|
|
||||||
|
|
||||||
out, err := ecdsa.SignASN1(rand.Reader, priv, []byte("big chungus"))
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
r, s, err := parseSignature(out)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
r, s1, err := swap(r, s)
|
|
||||||
require.NoError(t, err)
|
|
||||||
r, s2, err := swap(r, s1)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, s, s2)
|
|
||||||
require.NotEqual(t, s, s1)
|
|
||||||
}
|
|
||||||
@@ -9,8 +9,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/slackhq/nebula/cert/p256"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// TBSCertificate represents a certificate intended to be signed.
|
// TBSCertificate represents a certificate intended to be signed.
|
||||||
@@ -128,13 +126,6 @@ func (t *TBSCertificate) SignWith(signer Certificate, curve Curve, sp SignerLamb
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if curve == Curve_P256 {
|
|
||||||
sig, err = p256.Normalize(sig)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
err = c.setSignature(sig)
|
err = c.setSignature(sig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/slackhq/nebula/cert/p256"
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
@@ -90,48 +89,3 @@ func TestCertificateV1_SignP256(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.NotNil(t, uc)
|
assert.NotNil(t, uc)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCertificate_SignP256_AlwaysNormalized(t *testing.T) {
|
|
||||||
before := time.Now().Add(time.Second * -60).Round(time.Second)
|
|
||||||
after := time.Now().Add(time.Second * 60).Round(time.Second)
|
|
||||||
pubKey := []byte("01234567890abcedfghij1234567890ab1234567890abcedfghij1234567890ab")
|
|
||||||
|
|
||||||
tbs := TBSCertificate{
|
|
||||||
Version: Version1,
|
|
||||||
Name: "testing",
|
|
||||||
Networks: []netip.Prefix{
|
|
||||||
mustParsePrefixUnmapped("10.1.1.1/24"),
|
|
||||||
mustParsePrefixUnmapped("10.1.1.2/16"),
|
|
||||||
},
|
|
||||||
UnsafeNetworks: []netip.Prefix{
|
|
||||||
mustParsePrefixUnmapped("9.1.1.2/24"),
|
|
||||||
mustParsePrefixUnmapped("9.1.1.3/16"),
|
|
||||||
},
|
|
||||||
Groups: []string{"test-group1", "test-group2", "test-group3"},
|
|
||||||
NotBefore: before,
|
|
||||||
NotAfter: after,
|
|
||||||
PublicKey: pubKey,
|
|
||||||
IsCA: true,
|
|
||||||
Curve: Curve_P256,
|
|
||||||
}
|
|
||||||
|
|
||||||
priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
|
||||||
require.NoError(t, err)
|
|
||||||
pub := elliptic.Marshal(elliptic.P256(), priv.PublicKey.X, priv.PublicKey.Y)
|
|
||||||
rawPriv := priv.D.FillBytes(make([]byte, 32))
|
|
||||||
|
|
||||||
for i := 0; i < 1000; i++ {
|
|
||||||
if i&1 == 1 {
|
|
||||||
tbs.Version = Version1
|
|
||||||
} else {
|
|
||||||
tbs.Version = Version2
|
|
||||||
}
|
|
||||||
c, err := tbs.Sign(nil, Curve_P256, rawPriv)
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.NotNil(t, c)
|
|
||||||
assert.True(t, c.CheckSignature(pub))
|
|
||||||
normie, err := p256.IsNormalized(c.Signature())
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.True(t, normie)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
func handlePost(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Method != http.MethodPost {
|
||||||
|
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read the body
|
||||||
|
body, err := io.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "Error reading body", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer r.Body.Close()
|
||||||
|
|
||||||
|
// Print to console
|
||||||
|
//fmt.Printf("Path: %s\n", r.URL.Path)
|
||||||
|
//fmt.Printf("Headers: %v\n", r.Header)
|
||||||
|
fmt.Printf("%s\n", string(body))
|
||||||
|
|
||||||
|
// Send response
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
w.Write([]byte(""))
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
http.HandleFunc("/", handlePost)
|
||||||
|
|
||||||
|
fmt.Println("Server starting on :8080")
|
||||||
|
log.Fatal(http.ListenAndServe(":8080", nil))
|
||||||
|
}
|
||||||
+46
@@ -1,7 +1,11 @@
|
|||||||
package nebula
|
package nebula
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
@@ -9,6 +13,7 @@ import (
|
|||||||
|
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/slackhq/nebula/cert"
|
"github.com/slackhq/nebula/cert"
|
||||||
|
"github.com/slackhq/nebula/firewall"
|
||||||
"github.com/slackhq/nebula/header"
|
"github.com/slackhq/nebula/header"
|
||||||
"github.com/slackhq/nebula/overlay"
|
"github.com/slackhq/nebula/overlay"
|
||||||
)
|
)
|
||||||
@@ -71,10 +76,51 @@ func (c *Control) Start() {
|
|||||||
c.lighthouseStart()
|
c.lighthouseStart()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
go c.firewallEventSender(c.ctx)
|
||||||
|
|
||||||
// Start reading packets.
|
// Start reading packets.
|
||||||
c.f.run()
|
c.f.run()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Control) firewallEventSender(ctx context.Context) {
|
||||||
|
events := make([]firewall.Event, 1, 100) //todo configurable
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
//todo exceptionally lazy
|
||||||
|
case e := <-c.f.events:
|
||||||
|
events = append(events, e)
|
||||||
|
for { //slurp up everything into one big post
|
||||||
|
x := false
|
||||||
|
select {
|
||||||
|
case e = <-c.f.events:
|
||||||
|
events = append(events, e)
|
||||||
|
default:
|
||||||
|
x = true
|
||||||
|
}
|
||||||
|
if x {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(events) >= 5 {
|
||||||
|
out, err := json.Marshal(&events)
|
||||||
|
if err != nil {
|
||||||
|
c.l.WithError(err).Errorf("failed to marshal event")
|
||||||
|
} else {
|
||||||
|
r := io.Reader(bytes.NewBuffer(out))
|
||||||
|
_, err = http.Post("http://127.0.0.1:8080/nebula/event", "application/json", r)
|
||||||
|
if err != nil {
|
||||||
|
c.l.WithError(err).Errorf("failed to post event")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
events = events[:0]
|
||||||
|
}
|
||||||
|
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Control) Context() context.Context {
|
func (c *Control) Context() context.Context {
|
||||||
return c.ctx
|
return c.ctx
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
//go:build e2e_testing
|
//go:build e2e_testing
|
||||||
|
// +build e2e_testing
|
||||||
|
|
||||||
package nebula
|
package nebula
|
||||||
|
|
||||||
|
|||||||
Vendored
+14
-8
@@ -84,24 +84,30 @@ end
|
|||||||
|
|
||||||
function nebula.prefs_changed()
|
function nebula.prefs_changed()
|
||||||
if default_settings.all_ports == nebula.prefs.all_ports and default_settings.port == nebula.prefs.port then
|
if default_settings.all_ports == nebula.prefs.all_ports and default_settings.port == nebula.prefs.port then
|
||||||
|
-- Nothing changed, bail
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Remove all existing registrations
|
-- Remove our old dissector
|
||||||
DissectorTable.get("udp.port"):remove_all(nebula)
|
DissectorTable.get("udp.port"):remove_all(nebula)
|
||||||
|
|
||||||
if nebula.prefs.all_ports then
|
if nebula.prefs.all_ports and default_settings.all_ports ~= nebula.prefs.all_ports then
|
||||||
-- Register on every port for hole punch capture
|
default_settings.all_port = nebula.prefs.all_ports
|
||||||
|
|
||||||
for i=0, 65535 do
|
for i=0, 65535 do
|
||||||
DissectorTable.get("udp.port"):add(i, nebula)
|
DissectorTable.get("udp.port"):add(i, nebula)
|
||||||
end
|
end
|
||||||
else
|
|
||||||
-- Register on the configured port only
|
-- no need to establish again on specific ports
|
||||||
DissectorTable.get("udp.port"):add(nebula.prefs.port, nebula)
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
default_settings.all_ports = nebula.prefs.all_ports
|
|
||||||
default_settings.port = nebula.prefs.port
|
if default_settings.all_ports ~= nebula.prefs.all_ports then
|
||||||
|
-- Add our new port dissector
|
||||||
|
default_settings.port = nebula.prefs.port
|
||||||
|
DissectorTable.get("udp.port"):add(default_settings.port, nebula)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
DissectorTable.get("udp.port"):add(default_settings.port, nebula)
|
DissectorTable.get("udp.port"):add(default_settings.port, nebula)
|
||||||
|
|||||||
@@ -12,8 +12,6 @@ import (
|
|||||||
"github.com/slackhq/nebula/cert"
|
"github.com/slackhq/nebula/cert"
|
||||||
"github.com/slackhq/nebula/cert_test"
|
"github.com/slackhq/nebula/cert_test"
|
||||||
"github.com/slackhq/nebula/e2e/router"
|
"github.com/slackhq/nebula/e2e/router"
|
||||||
"github.com/slackhq/nebula/header"
|
|
||||||
"github.com/slackhq/nebula/udp"
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
@@ -367,106 +365,3 @@ func TestCrossStackRelaysWork(t *testing.T) {
|
|||||||
//theirControl.Stop()
|
//theirControl.Stop()
|
||||||
//relayControl.Stop()
|
//relayControl.Stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCloseTunnelAuthenticated(t *testing.T) {
|
|
||||||
ca, _, caKey, _ := cert_test.NewTestCaCert(cert.Version1, cert.Curve_CURVE25519, time.Now(), time.Now().Add(10*time.Minute), nil, nil, []string{})
|
|
||||||
myControl, myVpnIpNet, myUdpAddr, _ := newSimpleServer(cert.Version1, ca, caKey, "me", "10.128.0.1/24", m{"tunnels": m{"drop_inactive": true, "inactivity_timeout": "5s"}})
|
|
||||||
theirControl, theirVpnIpNet, theirUdpAddr, _ := newSimpleServer(cert.Version1, ca, caKey, "them", "10.128.0.2/24", m{"tunnels": m{"drop_inactive": true, "inactivity_timeout": "10m"}})
|
|
||||||
|
|
||||||
// Share our underlay information
|
|
||||||
myControl.InjectLightHouseAddr(theirVpnIpNet[0].Addr(), theirUdpAddr)
|
|
||||||
theirControl.InjectLightHouseAddr(myVpnIpNet[0].Addr(), myUdpAddr)
|
|
||||||
|
|
||||||
// Start the servers
|
|
||||||
myControl.Start()
|
|
||||||
theirControl.Start()
|
|
||||||
|
|
||||||
r := router.NewR(t, myControl, theirControl)
|
|
||||||
|
|
||||||
r.Log("Assert the tunnel between me and them works")
|
|
||||||
assertTunnel(t, myVpnIpNet[0].Addr(), theirVpnIpNet[0].Addr(), myControl, theirControl, r)
|
|
||||||
|
|
||||||
r.Log("Close the tunnel")
|
|
||||||
myControl.CloseTunnel(theirVpnIpNet[0].Addr(), false)
|
|
||||||
r.FlushAll()
|
|
||||||
|
|
||||||
waitStart := time.Now()
|
|
||||||
for {
|
|
||||||
myIndexes := len(myControl.GetHostmap().Indexes)
|
|
||||||
theirIndexes := len(theirControl.GetHostmap().Indexes)
|
|
||||||
if myIndexes == 0 && theirIndexes == 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
since := time.Since(waitStart)
|
|
||||||
r.Logf("my tunnels: %v; their tunnels: %v; duration: %v", myIndexes, theirIndexes, since)
|
|
||||||
if since > time.Second*6 {
|
|
||||||
t.Fatal("Tunnel should have been declared inactive after 2 seconds and before 6 seconds")
|
|
||||||
}
|
|
||||||
|
|
||||||
time.Sleep(1 * time.Second)
|
|
||||||
//r.FlushAll()
|
|
||||||
}
|
|
||||||
|
|
||||||
r.Logf("Happy path success, tunnels were dropped within %v", time.Since(waitStart))
|
|
||||||
|
|
||||||
myControl.InjectLightHouseAddr(theirVpnIpNet[0].Addr(), theirUdpAddr)
|
|
||||||
theirControl.InjectLightHouseAddr(myVpnIpNet[0].Addr(), myUdpAddr)
|
|
||||||
r.Log("Assert another tunnel between me and them works")
|
|
||||||
assertTunnel(t, myVpnIpNet[0].Addr(), theirVpnIpNet[0].Addr(), myControl, theirControl, r)
|
|
||||||
hi := myControl.GetHostInfoByVpnAddr(theirVpnIpNet[0].Addr(), false)
|
|
||||||
if hi == nil {
|
|
||||||
t.Fatal("There is no hostinfo for this tunnel")
|
|
||||||
}
|
|
||||||
myHi := theirControl.GetHostInfoByVpnAddr(myVpnIpNet[0].Addr(), false)
|
|
||||||
if myHi == nil {
|
|
||||||
t.Fatal("There is no hostinfo for my tunnel")
|
|
||||||
}
|
|
||||||
r.Log("It does")
|
|
||||||
|
|
||||||
buf := make([]byte, 1024)
|
|
||||||
hdr := header.H{
|
|
||||||
Version: 1,
|
|
||||||
Type: header.CloseTunnel,
|
|
||||||
Subtype: 0,
|
|
||||||
Reserved: 0,
|
|
||||||
RemoteIndex: hi.RemoteIndex,
|
|
||||||
MessageCounter: 5,
|
|
||||||
}
|
|
||||||
out, err := hdr.Encode(buf)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
pkt := &udp.Packet{
|
|
||||||
To: hi.CurrentRemote,
|
|
||||||
From: myHi.CurrentRemote,
|
|
||||||
Data: out,
|
|
||||||
}
|
|
||||||
r.InjectUDPPacket(myControl, theirControl, pkt)
|
|
||||||
r.Log("Injected bogus close tunnel. Let's see!")
|
|
||||||
waitStart = time.Now()
|
|
||||||
for {
|
|
||||||
myIndexes := len(myControl.GetHostmap().Indexes)
|
|
||||||
theirIndexes := len(theirControl.GetHostmap().Indexes)
|
|
||||||
if myIndexes == 0 {
|
|
||||||
t.Fatal("myIndexes should not be 0")
|
|
||||||
}
|
|
||||||
if theirIndexes == 0 {
|
|
||||||
t.Fatal("theirIndexes should not be 0, they should have rejected this bogus packet")
|
|
||||||
}
|
|
||||||
|
|
||||||
since := time.Since(waitStart)
|
|
||||||
r.Logf("my tunnels: %v; their tunnels: %v; duration: %v", myIndexes, theirIndexes, since)
|
|
||||||
if since > time.Second*4 {
|
|
||||||
t.Log("The tunnel would have been gone by now")
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
time.Sleep(1 * time.Second)
|
|
||||||
r.FlushAll()
|
|
||||||
}
|
|
||||||
|
|
||||||
myControl.Stop()
|
|
||||||
theirControl.Stop()
|
|
||||||
}
|
|
||||||
|
|||||||
+1
-9
@@ -342,14 +342,6 @@ logging:
|
|||||||
# after receiving the response for lighthouse queries
|
# after receiving the response for lighthouse queries
|
||||||
#trigger_buffer: 64
|
#trigger_buffer: 64
|
||||||
|
|
||||||
# max_rate limits the number of new inbound handshakes per second. Once the limit is reached,
|
|
||||||
# new handshakes are dropped until the next second. A value of 0 means unlimited (default).
|
|
||||||
# This is useful for preventing DoS attacks that attempt to exhaust CPU with handshake crypto.
|
|
||||||
# Running `openssl speed ecdhp256` on your hardware can be a good rule of thumb for choosing
|
|
||||||
# a max, as each handshake performs similar DH operations. Note that this benchmarks a single
|
|
||||||
# core, so you may wish to scale the value by the number of `routines` configured.
|
|
||||||
#max_rate: 0
|
|
||||||
|
|
||||||
# Tunnel manager settings
|
# Tunnel manager settings
|
||||||
#tunnels:
|
#tunnels:
|
||||||
# drop_inactive controls whether inactive tunnels are maintained or dropped after the inactive_timeout period has
|
# drop_inactive controls whether inactive tunnels are maintained or dropped after the inactive_timeout period has
|
||||||
@@ -390,8 +382,8 @@ firewall:
|
|||||||
# Rules are comprised of a protocol, port, and one or more of host, group, or CIDR
|
# Rules are comprised of a protocol, port, and one or more of host, group, or CIDR
|
||||||
# Logical evaluation is roughly: port AND proto AND (ca_sha OR ca_name) AND (host OR group OR groups OR cidr) AND (local cidr)
|
# Logical evaluation is roughly: port AND proto AND (ca_sha OR ca_name) AND (host OR group OR groups OR cidr) AND (local cidr)
|
||||||
# - port: Takes `0` or `any` as any, a single number `80`, a range `200-901`, or `fragment` to match second and further fragments of fragmented packets (since there is no port available).
|
# - port: Takes `0` or `any` as any, a single number `80`, a range `200-901`, or `fragment` to match second and further fragments of fragmented packets (since there is no port available).
|
||||||
|
# code: same as port but makes more sense when talking about ICMP, TODO: this is not currently implemented in a way that works, use `any`
|
||||||
# proto: `any`, `tcp`, `udp`, or `icmp`
|
# proto: `any`, `tcp`, `udp`, or `icmp`
|
||||||
# a port specification is ignored if proto is `icmp`
|
|
||||||
# host: `any` or a literal hostname, ie `test-host`
|
# host: `any` or a literal hostname, ie `test-host`
|
||||||
# group: `any` or a literal group name, ie `default-group`
|
# group: `any` or a literal group name, ie `default-group`
|
||||||
# groups: Same as group but accepts a list of values. Multiple values are AND'd together and a certificate would have to contain all groups to pass
|
# groups: Same as group but accepts a list of values. Multiple values are AND'd together and a certificate would have to contain all groups to pass
|
||||||
|
|||||||
+71
-85
@@ -45,6 +45,8 @@ type Firewall struct {
|
|||||||
|
|
||||||
InSendReject bool
|
InSendReject bool
|
||||||
OutSendReject bool
|
OutSendReject bool
|
||||||
|
InLogDrop bool
|
||||||
|
OutLogDrop bool
|
||||||
|
|
||||||
//TODO: we should have many more options for TCP, an option for ICMP, and mimic the kernel a bit better
|
//TODO: we should have many more options for TCP, an option for ICMP, and mimic the kernel a bit better
|
||||||
// https://www.kernel.org/doc/Documentation/networking/nf_conntrack-sysctl.txt
|
// https://www.kernel.org/doc/Documentation/networking/nf_conntrack-sysctl.txt
|
||||||
@@ -216,6 +218,7 @@ func NewFirewallFromConfig(l *logrus.Logger, cs *CertState, c *config.C) (*Firew
|
|||||||
switch inboundAction {
|
switch inboundAction {
|
||||||
case "reject":
|
case "reject":
|
||||||
fw.InSendReject = true
|
fw.InSendReject = true
|
||||||
|
fw.InLogDrop = true //todo
|
||||||
case "drop":
|
case "drop":
|
||||||
fw.InSendReject = false
|
fw.InSendReject = false
|
||||||
default:
|
default:
|
||||||
@@ -227,10 +230,11 @@ func NewFirewallFromConfig(l *logrus.Logger, cs *CertState, c *config.C) (*Firew
|
|||||||
switch outboundAction {
|
switch outboundAction {
|
||||||
case "reject":
|
case "reject":
|
||||||
fw.OutSendReject = true
|
fw.OutSendReject = true
|
||||||
|
fw.OutLogDrop = true //todo
|
||||||
case "drop":
|
case "drop":
|
||||||
fw.OutSendReject = false
|
fw.OutSendReject = false
|
||||||
default:
|
default:
|
||||||
l.WithField("action", outboundAction).Warn("invalid firewall.outbound_action, defaulting to `drop`")
|
l.WithField("action", inboundAction).Warn("invalid firewall.outbound_action, defaulting to `drop`")
|
||||||
fw.OutSendReject = false
|
fw.OutSendReject = false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -249,6 +253,20 @@ func NewFirewallFromConfig(l *logrus.Logger, cs *CertState, c *config.C) (*Firew
|
|||||||
|
|
||||||
// AddRule properly creates the in memory rule structure for a firewall table.
|
// AddRule properly creates the in memory rule structure for a firewall table.
|
||||||
func (f *Firewall) AddRule(incoming bool, proto uint8, startPort int32, endPort int32, groups []string, host string, cidr, localCidr, caName string, caSha string) error {
|
func (f *Firewall) AddRule(incoming bool, proto uint8, startPort int32, endPort int32, groups []string, host string, cidr, localCidr, caName string, caSha string) error {
|
||||||
|
// We need this rule string because we generate a hash. Removing this will break firewall reload.
|
||||||
|
ruleString := fmt.Sprintf(
|
||||||
|
"incoming: %v, proto: %v, startPort: %v, endPort: %v, groups: %v, host: %v, ip: %v, localIp: %v, caName: %v, caSha: %s",
|
||||||
|
incoming, proto, startPort, endPort, groups, host, cidr, localCidr, caName, caSha,
|
||||||
|
)
|
||||||
|
f.rules += ruleString + "\n"
|
||||||
|
|
||||||
|
direction := "incoming"
|
||||||
|
if !incoming {
|
||||||
|
direction = "outgoing"
|
||||||
|
}
|
||||||
|
f.l.WithField("firewallRule", m{"direction": direction, "proto": proto, "startPort": startPort, "endPort": endPort, "groups": groups, "host": host, "cidr": cidr, "localCidr": localCidr, "caName": caName, "caSha": caSha}).
|
||||||
|
Info("Firewall rule added")
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ft *FirewallTable
|
ft *FirewallTable
|
||||||
fp firewallPort
|
fp firewallPort
|
||||||
@@ -266,12 +284,6 @@ func (f *Firewall) AddRule(incoming bool, proto uint8, startPort int32, endPort
|
|||||||
case firewall.ProtoUDP:
|
case firewall.ProtoUDP:
|
||||||
fp = ft.UDP
|
fp = ft.UDP
|
||||||
case firewall.ProtoICMP, firewall.ProtoICMPv6:
|
case firewall.ProtoICMP, firewall.ProtoICMPv6:
|
||||||
//ICMP traffic doesn't have ports, so we always coerce to "any", even if a value is provided
|
|
||||||
if startPort != firewall.PortAny {
|
|
||||||
f.l.WithField("startPort", startPort).Warn("ignoring port specification for ICMP firewall rule")
|
|
||||||
}
|
|
||||||
startPort = firewall.PortAny
|
|
||||||
endPort = firewall.PortAny
|
|
||||||
fp = ft.ICMP
|
fp = ft.ICMP
|
||||||
case firewall.ProtoAny:
|
case firewall.ProtoAny:
|
||||||
fp = ft.AnyProto
|
fp = ft.AnyProto
|
||||||
@@ -279,20 +291,6 @@ func (f *Firewall) AddRule(incoming bool, proto uint8, startPort int32, endPort
|
|||||||
return fmt.Errorf("unknown protocol %v", proto)
|
return fmt.Errorf("unknown protocol %v", proto)
|
||||||
}
|
}
|
||||||
|
|
||||||
// We need this rule string because we generate a hash. Removing this will break firewall reload.
|
|
||||||
ruleString := fmt.Sprintf(
|
|
||||||
"incoming: %v, proto: %v, startPort: %v, endPort: %v, groups: %v, host: %v, ip: %v, localIp: %v, caName: %v, caSha: %s",
|
|
||||||
incoming, proto, startPort, endPort, groups, host, cidr, localCidr, caName, caSha,
|
|
||||||
)
|
|
||||||
f.rules += ruleString + "\n"
|
|
||||||
|
|
||||||
direction := "incoming"
|
|
||||||
if !incoming {
|
|
||||||
direction = "outgoing"
|
|
||||||
}
|
|
||||||
f.l.WithField("firewallRule", m{"direction": direction, "proto": proto, "startPort": startPort, "endPort": endPort, "groups": groups, "host": host, "cidr": cidr, "localCidr": localCidr, "caName": caName, "caSha": caSha}).
|
|
||||||
Info("Firewall rule added")
|
|
||||||
|
|
||||||
return fp.addRule(f, startPort, endPort, groups, host, cidr, localCidr, caName, caSha)
|
return fp.addRule(f, startPort, endPort, groups, host, cidr, localCidr, caName, caSha)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -355,31 +353,24 @@ func AddFirewallRulesFromConfig(l *logrus.Logger, inbound bool, c *config.C, fw
|
|||||||
sPort = r.Port
|
sPort = r.Port
|
||||||
}
|
}
|
||||||
|
|
||||||
|
startPort, endPort, err := parsePort(sPort)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("%s rule #%v; %s %s", table, i, errPort, err)
|
||||||
|
}
|
||||||
|
|
||||||
var proto uint8
|
var proto uint8
|
||||||
var startPort, endPort int32
|
|
||||||
switch r.Proto {
|
switch r.Proto {
|
||||||
case "any":
|
case "any":
|
||||||
proto = firewall.ProtoAny
|
proto = firewall.ProtoAny
|
||||||
startPort, endPort, err = parsePort(sPort)
|
|
||||||
case "tcp":
|
case "tcp":
|
||||||
proto = firewall.ProtoTCP
|
proto = firewall.ProtoTCP
|
||||||
startPort, endPort, err = parsePort(sPort)
|
|
||||||
case "udp":
|
case "udp":
|
||||||
proto = firewall.ProtoUDP
|
proto = firewall.ProtoUDP
|
||||||
startPort, endPort, err = parsePort(sPort)
|
|
||||||
case "icmp":
|
case "icmp":
|
||||||
proto = firewall.ProtoICMP
|
proto = firewall.ProtoICMP
|
||||||
startPort = firewall.PortAny
|
|
||||||
endPort = firewall.PortAny
|
|
||||||
if sPort != "" {
|
|
||||||
l.WithField("port", sPort).Warn("ignoring port specification for ICMP firewall rule")
|
|
||||||
}
|
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("%s rule #%v; proto was not understood; `%s`", table, i, r.Proto)
|
return fmt.Errorf("%s rule #%v; proto was not understood; `%s`", table, i, r.Proto)
|
||||||
}
|
}
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("%s rule #%v; %s %s", table, i, errPort, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.Cidr != "" && r.Cidr != "any" {
|
if r.Cidr != "" && r.Cidr != "any" {
|
||||||
_, err = netip.ParsePrefix(r.Cidr)
|
_, err = netip.ParsePrefix(r.Cidr)
|
||||||
@@ -414,9 +405,11 @@ var ErrInvalidRemoteIP = errors.New("remote address is not in remote certificate
|
|||||||
var ErrInvalidLocalIP = errors.New("local address is not in list of handled local addresses")
|
var ErrInvalidLocalIP = errors.New("local address is not in list of handled local addresses")
|
||||||
var ErrNoMatchingRule = errors.New("no matching rule in firewall table")
|
var ErrNoMatchingRule = errors.New("no matching rule in firewall table")
|
||||||
|
|
||||||
|
type DropHandler func(fp firewall.Packet, incoming bool, h *HostInfo, err error)
|
||||||
|
|
||||||
// Drop returns an error if the packet should be dropped, explaining why. It
|
// Drop returns an error if the packet should be dropped, explaining why. It
|
||||||
// returns nil if the packet should not be dropped.
|
// returns nil if the packet should not be dropped.
|
||||||
func (f *Firewall) Drop(fp firewall.Packet, incoming bool, h *HostInfo, caPool *cert.CAPool, localCache firewall.ConntrackCache) error {
|
func (f *Firewall) Drop(fp firewall.Packet, incoming bool, h *HostInfo, caPool *cert.CAPool, localCache firewall.ConntrackCache, onDrop DropHandler) error {
|
||||||
// Check if we spoke to this tuple, if we did then allow this packet
|
// Check if we spoke to this tuple, if we did then allow this packet
|
||||||
if f.inConns(fp, h, caPool, localCache) {
|
if f.inConns(fp, h, caPool, localCache) {
|
||||||
return nil
|
return nil
|
||||||
@@ -480,7 +473,7 @@ func (f *Firewall) metrics(incoming bool) firewallMetrics {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Destroy cleans up any known cyclical references so the object can be freed by GC. This should be called if a new
|
// Destroy cleans up any known cyclical references so the object can be free'd my GC. This should be called if a new
|
||||||
// firewall object is created
|
// firewall object is created
|
||||||
func (f *Firewall) Destroy() {
|
func (f *Firewall) Destroy() {
|
||||||
//TODO: clean references if/when needed
|
//TODO: clean references if/when needed
|
||||||
@@ -673,13 +666,6 @@ func (fp firewallPort) match(p firewall.Packet, incoming bool, c *cert.CachedCer
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// this branch is here to catch traffic from FirewallTable.Any.match and FirewallTable.ICMP.match
|
|
||||||
if p.Protocol == firewall.ProtoICMP || p.Protocol == firewall.ProtoICMPv6 {
|
|
||||||
// port numbers are re-used for connection tracking of ICMP,
|
|
||||||
// but we don't want to actually filter on them.
|
|
||||||
return fp[firewall.PortAny].match(p, c, caPool)
|
|
||||||
}
|
|
||||||
|
|
||||||
var port int32
|
var port int32
|
||||||
|
|
||||||
if p.Fragment {
|
if p.Fragment {
|
||||||
@@ -824,8 +810,10 @@ func (fr *FirewallRule) isAny(groups []string, host string, cidr string) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
if slices.Contains(groups, "any") {
|
for _, group := range groups {
|
||||||
return true
|
if group == "any" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if host == "any" {
|
if host == "any" {
|
||||||
@@ -1036,56 +1024,54 @@ func (r *rule) sanity() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if r.Code != "" {
|
|
||||||
return fmt.Errorf("code specified as [%s]. Support for 'code' will be dropped in a future release, as it has never been functional", r.Code)
|
|
||||||
}
|
|
||||||
|
|
||||||
//todo alert on cidr-any
|
//todo alert on cidr-any
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parsePort(s string) (int32, int32, error) {
|
func parsePort(s string) (startPort, endPort int32, err error) {
|
||||||
var err error
|
|
||||||
const notAPort int32 = -2
|
|
||||||
if s == "any" {
|
if s == "any" {
|
||||||
return firewall.PortAny, firewall.PortAny, nil
|
startPort = firewall.PortAny
|
||||||
}
|
endPort = firewall.PortAny
|
||||||
if s == "fragment" {
|
|
||||||
return firewall.PortFragment, firewall.PortFragment, nil
|
} else if s == "fragment" {
|
||||||
}
|
startPort = firewall.PortFragment
|
||||||
if !strings.Contains(s, `-`) {
|
endPort = firewall.PortFragment
|
||||||
|
|
||||||
|
} else if strings.Contains(s, `-`) {
|
||||||
|
sPorts := strings.SplitN(s, `-`, 2)
|
||||||
|
sPorts[0] = strings.Trim(sPorts[0], " ")
|
||||||
|
sPorts[1] = strings.Trim(sPorts[1], " ")
|
||||||
|
|
||||||
|
if len(sPorts) != 2 || sPorts[0] == "" || sPorts[1] == "" {
|
||||||
|
return 0, 0, fmt.Errorf("appears to be a range but could not be parsed; `%s`", s)
|
||||||
|
}
|
||||||
|
|
||||||
|
rStartPort, err := strconv.Atoi(sPorts[0])
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, fmt.Errorf("beginning range was not a number; `%s`", sPorts[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
rEndPort, err := strconv.Atoi(sPorts[1])
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, fmt.Errorf("ending range was not a number; `%s`", sPorts[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
startPort = int32(rStartPort)
|
||||||
|
endPort = int32(rEndPort)
|
||||||
|
|
||||||
|
if startPort == firewall.PortAny {
|
||||||
|
endPort = firewall.PortAny
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
rPort, err := strconv.Atoi(s)
|
rPort, err := strconv.Atoi(s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return notAPort, notAPort, fmt.Errorf("was not a number; `%s`", s)
|
return 0, 0, fmt.Errorf("was not a number; `%s`", s)
|
||||||
}
|
}
|
||||||
return int32(rPort), int32(rPort), nil
|
startPort = int32(rPort)
|
||||||
|
endPort = startPort
|
||||||
}
|
}
|
||||||
|
|
||||||
sPorts := strings.SplitN(s, `-`, 2)
|
return
|
||||||
for i := range sPorts {
|
|
||||||
sPorts[i] = strings.Trim(sPorts[i], " ")
|
|
||||||
}
|
|
||||||
if len(sPorts) != 2 || sPorts[0] == "" || sPorts[1] == "" {
|
|
||||||
return notAPort, notAPort, fmt.Errorf("appears to be a range but could not be parsed; `%s`", s)
|
|
||||||
}
|
|
||||||
|
|
||||||
rStartPort, err := strconv.Atoi(sPorts[0])
|
|
||||||
if err != nil {
|
|
||||||
return notAPort, notAPort, fmt.Errorf("beginning range was not a number; `%s`", sPorts[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
rEndPort, err := strconv.Atoi(sPorts[1])
|
|
||||||
if err != nil {
|
|
||||||
return notAPort, notAPort, fmt.Errorf("ending range was not a number; `%s`", sPorts[1])
|
|
||||||
}
|
|
||||||
|
|
||||||
startPort := int32(rStartPort)
|
|
||||||
endPort := int32(rEndPort)
|
|
||||||
|
|
||||||
if startPort == firewall.PortAny {
|
|
||||||
endPort = firewall.PortAny
|
|
||||||
}
|
|
||||||
|
|
||||||
return startPort, endPort, nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package firewall
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/netip"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Event struct {
|
||||||
|
Packet Packet `json:"packet,omitempty"`
|
||||||
|
At time.Time `json:"at,omitempty"`
|
||||||
|
Remote netip.AddrPort `json:"remote,omitempty"`
|
||||||
|
//todo cert info?
|
||||||
|
//todo connection indexes?
|
||||||
|
//todo underlay info would actually be amazing, for inbounds
|
||||||
|
}
|
||||||
+2
-7
@@ -22,10 +22,7 @@ const (
|
|||||||
type Packet struct {
|
type Packet struct {
|
||||||
LocalAddr netip.Addr
|
LocalAddr netip.Addr
|
||||||
RemoteAddr netip.Addr
|
RemoteAddr netip.Addr
|
||||||
// LocalPort is the destination port for incoming traffic, or the source port for outgoing. Zero for ICMP.
|
LocalPort uint16
|
||||||
LocalPort uint16
|
|
||||||
// RemotePort is the source port for incoming traffic, or the destination port for outgoing.
|
|
||||||
// For ICMP, it's the "identifier". This is only used for connection tracking, actual firewall rules will not filter on ICMP identifier
|
|
||||||
RemotePort uint16
|
RemotePort uint16
|
||||||
Protocol uint8
|
Protocol uint8
|
||||||
Fragment bool
|
Fragment bool
|
||||||
@@ -47,10 +44,8 @@ func (fp Packet) MarshalJSON() ([]byte, error) {
|
|||||||
switch fp.Protocol {
|
switch fp.Protocol {
|
||||||
case ProtoTCP:
|
case ProtoTCP:
|
||||||
proto = "tcp"
|
proto = "tcp"
|
||||||
case ProtoICMP:
|
case ProtoICMP, ProtoICMPv6:
|
||||||
proto = "icmp"
|
proto = "icmp"
|
||||||
case ProtoICMPv6:
|
|
||||||
proto = "icmpv6"
|
|
||||||
case ProtoUDP:
|
case ProtoUDP:
|
||||||
proto = "udp"
|
proto = "udp"
|
||||||
default:
|
default:
|
||||||
|
|||||||
+36
-188
@@ -87,10 +87,9 @@ func TestFirewall_AddRule(t *testing.T) {
|
|||||||
|
|
||||||
fw = NewFirewall(l, time.Second, time.Minute, time.Hour, c)
|
fw = NewFirewall(l, time.Second, time.Minute, time.Hour, c)
|
||||||
require.NoError(t, fw.AddRule(true, firewall.ProtoICMP, 1, 1, []string{}, "h1", "", "", "", ""))
|
require.NoError(t, fw.AddRule(true, firewall.ProtoICMP, 1, 1, []string{}, "h1", "", "", "", ""))
|
||||||
//no matter what port is given for icmp, it should end up as "any"
|
assert.Nil(t, fw.InRules.ICMP[1].Any.Any)
|
||||||
assert.Nil(t, fw.InRules.ICMP[firewall.PortAny].Any.Any)
|
assert.Empty(t, fw.InRules.ICMP[1].Any.Groups)
|
||||||
assert.Empty(t, fw.InRules.ICMP[firewall.PortAny].Any.Groups)
|
assert.Contains(t, fw.InRules.ICMP[1].Any.Hosts, "h1")
|
||||||
assert.Contains(t, fw.InRules.ICMP[firewall.PortAny].Any.Hosts, "h1")
|
|
||||||
|
|
||||||
fw = NewFirewall(l, time.Second, time.Minute, time.Hour, c)
|
fw = NewFirewall(l, time.Second, time.Minute, time.Hour, c)
|
||||||
require.NoError(t, fw.AddRule(false, firewall.ProtoAny, 1, 1, []string{}, "", ti.String(), "", "", ""))
|
require.NoError(t, fw.AddRule(false, firewall.ProtoAny, 1, 1, []string{}, "", ti.String(), "", "", ""))
|
||||||
@@ -213,44 +212,44 @@ func TestFirewall_Drop(t *testing.T) {
|
|||||||
cp := cert.NewCAPool()
|
cp := cert.NewCAPool()
|
||||||
|
|
||||||
// Drop outbound
|
// Drop outbound
|
||||||
assert.Equal(t, ErrNoMatchingRule, fw.Drop(p, false, &h, cp, nil))
|
assert.Equal(t, ErrNoMatchingRule, fw.Drop(p, false, &h, cp, nil, nil))
|
||||||
// Allow inbound
|
// Allow inbound
|
||||||
resetConntrack(fw)
|
resetConntrack(fw)
|
||||||
require.NoError(t, fw.Drop(p, true, &h, cp, nil))
|
require.NoError(t, fw.Drop(p, true, &h, cp, nil, nil))
|
||||||
// Allow outbound because conntrack
|
// Allow outbound because conntrack
|
||||||
require.NoError(t, fw.Drop(p, false, &h, cp, nil))
|
require.NoError(t, fw.Drop(p, false, &h, cp, nil, nil))
|
||||||
|
|
||||||
// test remote mismatch
|
// test remote mismatch
|
||||||
oldRemote := p.RemoteAddr
|
oldRemote := p.RemoteAddr
|
||||||
p.RemoteAddr = netip.MustParseAddr("1.2.3.10")
|
p.RemoteAddr = netip.MustParseAddr("1.2.3.10")
|
||||||
assert.Equal(t, fw.Drop(p, false, &h, cp, nil), ErrInvalidRemoteIP)
|
assert.Equal(t, fw.Drop(p, false, &h, cp, nil, nil), ErrInvalidRemoteIP)
|
||||||
p.RemoteAddr = oldRemote
|
p.RemoteAddr = oldRemote
|
||||||
|
|
||||||
// ensure signer doesn't get in the way of group checks
|
// ensure signer doesn't get in the way of group checks
|
||||||
fw = NewFirewall(l, time.Second, time.Minute, time.Hour, &c)
|
fw = NewFirewall(l, time.Second, time.Minute, time.Hour, &c)
|
||||||
require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"nope"}, "", "", "", "", "signer-shasum"))
|
require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"nope"}, "", "", "", "", "signer-shasum"))
|
||||||
require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"default-group"}, "", "", "", "", "signer-shasum-bad"))
|
require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"default-group"}, "", "", "", "", "signer-shasum-bad"))
|
||||||
assert.Equal(t, fw.Drop(p, true, &h, cp, nil), ErrNoMatchingRule)
|
assert.Equal(t, fw.Drop(p, true, &h, cp, nil, nil), ErrNoMatchingRule)
|
||||||
|
|
||||||
// test caSha doesn't drop on match
|
// test caSha doesn't drop on match
|
||||||
fw = NewFirewall(l, time.Second, time.Minute, time.Hour, &c)
|
fw = NewFirewall(l, time.Second, time.Minute, time.Hour, &c)
|
||||||
require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"nope"}, "", "", "", "", "signer-shasum-bad"))
|
require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"nope"}, "", "", "", "", "signer-shasum-bad"))
|
||||||
require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"default-group"}, "", "", "", "", "signer-shasum"))
|
require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"default-group"}, "", "", "", "", "signer-shasum"))
|
||||||
require.NoError(t, fw.Drop(p, true, &h, cp, nil))
|
require.NoError(t, fw.Drop(p, true, &h, cp, nil, nil))
|
||||||
|
|
||||||
// ensure ca name doesn't get in the way of group checks
|
// ensure ca name doesn't get in the way of group checks
|
||||||
cp.CAs["signer-shasum"] = &cert.CachedCertificate{Certificate: &dummyCert{name: "ca-good"}}
|
cp.CAs["signer-shasum"] = &cert.CachedCertificate{Certificate: &dummyCert{name: "ca-good"}}
|
||||||
fw = NewFirewall(l, time.Second, time.Minute, time.Hour, &c)
|
fw = NewFirewall(l, time.Second, time.Minute, time.Hour, &c)
|
||||||
require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"nope"}, "", "", "", "ca-good", ""))
|
require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"nope"}, "", "", "", "ca-good", ""))
|
||||||
require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"default-group"}, "", "", "", "ca-good-bad", ""))
|
require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"default-group"}, "", "", "", "ca-good-bad", ""))
|
||||||
assert.Equal(t, fw.Drop(p, true, &h, cp, nil), ErrNoMatchingRule)
|
assert.Equal(t, fw.Drop(p, true, &h, cp, nil, nil), ErrNoMatchingRule)
|
||||||
|
|
||||||
// test caName doesn't drop on match
|
// test caName doesn't drop on match
|
||||||
cp.CAs["signer-shasum"] = &cert.CachedCertificate{Certificate: &dummyCert{name: "ca-good"}}
|
cp.CAs["signer-shasum"] = &cert.CachedCertificate{Certificate: &dummyCert{name: "ca-good"}}
|
||||||
fw = NewFirewall(l, time.Second, time.Minute, time.Hour, &c)
|
fw = NewFirewall(l, time.Second, time.Minute, time.Hour, &c)
|
||||||
require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"nope"}, "", "", "", "ca-good-bad", ""))
|
require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"nope"}, "", "", "", "ca-good-bad", ""))
|
||||||
require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"default-group"}, "", "", "", "ca-good", ""))
|
require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"default-group"}, "", "", "", "ca-good", ""))
|
||||||
require.NoError(t, fw.Drop(p, true, &h, cp, nil))
|
require.NoError(t, fw.Drop(p, true, &h, cp, nil, nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFirewall_DropV6(t *testing.T) {
|
func TestFirewall_DropV6(t *testing.T) {
|
||||||
@@ -292,44 +291,44 @@ func TestFirewall_DropV6(t *testing.T) {
|
|||||||
cp := cert.NewCAPool()
|
cp := cert.NewCAPool()
|
||||||
|
|
||||||
// Drop outbound
|
// Drop outbound
|
||||||
assert.Equal(t, ErrNoMatchingRule, fw.Drop(p, false, &h, cp, nil))
|
assert.Equal(t, ErrNoMatchingRule, fw.Drop(p, false, &h, cp, nil, nil))
|
||||||
// Allow inbound
|
// Allow inbound
|
||||||
resetConntrack(fw)
|
resetConntrack(fw)
|
||||||
require.NoError(t, fw.Drop(p, true, &h, cp, nil))
|
require.NoError(t, fw.Drop(p, true, &h, cp, nil, nil))
|
||||||
// Allow outbound because conntrack
|
// Allow outbound because conntrack
|
||||||
require.NoError(t, fw.Drop(p, false, &h, cp, nil))
|
require.NoError(t, fw.Drop(p, false, &h, cp, nil, nil))
|
||||||
|
|
||||||
// test remote mismatch
|
// test remote mismatch
|
||||||
oldRemote := p.RemoteAddr
|
oldRemote := p.RemoteAddr
|
||||||
p.RemoteAddr = netip.MustParseAddr("fd12::56")
|
p.RemoteAddr = netip.MustParseAddr("fd12::56")
|
||||||
assert.Equal(t, fw.Drop(p, false, &h, cp, nil), ErrInvalidRemoteIP)
|
assert.Equal(t, fw.Drop(p, false, &h, cp, nil, nil), ErrInvalidRemoteIP)
|
||||||
p.RemoteAddr = oldRemote
|
p.RemoteAddr = oldRemote
|
||||||
|
|
||||||
// ensure signer doesn't get in the way of group checks
|
// ensure signer doesn't get in the way of group checks
|
||||||
fw = NewFirewall(l, time.Second, time.Minute, time.Hour, &c)
|
fw = NewFirewall(l, time.Second, time.Minute, time.Hour, &c)
|
||||||
require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"nope"}, "", "", "", "", "signer-shasum"))
|
require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"nope"}, "", "", "", "", "signer-shasum"))
|
||||||
require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"default-group"}, "", "", "", "", "signer-shasum-bad"))
|
require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"default-group"}, "", "", "", "", "signer-shasum-bad"))
|
||||||
assert.Equal(t, fw.Drop(p, true, &h, cp, nil), ErrNoMatchingRule)
|
assert.Equal(t, fw.Drop(p, true, &h, cp, nil, nil), ErrNoMatchingRule)
|
||||||
|
|
||||||
// test caSha doesn't drop on match
|
// test caSha doesn't drop on match
|
||||||
fw = NewFirewall(l, time.Second, time.Minute, time.Hour, &c)
|
fw = NewFirewall(l, time.Second, time.Minute, time.Hour, &c)
|
||||||
require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"nope"}, "", "", "", "", "signer-shasum-bad"))
|
require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"nope"}, "", "", "", "", "signer-shasum-bad"))
|
||||||
require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"default-group"}, "", "", "", "", "signer-shasum"))
|
require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"default-group"}, "", "", "", "", "signer-shasum"))
|
||||||
require.NoError(t, fw.Drop(p, true, &h, cp, nil))
|
require.NoError(t, fw.Drop(p, true, &h, cp, nil, nil))
|
||||||
|
|
||||||
// ensure ca name doesn't get in the way of group checks
|
// ensure ca name doesn't get in the way of group checks
|
||||||
cp.CAs["signer-shasum"] = &cert.CachedCertificate{Certificate: &dummyCert{name: "ca-good"}}
|
cp.CAs["signer-shasum"] = &cert.CachedCertificate{Certificate: &dummyCert{name: "ca-good"}}
|
||||||
fw = NewFirewall(l, time.Second, time.Minute, time.Hour, &c)
|
fw = NewFirewall(l, time.Second, time.Minute, time.Hour, &c)
|
||||||
require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"nope"}, "", "", "", "ca-good", ""))
|
require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"nope"}, "", "", "", "ca-good", ""))
|
||||||
require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"default-group"}, "", "", "", "ca-good-bad", ""))
|
require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"default-group"}, "", "", "", "ca-good-bad", ""))
|
||||||
assert.Equal(t, fw.Drop(p, true, &h, cp, nil), ErrNoMatchingRule)
|
assert.Equal(t, fw.Drop(p, true, &h, cp, nil, nil), ErrNoMatchingRule)
|
||||||
|
|
||||||
// test caName doesn't drop on match
|
// test caName doesn't drop on match
|
||||||
cp.CAs["signer-shasum"] = &cert.CachedCertificate{Certificate: &dummyCert{name: "ca-good"}}
|
cp.CAs["signer-shasum"] = &cert.CachedCertificate{Certificate: &dummyCert{name: "ca-good"}}
|
||||||
fw = NewFirewall(l, time.Second, time.Minute, time.Hour, &c)
|
fw = NewFirewall(l, time.Second, time.Minute, time.Hour, &c)
|
||||||
require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"nope"}, "", "", "", "ca-good-bad", ""))
|
require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"nope"}, "", "", "", "ca-good-bad", ""))
|
||||||
require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"default-group"}, "", "", "", "ca-good", ""))
|
require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"default-group"}, "", "", "", "ca-good", ""))
|
||||||
require.NoError(t, fw.Drop(p, true, &h, cp, nil))
|
require.NoError(t, fw.Drop(p, true, &h, cp, nil, nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkFirewallTable_match(b *testing.B) {
|
func BenchmarkFirewallTable_match(b *testing.B) {
|
||||||
@@ -537,10 +536,10 @@ func TestFirewall_Drop2(t *testing.T) {
|
|||||||
cp := cert.NewCAPool()
|
cp := cert.NewCAPool()
|
||||||
|
|
||||||
// h1/c1 lacks the proper groups
|
// h1/c1 lacks the proper groups
|
||||||
require.ErrorIs(t, fw.Drop(p, true, &h1, cp, nil), ErrNoMatchingRule)
|
require.ErrorIs(t, fw.Drop(p, true, &h1, cp, nil, nil), ErrNoMatchingRule)
|
||||||
// c has the proper groups
|
// c has the proper groups
|
||||||
resetConntrack(fw)
|
resetConntrack(fw)
|
||||||
require.NoError(t, fw.Drop(p, true, &h, cp, nil))
|
require.NoError(t, fw.Drop(p, true, &h, cp, nil, nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFirewall_Drop3(t *testing.T) {
|
func TestFirewall_Drop3(t *testing.T) {
|
||||||
@@ -618,18 +617,18 @@ func TestFirewall_Drop3(t *testing.T) {
|
|||||||
cp := cert.NewCAPool()
|
cp := cert.NewCAPool()
|
||||||
|
|
||||||
// c1 should pass because host match
|
// c1 should pass because host match
|
||||||
require.NoError(t, fw.Drop(p, true, &h1, cp, nil))
|
require.NoError(t, fw.Drop(p, true, &h1, cp, nil, nil))
|
||||||
// c2 should pass because ca sha match
|
// c2 should pass because ca sha match
|
||||||
resetConntrack(fw)
|
resetConntrack(fw)
|
||||||
require.NoError(t, fw.Drop(p, true, &h2, cp, nil))
|
require.NoError(t, fw.Drop(p, true, &h2, cp, nil, nil))
|
||||||
// c3 should fail because no match
|
// c3 should fail because no match
|
||||||
resetConntrack(fw)
|
resetConntrack(fw)
|
||||||
assert.Equal(t, fw.Drop(p, true, &h3, cp, nil), ErrNoMatchingRule)
|
assert.Equal(t, fw.Drop(p, true, &h3, cp, nil, nil), ErrNoMatchingRule)
|
||||||
|
|
||||||
// Test a remote address match
|
// Test a remote address match
|
||||||
fw = NewFirewall(l, time.Second, time.Minute, time.Hour, c.Certificate)
|
fw = NewFirewall(l, time.Second, time.Minute, time.Hour, c.Certificate)
|
||||||
require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 1, 1, []string{}, "", "1.2.3.4/24", "", "", ""))
|
require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 1, 1, []string{}, "", "1.2.3.4/24", "", "", ""))
|
||||||
require.NoError(t, fw.Drop(p, true, &h1, cp, nil))
|
require.NoError(t, fw.Drop(p, true, &h1, cp, nil, nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFirewall_Drop3V6(t *testing.T) {
|
func TestFirewall_Drop3V6(t *testing.T) {
|
||||||
@@ -667,7 +666,7 @@ func TestFirewall_Drop3V6(t *testing.T) {
|
|||||||
fw := NewFirewall(l, time.Second, time.Minute, time.Hour, c.Certificate)
|
fw := NewFirewall(l, time.Second, time.Minute, time.Hour, c.Certificate)
|
||||||
cp := cert.NewCAPool()
|
cp := cert.NewCAPool()
|
||||||
require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 1, 1, []string{}, "", "fd12::34/120", "", "", ""))
|
require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 1, 1, []string{}, "", "fd12::34/120", "", "", ""))
|
||||||
require.NoError(t, fw.Drop(p, true, &h, cp, nil))
|
require.NoError(t, fw.Drop(p, true, &h, cp, nil, nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFirewall_DropConntrackReload(t *testing.T) {
|
func TestFirewall_DropConntrackReload(t *testing.T) {
|
||||||
@@ -709,12 +708,12 @@ func TestFirewall_DropConntrackReload(t *testing.T) {
|
|||||||
cp := cert.NewCAPool()
|
cp := cert.NewCAPool()
|
||||||
|
|
||||||
// Drop outbound
|
// Drop outbound
|
||||||
assert.Equal(t, fw.Drop(p, false, &h, cp, nil), ErrNoMatchingRule)
|
assert.Equal(t, fw.Drop(p, false, &h, cp, nil, nil), ErrNoMatchingRule)
|
||||||
// Allow inbound
|
// Allow inbound
|
||||||
resetConntrack(fw)
|
resetConntrack(fw)
|
||||||
require.NoError(t, fw.Drop(p, true, &h, cp, nil))
|
require.NoError(t, fw.Drop(p, true, &h, cp, nil, nil))
|
||||||
// Allow outbound because conntrack
|
// Allow outbound because conntrack
|
||||||
require.NoError(t, fw.Drop(p, false, &h, cp, nil))
|
require.NoError(t, fw.Drop(p, false, &h, cp, nil, nil))
|
||||||
|
|
||||||
oldFw := fw
|
oldFw := fw
|
||||||
fw = NewFirewall(l, time.Second, time.Minute, time.Hour, c.Certificate)
|
fw = NewFirewall(l, time.Second, time.Minute, time.Hour, c.Certificate)
|
||||||
@@ -723,7 +722,7 @@ func TestFirewall_DropConntrackReload(t *testing.T) {
|
|||||||
fw.rulesVersion = oldFw.rulesVersion + 1
|
fw.rulesVersion = oldFw.rulesVersion + 1
|
||||||
|
|
||||||
// Allow outbound because conntrack and new rules allow port 10
|
// Allow outbound because conntrack and new rules allow port 10
|
||||||
require.NoError(t, fw.Drop(p, false, &h, cp, nil))
|
require.NoError(t, fw.Drop(p, false, &h, cp, nil, nil))
|
||||||
|
|
||||||
oldFw = fw
|
oldFw = fw
|
||||||
fw = NewFirewall(l, time.Second, time.Minute, time.Hour, c.Certificate)
|
fw = NewFirewall(l, time.Second, time.Minute, time.Hour, c.Certificate)
|
||||||
@@ -732,151 +731,7 @@ func TestFirewall_DropConntrackReload(t *testing.T) {
|
|||||||
fw.rulesVersion = oldFw.rulesVersion + 1
|
fw.rulesVersion = oldFw.rulesVersion + 1
|
||||||
|
|
||||||
// Drop outbound because conntrack doesn't match new ruleset
|
// Drop outbound because conntrack doesn't match new ruleset
|
||||||
assert.Equal(t, fw.Drop(p, false, &h, cp, nil), ErrNoMatchingRule)
|
assert.Equal(t, fw.Drop(p, false, &h, cp, nil, nil), ErrNoMatchingRule)
|
||||||
}
|
|
||||||
|
|
||||||
func TestFirewall_ICMPPortBehavior(t *testing.T) {
|
|
||||||
l := test.NewLogger()
|
|
||||||
ob := &bytes.Buffer{}
|
|
||||||
l.SetOutput(ob)
|
|
||||||
myVpnNetworksTable := new(bart.Lite)
|
|
||||||
myVpnNetworksTable.Insert(netip.MustParsePrefix("1.1.1.1/8"))
|
|
||||||
|
|
||||||
network := netip.MustParsePrefix("1.2.3.4/24")
|
|
||||||
|
|
||||||
c := cert.CachedCertificate{
|
|
||||||
Certificate: &dummyCert{
|
|
||||||
name: "host1",
|
|
||||||
networks: []netip.Prefix{network},
|
|
||||||
groups: []string{"default-group"},
|
|
||||||
issuer: "signer-shasum",
|
|
||||||
},
|
|
||||||
InvertedGroups: map[string]struct{}{"default-group": {}},
|
|
||||||
}
|
|
||||||
h := HostInfo{
|
|
||||||
ConnectionState: &ConnectionState{
|
|
||||||
peerCert: &c,
|
|
||||||
},
|
|
||||||
vpnAddrs: []netip.Addr{network.Addr()},
|
|
||||||
}
|
|
||||||
h.buildNetworks(myVpnNetworksTable, c.Certificate)
|
|
||||||
|
|
||||||
cp := cert.NewCAPool()
|
|
||||||
|
|
||||||
templ := firewall.Packet{
|
|
||||||
LocalAddr: netip.MustParseAddr("1.2.3.4"),
|
|
||||||
RemoteAddr: netip.MustParseAddr("1.2.3.4"),
|
|
||||||
Protocol: firewall.ProtoICMP,
|
|
||||||
Fragment: false,
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Run("ICMP allowed", func(t *testing.T) {
|
|
||||||
fw := NewFirewall(l, time.Second, time.Minute, time.Hour, c.Certificate)
|
|
||||||
require.NoError(t, fw.AddRule(true, firewall.ProtoICMP, 0, 0, []string{"any"}, "", "", "", "", ""))
|
|
||||||
t.Run("zero ports", func(t *testing.T) {
|
|
||||||
p := templ.Copy()
|
|
||||||
p.LocalPort = 0
|
|
||||||
p.RemotePort = 0
|
|
||||||
// Drop outbound
|
|
||||||
assert.Equal(t, fw.Drop(*p, false, &h, cp, nil), ErrNoMatchingRule)
|
|
||||||
// Allow inbound
|
|
||||||
resetConntrack(fw)
|
|
||||||
require.NoError(t, fw.Drop(*p, true, &h, cp, nil))
|
|
||||||
//now also allow outbound
|
|
||||||
require.NoError(t, fw.Drop(*p, false, &h, cp, nil))
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("nonzero ports", func(t *testing.T) {
|
|
||||||
p := templ.Copy()
|
|
||||||
p.LocalPort = 0xabcd
|
|
||||||
p.RemotePort = 0x1234
|
|
||||||
// Drop outbound
|
|
||||||
assert.Equal(t, fw.Drop(*p, false, &h, cp, nil), ErrNoMatchingRule)
|
|
||||||
// Allow inbound
|
|
||||||
resetConntrack(fw)
|
|
||||||
require.NoError(t, fw.Drop(*p, true, &h, cp, nil))
|
|
||||||
//now also allow outbound
|
|
||||||
require.NoError(t, fw.Drop(*p, false, &h, cp, nil))
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("Any proto, some ports allowed", func(t *testing.T) {
|
|
||||||
fw := NewFirewall(l, time.Second, time.Minute, time.Hour, c.Certificate)
|
|
||||||
require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 80, 444, []string{"any"}, "", "", "", "", ""))
|
|
||||||
t.Run("zero ports, still blocked", func(t *testing.T) {
|
|
||||||
p := templ.Copy()
|
|
||||||
p.LocalPort = 0
|
|
||||||
p.RemotePort = 0
|
|
||||||
// Drop outbound
|
|
||||||
assert.Equal(t, fw.Drop(*p, false, &h, cp, nil), ErrNoMatchingRule)
|
|
||||||
// Allow inbound
|
|
||||||
resetConntrack(fw)
|
|
||||||
assert.Equal(t, fw.Drop(*p, true, &h, cp, nil), ErrNoMatchingRule)
|
|
||||||
//now also allow outbound
|
|
||||||
assert.Equal(t, fw.Drop(*p, false, &h, cp, nil), ErrNoMatchingRule)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("nonzero ports, still blocked", func(t *testing.T) {
|
|
||||||
p := templ.Copy()
|
|
||||||
p.LocalPort = 0xabcd
|
|
||||||
p.RemotePort = 0x1234
|
|
||||||
// Drop outbound
|
|
||||||
assert.Equal(t, fw.Drop(*p, false, &h, cp, nil), ErrNoMatchingRule)
|
|
||||||
// Allow inbound
|
|
||||||
resetConntrack(fw)
|
|
||||||
assert.Equal(t, fw.Drop(*p, true, &h, cp, nil), ErrNoMatchingRule)
|
|
||||||
//now also allow outbound
|
|
||||||
assert.Equal(t, fw.Drop(*p, false, &h, cp, nil), ErrNoMatchingRule)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("nonzero, matching ports, still blocked", func(t *testing.T) {
|
|
||||||
p := templ.Copy()
|
|
||||||
p.LocalPort = 80
|
|
||||||
p.RemotePort = 80
|
|
||||||
// Drop outbound
|
|
||||||
assert.Equal(t, fw.Drop(*p, false, &h, cp, nil), ErrNoMatchingRule)
|
|
||||||
// Allow inbound
|
|
||||||
resetConntrack(fw)
|
|
||||||
assert.Equal(t, fw.Drop(*p, true, &h, cp, nil), ErrNoMatchingRule)
|
|
||||||
//now also allow outbound
|
|
||||||
assert.Equal(t, fw.Drop(*p, false, &h, cp, nil), ErrNoMatchingRule)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
t.Run("Any proto, any port", func(t *testing.T) {
|
|
||||||
fw := NewFirewall(l, time.Second, time.Minute, time.Hour, c.Certificate)
|
|
||||||
require.NoError(t, fw.AddRule(true, firewall.ProtoAny, 0, 0, []string{"any"}, "", "", "", "", ""))
|
|
||||||
t.Run("zero ports, allowed", func(t *testing.T) {
|
|
||||||
resetConntrack(fw)
|
|
||||||
p := templ.Copy()
|
|
||||||
p.LocalPort = 0
|
|
||||||
p.RemotePort = 0
|
|
||||||
// Drop outbound
|
|
||||||
assert.Equal(t, fw.Drop(*p, false, &h, cp, nil), ErrNoMatchingRule)
|
|
||||||
// Allow inbound
|
|
||||||
resetConntrack(fw)
|
|
||||||
require.NoError(t, fw.Drop(*p, true, &h, cp, nil))
|
|
||||||
//now also allow outbound
|
|
||||||
require.NoError(t, fw.Drop(*p, false, &h, cp, nil))
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("nonzero ports, allowed", func(t *testing.T) {
|
|
||||||
resetConntrack(fw)
|
|
||||||
p := templ.Copy()
|
|
||||||
p.LocalPort = 0xabcd
|
|
||||||
p.RemotePort = 0x1234
|
|
||||||
// Drop outbound
|
|
||||||
assert.Equal(t, fw.Drop(*p, false, &h, cp, nil), ErrNoMatchingRule)
|
|
||||||
// Allow inbound
|
|
||||||
resetConntrack(fw)
|
|
||||||
require.NoError(t, fw.Drop(*p, true, &h, cp, nil))
|
|
||||||
//now also allow outbound
|
|
||||||
require.NoError(t, fw.Drop(*p, false, &h, cp, nil))
|
|
||||||
//different ID is blocked
|
|
||||||
p.RemotePort++
|
|
||||||
require.Equal(t, fw.Drop(*p, false, &h, cp, nil), ErrNoMatchingRule)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFirewall_DropIPSpoofing(t *testing.T) {
|
func TestFirewall_DropIPSpoofing(t *testing.T) {
|
||||||
@@ -922,7 +777,7 @@ func TestFirewall_DropIPSpoofing(t *testing.T) {
|
|||||||
Protocol: firewall.ProtoUDP,
|
Protocol: firewall.ProtoUDP,
|
||||||
Fragment: false,
|
Fragment: false,
|
||||||
}
|
}
|
||||||
assert.Equal(t, fw.Drop(p, true, &h1, cp, nil), ErrInvalidRemoteIP)
|
assert.Equal(t, fw.Drop(p, true, &h1, cp, nil, nil), ErrInvalidRemoteIP)
|
||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkLookup(b *testing.B) {
|
func BenchmarkLookup(b *testing.B) {
|
||||||
@@ -1064,11 +919,11 @@ func TestNewFirewallFromConfig(t *testing.T) {
|
|||||||
|
|
||||||
// Test code/port error
|
// Test code/port error
|
||||||
conf = config.NewC(l)
|
conf = config.NewC(l)
|
||||||
conf.Settings["firewall"] = map[string]any{"outbound": []any{map[string]any{"code": "a", "host": "testh", "proto": "any"}}}
|
conf.Settings["firewall"] = map[string]any{"outbound": []any{map[string]any{"code": "a", "host": "testh"}}}
|
||||||
_, err = NewFirewallFromConfig(l, cs, conf)
|
_, err = NewFirewallFromConfig(l, cs, conf)
|
||||||
require.EqualError(t, err, "firewall.outbound rule #0; code was not a number; `a`")
|
require.EqualError(t, err, "firewall.outbound rule #0; code was not a number; `a`")
|
||||||
|
|
||||||
conf.Settings["firewall"] = map[string]any{"outbound": []any{map[string]any{"port": "a", "host": "testh", "proto": "any"}}}
|
conf.Settings["firewall"] = map[string]any{"outbound": []any{map[string]any{"port": "a", "host": "testh"}}}
|
||||||
_, err = NewFirewallFromConfig(l, cs, conf)
|
_, err = NewFirewallFromConfig(l, cs, conf)
|
||||||
require.EqualError(t, err, "firewall.outbound rule #0; port was not a number; `a`")
|
require.EqualError(t, err, "firewall.outbound rule #0; port was not a number; `a`")
|
||||||
|
|
||||||
@@ -1118,14 +973,7 @@ func TestAddFirewallRulesFromConfig(t *testing.T) {
|
|||||||
mf = &mockFirewall{}
|
mf = &mockFirewall{}
|
||||||
conf.Settings["firewall"] = map[string]any{"outbound": []any{map[string]any{"port": "1", "proto": "icmp", "host": "a"}}}
|
conf.Settings["firewall"] = map[string]any{"outbound": []any{map[string]any{"port": "1", "proto": "icmp", "host": "a"}}}
|
||||||
require.NoError(t, AddFirewallRulesFromConfig(l, false, conf, mf))
|
require.NoError(t, AddFirewallRulesFromConfig(l, false, conf, mf))
|
||||||
assert.Equal(t, addRuleCall{incoming: false, proto: firewall.ProtoICMP, startPort: firewall.PortAny, endPort: firewall.PortAny, groups: nil, host: "a", ip: "", localIp: ""}, mf.lastCall)
|
assert.Equal(t, addRuleCall{incoming: false, proto: firewall.ProtoICMP, startPort: 1, endPort: 1, groups: nil, host: "a", ip: "", localIp: ""}, mf.lastCall)
|
||||||
|
|
||||||
// Test adding icmp rule no port
|
|
||||||
conf = config.NewC(l)
|
|
||||||
mf = &mockFirewall{}
|
|
||||||
conf.Settings["firewall"] = map[string]any{"outbound": []any{map[string]any{"proto": "icmp", "host": "a"}}}
|
|
||||||
require.NoError(t, AddFirewallRulesFromConfig(l, false, conf, mf))
|
|
||||||
assert.Equal(t, addRuleCall{incoming: false, proto: firewall.ProtoICMP, startPort: firewall.PortAny, endPort: firewall.PortAny, groups: nil, host: "a", ip: "", localIp: ""}, mf.lastCall)
|
|
||||||
|
|
||||||
// Test adding any rule
|
// Test adding any rule
|
||||||
conf = config.NewC(l)
|
conf = config.NewC(l)
|
||||||
@@ -1336,7 +1184,7 @@ func (c *testcase) Test(t *testing.T, fw *Firewall) {
|
|||||||
t.Helper()
|
t.Helper()
|
||||||
cp := cert.NewCAPool()
|
cp := cert.NewCAPool()
|
||||||
resetConntrack(fw)
|
resetConntrack(fw)
|
||||||
err := fw.Drop(c.p, true, c.h, cp, nil)
|
err := fw.Drop(c.p, true, c.h, cp, nil, nil)
|
||||||
if c.err == nil {
|
if c.err == nil {
|
||||||
require.NoError(t, err, "failed to not drop remote address %s", c.p.RemoteAddr)
|
require.NoError(t, err, "failed to not drop remote address %s", c.p.RemoteAddr)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ go 1.25
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
dario.cat/mergo v1.0.2
|
dario.cat/mergo v1.0.2
|
||||||
filippo.io/bigmod v0.1.0
|
|
||||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be
|
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be
|
||||||
github.com/armon/go-radix v1.0.0
|
github.com/armon/go-radix v1.0.0
|
||||||
github.com/cyberdelia/go-metrics-graphite v0.0.0-20161219230853-39f87cc3b432
|
github.com/cyberdelia/go-metrics-graphite v0.0.0-20161219230853-39f87cc3b432
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||||
dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8=
|
dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8=
|
||||||
dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA=
|
dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA=
|
||||||
filippo.io/bigmod v0.1.0 h1:UNzDk7y9ADKST+axd9skUpBQeW7fG2KrTZyOE4uGQy8=
|
|
||||||
filippo.io/bigmod v0.1.0/go.mod h1:OjOXDNlClLblvXdwgFFOQFJEocLhhtai8vGLy0JCZlI=
|
|
||||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||||
|
|||||||
+9
-58
@@ -23,25 +23,22 @@ const (
|
|||||||
DefaultHandshakeRetries = 10
|
DefaultHandshakeRetries = 10
|
||||||
DefaultHandshakeTriggerBuffer = 64
|
DefaultHandshakeTriggerBuffer = 64
|
||||||
DefaultUseRelays = true
|
DefaultUseRelays = true
|
||||||
DefaultMaxHandshakeRate = 0 // 0 means unlimited
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
defaultHandshakeConfig = HandshakeConfig{
|
defaultHandshakeConfig = HandshakeConfig{
|
||||||
tryInterval: DefaultHandshakeTryInterval,
|
tryInterval: DefaultHandshakeTryInterval,
|
||||||
retries: DefaultHandshakeRetries,
|
retries: DefaultHandshakeRetries,
|
||||||
triggerBuffer: DefaultHandshakeTriggerBuffer,
|
triggerBuffer: DefaultHandshakeTriggerBuffer,
|
||||||
useRelays: DefaultUseRelays,
|
useRelays: DefaultUseRelays,
|
||||||
maxHandshakeRate: DefaultMaxHandshakeRate,
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
type HandshakeConfig struct {
|
type HandshakeConfig struct {
|
||||||
tryInterval time.Duration
|
tryInterval time.Duration
|
||||||
retries int64
|
retries int64
|
||||||
triggerBuffer int
|
triggerBuffer int
|
||||||
useRelays bool
|
useRelays bool
|
||||||
maxHandshakeRate int
|
|
||||||
|
|
||||||
messageMetrics *MessageMetrics
|
messageMetrics *MessageMetrics
|
||||||
}
|
}
|
||||||
@@ -61,15 +58,9 @@ type HandshakeManager struct {
|
|||||||
messageMetrics *MessageMetrics
|
messageMetrics *MessageMetrics
|
||||||
metricInitiated metrics.Counter
|
metricInitiated metrics.Counter
|
||||||
metricTimedOut metrics.Counter
|
metricTimedOut metrics.Counter
|
||||||
metricRateLimited metrics.Counter
|
|
||||||
f *Interface
|
f *Interface
|
||||||
l *logrus.Logger
|
l *logrus.Logger
|
||||||
|
|
||||||
// Rate limiting for new handshakes (token bucket)
|
|
||||||
rateBucket int // tokens currently available
|
|
||||||
rateMax int // max tokens (== max handshakes per second), 0 means unlimited
|
|
||||||
rateLastTick time.Time
|
|
||||||
|
|
||||||
// can be used to trigger outbound handshake for the given vpnIp
|
// can be used to trigger outbound handshake for the given vpnIp
|
||||||
trigger chan netip.Addr
|
trigger chan netip.Addr
|
||||||
}
|
}
|
||||||
@@ -125,41 +116,10 @@ func NewHandshakeManager(l *logrus.Logger, mainHostMap *HostMap, lightHouse *Lig
|
|||||||
messageMetrics: config.messageMetrics,
|
messageMetrics: config.messageMetrics,
|
||||||
metricInitiated: metrics.GetOrRegisterCounter("handshake_manager.initiated", nil),
|
metricInitiated: metrics.GetOrRegisterCounter("handshake_manager.initiated", nil),
|
||||||
metricTimedOut: metrics.GetOrRegisterCounter("handshake_manager.timed_out", nil),
|
metricTimedOut: metrics.GetOrRegisterCounter("handshake_manager.timed_out", nil),
|
||||||
metricRateLimited: metrics.GetOrRegisterCounter("handshake_manager.rate_limited", nil),
|
|
||||||
rateBucket: config.maxHandshakeRate,
|
|
||||||
rateMax: config.maxHandshakeRate,
|
|
||||||
rateLastTick: time.Now(),
|
|
||||||
l: l,
|
l: l,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// handshakeRateAllow checks the token bucket rate limiter and returns true if a
|
|
||||||
// new handshake is allowed. Must be called with hm.Lock held.
|
|
||||||
func (hm *HandshakeManager) handshakeRateAllow(now time.Time) bool {
|
|
||||||
if hm.rateMax == 0 {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Refill tokens based on elapsed time
|
|
||||||
elapsed := now.Sub(hm.rateLastTick)
|
|
||||||
if elapsed >= time.Second {
|
|
||||||
// Add tokens for full seconds elapsed
|
|
||||||
tokens := int(elapsed/time.Second) * hm.rateMax
|
|
||||||
hm.rateBucket += tokens
|
|
||||||
if hm.rateBucket > hm.rateMax {
|
|
||||||
hm.rateBucket = hm.rateMax
|
|
||||||
}
|
|
||||||
hm.rateLastTick = now
|
|
||||||
}
|
|
||||||
|
|
||||||
if hm.rateBucket > 0 {
|
|
||||||
hm.rateBucket--
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (hm *HandshakeManager) Run(ctx context.Context) {
|
func (hm *HandshakeManager) Run(ctx context.Context) {
|
||||||
clockSource := time.NewTicker(hm.config.tryInterval)
|
clockSource := time.NewTicker(hm.config.tryInterval)
|
||||||
defer clockSource.Stop()
|
defer clockSource.Stop()
|
||||||
@@ -189,15 +149,6 @@ func (hm *HandshakeManager) HandleIncoming(via ViaSender, packet []byte, h *head
|
|||||||
case header.HandshakeIXPSK0:
|
case header.HandshakeIXPSK0:
|
||||||
switch h.MessageCounter {
|
switch h.MessageCounter {
|
||||||
case 1:
|
case 1:
|
||||||
// Check rate limit for new incoming handshakes
|
|
||||||
hm.Lock()
|
|
||||||
allowed := hm.handshakeRateAllow(time.Now())
|
|
||||||
hm.Unlock()
|
|
||||||
if !allowed {
|
|
||||||
hm.metricRateLimited.Inc(1)
|
|
||||||
hm.l.WithField("from", via).Debug("Handshake rate limit reached, dropping incoming handshake")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ixHandshakeStage1(hm.f, via, packet, h)
|
ixHandshakeStage1(hm.f, via, packet, h)
|
||||||
|
|
||||||
case 2:
|
case 2:
|
||||||
@@ -639,7 +590,7 @@ func (hm *HandshakeManager) allocateIndex(hh *HandshakeHostInfo) error {
|
|||||||
hm.Lock()
|
hm.Lock()
|
||||||
defer hm.Unlock()
|
defer hm.Unlock()
|
||||||
|
|
||||||
for range 32 {
|
for i := 0; i < 32; i++ {
|
||||||
index, err := generateIndex(hm.l)
|
index, err := generateIndex(hm.l)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -65,68 +65,6 @@ func Test_NewHandshakeManagerVpnIp(t *testing.T) {
|
|||||||
assert.NotContains(t, blah.vpnIps, ip)
|
assert.NotContains(t, blah.vpnIps, ip)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_HandshakeManagerRateLimit(t *testing.T) {
|
|
||||||
l := test.NewLogger()
|
|
||||||
localrange := netip.MustParsePrefix("10.1.1.1/24")
|
|
||||||
preferredRanges := []netip.Prefix{localrange}
|
|
||||||
mainHM := newHostMap(l)
|
|
||||||
mainHM.preferredRanges.Store(&preferredRanges)
|
|
||||||
|
|
||||||
lh := newTestLighthouse()
|
|
||||||
|
|
||||||
config := defaultHandshakeConfig
|
|
||||||
config.maxHandshakeRate = 2
|
|
||||||
|
|
||||||
hm := NewHandshakeManager(l, mainHM, lh, &udp.NoopConn{}, config)
|
|
||||||
hm.f = &Interface{handshakeManager: hm, pki: &PKI{}, l: l}
|
|
||||||
|
|
||||||
now := time.Now()
|
|
||||||
|
|
||||||
// Should allow up to maxHandshakeRate handshakes
|
|
||||||
hm.Lock()
|
|
||||||
assert.True(t, hm.handshakeRateAllow(now), "first handshake should be allowed")
|
|
||||||
assert.True(t, hm.handshakeRateAllow(now), "second handshake should be allowed")
|
|
||||||
assert.False(t, hm.handshakeRateAllow(now), "third handshake should be rate limited")
|
|
||||||
hm.Unlock()
|
|
||||||
|
|
||||||
// After advancing time by 1 second, tokens should refill
|
|
||||||
hm.Lock()
|
|
||||||
assert.True(t, hm.handshakeRateAllow(now.Add(time.Second)), "handshake should be allowed after token refill")
|
|
||||||
hm.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_HandshakeManagerRateLimitUnlimited(t *testing.T) {
|
|
||||||
l := test.NewLogger()
|
|
||||||
localrange := netip.MustParsePrefix("10.1.1.1/24")
|
|
||||||
preferredRanges := []netip.Prefix{localrange}
|
|
||||||
mainHM := newHostMap(l)
|
|
||||||
mainHM.preferredRanges.Store(&preferredRanges)
|
|
||||||
|
|
||||||
lh := newTestLighthouse()
|
|
||||||
|
|
||||||
cs := &CertState{
|
|
||||||
initiatingVersion: cert.Version1,
|
|
||||||
privateKey: []byte{},
|
|
||||||
v1Cert: &dummyCert{version: cert.Version1},
|
|
||||||
v1HandshakeBytes: []byte{},
|
|
||||||
}
|
|
||||||
|
|
||||||
// Default config has maxHandshakeRate=0 (unlimited)
|
|
||||||
hm := NewHandshakeManager(l, mainHM, lh, &udp.NoopConn{}, defaultHandshakeConfig)
|
|
||||||
hm.f = &Interface{handshakeManager: hm, pki: &PKI{}, l: l}
|
|
||||||
hm.f.pki.cs.Store(cs)
|
|
||||||
|
|
||||||
// Should allow many handshakes with no limit
|
|
||||||
// Limited to 10 due to test lighthouse query channel buffer
|
|
||||||
for i := 0; i < 10; i++ {
|
|
||||||
ip := netip.MustParseAddr("172.1.1.1").As16()
|
|
||||||
ip[15] = byte(i + 1)
|
|
||||||
addr := netip.AddrFrom16(ip)
|
|
||||||
h := hm.StartHandshake(addr, nil)
|
|
||||||
assert.NotNil(t, h, "handshake %d should be allowed with unlimited rate", i)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func testCountTimerWheelEntries(tw *LockingTimerWheel[netip.Addr]) (c int) {
|
func testCountTimerWheelEntries(tw *LockingTimerWheel[netip.Addr]) (c int) {
|
||||||
for _, i := range tw.t.wheel {
|
for _, i := range tw.t.wheel {
|
||||||
n := i.Head
|
n := i.Head
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
//go:build e2e_testing
|
//go:build e2e_testing
|
||||||
|
// +build e2e_testing
|
||||||
|
|
||||||
package nebula
|
package nebula
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package nebula
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"net/netip"
|
"net/netip"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/slackhq/nebula/firewall"
|
"github.com/slackhq/nebula/firewall"
|
||||||
@@ -53,7 +54,7 @@ func (f *Interface) consumeInsidePacket(packet []byte, fwPacket *firewall.Packet
|
|||||||
})
|
})
|
||||||
|
|
||||||
if hostinfo == nil {
|
if hostinfo == nil {
|
||||||
f.rejectInside(packet, out, q)
|
f.rejectInside(fwPacket, packet, out, q)
|
||||||
if f.l.Level >= logrus.DebugLevel {
|
if f.l.Level >= logrus.DebugLevel {
|
||||||
f.l.WithField("vpnAddr", fwPacket.RemoteAddr).
|
f.l.WithField("vpnAddr", fwPacket.RemoteAddr).
|
||||||
WithField("fwPacket", fwPacket).
|
WithField("fwPacket", fwPacket).
|
||||||
@@ -66,12 +67,12 @@ func (f *Interface) consumeInsidePacket(packet []byte, fwPacket *firewall.Packet
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
dropReason := f.firewall.Drop(*fwPacket, false, hostinfo, f.pki.GetCAPool(), localCache)
|
dropReason := f.firewall.Drop(*fwPacket, false, hostinfo, f.pki.GetCAPool(), localCache, nil)
|
||||||
if dropReason == nil {
|
if dropReason == nil {
|
||||||
f.sendNoMetrics(header.Message, 0, hostinfo.ConnectionState, hostinfo, netip.AddrPort{}, packet, nb, out, q)
|
f.sendNoMetrics(header.Message, 0, hostinfo.ConnectionState, hostinfo, netip.AddrPort{}, packet, nb, out, q)
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
f.rejectInside(packet, out, q)
|
f.rejectInside(fwPacket, packet, out, q)
|
||||||
if f.l.Level >= logrus.DebugLevel {
|
if f.l.Level >= logrus.DebugLevel {
|
||||||
hostinfo.logger(f.l).
|
hostinfo.logger(f.l).
|
||||||
WithField("fwPacket", fwPacket).
|
WithField("fwPacket", fwPacket).
|
||||||
@@ -81,7 +82,10 @@ func (f *Interface) consumeInsidePacket(packet []byte, fwPacket *firewall.Packet
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Interface) rejectInside(packet []byte, out []byte, q int) {
|
func (f *Interface) rejectInside(fp *firewall.Packet, packet []byte, out []byte, q int) {
|
||||||
|
if f.firewall.InLogDrop {
|
||||||
|
//todo
|
||||||
|
}
|
||||||
if !f.firewall.InSendReject {
|
if !f.firewall.InSendReject {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -97,7 +101,16 @@ func (f *Interface) rejectInside(packet []byte, out []byte, q int) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Interface) rejectOutside(packet []byte, ci *ConnectionState, hostinfo *HostInfo, nb, out []byte, q int) {
|
func (f *Interface) rejectOutside(fp *firewall.Packet, packet []byte, ci *ConnectionState, hostinfo *HostInfo, nb, out []byte, q int) {
|
||||||
|
if f.firewall.OutLogDrop {
|
||||||
|
e := firewall.Event{
|
||||||
|
Packet: *fp,
|
||||||
|
At: time.Now(),
|
||||||
|
Remote: hostinfo.remote, //todo if nil capture relay?
|
||||||
|
}
|
||||||
|
f.events <- e
|
||||||
|
}
|
||||||
|
|
||||||
if !f.firewall.OutSendReject {
|
if !f.firewall.OutSendReject {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -218,7 +231,7 @@ func (f *Interface) sendMessageNow(t header.MessageType, st header.MessageSubTyp
|
|||||||
}
|
}
|
||||||
|
|
||||||
// check if packet is in outbound fw rules
|
// check if packet is in outbound fw rules
|
||||||
dropReason := f.firewall.Drop(*fp, false, hostinfo, f.pki.GetCAPool(), nil)
|
dropReason := f.firewall.Drop(*fp, false, hostinfo, f.pki.GetCAPool(), nil, nil)
|
||||||
if dropReason != nil {
|
if dropReason != nil {
|
||||||
if f.l.Level >= logrus.DebugLevel {
|
if f.l.Level >= logrus.DebugLevel {
|
||||||
f.l.WithField("fwPacket", fp).
|
f.l.WithField("fwPacket", fp).
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
//go:build darwin || dragonfly || freebsd || netbsd || openbsd
|
//go:build darwin || dragonfly || freebsd || netbsd || openbsd
|
||||||
|
// +build darwin dragonfly freebsd netbsd openbsd
|
||||||
|
|
||||||
package nebula
|
package nebula
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
//go:build !darwin && !dragonfly && !freebsd && !netbsd && !openbsd
|
//go:build !darwin && !dragonfly && !freebsd && !netbsd && !openbsd
|
||||||
|
// +build !darwin,!dragonfly,!freebsd,!netbsd,!openbsd
|
||||||
|
|
||||||
package nebula
|
package nebula
|
||||||
|
|
||||||
|
|||||||
+3
-8
@@ -93,6 +93,8 @@ type Interface struct {
|
|||||||
messageMetrics *MessageMetrics
|
messageMetrics *MessageMetrics
|
||||||
cachedPacketMetrics *cachedPacketMetrics
|
cachedPacketMetrics *cachedPacketMetrics
|
||||||
|
|
||||||
|
events chan firewall.Event
|
||||||
|
|
||||||
l *logrus.Logger
|
l *logrus.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -194,6 +196,7 @@ func NewInterface(ctx context.Context, c *InterfaceConfig) (*Interface, error) {
|
|||||||
sent: metrics.GetOrRegisterCounter("hostinfo.cached_packets.sent", nil),
|
sent: metrics.GetOrRegisterCounter("hostinfo.cached_packets.sent", nil),
|
||||||
dropped: metrics.GetOrRegisterCounter("hostinfo.cached_packets.dropped", nil),
|
dropped: metrics.GetOrRegisterCounter("hostinfo.cached_packets.dropped", nil),
|
||||||
},
|
},
|
||||||
|
events: make(chan firewall.Event, 100), //todo configurable
|
||||||
|
|
||||||
l: c.l,
|
l: c.l,
|
||||||
}
|
}
|
||||||
@@ -490,14 +493,6 @@ func (f *Interface) Close() error {
|
|||||||
f.l.WithError(err).Error("Error while closing udp socket")
|
f.l.WithError(err).Error("Error while closing udp socket")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for i, r := range f.readers {
|
|
||||||
if i == 0 {
|
|
||||||
continue // f.readers[0] is f.inside, which we want to save for last
|
|
||||||
}
|
|
||||||
if err := r.Close(); err != nil {
|
|
||||||
f.l.WithError(err).Error("Error while closing tun reader")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Release the tun device
|
// Release the tun device
|
||||||
return f.inside.Close()
|
return f.inside.Close()
|
||||||
|
|||||||
+10
-3
@@ -713,14 +713,21 @@ func (lh *LightHouse) unlockedShouldAddV6(vpnAddr netip.Addr, to *V6AddrPort) bo
|
|||||||
|
|
||||||
func (lh *LightHouse) IsLighthouseAddr(vpnAddr netip.Addr) bool {
|
func (lh *LightHouse) IsLighthouseAddr(vpnAddr netip.Addr) bool {
|
||||||
l := lh.GetLighthouses()
|
l := lh.GetLighthouses()
|
||||||
return slices.Contains(l, vpnAddr)
|
for i := range l {
|
||||||
|
if l[i] == vpnAddr {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (lh *LightHouse) IsAnyLighthouseAddr(vpnAddrs []netip.Addr) bool {
|
func (lh *LightHouse) IsAnyLighthouseAddr(vpnAddrs []netip.Addr) bool {
|
||||||
l := lh.GetLighthouses()
|
l := lh.GetLighthouses()
|
||||||
for i := range vpnAddrs {
|
for i := range vpnAddrs {
|
||||||
if slices.Contains(l, vpnAddrs[i]) {
|
for j := range l {
|
||||||
return true
|
if l[j] == vpnAddrs[i] {
|
||||||
|
return true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
|
|||||||
+9
-8
@@ -1,6 +1,7 @@
|
|||||||
package nebula
|
package nebula
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
@@ -41,14 +42,14 @@ func Test_lhStaticMapping(t *testing.T) {
|
|||||||
c := config.NewC(l)
|
c := config.NewC(l)
|
||||||
c.Settings["lighthouse"] = map[string]any{"hosts": []any{lh1}}
|
c.Settings["lighthouse"] = map[string]any{"hosts": []any{lh1}}
|
||||||
c.Settings["static_host_map"] = map[string]any{lh1: []any{"1.1.1.1:4242"}}
|
c.Settings["static_host_map"] = map[string]any{lh1: []any{"1.1.1.1:4242"}}
|
||||||
_, err := NewLightHouseFromConfig(t.Context(), l, c, cs, nil, nil)
|
_, err := NewLightHouseFromConfig(context.Background(), l, c, cs, nil, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
lh2 := "10.128.0.3"
|
lh2 := "10.128.0.3"
|
||||||
c = config.NewC(l)
|
c = config.NewC(l)
|
||||||
c.Settings["lighthouse"] = map[string]any{"hosts": []any{lh1, lh2}}
|
c.Settings["lighthouse"] = map[string]any{"hosts": []any{lh1, lh2}}
|
||||||
c.Settings["static_host_map"] = map[string]any{lh1: []any{"100.1.1.1:4242"}}
|
c.Settings["static_host_map"] = map[string]any{lh1: []any{"100.1.1.1:4242"}}
|
||||||
_, err = NewLightHouseFromConfig(t.Context(), l, c, cs, nil, nil)
|
_, err = NewLightHouseFromConfig(context.Background(), l, c, cs, nil, nil)
|
||||||
require.EqualError(t, err, "lighthouse 10.128.0.3 does not have a static_host_map entry")
|
require.EqualError(t, err, "lighthouse 10.128.0.3 does not have a static_host_map entry")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,7 +71,7 @@ func TestReloadLighthouseInterval(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
c.Settings["static_host_map"] = map[string]any{lh1: []any{"1.1.1.1:4242"}}
|
c.Settings["static_host_map"] = map[string]any{lh1: []any{"1.1.1.1:4242"}}
|
||||||
lh, err := NewLightHouseFromConfig(t.Context(), l, c, cs, nil, nil)
|
lh, err := NewLightHouseFromConfig(context.Background(), l, c, cs, nil, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
lh.ifce = &mockEncWriter{}
|
lh.ifce = &mockEncWriter{}
|
||||||
|
|
||||||
@@ -98,7 +99,7 @@ func BenchmarkLighthouseHandleRequest(b *testing.B) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
c := config.NewC(l)
|
c := config.NewC(l)
|
||||||
lh, err := NewLightHouseFromConfig(b.Context(), l, c, cs, nil, nil)
|
lh, err := NewLightHouseFromConfig(context.Background(), l, c, cs, nil, nil)
|
||||||
require.NoError(b, err)
|
require.NoError(b, err)
|
||||||
|
|
||||||
hAddr := netip.MustParseAddrPort("4.5.6.7:12345")
|
hAddr := netip.MustParseAddrPort("4.5.6.7:12345")
|
||||||
@@ -201,7 +202,7 @@ func TestLighthouse_Memory(t *testing.T) {
|
|||||||
myVpnNetworks: []netip.Prefix{myVpnNet},
|
myVpnNetworks: []netip.Prefix{myVpnNet},
|
||||||
myVpnNetworksTable: nt,
|
myVpnNetworksTable: nt,
|
||||||
}
|
}
|
||||||
lh, err := NewLightHouseFromConfig(t.Context(), l, c, cs, nil, nil)
|
lh, err := NewLightHouseFromConfig(context.Background(), l, c, cs, nil, nil)
|
||||||
lh.ifce = &mockEncWriter{}
|
lh.ifce = &mockEncWriter{}
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
lhh := lh.NewRequestHandler()
|
lhh := lh.NewRequestHandler()
|
||||||
@@ -287,7 +288,7 @@ func TestLighthouse_reload(t *testing.T) {
|
|||||||
myVpnNetworksTable: nt,
|
myVpnNetworksTable: nt,
|
||||||
}
|
}
|
||||||
|
|
||||||
lh, err := NewLightHouseFromConfig(t.Context(), l, c, cs, nil, nil)
|
lh, err := NewLightHouseFromConfig(context.Background(), l, c, cs, nil, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
nc := map[string]any{
|
nc := map[string]any{
|
||||||
@@ -522,7 +523,7 @@ func TestLighthouse_Dont_Delete_Static_Hosts(t *testing.T) {
|
|||||||
myVpnNetworks: []netip.Prefix{myVpnNet},
|
myVpnNetworks: []netip.Prefix{myVpnNet},
|
||||||
myVpnNetworksTable: nt,
|
myVpnNetworksTable: nt,
|
||||||
}
|
}
|
||||||
lh, err := NewLightHouseFromConfig(t.Context(), l, c, cs, nil, nil)
|
lh, err := NewLightHouseFromConfig(context.Background(), l, c, cs, nil, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
lh.ifce = &mockEncWriter{}
|
lh.ifce = &mockEncWriter{}
|
||||||
|
|
||||||
@@ -588,7 +589,7 @@ func TestLighthouse_DeletesWork(t *testing.T) {
|
|||||||
myVpnNetworks: []netip.Prefix{myVpnNet},
|
myVpnNetworks: []netip.Prefix{myVpnNet},
|
||||||
myVpnNetworksTable: nt,
|
myVpnNetworksTable: nt,
|
||||||
}
|
}
|
||||||
lh, err := NewLightHouseFromConfig(t.Context(), l, c, cs, nil, nil)
|
lh, err := NewLightHouseFromConfig(context.Background(), l, c, cs, nil, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
lh.ifce = &mockEncWriter{}
|
lh.ifce = &mockEncWriter{}
|
||||||
|
|
||||||
|
|||||||
@@ -105,7 +105,11 @@ func Main(c *config.C, configTest bool, buildVersion string, logger *logrus.Logg
|
|||||||
// deprecated and undocumented
|
// deprecated and undocumented
|
||||||
tunQueues := c.GetInt("tun.routines", 1)
|
tunQueues := c.GetInt("tun.routines", 1)
|
||||||
udpQueues := c.GetInt("listen.routines", 1)
|
udpQueues := c.GetInt("listen.routines", 1)
|
||||||
routines = max(tunQueues, udpQueues)
|
if tunQueues > udpQueues {
|
||||||
|
routines = tunQueues
|
||||||
|
} else {
|
||||||
|
routines = udpQueues
|
||||||
|
}
|
||||||
if routines != 1 {
|
if routines != 1 {
|
||||||
l.WithField("routines", routines).Warn("Setting tun.routines and listen.routines is deprecated. Use `routines` instead")
|
l.WithField("routines", routines).Warn("Setting tun.routines and listen.routines is deprecated. Use `routines` instead")
|
||||||
}
|
}
|
||||||
@@ -204,11 +208,10 @@ func Main(c *config.C, configTest bool, buildVersion string, logger *logrus.Logg
|
|||||||
useRelays := c.GetBool("relay.use_relays", DefaultUseRelays) && !c.GetBool("relay.am_relay", false)
|
useRelays := c.GetBool("relay.use_relays", DefaultUseRelays) && !c.GetBool("relay.am_relay", false)
|
||||||
|
|
||||||
handshakeConfig := HandshakeConfig{
|
handshakeConfig := HandshakeConfig{
|
||||||
tryInterval: c.GetDuration("handshakes.try_interval", DefaultHandshakeTryInterval),
|
tryInterval: c.GetDuration("handshakes.try_interval", DefaultHandshakeTryInterval),
|
||||||
retries: int64(c.GetInt("handshakes.retries", DefaultHandshakeRetries)),
|
retries: int64(c.GetInt("handshakes.retries", DefaultHandshakeRetries)),
|
||||||
triggerBuffer: c.GetInt("handshakes.trigger_buffer", DefaultHandshakeTriggerBuffer),
|
triggerBuffer: c.GetInt("handshakes.trigger_buffer", DefaultHandshakeTriggerBuffer),
|
||||||
useRelays: useRelays,
|
useRelays: useRelays,
|
||||||
maxHandshakeRate: c.GetInt("handshakes.max_rate", DefaultMaxHandshakeRate),
|
|
||||||
|
|
||||||
messageMetrics: messageMetrics,
|
messageMetrics: messageMetrics,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
//go:build !boringcrypto
|
//go:build !boringcrypto
|
||||||
|
// +build !boringcrypto
|
||||||
|
|
||||||
package nebula
|
package nebula
|
||||||
|
|
||||||
|
|||||||
+21
-48
@@ -190,13 +190,6 @@ func (f *Interface) readOutsidePackets(via ViaSender, out []byte, packet []byte,
|
|||||||
if !f.handleEncrypted(ci, via, h) {
|
if !f.handleEncrypted(ci, via, h) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
_, err = f.decrypt(hostinfo, h.MessageCounter, out, packet, h, nb)
|
|
||||||
if err != nil {
|
|
||||||
hostinfo.logger(f.l).WithError(err).WithField("from", via).
|
|
||||||
WithField("packet", packet).
|
|
||||||
Error("Failed to decrypt CloseTunnel packet")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
hostinfo.logger(f.l).WithField("from", via).
|
hostinfo.logger(f.l).WithField("from", via).
|
||||||
Info("Close tunnel received, tearing down.")
|
Info("Close tunnel received, tearing down.")
|
||||||
@@ -334,29 +327,13 @@ func parseV6(data []byte, incoming bool, fp *firewall.Packet) error {
|
|||||||
proto := layers.IPProtocol(data[protoAt])
|
proto := layers.IPProtocol(data[protoAt])
|
||||||
|
|
||||||
switch proto {
|
switch proto {
|
||||||
case layers.IPProtocolESP, layers.IPProtocolNoNextHeader:
|
case layers.IPProtocolICMPv6, layers.IPProtocolESP, layers.IPProtocolNoNextHeader:
|
||||||
fp.Protocol = uint8(proto)
|
fp.Protocol = uint8(proto)
|
||||||
fp.RemotePort = 0
|
fp.RemotePort = 0
|
||||||
fp.LocalPort = 0
|
fp.LocalPort = 0
|
||||||
fp.Fragment = false
|
fp.Fragment = false
|
||||||
return nil
|
return nil
|
||||||
|
|
||||||
case layers.IPProtocolICMPv6:
|
|
||||||
if dataLen < offset+6 {
|
|
||||||
return ErrIPv6PacketTooShort
|
|
||||||
}
|
|
||||||
fp.Protocol = uint8(proto)
|
|
||||||
fp.LocalPort = 0 //incoming vs outgoing doesn't matter for icmpv6
|
|
||||||
icmptype := data[offset+1]
|
|
||||||
switch icmptype {
|
|
||||||
case layers.ICMPv6TypeEchoRequest, layers.ICMPv6TypeEchoReply:
|
|
||||||
fp.RemotePort = binary.BigEndian.Uint16(data[offset+4 : offset+6]) //identifier
|
|
||||||
default:
|
|
||||||
fp.RemotePort = 0
|
|
||||||
}
|
|
||||||
fp.Fragment = false
|
|
||||||
return nil
|
|
||||||
|
|
||||||
case layers.IPProtocolTCP, layers.IPProtocolUDP:
|
case layers.IPProtocolTCP, layers.IPProtocolUDP:
|
||||||
if dataLen < offset+4 {
|
if dataLen < offset+4 {
|
||||||
return ErrIPv6PacketTooShort
|
return ErrIPv6PacketTooShort
|
||||||
@@ -446,38 +423,34 @@ func parseV4(data []byte, incoming bool, fp *firewall.Packet) error {
|
|||||||
|
|
||||||
// Accounting for a variable header length, do we have enough data for our src/dst tuples?
|
// Accounting for a variable header length, do we have enough data for our src/dst tuples?
|
||||||
minLen := ihl
|
minLen := ihl
|
||||||
if !fp.Fragment {
|
if !fp.Fragment && fp.Protocol != firewall.ProtoICMP {
|
||||||
if fp.Protocol == firewall.ProtoICMP {
|
minLen += minFwPacketLen
|
||||||
minLen += minFwPacketLen + 2
|
|
||||||
} else {
|
|
||||||
minLen += minFwPacketLen
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(data) < minLen {
|
if len(data) < minLen {
|
||||||
return ErrIPv4InvalidHeaderLength
|
return ErrIPv4InvalidHeaderLength
|
||||||
}
|
}
|
||||||
|
|
||||||
if incoming { // Firewall packets are locally oriented
|
// Firewall packets are locally oriented
|
||||||
|
if incoming {
|
||||||
fp.RemoteAddr, _ = netip.AddrFromSlice(data[12:16])
|
fp.RemoteAddr, _ = netip.AddrFromSlice(data[12:16])
|
||||||
fp.LocalAddr, _ = netip.AddrFromSlice(data[16:20])
|
fp.LocalAddr, _ = netip.AddrFromSlice(data[16:20])
|
||||||
|
if fp.Fragment || fp.Protocol == firewall.ProtoICMP {
|
||||||
|
fp.RemotePort = 0
|
||||||
|
fp.LocalPort = 0
|
||||||
|
} else {
|
||||||
|
fp.RemotePort = binary.BigEndian.Uint16(data[ihl : ihl+2])
|
||||||
|
fp.LocalPort = binary.BigEndian.Uint16(data[ihl+2 : ihl+4])
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
fp.LocalAddr, _ = netip.AddrFromSlice(data[12:16])
|
fp.LocalAddr, _ = netip.AddrFromSlice(data[12:16])
|
||||||
fp.RemoteAddr, _ = netip.AddrFromSlice(data[16:20])
|
fp.RemoteAddr, _ = netip.AddrFromSlice(data[16:20])
|
||||||
}
|
if fp.Fragment || fp.Protocol == firewall.ProtoICMP {
|
||||||
|
fp.RemotePort = 0
|
||||||
if fp.Fragment {
|
fp.LocalPort = 0
|
||||||
fp.RemotePort = 0
|
} else {
|
||||||
fp.LocalPort = 0
|
fp.LocalPort = binary.BigEndian.Uint16(data[ihl : ihl+2])
|
||||||
} else if fp.Protocol == firewall.ProtoICMP { //note that orientation doesn't matter on ICMP
|
fp.RemotePort = binary.BigEndian.Uint16(data[ihl+2 : ihl+4])
|
||||||
fp.RemotePort = binary.BigEndian.Uint16(data[ihl+4 : ihl+6]) //identifier
|
}
|
||||||
fp.LocalPort = 0 //code would be uint16(data[ihl+1])
|
|
||||||
} else if incoming {
|
|
||||||
fp.RemotePort = binary.BigEndian.Uint16(data[ihl : ihl+2]) //src port
|
|
||||||
fp.LocalPort = binary.BigEndian.Uint16(data[ihl+2 : ihl+4]) //dst port
|
|
||||||
} else {
|
|
||||||
fp.LocalPort = binary.BigEndian.Uint16(data[ihl : ihl+2]) //src port
|
|
||||||
fp.RemotePort = binary.BigEndian.Uint16(data[ihl+2 : ihl+4]) //dst port
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -521,11 +494,11 @@ func (f *Interface) decryptToTun(hostinfo *HostInfo, messageCounter uint64, out
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
dropReason := f.firewall.Drop(*fwPacket, true, hostinfo, f.pki.GetCAPool(), localCache)
|
dropReason := f.firewall.Drop(*fwPacket, true, hostinfo, f.pki.GetCAPool(), localCache, nil)
|
||||||
if dropReason != nil {
|
if dropReason != nil {
|
||||||
// NOTE: We give `packet` as the `out` here since we already decrypted from it and we don't need it anymore
|
// NOTE: We give `packet` as the `out` here since we already decrypted from it and we don't need it anymore
|
||||||
// This gives us a buffer to build the reject packet in
|
// This gives us a buffer to build the reject packet in
|
||||||
f.rejectOutside(out, hostinfo.ConnectionState, hostinfo, nb, packet, q)
|
f.rejectOutside(fwPacket, out, hostinfo.ConnectionState, hostinfo, nb, packet, q)
|
||||||
if f.l.Level >= logrus.DebugLevel {
|
if f.l.Level >= logrus.DebugLevel {
|
||||||
hostinfo.logger(f.l).WithField("fwPacket", fwPacket).
|
hostinfo.logger(f.l).WithField("fwPacket", fwPacket).
|
||||||
WithField("reason", dropReason).
|
WithField("reason", dropReason).
|
||||||
|
|||||||
+9
-16
@@ -155,7 +155,6 @@ func Test_newPacket_v6(t *testing.T) {
|
|||||||
// next layer, missing length byte
|
// next layer, missing length byte
|
||||||
err = newPacket(buffer.Bytes()[:49], true, p)
|
err = newPacket(buffer.Bytes()[:49], true, p)
|
||||||
require.ErrorIs(t, err, ErrIPv6CouldNotFindPayload)
|
require.ErrorIs(t, err, ErrIPv6CouldNotFindPayload)
|
||||||
err = nil
|
|
||||||
|
|
||||||
// A good ICMP packet
|
// A good ICMP packet
|
||||||
ip = layers.IPv6{
|
ip = layers.IPv6{
|
||||||
@@ -166,26 +165,20 @@ func Test_newPacket_v6(t *testing.T) {
|
|||||||
DstIP: net.IPv6linklocalallnodes,
|
DstIP: net.IPv6linklocalallnodes,
|
||||||
}
|
}
|
||||||
|
|
||||||
icmp := layers.ICMPv6{
|
icmp := layers.ICMPv6{}
|
||||||
TypeCode: layers.ICMPv6TypeEchoRequest,
|
|
||||||
Checksum: 0x1234,
|
|
||||||
}
|
|
||||||
|
|
||||||
buffer.Clear()
|
buffer.Clear()
|
||||||
require.NoError(t, gopacket.SerializeLayers(buffer, opt, &ip, &icmp))
|
err = gopacket.SerializeLayers(buffer, opt, &ip, &icmp)
|
||||||
require.Error(t, newPacket(buffer.Bytes(), true, p))
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
buffer.Clear()
|
|
||||||
echo := layers.ICMPv6Echo{
|
|
||||||
Identifier: 0xabcd,
|
|
||||||
SeqNumber: 1234,
|
|
||||||
}
|
}
|
||||||
require.NoError(t, gopacket.SerializeLayers(buffer, opt, &ip, &icmp, &echo))
|
|
||||||
require.NoError(t, newPacket(buffer.Bytes(), true, p))
|
err = newPacket(buffer.Bytes(), true, p)
|
||||||
|
require.NoError(t, err)
|
||||||
assert.Equal(t, uint8(layers.IPProtocolICMPv6), p.Protocol)
|
assert.Equal(t, uint8(layers.IPProtocolICMPv6), p.Protocol)
|
||||||
assert.Equal(t, netip.MustParseAddr("ff02::2"), p.RemoteAddr)
|
assert.Equal(t, netip.MustParseAddr("ff02::2"), p.RemoteAddr)
|
||||||
assert.Equal(t, netip.MustParseAddr("ff02::1"), p.LocalAddr)
|
assert.Equal(t, netip.MustParseAddr("ff02::1"), p.LocalAddr)
|
||||||
assert.Equal(t, uint16(0xabcd), p.RemotePort)
|
assert.Equal(t, uint16(0), p.RemotePort)
|
||||||
assert.Equal(t, uint16(0), p.LocalPort)
|
assert.Equal(t, uint16(0), p.LocalPort)
|
||||||
assert.False(t, p.Fragment)
|
assert.False(t, p.Fragment)
|
||||||
|
|
||||||
@@ -581,7 +574,7 @@ func BenchmarkParseV6(b *testing.B) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
evilBytes := buffer.Bytes()
|
evilBytes := buffer.Bytes()
|
||||||
for range 200 {
|
for i := 0; i < 200; i++ {
|
||||||
evilBytes = append(evilBytes, hopHeader...)
|
evilBytes = append(evilBytes, hopHeader...)
|
||||||
}
|
}
|
||||||
evilBytes = append(evilBytes, lastHopHeader...)
|
evilBytes = append(evilBytes, lastHopHeader...)
|
||||||
|
|||||||
@@ -12,15 +12,6 @@ import (
|
|||||||
|
|
||||||
const DefaultMTU = 1300
|
const DefaultMTU = 1300
|
||||||
|
|
||||||
type NameError struct {
|
|
||||||
Name string
|
|
||||||
Underlying error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *NameError) Error() string {
|
|
||||||
return fmt.Sprintf("could not set tun device name: %s because %s", e.Name, e.Underlying)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: We may be able to remove routines
|
// TODO: We may be able to remove routines
|
||||||
type DeviceFactory func(c *config.C, l *logrus.Logger, vpnNetworks []netip.Prefix, routines int) (Device, error)
|
type DeviceFactory func(c *config.C, l *logrus.Logger, vpnNetworks []netip.Prefix, routines int) (Device, error)
|
||||||
|
|
||||||
|
|||||||
@@ -266,7 +266,7 @@ func newTun(c *config.C, l *logrus.Logger, vpnNetworks []netip.Prefix, _ bool) (
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Set the device name
|
// Set the device name
|
||||||
_ = ioctl(fd, syscall.SIOCSIFNAME, uintptr(unsafe.Pointer(&ifrr)))
|
ioctl(fd, syscall.SIOCSIFNAME, uintptr(unsafe.Pointer(&ifrr)))
|
||||||
}
|
}
|
||||||
|
|
||||||
t := &tun{
|
t := &tun{
|
||||||
|
|||||||
@@ -112,13 +112,9 @@ func newTun(c *config.C, l *logrus.Logger, vpnNetworks []netip.Prefix, multiqueu
|
|||||||
if multiqueue {
|
if multiqueue {
|
||||||
req.Flags |= unix.IFF_MULTI_QUEUE
|
req.Flags |= unix.IFF_MULTI_QUEUE
|
||||||
}
|
}
|
||||||
nameStr := c.GetString("tun.dev", "")
|
copy(req.Name[:], c.GetString("tun.dev", ""))
|
||||||
copy(req.Name[:], nameStr)
|
|
||||||
if err = ioctl(uintptr(fd), uintptr(unix.TUNSETIFF), uintptr(unsafe.Pointer(&req))); err != nil {
|
if err = ioctl(uintptr(fd), uintptr(unix.TUNSETIFF), uintptr(unsafe.Pointer(&req))); err != nil {
|
||||||
return nil, &NameError{
|
return nil, err
|
||||||
Name: nameStr,
|
|
||||||
Underlying: err,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
name := strings.Trim(string(req.Name[:]), "\x00")
|
name := strings.Trim(string(req.Name[:]), "\x00")
|
||||||
|
|
||||||
@@ -717,7 +713,6 @@ func (t *tun) Close() error {
|
|||||||
|
|
||||||
if t.ioctlFd > 0 {
|
if t.ioctlFd > 0 {
|
||||||
_ = os.NewFile(t.ioctlFd, "ioctlFd").Close()
|
_ = os.NewFile(t.ioctlFd, "ioctlFd").Close()
|
||||||
t.ioctlFd = 0
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -74,10 +74,7 @@ func newTun(c *config.C, l *logrus.Logger, vpnNetworks []netip.Prefix, _ bool) (
|
|||||||
l.WithError(err).Debug("Failed to create wintun device, retrying")
|
l.WithError(err).Debug("Failed to create wintun device, retrying")
|
||||||
tunDevice, err = wintun.CreateTUNWithRequestedGUID(deviceName, guid, t.MTU)
|
tunDevice, err = wintun.CreateTUNWithRequestedGUID(deviceName, guid, t.MTU)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, &NameError{
|
return nil, fmt.Errorf("create TUN device failed: %w", err)
|
||||||
Name: deviceName,
|
|
||||||
Underlying: fmt.Errorf("create TUN device failed: %w", err),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
t.tun = tunDevice.(*wintun.NativeTun)
|
t.tun = tunDevice.(*wintun.NativeTun)
|
||||||
|
|||||||
+1
-1
@@ -55,7 +55,7 @@ func (rm *relayManager) setAmRelay(v bool) {
|
|||||||
func AddRelay(l *logrus.Logger, relayHostInfo *HostInfo, hm *HostMap, vpnIp netip.Addr, remoteIdx *uint32, relayType int, state int) (uint32, error) {
|
func AddRelay(l *logrus.Logger, relayHostInfo *HostInfo, hm *HostMap, vpnIp netip.Addr, remoteIdx *uint32, relayType int, state int) (uint32, error) {
|
||||||
hm.Lock()
|
hm.Lock()
|
||||||
defer hm.Unlock()
|
defer hm.Unlock()
|
||||||
for range 32 {
|
for i := 0; i < 32; i++ {
|
||||||
index, err := generateIndex(l)
|
index, err := generateIndex(l)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
|
|||||||
+6
-1
@@ -404,7 +404,12 @@ func (r *RemoteList) Rebuild(preferredRanges []netip.Prefix) {
|
|||||||
|
|
||||||
// unlockedIsBad assumes you have the write lock and checks if the remote matches any entry in the blocked address list
|
// unlockedIsBad assumes you have the write lock and checks if the remote matches any entry in the blocked address list
|
||||||
func (r *RemoteList) unlockedIsBad(remote netip.AddrPort) bool {
|
func (r *RemoteList) unlockedIsBad(remote netip.AddrPort) bool {
|
||||||
return slices.Contains(r.badRemotes, remote)
|
for _, v := range r.badRemotes {
|
||||||
|
if v == remote {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// unlockedSetLearnedV4 assumes you have the write lock and sets the current learned address for this owner and marks the
|
// unlockedSetLearnedV4 assumes you have the write lock and sets the current learned address for this owner and marks the
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"maps"
|
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"os"
|
"os"
|
||||||
@@ -632,9 +631,6 @@ func sshCreateTunnel(ifce *Interface, fs any, a []string, w sshd.StringWriter) e
|
|||||||
}
|
}
|
||||||
|
|
||||||
hostInfo = ifce.handshakeManager.StartHandshake(vpnAddr, nil)
|
hostInfo = ifce.handshakeManager.StartHandshake(vpnAddr, nil)
|
||||||
if hostInfo == nil {
|
|
||||||
return w.WriteLine("Handshake rate limit reached")
|
|
||||||
}
|
|
||||||
if addr.IsValid() {
|
if addr.IsValid() {
|
||||||
hostInfo.SetRemote(addr)
|
hostInfo.SetRemote(addr)
|
||||||
}
|
}
|
||||||
@@ -835,7 +831,9 @@ func sshPrintRelays(ifce *Interface, fs any, a []string, w sshd.StringWriter) er
|
|||||||
|
|
||||||
relays := map[uint32]*HostInfo{}
|
relays := map[uint32]*HostInfo{}
|
||||||
ifce.hostMap.Lock()
|
ifce.hostMap.Lock()
|
||||||
maps.Copy(relays, ifce.hostMap.Relays)
|
for k, v := range ifce.hostMap.Relays {
|
||||||
|
relays[k] = v
|
||||||
|
}
|
||||||
ifce.hostMap.Unlock()
|
ifce.hostMap.Unlock()
|
||||||
|
|
||||||
type RelayFor struct {
|
type RelayFor struct {
|
||||||
|
|||||||
+2
-2
@@ -134,7 +134,7 @@ func TestTimerWheel_Purge(t *testing.T) {
|
|||||||
assert.True(t, tw.lastTick.After(lastTick))
|
assert.True(t, tw.lastTick.After(lastTick))
|
||||||
|
|
||||||
// Make sure we get all 4 packets back
|
// Make sure we get all 4 packets back
|
||||||
for i := range 4 {
|
for i := 0; i < 4; i++ {
|
||||||
p, has := tw.Purge()
|
p, has := tw.Purge()
|
||||||
assert.True(t, has)
|
assert.True(t, has)
|
||||||
assert.Equal(t, fps[i], p)
|
assert.Equal(t, fps[i], p)
|
||||||
@@ -149,7 +149,7 @@ func TestTimerWheel_Purge(t *testing.T) {
|
|||||||
// Make sure we cached the free'd items
|
// Make sure we cached the free'd items
|
||||||
assert.Equal(t, 4, tw.itemsCached)
|
assert.Equal(t, 4, tw.itemsCached)
|
||||||
ci := tw.itemCache
|
ci := tw.itemCache
|
||||||
for range 4 {
|
for i := 0; i < 4; i++ {
|
||||||
assert.NotNil(t, ci)
|
assert.NotNil(t, ci)
|
||||||
ci = ci.Next
|
ci = ci.Next
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user