Python
AWS

マネージド・サーバーレス・踏み台サーバーのススメ

More than 1 year has passed since last update.

※ ネタです。

AWSのVPCでプライベートなサブネットにおいてあるEC2にSSHでアクセスしたいとき、

踏み台サーバーを作成して、多段でアクセスする方法があります。

ただ、金やセキュリティのために踏み台EC2を削除をするので、SSHする度に作ったり落としたりするのもめんどくさいです。

そんなことを考えながら、ネット記事を見ていたらGoでaws lambdaにアクセスする方法が書いてありました。

SSH-ing into your AWS Lambda Functions

https://medium.com/clog/ssh-ing-into-your-aws-lambda-functions-c940cebf7646

SSHできるんだったら、トンネル開けてやれば5分間踏み台サーバーとして利用できそうです。

記事ではGoだったので、なんとなくpython(3.6)で。


考えること

(1)トンネル掘りのため、sshができるparamikoが必要

(2)lambdaはセキュリティグループで設定してもインバウンドのポート閉じちゃってるらしい

(1)については、lambda用にzipを作るときにpip install -t paramiko . でOK。

(2)については、lambdaからSSHでアクセスしてもらい、逆ポートフォワーディングでトンネルを作成。


準備


  1. VPCでプライベートなEC2インスタンス

  2. プライベートなサブネット2個以上。(VPCにlambda設置のため)

  3. サブネットにNATゲートウェイ(プライベートサブネット内のlambdaが外と通信するため)

  4. NAT越えのためngrok(手元のPCとlambdaをつなぎたいので)


AWS Lambdaのコード

paramikoのところにデモがあったのでそれを流用。

import os

import socket
import select
import sys
import threading
import paramiko

def handler(chan, host, port):
sock = socket.socket()
try:
sock.connect((host, port))
except Exception as e:
print('Forwarding request to %s:%d failed: %r' % (host, port, e))
return None

print('Connected! Tunnel open %r -> %r -> %r' %(chan.origin_addr, chan.getpeername(), (host, port)))

while True:
r, _w, _x = select.select([sock, chan], [], [])
if sock in r:
data = sock.recv(1024)
if len(data) == 0:
break
chan.send(data)
if chan in r:
data = chan.recv(1024)
if len(data) == 0:
break
sock.send(data)

chan.close()
sock.close()

return None

def reverse_forward_tunnel(server_port, remote_host, remote_port, transport):
transport.request_port_forward('', server_port)
while True:
chan = transport.accept(1000)
if chan is None:
continue
thr = threading.Thread(target=handler, args=(chan, remote_host, remote_port))
thr.setDaemon(True)
thr.start()

def main_handler(event, context):
client = paramiko.SSHClient()
client.load_system_host_keys()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())

if os.environ.get('CIRCUIT_BREAKER') is not None: # 再起動用
print("canncel handler.")
return None

try:
host = os.environ.get('LOCAL_HOST', '0.tcp.ap.ngrok.io')
port = os.environ['LOCAL_PORT']
username = os.environ['LOCAL_USERNAME']
password = os.environ['LOCAL_PASSWORD']

reverse_port = os.environ.get('REVERSE_PORT', 9999)
remote_host = os.environ['REMOTE_HOST']
remote_port = os.environ.get('REMOTE_PORT', 22)

client.connect(host, int(port), username = username, password = password)

print('Create tunnel %s:%s:%s' % (reverse_port, remote_host, remote_port))
reverse_forward_tunnel(int(reverse_port), remote_host, int(remote_port), client.get_transport())
except Exception as e:
print('Connect failed: %r' % e)

return None

if __name__ == '__main__':
from os.path import join, dirname
from dotenv import load_dotenv

dotenv_path = join(dirname(__file__), '.env')
load_dotenv(dotenv_path)

main_handler({}, {})


実行


  1. ↑のソースコードをparamiko付きでプライベートなサブネット内lambdaに設置

  2. lambdaの環境変数を設定

  3. 手元のPCにngrokをダウンロード&認証をしてやる

  4. ngrokをtcp 22で起動(ngrok tcp 22 --region ap)。

    ngrokのトンネル用ポートが表示されるので、lambdaの環境変数に設定(LOCAL_PORT)

  5. lambda起動

  6. うまく起動できたら、ngrokを起動しているPCにトンネルが接続されているので

    SSHでプライベートなEC2にアクセス

SSHコマンドだと、こんな感じになっているかと。

# lambda内

$ ssh -R <REVERSE_PORT>:<REMOTE_HOST>:<REMOTE_PORT> <LOCAL_USERNAME>@<LOCAL_HOST> -p <LOCAL_PORT>

# 手元のPCなど
$ ssh 127.0.0.1 -p <REVERSE_PORT> -l ec2-user -i ~/.ssh/ec2-keypair.pem


感想

当たり前ですが、踏み台lambdaが落ちれば接続も切れますので5分しか使えません。

なので、5分間で作業を終わらしてください。(接続先EC2内にtmuxとか起動していれば……)

また、Goは標準でssh用のやつ(x/crypto/ssh)を持っているので、Goの方が楽そうです。