feat(vpn): use authenticated VPN route grants
This commit is contained in:
@@ -77,7 +77,7 @@ tap.test('DcRouter.updateVpnConfig swaps the runtime VPN resolver and restarts V
|
||||
},
|
||||
} as any;
|
||||
(dcRouter as any).routeConfigManager = {
|
||||
setVpnClientIpsResolver: (resolver: unknown) => {
|
||||
setVpnClientAccessResolver: (resolver: unknown) => {
|
||||
resolverValues.push(resolver);
|
||||
},
|
||||
applyRoutes: async () => {
|
||||
@@ -121,15 +121,15 @@ tap.test('RouteConfigManager makes vpnOnly routes fail closed without VPN client
|
||||
|
||||
const prepared = (manager as any).injectVpnSecurity(route);
|
||||
|
||||
expect(prepared.security.ipAllowList).toEqual([]);
|
||||
expect(prepared.security.ipBlockList).toContain('*');
|
||||
expect(prepared.security.ipAllowList).toEqual(['*']);
|
||||
expect(prepared.security.vpn).toEqual({ required: true, allowedClients: [] });
|
||||
});
|
||||
|
||||
tap.test('RouteConfigManager replaces public allow lists for vpnOnly routes', async () => {
|
||||
tap.test('RouteConfigManager adds VPN client grants for vpnOnly routes', async () => {
|
||||
const manager = new RouteConfigManager(
|
||||
() => undefined,
|
||||
undefined,
|
||||
() => ['10.8.0.2'],
|
||||
() => ['client-1'],
|
||||
);
|
||||
const route = {
|
||||
name: 'private-route',
|
||||
@@ -144,15 +144,16 @@ tap.test('RouteConfigManager replaces public allow lists for vpnOnly routes', as
|
||||
|
||||
const prepared = (manager as any).injectVpnSecurity(route);
|
||||
|
||||
expect(prepared.security.ipAllowList).toEqual(['10.8.0.2']);
|
||||
expect(prepared.security.ipAllowList).toEqual(['*', '203.0.113.10']);
|
||||
expect(prepared.security.ipBlockList).toEqual(['198.51.100.5']);
|
||||
expect(prepared.security.vpn).toEqual({ required: true, allowedClients: ['client-1'] });
|
||||
});
|
||||
|
||||
tap.test('RouteConfigManager adds matching VPN clients to restricted non-vpnOnly routes', async () => {
|
||||
const manager = new RouteConfigManager(
|
||||
() => undefined,
|
||||
undefined,
|
||||
() => ['10.8.0.2'],
|
||||
() => ['client-1'],
|
||||
);
|
||||
const route = {
|
||||
name: 'shared-private-route',
|
||||
@@ -166,8 +167,9 @@ tap.test('RouteConfigManager adds matching VPN clients to restricted non-vpnOnly
|
||||
|
||||
const prepared = (manager as any).injectVpnSecurity(route);
|
||||
|
||||
expect(prepared.security.ipAllowList).toEqual(['203.0.113.10', '10.8.0.2']);
|
||||
expect(prepared.security.ipAllowList).toEqual(['203.0.113.10']);
|
||||
expect(prepared.security.ipBlockList).toEqual(['198.51.100.5']);
|
||||
expect(prepared.security.vpn).toEqual({ required: undefined, allowedClients: ['client-1'] });
|
||||
});
|
||||
|
||||
tap.test('TargetProfileManager matches wildcard profiles against string route domains', async () => {
|
||||
@@ -181,17 +183,17 @@ tap.test('TargetProfileManager matches wildcard profiles against string route do
|
||||
createdBy: 'test',
|
||||
});
|
||||
|
||||
const entries = manager.getMatchingClientIps(
|
||||
const entries = manager.getMatchingVpnClients(
|
||||
{
|
||||
name: 'hagen-app',
|
||||
match: { domains: 'app.hagen.team', ports: [443] },
|
||||
action: { type: 'forward', targets: [{ host: '10.0.0.5', port: 443 }] },
|
||||
} as any,
|
||||
'route-1',
|
||||
[{ enabled: true, assignedIp: '10.8.0.2', targetProfileIds: ['profile-1'] }] as any,
|
||||
[{ clientId: 'client-1', enabled: true, assignedIp: '10.8.0.2', targetProfileIds: ['profile-1'] }] as any,
|
||||
);
|
||||
|
||||
expect(entries).toEqual(['10.8.0.2']);
|
||||
expect(entries).toEqual(['client-1']);
|
||||
});
|
||||
|
||||
tap.test('TargetProfileManager expands wildcard profile domains to matching concrete route domains', async () => {
|
||||
@@ -238,7 +240,7 @@ tap.test('TargetProfileManager allows source-IP reachable routes for opted-in pr
|
||||
createdBy: 'test',
|
||||
});
|
||||
|
||||
const entries = manager.getMatchingClientIps(
|
||||
const entries = manager.getMatchingVpnClients(
|
||||
{
|
||||
name: 'restricted-public-route',
|
||||
match: { domains: 'app.example.com', ports: [443] },
|
||||
@@ -248,13 +250,12 @@ tap.test('TargetProfileManager allows source-IP reachable routes for opted-in pr
|
||||
'route-1',
|
||||
[{ clientId: 'client-1', enabled: true, assignedIp: '10.8.0.2', targetProfileIds: ['profile-1'] }] as any,
|
||||
new Map(),
|
||||
new Map([['client-1', '203.0.113.10']]),
|
||||
);
|
||||
|
||||
expect(entries).toEqual(['10.8.0.2']);
|
||||
expect(entries).toEqual(['client-1']);
|
||||
});
|
||||
|
||||
tap.test('TargetProfileManager does not allow non-matching client source IPs', async () => {
|
||||
tap.test('TargetProfileManager leaves real source-IP enforcement to SmartProxy', async () => {
|
||||
const manager = new TargetProfileManager();
|
||||
(manager as any).profiles.set('profile-1', {
|
||||
id: 'profile-1',
|
||||
@@ -265,7 +266,7 @@ tap.test('TargetProfileManager does not allow non-matching client source IPs', a
|
||||
createdBy: 'test',
|
||||
});
|
||||
|
||||
const entries = manager.getMatchingClientIps(
|
||||
const entries = manager.getMatchingVpnClients(
|
||||
{
|
||||
name: 'restricted-public-route',
|
||||
match: { domains: 'app.example.com', ports: [443] },
|
||||
@@ -275,13 +276,12 @@ tap.test('TargetProfileManager does not allow non-matching client source IPs', a
|
||||
'route-1',
|
||||
[{ clientId: 'client-1', enabled: true, assignedIp: '10.8.0.2', targetProfileIds: ['profile-1'] }] as any,
|
||||
new Map(),
|
||||
new Map([['client-1', '198.51.100.10']]),
|
||||
);
|
||||
|
||||
expect(entries).toEqual([]);
|
||||
expect(entries).toEqual(['client-1']);
|
||||
});
|
||||
|
||||
tap.test('TargetProfileManager source-IP matching respects route block lists', async () => {
|
||||
tap.test('TargetProfileManager does not grant routes with wildcard source block', async () => {
|
||||
const manager = new TargetProfileManager();
|
||||
(manager as any).profiles.set('profile-1', {
|
||||
id: 'profile-1',
|
||||
@@ -292,20 +292,19 @@ tap.test('TargetProfileManager source-IP matching respects route block lists', a
|
||||
createdBy: 'test',
|
||||
});
|
||||
|
||||
const entries = manager.getMatchingClientIps(
|
||||
const entries = manager.getMatchingVpnClients(
|
||||
{
|
||||
name: 'blocked-route',
|
||||
match: { domains: 'app.example.com', ports: [443] },
|
||||
action: { type: 'forward', targets: [{ host: '10.0.0.5', port: 443 }] },
|
||||
security: {
|
||||
ipAllowList: ['203.0.113.0/24'],
|
||||
ipBlockList: ['203.0.113.10'],
|
||||
ipBlockList: ['*'],
|
||||
},
|
||||
} as any,
|
||||
'route-1',
|
||||
[{ clientId: 'client-1', enabled: true, assignedIp: '10.8.0.2', targetProfileIds: ['profile-1'] }] as any,
|
||||
new Map(),
|
||||
new Map([['client-1', '203.0.113.10']]),
|
||||
);
|
||||
|
||||
expect(entries).toEqual([]);
|
||||
@@ -322,7 +321,7 @@ tap.test('TargetProfileManager treats public non-vpnOnly routes as source-IP rea
|
||||
createdBy: 'test',
|
||||
});
|
||||
|
||||
const entries = manager.getMatchingClientIps(
|
||||
const entries = manager.getMatchingVpnClients(
|
||||
{
|
||||
name: 'public-route',
|
||||
match: { domains: 'public.example.com', ports: [443] },
|
||||
@@ -331,13 +330,12 @@ tap.test('TargetProfileManager treats public non-vpnOnly routes as source-IP rea
|
||||
'route-1',
|
||||
[{ clientId: 'client-1', enabled: true, assignedIp: '10.8.0.2', targetProfileIds: ['profile-1'] }] as any,
|
||||
new Map(),
|
||||
new Map([['client-1', '203.0.113.10']]),
|
||||
);
|
||||
|
||||
expect(entries).toEqual(['10.8.0.2']);
|
||||
expect(entries).toEqual(['client-1']);
|
||||
});
|
||||
|
||||
tap.test('TargetProfileManager does not grant vpnOnly routes through source-IP matching alone', async () => {
|
||||
tap.test('TargetProfileManager grants vpnOnly routes through source-policy profiles', async () => {
|
||||
const manager = new TargetProfileManager();
|
||||
(manager as any).profiles.set('profile-1', {
|
||||
id: 'profile-1',
|
||||
@@ -348,7 +346,7 @@ tap.test('TargetProfileManager does not grant vpnOnly routes through source-IP m
|
||||
createdBy: 'test',
|
||||
});
|
||||
|
||||
const entries = manager.getMatchingClientIps(
|
||||
const entries = manager.getMatchingVpnClients(
|
||||
{
|
||||
name: 'vpn-only-route',
|
||||
vpnOnly: true,
|
||||
@@ -359,10 +357,9 @@ tap.test('TargetProfileManager does not grant vpnOnly routes through source-IP m
|
||||
'route-1',
|
||||
[{ clientId: 'client-1', enabled: true, assignedIp: '10.8.0.2', targetProfileIds: ['profile-1'] }] as any,
|
||||
new Map(),
|
||||
new Map([['client-1', '203.0.113.10']]),
|
||||
);
|
||||
|
||||
expect(entries).toEqual([]);
|
||||
expect(entries).toEqual(['client-1']);
|
||||
});
|
||||
|
||||
tap.test('TargetProfileManager includes source-IP reachable route domains in client access specs', async () => {
|
||||
@@ -393,7 +390,7 @@ tap.test('TargetProfileManager includes source-IP reachable route domains in cli
|
||||
}],
|
||||
]) as any;
|
||||
|
||||
const accessSpec = manager.getClientAccessSpec(['profile-1'], routes, '203.0.113.10');
|
||||
const accessSpec = manager.getClientAccessSpec(['profile-1'], routes);
|
||||
|
||||
expect(accessSpec.domains).toContain('app.example.com');
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user