mirror of
				https://github.com/juanfont/headscale.git
				synced 2025-10-28 10:51:44 +01:00 
			
		
		
		
	ensure renabled auto-approve routes works (#1670)
This commit is contained in:
		
							parent
							
								
									7e8bf4bfe5
								
							
						
					
					
						commit
						65376e2842
					
				
							
								
								
									
										67
									
								
								.github/workflows/test-integration-v2-TestEnableDisableAutoApprovedRoute.yaml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								.github/workflows/test-integration-v2-TestEnableDisableAutoApprovedRoute.yaml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,67 @@ | |||||||
|  | # 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 - TestEnableDisableAutoApprovedRoute | ||||||
|  | 
 | ||||||
|  | on: [pull_request] | ||||||
|  | 
 | ||||||
|  | concurrency: | ||||||
|  |   group: ${{ github.workflow }}-$${{ github.head_ref || github.run_id }} | ||||||
|  |   cancel-in-progress: true | ||||||
|  | 
 | ||||||
|  | jobs: | ||||||
|  |   TestEnableDisableAutoApprovedRoute: | ||||||
|  |     runs-on: ubuntu-latest | ||||||
|  | 
 | ||||||
|  |     steps: | ||||||
|  |       - uses: actions/checkout@v3 | ||||||
|  |         with: | ||||||
|  |           fetch-depth: 2 | ||||||
|  | 
 | ||||||
|  |       - uses: DeterminateSystems/nix-installer-action@main | ||||||
|  |       - uses: DeterminateSystems/magic-nix-cache-action@main | ||||||
|  |       - uses: satackey/action-docker-layer-caching@main | ||||||
|  |         continue-on-error: true | ||||||
|  | 
 | ||||||
|  |       - name: Get changed files | ||||||
|  |         id: changed-files | ||||||
|  |         uses: tj-actions/changed-files@v34 | ||||||
|  |         with: | ||||||
|  |           files: | | ||||||
|  |             *.nix | ||||||
|  |             go.* | ||||||
|  |             **/*.go | ||||||
|  |             integration_test/ | ||||||
|  |             config-example.yaml | ||||||
|  | 
 | ||||||
|  |       - name: Run TestEnableDisableAutoApprovedRoute | ||||||
|  |         uses: Wandalen/wretry.action@master | ||||||
|  |         if: steps.changed-files.outputs.any_changed == 'true' | ||||||
|  |         with: | ||||||
|  |           attempt_limit: 5 | ||||||
|  |           command: | | ||||||
|  |             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 run gotest.tools/gotestsum@latest -- ./... \ | ||||||
|  |                   -failfast \ | ||||||
|  |                   -timeout 120m \ | ||||||
|  |                   -parallel 1 \ | ||||||
|  |                   -run "^TestEnableDisableAutoApprovedRoute$" | ||||||
|  | 
 | ||||||
|  |       - uses: actions/upload-artifact@v3 | ||||||
|  |         if: always() && steps.changed-files.outputs.any_changed == 'true' | ||||||
|  |         with: | ||||||
|  |           name: logs | ||||||
|  |           path: "control_logs/*.log" | ||||||
|  | 
 | ||||||
|  |       - uses: actions/upload-artifact@v3 | ||||||
|  |         if: always() && steps.changed-files.outputs.any_changed == 'true' | ||||||
|  |         with: | ||||||
|  |           name: pprof | ||||||
|  |           path: "control_logs/*.pprof.tar" | ||||||
| @ -639,13 +639,19 @@ func (hsdb *HSDatabase) EnableAutoApprovedRoutes( | |||||||
| 	aclPolicy *policy.ACLPolicy, | 	aclPolicy *policy.ACLPolicy, | ||||||
| 	node *types.Node, | 	node *types.Node, | ||||||
| ) error { | ) error { | ||||||
| 	hsdb.mu.Lock() | 	if len(aclPolicy.AutoApprovers.ExitNode) == 0 && len(aclPolicy.AutoApprovers.Routes) == 0 { | ||||||
| 	defer hsdb.mu.Unlock() | 		// No autoapprovers configured
 | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
| 
 | 
 | ||||||
| 	if len(node.IPAddresses) == 0 { | 	if len(node.IPAddresses) == 0 { | ||||||
| 		return nil // This node has no IPAddresses, so can't possibly match any autoApprovers ACLs
 | 		// This node has no IPAddresses, so can't possibly match any autoApprovers ACLs
 | ||||||
|  | 		return nil | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	hsdb.mu.Lock() | ||||||
|  | 	defer hsdb.mu.Unlock() | ||||||
|  | 
 | ||||||
| 	routes, err := hsdb.getNodeAdvertisedRoutes(node) | 	routes, err := hsdb.getNodeAdvertisedRoutes(node) | ||||||
| 	if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { | 	if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { | ||||||
| 		log.Error(). | 		log.Error(). | ||||||
| @ -657,6 +663,8 @@ func (hsdb *HSDatabase) EnableAutoApprovedRoutes( | |||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	log.Trace().Interface("routes", routes).Msg("routes for autoapproving") | ||||||
|  | 
 | ||||||
| 	approvedRoutes := types.Routes{} | 	approvedRoutes := types.Routes{} | ||||||
| 
 | 
 | ||||||
| 	for _, advertisedRoute := range routes { | 	for _, advertisedRoute := range routes { | ||||||
| @ -676,6 +684,13 @@ func (hsdb *HSDatabase) EnableAutoApprovedRoutes( | |||||||
| 			return err | 			return err | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
|  | 		log.Trace(). | ||||||
|  | 			Str("node", node.Hostname). | ||||||
|  | 			Str("user", node.User.Name). | ||||||
|  | 			Strs("routeApprovers", routeApprovers). | ||||||
|  | 			Str("prefix", netip.Prefix(advertisedRoute.Prefix).String()). | ||||||
|  | 			Msg("looking up route for autoapproving") | ||||||
|  | 
 | ||||||
| 		for _, approvedAlias := range routeApprovers { | 		for _, approvedAlias := range routeApprovers { | ||||||
| 			if approvedAlias == node.User.Name { | 			if approvedAlias == node.User.Name { | ||||||
| 				approvedRoutes = append(approvedRoutes, advertisedRoute) | 				approvedRoutes = append(approvedRoutes, advertisedRoute) | ||||||
|  | |||||||
| @ -125,6 +125,14 @@ func (h *Headscale) handlePoll( | |||||||
| 
 | 
 | ||||||
| 					return | 					return | ||||||
| 				} | 				} | ||||||
|  | 
 | ||||||
|  | 				if h.ACLPolicy != nil { | ||||||
|  | 					// update routes with peer information
 | ||||||
|  | 					err = h.db.EnableAutoApprovedRoutes(h.ACLPolicy, node) | ||||||
|  | 					if err != nil { | ||||||
|  | 						logErr(err, "Error running auto approved routes") | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
| 			// Services is mostly useful for discovery and not critical,
 | 			// Services is mostly useful for discovery and not critical,
 | ||||||
|  | |||||||
| @ -10,6 +10,7 @@ import ( | |||||||
| 	"time" | 	"time" | ||||||
| 
 | 
 | ||||||
| 	v1 "github.com/juanfont/headscale/gen/go/headscale/v1" | 	v1 "github.com/juanfont/headscale/gen/go/headscale/v1" | ||||||
|  | 	"github.com/juanfont/headscale/hscontrol/policy" | ||||||
| 	"github.com/juanfont/headscale/integration/hsic" | 	"github.com/juanfont/headscale/integration/hsic" | ||||||
| 	"github.com/juanfont/headscale/integration/tsic" | 	"github.com/juanfont/headscale/integration/tsic" | ||||||
| 	"github.com/stretchr/testify/assert" | 	"github.com/stretchr/testify/assert" | ||||||
| @ -778,3 +779,145 @@ func TestHASubnetRouterFailover(t *testing.T) { | |||||||
| 		) | 		) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | func TestEnableDisableAutoApprovedRoute(t *testing.T) { | ||||||
|  | 	IntegrationSkip(t) | ||||||
|  | 	t.Parallel() | ||||||
|  | 
 | ||||||
|  | 	expectedRoutes := "172.0.0.0/24" | ||||||
|  | 
 | ||||||
|  | 	user := "enable-disable-routing" | ||||||
|  | 
 | ||||||
|  | 	scenario, err := NewScenario() | ||||||
|  | 	assertNoErrf(t, "failed to create scenario: %s", err) | ||||||
|  | 	defer scenario.Shutdown() | ||||||
|  | 
 | ||||||
|  | 	spec := map[string]int{ | ||||||
|  | 		user: 1, | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	err = scenario.CreateHeadscaleEnv(spec, []tsic.Option{tsic.WithTags([]string{"tag:approve"})}, hsic.WithTestName("clienableroute"), hsic.WithACLPolicy( | ||||||
|  | 		&policy.ACLPolicy{ | ||||||
|  | 			ACLs: []policy.ACL{ | ||||||
|  | 				{ | ||||||
|  | 					Action:       "accept", | ||||||
|  | 					Sources:      []string{"*"}, | ||||||
|  | 					Destinations: []string{"*:*"}, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 			TagOwners: map[string][]string{ | ||||||
|  | 				"tag:approve": {user}, | ||||||
|  | 			}, | ||||||
|  | 			AutoApprovers: policy.AutoApprovers{ | ||||||
|  | 				Routes: map[string][]string{ | ||||||
|  | 					expectedRoutes: {"tag:approve"}, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 	)) | ||||||
|  | 	assertNoErrHeadscaleEnv(t, err) | ||||||
|  | 
 | ||||||
|  | 	allClients, err := scenario.ListTailscaleClients() | ||||||
|  | 	assertNoErrListClients(t, err) | ||||||
|  | 
 | ||||||
|  | 	err = scenario.WaitForTailscaleSync() | ||||||
|  | 	assertNoErrSync(t, err) | ||||||
|  | 
 | ||||||
|  | 	headscale, err := scenario.Headscale() | ||||||
|  | 	assertNoErrGetHeadscale(t, err) | ||||||
|  | 
 | ||||||
|  | 	subRouter1 := allClients[0] | ||||||
|  | 
 | ||||||
|  | 	// Initially advertise route
 | ||||||
|  | 	command := []string{ | ||||||
|  | 		"tailscale", | ||||||
|  | 		"set", | ||||||
|  | 		"--advertise-routes=" + expectedRoutes, | ||||||
|  | 	} | ||||||
|  | 	_, _, err = subRouter1.Execute(command) | ||||||
|  | 	assertNoErrf(t, "failed to advertise route: %s", err) | ||||||
|  | 
 | ||||||
|  | 	time.Sleep(10 * time.Second) | ||||||
|  | 
 | ||||||
|  | 	var routes []*v1.Route | ||||||
|  | 	err = executeAndUnmarshal( | ||||||
|  | 		headscale, | ||||||
|  | 		[]string{ | ||||||
|  | 			"headscale", | ||||||
|  | 			"routes", | ||||||
|  | 			"list", | ||||||
|  | 			"--output", | ||||||
|  | 			"json", | ||||||
|  | 		}, | ||||||
|  | 		&routes, | ||||||
|  | 	) | ||||||
|  | 	assertNoErr(t, err) | ||||||
|  | 	assert.Len(t, routes, 1) | ||||||
|  | 
 | ||||||
|  | 	// All routes should be auto approved and enabled
 | ||||||
|  | 	assert.Equal(t, true, routes[0].GetAdvertised()) | ||||||
|  | 	assert.Equal(t, true, routes[0].GetEnabled()) | ||||||
|  | 	assert.Equal(t, true, routes[0].GetIsPrimary()) | ||||||
|  | 
 | ||||||
|  | 	// Stop advertising route
 | ||||||
|  | 	command = []string{ | ||||||
|  | 		"tailscale", | ||||||
|  | 		"set", | ||||||
|  | 		"--advertise-routes=", | ||||||
|  | 	} | ||||||
|  | 	_, _, err = subRouter1.Execute(command) | ||||||
|  | 	assertNoErrf(t, "failed to remove advertised route: %s", err) | ||||||
|  | 
 | ||||||
|  | 	time.Sleep(10 * time.Second) | ||||||
|  | 
 | ||||||
|  | 	var notAdvertisedRoutes []*v1.Route | ||||||
|  | 	err = executeAndUnmarshal( | ||||||
|  | 		headscale, | ||||||
|  | 		[]string{ | ||||||
|  | 			"headscale", | ||||||
|  | 			"routes", | ||||||
|  | 			"list", | ||||||
|  | 			"--output", | ||||||
|  | 			"json", | ||||||
|  | 		}, | ||||||
|  | 		¬AdvertisedRoutes, | ||||||
|  | 	) | ||||||
|  | 	assertNoErr(t, err) | ||||||
|  | 	assert.Len(t, notAdvertisedRoutes, 1) | ||||||
|  | 
 | ||||||
|  | 	// Route is no longer advertised
 | ||||||
|  | 	assert.Equal(t, false, notAdvertisedRoutes[0].GetAdvertised()) | ||||||
|  | 	assert.Equal(t, false, notAdvertisedRoutes[0].GetEnabled()) | ||||||
|  | 	assert.Equal(t, true, notAdvertisedRoutes[0].GetIsPrimary()) | ||||||
|  | 
 | ||||||
|  | 	// Advertise route again
 | ||||||
|  | 	command = []string{ | ||||||
|  | 		"tailscale", | ||||||
|  | 		"set", | ||||||
|  | 		"--advertise-routes=" + expectedRoutes, | ||||||
|  | 	} | ||||||
|  | 	_, _, err = subRouter1.Execute(command) | ||||||
|  | 	assertNoErrf(t, "failed to advertise route: %s", err) | ||||||
|  | 
 | ||||||
|  | 	time.Sleep(10 * time.Second) | ||||||
|  | 
 | ||||||
|  | 	var reAdvertisedRoutes []*v1.Route | ||||||
|  | 	err = executeAndUnmarshal( | ||||||
|  | 		headscale, | ||||||
|  | 		[]string{ | ||||||
|  | 			"headscale", | ||||||
|  | 			"routes", | ||||||
|  | 			"list", | ||||||
|  | 			"--output", | ||||||
|  | 			"json", | ||||||
|  | 		}, | ||||||
|  | 		&reAdvertisedRoutes, | ||||||
|  | 	) | ||||||
|  | 	assertNoErr(t, err) | ||||||
|  | 	assert.Len(t, reAdvertisedRoutes, 1) | ||||||
|  | 
 | ||||||
|  | 	// All routes should be auto approved and enabled
 | ||||||
|  | 	assert.Equal(t, true, reAdvertisedRoutes[0].GetAdvertised()) | ||||||
|  | 	assert.Equal(t, true, reAdvertisedRoutes[0].GetEnabled()) | ||||||
|  | 	assert.Equal(t, true, reAdvertisedRoutes[0].GetIsPrimary()) | ||||||
|  | } | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user