さくらサーバーにPython3.6とOpenSSL1.1

さくらのレンタルサーバー「スタンダードプラン」にPython3.6とOpenSSL1.1をインストールしました。

インストール後、Spreadsheet APIを更新しました。

経緯

わたしが一部管理をしているゴーウェスト呼びかけ人・賛同人一覧のページで使用している更新スクリプト(Spreadsheetからデータを取得してJSONに出力するPythonスクリプト)がある時から動かなくなりました。

cronが出していたエラーは以下のようなものです。

※この時使用していたスクリプトはPython Quickstartに載っているスクリプトの古いバージョンです。

Failed to start a local webserver listening on either port 8080 or port 8090. Please check your firewall settings and locally running programs that may be blocking or using those ports.

Falling back to –noauth_local_webserver and continuing with authorization.

Go to the following link in your browser:
https://accounts.google.com/o/oauth2/auth?scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fspreadsheets.readonly&redirect_uri=urn%3Aietf%3Awg%3Aoauth%3A2.0%3Aoob&response_type=code&client_id=****-****.apps.googleusercontent.com&access_type=offline

Enter verification code: Traceback (most recent call last):
File “/home/myaccount/www/gowest-comewest.net/py/yobikake-sandou.py”, line 93, in
main()
File “/home/myaccount/www/gowest-comewest.net/py/yobikake-sandou.py”, line 64, in main
credentials = get_credentials()
File “/home/myaccount/www/gowest-comewest.net/py/yobikake-sandou.py”, line 51, in get_credentials
credentials = tools.run_flow(flow, store, flags)
File “build/bdist.freebsd-9.1-RELEASE-p24-amd64/egg/oauth2client/_helpers.py”, line 133, in positional_wrapper
File “build/bdist.freebsd-9.1-RELEASE-p24-amd64/egg/oauth2client/tools.py”, line 240, in run_flow
EOFError: EOF when reading a line

「OAuthのトークンの更新が失敗している」のが原因だということは一見してわかるのですが、なぜ急にそんなことになったのかはすぐにはわかりませんでした。なので、申し訳ないことですが、フロント側にエラーが出ていないのをいいことに、しばらく放置していました。

腰を据えて調べてみたところ、原因は「TLS1.0がAPIの暗号化方式から削除された」からだとわかりました。世の趨勢ですね。どこかでアナウンスされていたのでしょうが、わたしには無関係だろうと甘く見ていました。

修正と学習を兼ねて、動くようにすることにしました。

現在のバージョンの確認

必要な暗号化方式はTLS >= 1.2です。わたしのサーバー(さくらのレンタルサーバー)は、2020年7月時点では以下の構成です。もしかしたら新しい構成のサーバーに移すことができるのかもしれませんが、作業するほうが楽しい勉強になるので調べませんでした。

  • FreeBSD 9.1
  • Python 2.7.6
  • OpenSSL 0.9.8

TLS1.2の動作要件は以下です。OpenSSLについてはTLS1.2に対応するために、PythonについてはOpenSSL >= 1.0に対応するために、それぞれ導入が必要です。

  • Python >= 3
  • OpenSSL >= 1.1

利用しているサーバーのPythonとOpenSSLのバージョンは以下のコマンドの実行結果で確認できます。エラーが出るようなら、当該サーバーではTLS1.2は使えません。ちなみに、同じ理由でpipも動きません。pypi.orgへのアクセスに失敗してしまいます。困ったものです。

参考: Not able to install Python packages [SSL: TLSV1_ALERT_PROTOCOL_VERSION]

% /usr/local/bin/python --version
Python 2.7.6
% /usr/local/ssl/bin/openssl version -a
OpenSSL 1.0.2 foo bar
% python
>>> import ssl
>>> ssl.OPENSSL_VERSION
 'OpenSSL 0.9.8o 01 Jun 2010'
>>> ssl.PROTOCOL_TLSv1_2
 AttributeError: 'module' object has no attribute 'PROTOCOL_TLSv1_2'

または

% python -c "import ssl; print(ssl.OPENSSL_VERSION); print(ssl.PROTOCOL_TLSv1_2)"
 foo bar

ファイルシステムの準備

さくらのレンタルサーバーのスタンダードプランではrootにはなれません。また、バーチャルホストで構成されているので、自分のホームディレクトリにインストールする必要があります。なので、ホームディレクトリ以下にetcやusrを作ります。この辺りは好みですが、OSに似た構成にしたほうがいいと思います。

% cd
% pwd -P
/home/myaccount
% mkdir -p ~/etc ~/usr/local/include/openssl ~/usr/local/lib ~/usr/src

ライブラリや設定ファイルの参照先を指定するために、シェルの変数も追加します。不要な設定やパスの項目は削除してあります。

% vi ~/.cshrc
# General environment vars
setenv  USRDIR          $HOME/usr
setenv  LOCALBASE       $USRDIR/local
setenv  SRCDIR          $USRDIR/src
setenv  ETCDIR          $HOME/etc

# make
setenv  SRCCONF         $ETCDIR/src.conf
setenv  __MAKE_CONF     $ETCDIR/make.conf
set     LD_LIBRARY_PATH = ($LOCALBASE/lib)
set     LD_RUN_PATH     = $LD_LIBRARY_PATH

# Paths
set     manpath         = ($LOCALBASE/man $USRDIR/share/man $LOCALBASE/share/man /usr/share/man /usr/local/man)
set path = ($LOCALBASE/bin /sbin /bin /usr/sbin /usr/bin /usr/local/sbin /usr/local/bin)

# Softwares
setenv  PYTHONPATH      $HOME/.local/lib/python3.6/site-packages
setenv  PW              $LOCALBASE/ssl

% source ~/.cshrc

ソースコードのダウンロード

OSがFreeBSD 9.1と古いので、システムのギャップのせいで無駄にハマるということのないよう、一つ前のバージョンを導入することにしました。

勘違いかもしれませんが、fetchが使えなかったので、curlでダウンロードしました。その後、解答します。

% cd ~/usr/src
% curl -o ./Python-3.6.11.tar.xz https://www.python.org/ftp/python/3.6.11/Python-3.6.11.tar.xz
% curl -o ./openssl-1.1.1g.tar.gz https://www.openssl.org/source/openssl-1.1.1g.tar.gz
% tar Jxvf ./Python-3.6.11.tar.xz
% tar zxvf ./openssl-1.1.1g.tar.gz

コンパイルとビルド

ライブラリの参照先をさらに指定します。今見ても微妙ですが、とにかく、わたしはmake.confでやりました。その後コンパイルとビルドを順に実行します。portsやpkgngがあるFreeBSDでは(わたしは)あまりやる機会がない作業ですね。

% vi ~/etc/make.conf
LDFLAGS=-L/home/myaccount/usr/local/lib \
        -rpath /home/myaccount/usr/local/lib
CFLAGS=-I/home/myaccount/usr/local/include \
        -I/home/myaccount/usr/local/include/openssl \
        -L/home/myaccount/usr/local/lib
% cd ~/usr/src/openssl-1.1.1g
% ./Configure --prefix=/home/myaccount/usr/local --openssldir=/home/myaccount/usr/local/ssl -rpath=/home/myaccount/usr/local/lib BSD-x86_64
% make
% make install
% cd ~/usr/src/Python-3.6.11
% ./configure --with-ensurepip=upgrade --prefix=/home/myaccount/usr/local
% make
% make install

シンボリックリンク

コマンドのファイル名を改めます。

% cd ~/usr/local/bin
% ln -s ./python3 ./python
% ln -s ./pip3 ./pip
% source ~/.cshrc

最終確認

細部まで覚えていないくて申し訳ないのですが、以下のような状態に持って行けたら終わりです。私の場合、pathの設定がうまく行きませんでした。

% python -c "import ssl; print(ssl.OPENSSL_VERSION); print(ssl.PROTOCOL_TLSv1_2)"
OpenSSL 1.1.1g  21 Apr 2020
_SSLMethod.PROTOCOL_TLSv1_2
% python --version
Python 3.6.11

[おまけ] Spreadsheet APIのスクリプトの更新

以下のようなPythonスクリプトを書いてcronで定期的に叩けば、yobikake-sandou.jsonが自動的に更新されます。

データを取得するだけならOAuthでやる必要はありません。Spreadsheet APIが有効になっていて、読み込み許可が設定されていれば動作します。

% vi make_yobikake-sandou_json.py
#!/home/myaccount/usr/local/bin/python

from __future__ import print_function
import json
from googleapiclient import discovery

# The ID, range and API key.
SPREADSHEET_ID = 'spreadsheet-id-of-its-url'
RANGES = 'Table1!A2:H'
APIKEY = 'your_api_key'
credential = None

# Path to store JSON.
OUTFILE_PATH = '/home/myaccount/www/gowest-comewest.net/yobikake-sandou.json'

def main():
    # Connect to the spreadsheet.
    service = discovery.build('sheets', 'v4', developerKey=APIKEY)

    # Call the Sheets API and get values.
    result = service.spreadsheets().values().get(spreadsheetId=SPREADSHEET_ID, range=RANGES).execute()
    values = result.get('values', [])

    if len(values) >= 1:
        # Create JSON data.
        jsonData = ['foobar']
        with open(OUTFILE_PATH, "w", 1) as F:
            json.dump(jsonData, F)
    else:
        print('Sheet is empty or reading sheet was failed.')

if __name__ == '__main__':
    main()

% chmod 700 make_yobikake-sandou_json.py

参考: APIキーを使用する, google-api-python-client document

コメントを残す

メールアドレスが公開されることはありません。

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください