[Python] ast.literal_evalを使って文字列を評価する

辞書型の文字列をPythonで評価する場合、他言語同様evalが使えます。

In [1]: s = eval("{'a':'aaa', 'b':'bbb', 'c':'ccc'}")

In [2]: s['a']
Out[2]: 'aaa'

evalは便利ですが、強力故にこのような文字列も評価可能です。

In [3]: ls -l
total 8
-rw-r--r--   1 aoshiman  staff    79 11 24 21:39 README.tmpenv
drwxr-xr-x   2 aoshiman  staff    68 11 24 21:40 a-path-you-really-care-about/
drwxr-xr-x  36 aoshiman  staff  1224 11 24 21:39 bin/
drwxr-xr-x   3 aoshiman  staff   102 11 24 21:39 include/
drwxr-xr-x   3 aoshiman  staff   102 11 24 21:39 lib/
drwxr-xr-x   3 aoshiman  staff   102 11 24 21:39 share/

In [4]: eval("__import__('os').system('rm -rf ./a-path-you-really-care-about')")
Out[4]: 0

In [5]: ls -l
total 8
-rw-r--r--   1 aoshiman  staff    79 11 24 21:39 README.tmpenv
drwxr-xr-x  36 aoshiman  staff  1224 11 24 21:39 bin/
drwxr-xr-x   3 aoshiman  staff   102 11 24 21:39 include/
drwxr-xr-x   3 aoshiman  staff   102 11 24 21:39 lib/
drwxr-xr-x   3 aoshiman  staff   102 11 24 21:39 share/

もう一つのやり方としてast.literal_evalを使う方法があります。literal_evalは文字列、バイト列、数、タプル、リスト、辞書、集合、ブール値など評価対象が限定されているのでevalより安全に評価出来ます。
rmコマンドを評価させると例外を発生させます。


In [8]: mkdir a-path-you-really-care-about

In [9]: ls -l
total 8
-rw-r--r--   1 aoshiman  staff    79 11 24 21:39 README.tmpenv
drwxr-xr-x   2 aoshiman  staff    68 11 24 21:48 a-path-you-really-care-about/
drwxr-xr-x  36 aoshiman  staff  1224 11 24 21:39 bin/
drwxr-xr-x   3 aoshiman  staff   102 11 24 21:39 include/
drwxr-xr-x   3 aoshiman  staff   102 11 24 21:39 lib/
drwxr-xr-x   3 aoshiman  staff   102 11 24 21:39 share/

In [10]: import ast

In [11]: s = ast.literal_eval("{'a':'aaa', 'b':'bbb', 'c':'ccc'}")

In [12]: s['a']
Out[12]: 'aaa'

In [13]: ast.literal_eval("__import__('os').system('rm -rf ./a-path-you-really-care-about')")
    ...:
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-14-2546c9cde63c> in <module>()
----> 1 ast.literal_eval("__import__('os').system('rm -rf ./a-path-you-really-care-about')")

/usr/local/Cellar/python3/3.5.1/Frameworks/Python.framework/Versions/3.5/lib/python3.5/ast.py in literal_eval(node_or_string)
     82                 return left - right
     83         raise ValueError('malformed node or string: ' + repr(node))
---> 84     return _convert(node_or_string)
     85
     86

/usr/local/Cellar/python3/3.5.1/Frameworks/Python.framework/Versions/3.5/lib/python3.5/ast.py in _convert(node)
     81             else:
     82                 return left - right
---> 83         raise ValueError('malformed node or string: ' + repr(node))
     84     return _convert(node_or_string)
     85

ValueError: malformed node or string: <_ast.Call object at 0x106c53c88>

In [14]:

そもそも辞書形式の文字列を使う場面なんかあるのかという疑問なのですが、 例えばAWS Lambdaで機密情報を取り扱う場合、KMSを利用して暗号化してしまうのが手っ取り早く且つ良い方法だと思うのですが、機密情報を辞書形式の文字列で暗号化しておくと復号後の値の取り扱いがやりやすくなります。

先ずSIDとSECRETをkeyに持つ辞書型文字列をKMSを使って暗号化します。ここでは暗号化、デプロイ、実行にLamveryを使っています。

$ lamvery encrypt -s -n conf "{'SID':'1234567890','SECRET':'abcdefghijklmn'}"

cipher_textsを確認すると暗号化されています。

$ cat .lamvery.secret.yml
cipher_texts:
  conf: AQECAHgtwl2B8ygtOB4TH6X5x0TAaeSX4r7lIOFdxo1SFZyMAgAAAI0wgYoGCSqGSIb3DQEHBqB9MHsCAQAwdgYJKoZIhvcNAQcBMB4GCWCGSAFlAwQBLjARBAx5ehDOIvYwyaZLmE8CARCASaNQOkC3tKi5tMpOAmEy0ChRkrrB7y0yJz1ldwtFBI2O6Kq1oWpsTbQJNAEbbxB4uvWXrR0vOid0G8hQAdy6lKy0D0FlFHzFoKg=
secret_files: {}

次に以下のようなコードを書き、AWS Lambdaにデプロイします。

import ast
import lamvery

def lambda_handler(event, context):
    conf = ast.literal_eval(lamvery.secret.get('conf'))
    print(conf['SECRET'])
$ lamvery deploy
lamvery: [Function-VPC] subnets: None -> []
lamvery: [Function-VPC] security_groups: None -> []
lamvery: [Function] Capacity: 7,594,304 Bytes -> 7,593,926 Bytes
lamvery: [Function] Deployed version: $LATEST

Lambdaを実行させるとSECRETの値が出力されているのがわかります。

$ lamvery invoke {}
START RequestId: 3b8da74d-b247-11e6-a8a7-45bdd8f2a5bd Version: $LATEST
abcdefghijklmn
END RequestId: 3b8da74d-b247-11e6-a8a7-45bdd8f2a5bd
REPORT RequestId: 3b8da74d-b247-11e6-a8a7-45bdd8f2a5bd	Duration: 725.51 ms	Billed Duration: 800 ms 	Memory Size: 128 MB	Max Memory Used: 32 MB

参考サイト
Using python's eval() vs. ast.literal_eval()? - Stack Overflow
marcy-terui/lamvery: User-friendly deployment and management tool for AWS Lambda function

  • このエントリーをはてなブックマークに追加
PAGE TOP