※ ネタです。
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でアクセスしてもらい、逆ポートフォワーディングでトンネルを作成。
準備
- VPCでプライベートなEC2インスタンス
- プライベートなサブネット2個以上。(VPCにlambda設置のため)
- サブネットにNATゲートウェイ(プライベートサブネット内のlambdaが外と通信するため)
- 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({}, {})
実行
- ↑のソースコードをparamiko付きでプライベートなサブネット内lambdaに設置
- lambdaの環境変数を設定
- 手元のPCにngrokをダウンロード&認証をしてやる
- ngrokをtcp 22で起動(
ngrok tcp 22 --region ap
)。
ngrokのトンネル用ポートが表示されるので、lambdaの環境変数に設定(LOCAL_PORT) - lambda起動
- うまく起動できたら、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の方が楽そうです。