Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix issue where ACL * would filter out returning connections #1279

Merged
merged 3 commits into from
Mar 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions .github/workflows/test-integration-v2-TestACLAllowStarDst.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# DO NOT EDIT, generated with cmd/gh-action-integration-generator/main.go
# To regenerate, run "go generate" in cmd/gh-action-integration-generator/

name: Integration Test v2 - TestACLAllowStarDst

on: [pull_request]

concurrency:
group: ${{ github.workflow }}-$${{ github.head_ref || github.run_id }}
cancel-in-progress: true

jobs:
test:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3
with:
fetch-depth: 2

- name: Get changed files
id: changed-files
uses: tj-actions/changed-files@v34
with:
files: |
*.nix
go.*
**/*.go
integration_test/
config-example.yaml

- uses: cachix/install-nix-action@v18
if: ${{ env.ACT }} || steps.changed-files.outputs.any_changed == 'true'

- name: Run general integration tests
if: steps.changed-files.outputs.any_changed == 'true'
run: |
nix develop --command -- docker run \
--tty --rm \
--volume ~/.cache/hs-integration-go:/go \
--name headscale-test-suite \
--volume $PWD:$PWD -w $PWD/integration \
--volume /var/run/docker.sock:/var/run/docker.sock \
--volume $PWD/control_logs:/tmp/control \
golang:1 \
go test ./... \
-tags ts2019 \
-failfast \
-timeout 120m \
-parallel 1 \
-run "^TestACLAllowStarDst$"

- uses: actions/upload-artifact@v3
if: always() && steps.changed-files.outputs.any_changed == 'true'
with:
name: logs
path: "control_logs/*.log"
57 changes: 57 additions & 0 deletions .github/workflows/test-integration-v2-TestACLAllowUserDst.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# DO NOT EDIT, generated with cmd/gh-action-integration-generator/main.go
# To regenerate, run "go generate" in cmd/gh-action-integration-generator/

name: Integration Test v2 - TestACLAllowUserDst

on: [pull_request]

concurrency:
group: ${{ github.workflow }}-$${{ github.head_ref || github.run_id }}
cancel-in-progress: true

jobs:
test:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3
with:
fetch-depth: 2

- name: Get changed files
id: changed-files
uses: tj-actions/changed-files@v34
with:
files: |
*.nix
go.*
**/*.go
integration_test/
config-example.yaml

- uses: cachix/install-nix-action@v18
if: ${{ env.ACT }} || steps.changed-files.outputs.any_changed == 'true'

- name: Run general integration tests
if: steps.changed-files.outputs.any_changed == 'true'
run: |
nix develop --command -- docker run \
--tty --rm \
--volume ~/.cache/hs-integration-go:/go \
--name headscale-test-suite \
--volume $PWD:$PWD -w $PWD/integration \
--volume /var/run/docker.sock:/var/run/docker.sock \
--volume $PWD/control_logs:/tmp/control \
golang:1 \
go test ./... \
-tags ts2019 \
-failfast \
-timeout 120m \
-parallel 1 \
-run "^TestACLAllowUserDst$"

- uses: actions/upload-artifact@v3
if: always() && steps.changed-files.outputs.any_changed == 'true'
with:
name: logs
path: "control_logs/*.log"
57 changes: 57 additions & 0 deletions .github/workflows/test-integration-v2-TestACLDenyAllPort80.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# DO NOT EDIT, generated with cmd/gh-action-integration-generator/main.go
# To regenerate, run "go generate" in cmd/gh-action-integration-generator/

name: Integration Test v2 - TestACLDenyAllPort80

on: [pull_request]

concurrency:
group: ${{ github.workflow }}-$${{ github.head_ref || github.run_id }}
cancel-in-progress: true

jobs:
test:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3
with:
fetch-depth: 2

- name: Get changed files
id: changed-files
uses: tj-actions/changed-files@v34
with:
files: |
*.nix
go.*
**/*.go
integration_test/
config-example.yaml

- uses: cachix/install-nix-action@v18
if: ${{ env.ACT }} || steps.changed-files.outputs.any_changed == 'true'

- name: Run general integration tests
if: steps.changed-files.outputs.any_changed == 'true'
run: |
nix develop --command -- docker run \
--tty --rm \
--volume ~/.cache/hs-integration-go:/go \
--name headscale-test-suite \
--volume $PWD:$PWD -w $PWD/integration \
--volume /var/run/docker.sock:/var/run/docker.sock \
--volume $PWD/control_logs:/tmp/control \
golang:1 \
go test ./... \
-tags ts2019 \
-failfast \
-timeout 120m \
-parallel 1 \
-run "^TestACLDenyAllPort80$"

- uses: actions/upload-artifact@v3
if: always() && steps.changed-files.outputs.any_changed == 'true'
with:
name: logs
path: "control_logs/*.log"
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

### Changes

- Fix longstanding bug that would prevent "\*" from working properly in ACLs (issue [#699](https://github.com/juanfont/headscale/issues/699)) [#1279](https://github.com/juanfont/headscale/pull/1279)

## 0.21.0 (2023-03-20)

### Changes
Expand Down
2 changes: 2 additions & 0 deletions acls.go
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,8 @@ func generateACLPeerCacheMap(rules []tailcfg.FilterRule) map[string]map[string]s
}
}

log.Trace().Interface("ACL Cache Map", aclCachePeerMap).Msg("ACL Peer Cache Map generated")

return aclCachePeerMap
}

Expand Down
163 changes: 162 additions & 1 deletion integration/acl_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package integration

import (
"fmt"
"strings"
"testing"

"github.com/juanfont/headscale"
Expand Down Expand Up @@ -32,7 +33,7 @@ func aclScenario(t *testing.T, policy headscale.ACLPolicy) *Scenario {
tsic.WithDockerWorkdir("/"),
},
hsic.WithACLPolicy(&policy),
hsic.WithTestName("acldenyallping"),
hsic.WithTestName("acl"),
)
assert.NoError(t, err)

Expand Down Expand Up @@ -278,3 +279,163 @@ func TestACLAllowUser80Dst(t *testing.T) {
err = scenario.Shutdown()
assert.NoError(t, err)
}

func TestACLDenyAllPort80(t *testing.T) {
IntegrationSkip(t)

scenario := aclScenario(t,
headscale.ACLPolicy{
Groups: map[string][]string{
"group:integration-acl-test": {"user1", "user2"},
},
ACLs: []headscale.ACL{
{
Action: "accept",
Sources: []string{"group:integration-acl-test"},
Destinations: []string{"*:22"},
},
},
},
)

allClients, err := scenario.ListTailscaleClients()
assert.NoError(t, err)

allHostnames, err := scenario.ListTailscaleClientsFQDNs()
assert.NoError(t, err)

for _, client := range allClients {
for _, hostname := range allHostnames {
// We will always be allowed to check _self_ so shortcircuit
// the test here.
if strings.Contains(hostname, client.Hostname()) {
continue
}

url := fmt.Sprintf("http://%s/etc/hostname", hostname)
t.Logf("url from %s to %s", client.Hostname(), url)

result, err := client.Curl(url)
assert.Empty(t, result)
assert.Error(t, err)
}
}

err = scenario.Shutdown()
assert.NoError(t, err)
}

// Test to confirm that we can use user:* from one user.
// This ACL will not allow user1 access its own machines.
// Reported: https://github.com/juanfont/headscale/issues/699
func TestACLAllowUserDst(t *testing.T) {
IntegrationSkip(t)

scenario := aclScenario(t,
headscale.ACLPolicy{
ACLs: []headscale.ACL{
{
Action: "accept",
Sources: []string{"user1"},
Destinations: []string{"user2:*"},
},
},
},
)

user1Clients, err := scenario.ListTailscaleClients("user1")
assert.NoError(t, err)

user2Clients, err := scenario.ListTailscaleClients("user2")
assert.NoError(t, err)

// Test that user1 can visit all user2
for _, client := range user1Clients {
for _, peer := range user2Clients {
fqdn, err := peer.FQDN()
assert.NoError(t, err)

url := fmt.Sprintf("http://%s/etc/hostname", fqdn)
t.Logf("url from %s to %s", client.Hostname(), url)

result, err := client.Curl(url)
assert.Len(t, result, 13)
assert.NoError(t, err)
}
}

// Test that user2 _cannot_ visit user1
for _, client := range user2Clients {
for _, peer := range user1Clients {
fqdn, err := peer.FQDN()
assert.NoError(t, err)

url := fmt.Sprintf("http://%s/etc/hostname", fqdn)
t.Logf("url from %s to %s", client.Hostname(), url)

result, err := client.Curl(url)
assert.Empty(t, result)
assert.Error(t, err)
}
}

err = scenario.Shutdown()
assert.NoError(t, err)
}

// Test to confirm that we can use *:* from one user
// Reported: https://github.com/juanfont/headscale/issues/699
func TestACLAllowStarDst(t *testing.T) {
IntegrationSkip(t)

scenario := aclScenario(t,
headscale.ACLPolicy{
ACLs: []headscale.ACL{
{
Action: "accept",
Sources: []string{"user1"},
Destinations: []string{"*:*"},
},
},
},
)

user1Clients, err := scenario.ListTailscaleClients("user1")
assert.NoError(t, err)

user2Clients, err := scenario.ListTailscaleClients("user2")
assert.NoError(t, err)

// Test that user1 can visit all user2
for _, client := range user1Clients {
for _, peer := range user2Clients {
fqdn, err := peer.FQDN()
assert.NoError(t, err)

url := fmt.Sprintf("http://%s/etc/hostname", fqdn)
t.Logf("url from %s to %s", client.Hostname(), url)

result, err := client.Curl(url)
assert.Len(t, result, 13)
assert.NoError(t, err)
}
}

// Test that user2 _cannot_ visit user1
for _, client := range user2Clients {
for _, peer := range user1Clients {
fqdn, err := peer.FQDN()
assert.NoError(t, err)

url := fmt.Sprintf("http://%s/etc/hostname", fqdn)
t.Logf("url from %s to %s", client.Hostname(), url)

result, err := client.Curl(url)
assert.Empty(t, result)
assert.Error(t, err)
}
}

err = scenario.Shutdown()
assert.NoError(t, err)
}
6 changes: 6 additions & 0 deletions machine.go
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,12 @@ func filterMachinesByACL(

for _, peerIP := range peerIPs {
if dstMap, ok := aclPeerCacheMap[peerIP]; ok {
// match source and all destination
if _, dstOk := dstMap["*"]; dstOk {
peers[peer.ID] = peer

continue
}
// match return path
for _, machineIP := range machineIPs {
if _, dstOk := dstMap[machineIP]; dstOk {
Expand Down
Loading