Skip to content

Commit

Permalink
feat: Support 4via6 subnet routing (#10)
Browse files Browse the repository at this point in the history
* feat: Support 4via6 subnet routing

* chore: self mutation

Signed-off-by: github-actions <[email protected]>

* Adjust docs

* chore: self mutation

Signed-off-by: github-actions <[email protected]>

Signed-off-by: github-actions <[email protected]>
Co-authored-by: github-actions <[email protected]>
  • Loading branch information
Hawxy and github-actions authored Dec 3, 2022
1 parent 7ba3bfa commit e6226b4
Show file tree
Hide file tree
Showing 5 changed files with 66 additions and 20 deletions.
13 changes: 13 additions & 0 deletions API.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 16 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ The Tailscale Auth key should be passed in via secrets manager and NOT hardcoded
import { TailscaleBastion } from 'cdk-tailscale-bastion';

// Secrets Manager
const secret = Secret.fromSecretNameV2(stack, 'ApiSecrets', 'tailscale').secretValueFromJson('AUTH_KEY');
const secret = Secret.fromSecretNameV2(stack, 'ApiSecrets', 'tailscale');

const bastion = new TailscaleBastion(stack, 'Sample-Bastion', {
vpc,
Expand Down Expand Up @@ -51,14 +51,27 @@ You'll also need to setup the nameserver. The bastion construct conveniently out

Given your configuration is correct, a direct connection to your internal resources should now be possible.


## 4via6 Support

If you wish to use [4via6 subnet routers](https://tailscale.com/kb/1201/4via6-subnets/), you can pass the IPv6 address via the `advertiseRoute` property:

```ts
new TailscaleBastion(stack, 'Cdk-Sample-Lib', {
vpc,
tailscaleCredentials: ...,
advertiseRoute: 'fd7a:115c:a1e0:b1a:0:7:a01:100/120',
});
```

## Incoming routes

If you have other subnet routers configured in Tailscale, you can use the `incomingRoutes` property to configure VPC route table entries for all private subnets.

```
```ts
new TailscaleBastion(stack, 'Sample-Bastion', {
vpc,
tailscaleCredentials: ...
tailscaleCredentials: ...,
incomingRoutes: [
'192.168.1.0/24',
],
Expand Down
19 changes: 14 additions & 5 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { CfnOutput, Fn } from 'aws-cdk-lib';
import { CfnOutput, Fn, Stack, Token } from 'aws-cdk-lib';
import { BastionHostLinux, CloudFormationInit, InitCommand, ISecurityGroup, Peer, Port, SubnetSelection, Vpc, InstanceType, SubnetType, InitElement, CfnRoute } from 'aws-cdk-lib/aws-ec2';
import { ISecret } from 'aws-cdk-lib/aws-secretsmanager';
import { Construct } from 'constructs';
Expand Down Expand Up @@ -78,6 +78,10 @@ export interface TailscaleBastionProps {
* @default none
*/
readonly incomingRoutes?: string[];
/**
* Advertise a custom route instead of using the VPC CIDR, used for Tailscale 4via6 support.
*/
readonly advertiseRoute?: string;
}

export class TailscaleBastion extends Construct {
Expand All @@ -95,6 +99,7 @@ export class TailscaleBastion extends Construct {
instanceType,
additionalInit,
incomingRoutes,
advertiseRoute,
} = props;

const authKeyCommand = this.computeTsKeyCli(tailscaleCredentials);
Expand All @@ -115,14 +120,14 @@ export class TailscaleBastion extends Construct {
InitCommand.shellCommand('yum -y install jq'),
InitCommand.shellCommand('systemctl enable --now tailscaled'),
InitCommand.shellCommand(`echo TS_AUTHKEY=${authKeyCommand} >> /etc/environment`),
InitCommand.shellCommand(`source /etc/environment && tailscale up --authkey $TS_AUTHKEY --advertise-routes=${props.vpc.vpcCidrBlock} --accept-routes --accept-dns=false`),
InitCommand.shellCommand(`source /etc/environment && tailscale up --authkey $TS_AUTHKEY --advertise-routes=${advertiseRoute ?? vpc.vpcCidrBlock} --accept-routes --accept-dns=false`),
...(additionalInit ?? []),
),
initOptions: {},
});

if (props.tailscaleCredentials.secretsManager) {
props.tailscaleCredentials.secretsManager.secret.grantRead(bastion);
if (tailscaleCredentials.secretsManager) {
tailscaleCredentials.secretsManager.secret.grantRead(bastion);
}

bastion.connections.allowFromAnyIpv4(Port.udp(41641));
Expand All @@ -133,7 +138,11 @@ export class TailscaleBastion extends Construct {
const dnsServer = `${splitIp[0]}.${splitIp[1]}.${splitIp[2]}.2`;

new CfnOutput(this, 'Vpc-Dns-Nameserver', { value: dnsServer });
new CfnOutput(this, 'Vpc-Dns-Domain', { value: 'compute.internal' });

const stack = Stack.of(this);
const domain = Token.isUnresolved(stack.region) ? 'compute.internal' : `${stack.region}.compute.internal`;

new CfnOutput(this, 'Vpc-Dns-Domain', { value: domain });

for (const incomingRoute of incomingRoutes ?? []) {
for (const subnet of vpc.privateSubnets) {
Expand Down
20 changes: 8 additions & 12 deletions test/construct.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import { Secret } from 'aws-cdk-lib/aws-secretsmanager';
import { TailscaleBastion } from '../src';

const mockApp = new App();

const stack = new Stack(mockApp, 'MyStack');
const env = { region: 'ap-southeast-2' };
const stack = new Stack(mockApp, 'MyStack', { env });

const vpc = new Vpc(stack, 'MyVpc');

Expand Down Expand Up @@ -72,19 +72,11 @@ test('Bastion host should be created', () => {
'Fn::Join': [
'',
[
'echo TS_AUTHKEY=$(aws secretsmanager get-secret-value --region ',
{
Ref: 'AWS::Region',
},
' --secret-id arn:',
'echo TS_AUTHKEY=$(aws secretsmanager get-secret-value --region ap-southeast-2 --secret-id arn:',
{
Ref: 'AWS::Partition',
},
':secretsmanager:',
{
Ref: 'AWS::Region',
},
':',
':secretsmanager:ap-southeast-2:',
{
Ref: 'AWS::AccountId',
},
Expand Down Expand Up @@ -115,6 +107,10 @@ test('Bastion host should be created', () => {
},
},
});

template.hasOutput('*', {
Value: 'ap-southeast-2.compute.internal',
});
});


15 changes: 15 additions & 0 deletions test/routes.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ const bastion = new TailscaleBastion(stack, 'Test-Bastion', {
incomingRoutes: [
'192.168.1.0/24',
],
advertiseRoute: 'fd7a:115c:a1e0:b1a:0:7:a01:100/120',
});

secret.grantRead(bastion.bastion);
Expand All @@ -43,6 +44,20 @@ test('Bastion host should have routing set up', () => {
},
});

template.hasResource('AWS::EC2::Instance', {
Metadata: {
'AWS::CloudFormation::Init': {
config: {
commands: {
'008': {
command: 'source /etc/environment && tailscale up --authkey $TS_AUTHKEY --advertise-routes=fd7a:115c:a1e0:b1a:0:7:a01:100/120 --accept-routes --accept-dns=false',
},
},
},
},
},
});

});


0 comments on commit e6226b4

Please sign in to comment.