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, | ||||
| 	node *types.Node, | ||||
| ) error { | ||||
| 	hsdb.mu.Lock() | ||||
| 	defer hsdb.mu.Unlock() | ||||
| 	if len(aclPolicy.AutoApprovers.ExitNode) == 0 && len(aclPolicy.AutoApprovers.Routes) == 0 { | ||||
| 		// No autoapprovers configured
 | ||||
| 		return nil | ||||
| 	} | ||||
| 
 | ||||
| 	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) | ||||
| 	if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { | ||||
| 		log.Error(). | ||||
| @ -657,6 +663,8 @@ func (hsdb *HSDatabase) EnableAutoApprovedRoutes( | ||||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
| 	log.Trace().Interface("routes", routes).Msg("routes for autoapproving") | ||||
| 
 | ||||
| 	approvedRoutes := types.Routes{} | ||||
| 
 | ||||
| 	for _, advertisedRoute := range routes { | ||||
| @ -676,6 +684,13 @@ func (hsdb *HSDatabase) EnableAutoApprovedRoutes( | ||||
| 			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 { | ||||
| 			if approvedAlias == node.User.Name { | ||||
| 				approvedRoutes = append(approvedRoutes, advertisedRoute) | ||||
|  | ||||
| @ -125,6 +125,14 @@ func (h *Headscale) handlePoll( | ||||
| 
 | ||||
| 					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,
 | ||||
|  | ||||
| @ -10,6 +10,7 @@ import ( | ||||
| 	"time" | ||||
| 
 | ||||
| 	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/tsic" | ||||
| 	"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