やりたいこと
タイトルそのまんまですが、lbxxx.soなどのライブラリを含めてデプロイし、AWS lambda上でパスワード付きのzipを生成し、S3のバケットに格納します。
立ちふさがる壁
そんな私のやりたいことの前に壁が立ちふさがります。
1. zipfile(Pythonで標準で用意されているzipファイルの作成などを行うモジュール)では、パスワード付きzipファイルの暗号化がサポートされていない!
2. ローカルのソースコードをzipファイルに固めて、lambdaにデプロイしただけでは、パスワード付きzipを作成することができない!
いかにして、この壁を乗り越えたのかをここに残したいと思います。
環境
ローカル開発環境
- Win10(ホストOS)
- Docker
- Linux 10(DockerコンテナのOS)
- VS Code
- Python 3.8.6
- pip 20.2.3
- aws-cli 2.0.56
VS Codeの「Remote-Containers」機能を使って、便利に開発しておりました。
Remote-Containers についてはこちらの記事を参考にしてくださいませ。
※上記のDockerや、VS Codeのインストールなどはあらかじめ完了していることとします。
AWS環境
- lambda pythonランタイム 3.8
特に書くことはないですね・・・
壁にぶつかる前まで
-
AWS マネジメントコンソール - Lambda - 「関数の作成」
- 「一から作成」
- 関数名「zip-sample」
- ランタイム「Python3.8」
ローカルにて、VS Codeを立ち上げ、以下のディレクトリ構成を作る
zip-sample/
└ .devcontainer/
├ Dockerfile
└ devcontainer.json
└ package/
└ lambda_hundler.py
└ requirements.txt
Dockerfileとdevcontainer.jsonについては、こちらのソースコードからいただきました。
lambda_hundler.pyは以下になります。
# lambdaの素のソースコードです
import json
def lambda_handler(event, context):
# TODO implement
return {
'statusCode': 200,
'body': json.dumps('Hello from Lambda!')
}
壁①の乗り越え方
準備ができたところで早速取り掛かりましょう。
なんと、zipfileモジュールではパスワード付きzipファイルを生成できないではありませんか!(唐突)
調べてみたところ、pyminizipではパスワード付きzipファイルが生成できるらしいです。
早速確かめてみましょう。
準備
pyminizipはzlib(データの圧縮および伸張を行うためのフリーのライブラリ)が必要なので、インストールします。
$ sudo apt install zlib
Reading package lists... Done
Building dependency tree
Reading state information... Done
E: Unable to locate package zlib
怒られました。
代わりに、「zlib1g」を使用します。
$ sudo apt install zlib1g
requirements.txtに以下を記載し、pip install
コマンドを打ちます。
pyminizip==0.2.4
$ cd /workspaces/zip-sample
$ pip install -r requirements.txt -t ./package
lambda_hundler.pyに以下を追記します。
import json
import os
import pyminizip
def lambda_handler(event, context):
# 後々lambdaに上げることを考慮して、tmpディレクトリを利用します。
zip_path = "/tmp/zip/"
# /tmp/zipディレクトリがなければディレクトリを作成します。
if not os.path.isdir(zip_path):
os.mkdir(zip_path)
KEY = "/tmp/hello.txt"
with open(KEY, mode='w') as f:
f.write('this is test.')
password = "password"
compression_level = 9 # 圧縮レベル1-9、大きいほど圧縮が強い
# 第一引数は、zipファイルに含めるファイルのパスの配列
# 第二引数は、zipファイル内の階層
# 第三引数は、zipファイルの配置先及びファイル名
# 第四引数は、パスワード
# 第五引数は、圧縮レベル
pyminizip.compress_multiple([KEY], ["\\"], "/tmp/zip/sample.zip", password, compression_level)
return {
'statusCode': 200,
'body': json.dumps('Hello from Lambda!')
}
# 以下はデプロイ時に除きます。
lambda_hundler('a','a')
以下で起動してみましょう。
$ python lambda_hundler.py
$ ls /tmp/zip
sample.zip
無事できましたね!
壁②の乗り越え方
無事にローカル(dockerコンテナ)で、パスワード付きzipファイルが生成できたので、lambdaにデプロイしてみましょう。
- MFA制限をしている場合は、事前にaws-mfaなどで、aws-cliを使えるようにしてください。
- lambda_handler.pyの、
lambda_hundler('a','a')
はコメントアウトなり、削除するなりしておいてください。
$ cd /workspaces/zip-sample/package
$ zip -r ../function.zip .
$ aws lambda update-function-code --function-name sample-zip --zip-file fileb://../function.zip
デプロイが完了したら、AWSマネジメントコンソール - lambda - sample-zipより、「テスト」を実行してみましょう。
[ERROR] OSError: error in closing \tmp\zip\sample.zip (-102)
Traceback (most recent call last):
File "/var/task/lambda_function.py", line 78, in lambda_handler
pyminizip.compress_multiple([KEY], ["\\"], r"\tmp\zip\sample.zip", "password03", 1)
怒られた
色々と調べてみると、zlibがデプロイパッケージに含まれていないために起きているらしい・・・
※解決した後にカスタムランタイムを利用する解決方法もあるらしいですが、そちらは割愛します。
zlibをデプロイパッケージに含める戦いが始まる・・・!
以下のコマンドを打っていけばデプロイパッケージに含められました。
$ wget http://www.zlib.net/zlib-1.2.11.tar.gz
$ tar -xvzf zlib-1.2.11.tar.gz
$ cd zlib-1.2.11
$ ./configure --prefix=/workspaces/sample-zip
share lib includeが/workspaces/sample-zip/にできるはず
$ sudo make install
$ cd /workspaces/sample-zip/
zipファイルにshare lib includeを含める
$ zip -gr function.zip lambda_function.py share lib include
デプロイ
$ aws lambda update-function-code --function-name sample-zip --zip-file fileb://function.zip
linuxのライブラリについては一度上記をやれば変更しなくてよいので、以降はソースコードのzip化のみでよいです。
$ cd /workspaces/zip-sample/package
$ zip -r ../function.zip .
$ aws lambda update-function-code --function-name sample-zip --zip-file fileb://../function.zip
デプロイが完了したら、AWSマネジメントコンソール - lambda - sample-zipより、「テスト」を実行してみましょう。
成功!(するはず)
完成系
壁を乗り越えた後のソースコードは以下になります。
def lambda_handler(event, context):
zip_path = "/tmp/zip/"
if not os.path.isdir(zip_path):
os.mkdir(zip_path)
KEY = '/tmp/hello.txt'
with open(KEY, mode='w') as f:
f.write("this is test.")
password = "password"
compression_level = 9 # 圧縮レベル1-9、大きいほど圧縮が強い
pyminizip.compress_multiple([KEY], ["\\"], "/tmp/zip/sample.zip", password, compression_level)
# 作成したzipファイルをs3にアップロードします
s3 = boto3.resource('s3')
s3.Bucket(BUCKET).upload_file(Filename="/tmp/zip/sample.zip", Key="sample.zip")
return {
'status': 200,
'body': '処理が終了しました'
}
まとめ
- Pythonでパスワード付きzipファイルを作成したかったら、「pyminizip」!
- lambdaにデプロイするときは、「zlib」もデプロイパッケージに含める!