Skip to content

Commit

Permalink
Allow Proxy/Bastion host (#269)
Browse files Browse the repository at this point in the history
Rebase of #192.

---------

Co-authored-by: Marc Ende <me@e-beyond.de>
  • Loading branch information
iwahbe and eBeyond authored Sep 19, 2023
1 parent f748061 commit 18d9626
Show file tree
Hide file tree
Showing 28 changed files with 2,040 additions and 86 deletions.
3 changes: 3 additions & 0 deletions examples/ec2_remote_proxy/Pulumi.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
name: command-ec2-remote
runtime: nodejs
description: A simple command example
112 changes: 112 additions & 0 deletions examples/ec2_remote_proxy/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import { interpolate, Config, secret } from "@pulumi/pulumi";
import { local, remote, types } from "@pulumi/command";
import * as aws from "@pulumi/aws";
import * as fs from "fs";
import * as os from "os";
import * as path from "path";
import { size } from "./size";

const config = new Config();
const keyName = config.get("keyName") ?? new aws.ec2.KeyPair("key", { publicKey: config.require("publicKey") }).keyName;
const privateKeyBase64 = config.get("privateKeyBase64");
const privateKey = privateKeyBase64 ? Buffer.from(privateKeyBase64, 'base64').toString('ascii') : fs.readFileSync(path.join(os.homedir(), ".ssh", "id_rsa")).toString("utf8");

const ingress = new aws.ec2.SecurityGroup("ingress", {
description: "A security group that will accept SSH connections from the outside world.",
ingress: [
{ protocol: "tcp", fromPort: 22, toPort: 22, cidrBlocks: ["0.0.0.0/0"] },
{ protocol: "tcp", fromPort: 80, toPort: 80, cidrBlocks: ["0.0.0.0/0"] },
],
egress: [
{ fromPort: 0, toPort: 0, protocol: "-1", cidrBlocks: ["0.0.0.0/0"], ipv6CidrBlocks: ["::/0"], },
],
});

const validated = new aws.ec2.SecurityGroup("validated", {
description: "A security group that will only accept connections that have already been validated.",
ingress: [
{ protocol: "tcp", fromPort: 22, toPort: 22, securityGroups: [ingress.id] },
{ protocol: "tcp", fromPort: 80, toPort: 80, securityGroups: [ingress.id] },
],
egress: [
{ fromPort: 0, toPort: 0, protocol: "-1", cidrBlocks: ["0.0.0.0/0"], ipv6CidrBlocks: ["::/0"], },
],
});

const ami = aws.ec2.getAmiOutput({
owners: ["amazon"],
mostRecent: true,
filters: [{
name: "name",
values: ["amzn-ami-hvm-*-x86_64-gp2"],
}],
});

const server = new aws.ec2.Instance("server", {
instanceType: size,
ami: ami.id,
keyName: keyName,
vpcSecurityGroupIds: [validated.id],
}, { replaceOnChanges: ["instanceType"] });

const proxy = new aws.ec2.Instance("proxy", {
instanceType: size,
ami: ami.id,
keyName: keyName,
vpcSecurityGroupIds: [ingress.id],
}, { replaceOnChanges: ["instanceType"] });

const connection: types.input.remote.ConnectionArgs = {
host: server.privateDns,
user: "ec2-user",
privateKey: privateKey,
proxy: {
host: proxy.publicIp,
user: "ec2-user",
privateKey: privateKey,
},
};

const hostname = new remote.Command("hostname", {
connection: {
...connection,
dialErrorLimit: -1,
proxy: {
...connection.proxy,
dialErrorLimit: -1,
}
},
create: "hostname",
environment: secret({
"secret-key": secret("super-secret-value")
}),
}, { customTimeouts: { create: "10m" } });

new remote.Command("remotePrivateIP", {
connection,
create: interpolate`echo ${server.privateIp} > private_ip.txt`,
delete: `rm private_ip.txt`,
}, { deleteBeforeReplace: true, dependsOn: hostname });

new local.Command("localPrivateIP", {
create: interpolate`echo ${server.privateIp} > private_ip.txt`,
delete: `rm private_ip.txt`,
}, { deleteBeforeReplace: true, dependsOn: hostname });

const sizeFile = new remote.CopyFile("size", {
connection: connection,
localPath: "./size.ts",
remotePath: "size.ts",
}, { dependsOn: hostname })

const catSize = new remote.Command("checkSize", {
connection: connection,
create: "cat size.ts",
}, { dependsOn: sizeFile })

export const connectionSecret = hostname.connection;
export const secretEnv = hostname.environment;
export const confirmSize = catSize.stdout;
export const publicIp = server.publicIp;
export const publicHostName = server.publicDns;
export const hostnameStdout = hostname.stdout;
12 changes: 12 additions & 0 deletions examples/ec2_remote_proxy/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"name": "command-typescript",
"version": "0.1.0",
"devDependencies": {
"@types/node": "latest"
},
"dependencies": {
"@pulumi/aws": "^4.18.0",
"@pulumi/pulumi": "latest",
"@pulumi/random": "^4.2.0"
}
}
1 change: 1 addition & 0 deletions examples/ec2_remote_proxy/replace_instance/size.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const size = "t2.micro";
1 change: 1 addition & 0 deletions examples/ec2_remote_proxy/size.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const size = "t2.nano";
10 changes: 7 additions & 3 deletions examples/examples_nodejs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ func TestSimpleWithUpdate(t *testing.T) {
integration.ProgramTest(t, &test)
}

func TestEc2RemoteTs(t *testing.T) {
func testEc2Ts(t *testing.T, targetDir string) {
sess, err := session.NewSession(&aws.Config{
Region: aws.String(getRegion(t))},
)
Expand All @@ -186,15 +186,15 @@ func TestEc2RemoteTs(t *testing.T) {
}()
test := getJSBaseOptions(t).
With(integration.ProgramTestOptions{
Dir: filepath.Join(getCwd(t), "ec2_remote"),
Dir: filepath.Join(getCwd(t), targetDir),
Config: map[string]string{
"keyName": aws.StringValue(key.KeyName),
},
Secrets: map[string]string{
"privateKeyBase64": base64.StdEncoding.EncodeToString([]byte(aws.StringValue(key.KeyMaterial))),
},
EditDirs: []integration.EditDir{{
Dir: filepath.Join("ec2_remote", "replace_instance"),
Dir: filepath.Join(targetDir, "replace_instance"),
Additive: true,
}},
ExtraRuntimeValidation: func(t *testing.T, stack integration.RuntimeValidationStackInfo) {
Expand Down Expand Up @@ -237,6 +237,10 @@ func TestEc2RemoteTs(t *testing.T) {
integration.ProgramTest(t, &test)
}

func TestEc2RemoteTs(t *testing.T) { testEc2Ts(t, "ec2_remote") }

func TestEc2RemoteProxyTs(t *testing.T) { testEc2Ts(t, "ec2_remote_proxy") }

func TestLambdaInvoke(t *testing.T) {
test := getJSBaseOptions(t).
With(integration.ProgramTestOptions{
Expand Down
53 changes: 53 additions & 0 deletions provider/cmd/pulumi-resource-command/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -505,6 +505,10 @@
"description": "The password to use in case the private key is encrypted.",
"type": "string"
},
"proxy": {
"$ref": "#/types/command:remote:ProxyConnection",
"description": "The connection settings for the bastion/proxy host."
},
"user": {
"default": "root",
"description": "The user that we should use for the connection.",
Expand All @@ -515,6 +519,55 @@
"host"
],
"type": "object"
},
"command:remote:ProxyConnection": {
"description": "Instructions for how to connect to a remote endpoint via a bastion host.",
"properties": {
"agentSocketPath": {
"description": "SSH Agent socket path. Default to environment variable SSH_AUTH_SOCK if present.",
"type": "string"
},
"dialErrorLimit": {
"default": 10,
"description": "Max allowed errors on trying to dial the remote host. -1 set count to unlimited. Default value is 10.",
"type": "integer"
},
"host": {
"description": "The address of the bastion host to connect to.",
"type": "string"
},
"password": {
"description": "The password we should use for the connection to the bastion host.",
"type": "string"
},
"perDialTimeout": {
"default": 15,
"description": "Max number of seconds for each dial attempt. 0 implies no maximum. Default value is 15 seconds.",
"type": "integer"
},
"port": {
"default": 22,
"description": "The port of the bastion host to connect to.",
"type": "number"
},
"privateKey": {
"description": "The contents of an SSH key to use for the connection. This takes preference over the password if provided.",
"type": "string"
},
"privateKeyPassword": {
"description": "The password to use in case the private key is encrypted.",
"type": "string"
},
"user": {
"default": "root",
"description": "The user that we should use for the connection to the bastion host.",
"type": "string"
}
},
"required": [
"host"
],
"type": "object"
}
}
}
10 changes: 5 additions & 5 deletions provider/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ require (
github.com/blang/semver v3.5.1+incompatible
github.com/gobwas/glob v0.2.3
github.com/pkg/sftp v1.13.4
github.com/pulumi/pulumi-go-provider v0.12.1
github.com/pulumi/pulumi-go-provider v0.12.2-0.20230914002200-b7679cf2e409
github.com/pulumi/pulumi-go-provider/integration v0.10.0
github.com/pulumi/pulumi/sdk/v3 v3.82.1
golang.org/x/crypto v0.11.0
golang.org/x/crypto v0.13.0
)

require (
Expand Down Expand Up @@ -85,9 +85,9 @@ require (
go.uber.org/atomic v1.10.0 // indirect
golang.org/x/net v0.10.0 // indirect
golang.org/x/sync v0.2.0 // indirect
golang.org/x/sys v0.10.0 // indirect
golang.org/x/term v0.10.0 // indirect
golang.org/x/text v0.11.0 // indirect
golang.org/x/sys v0.12.0 // indirect
golang.org/x/term v0.12.0 // indirect
golang.org/x/text v0.13.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20230706204954-ccb25ca9f130 // indirect
google.golang.org/grpc v1.57.0 // indirect
google.golang.org/protobuf v1.31.0 // indirect
Expand Down
20 changes: 10 additions & 10 deletions provider/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -157,8 +157,8 @@ github.com/pkg/term v1.1.0 h1:xIAAdCMh3QIAy+5FrE8Ad8XoDhEU4ufwbaSozViP9kk=
github.com/pkg/term v1.1.0/go.mod h1:E25nymQcrSllhX42Ok8MRm1+hyBdHY0dCeiKZ9jpNGw=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pulumi/pulumi-go-provider v0.12.1 h1:YEkB574eCEZn5MVgg4FN6aHKjY8xfPx0l5UM2/5hpMU=
github.com/pulumi/pulumi-go-provider v0.12.1/go.mod h1:EOQY/y4d2A8G/0CjkxlFBbrmLoLFkPf6WCZIiyBsP1M=
github.com/pulumi/pulumi-go-provider v0.12.2-0.20230914002200-b7679cf2e409 h1:8CmebgNDPDgvlLpUEvYe54AVnsUOQm498puZ81pIw+w=
github.com/pulumi/pulumi-go-provider v0.12.2-0.20230914002200-b7679cf2e409/go.mod h1:EOQY/y4d2A8G/0CjkxlFBbrmLoLFkPf6WCZIiyBsP1M=
github.com/pulumi/pulumi-go-provider/integration v0.10.0 h1:GHesnrrvkboSjkZpC+qRwjkXBp5d+fSXqlIO92zQxvc=
github.com/pulumi/pulumi-go-provider/integration v0.10.0/go.mod h1:qAbKHpPzANFKOyjiQ0CzdgJh4DtM0gtujKhO7+l3/+w=
github.com/pulumi/pulumi/pkg/v3 v3.82.1 h1:NqJhSWjhmvOF3WPF/IU/Hghyh2bpYNLzP5DlfFaAJ2A=
Expand Down Expand Up @@ -229,8 +229,8 @@ golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0
golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA=
golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=
golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck=
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
Expand Down Expand Up @@ -282,22 +282,22 @@ golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA=
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.0.0-20220722155259-a9ba230a4035/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
golang.org/x/term v0.10.0 h1:3R7pNqamzBraeqj/Tj8qt1aQ2HpmlC+Cx/qL/7hn4/c=
golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o=
golang.org/x/term v0.12.0 h1:/ZfYdc3zq+q02Rv9vGqTeSItdzZTSNDmfTi0mBAuidU=
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4=
golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
Expand Down
7 changes: 1 addition & 6 deletions provider/pkg/provider/remote/commandOutputs.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,7 @@ import (
)

func (c *CommandOutputs) run(ctx p.Context, cmd string) error {
config, err := c.Connection.SShConfig()
if err != nil {
return err
}

client, err := c.Connection.Dial(ctx, config)
client, err := c.Connection.Dial(ctx)
if err != nil {
return err
}
Expand Down
Loading

0 comments on commit 18d9626

Please sign in to comment.