yuicho@brain:~# tail -f /dev/memory

備忘録。このブログについては https://yuicho.hateblo.jp/about を見やがれ。

Python の append がバグった時のメモ

dictを使い回してappendするような時はコピーしないとバグる元になる。

どゆこと?

例えばこんな感じ

>>> _tmp = dict()
>>> _list = list()
>>> 
>>> _tmp['a'] = 'hoge'
>>> _list.append(_dict)
>>> 
>>> _tmp['b'] = 'huga'
>>> _list.append(_dict)
>>> 
>>> _list
[{'a': 'hoge', 'b': 'huga'}, {'a': 'hoge', 'b': 'huga'}]
>>> 

この時 _list[{'a': 'hoge'}, {'a': 'hoge', 'b': 'huga'}] となるはずでは……?

なんでなん?

listにおける append は、要素のコピーではなく id の追加だから

>>> id(_list[0])
139632634542408
>>> id(_list[1])
139632634542408
>>> 

つまり、一時的な _tmp を作って _list[] へ転記するイメージではなく、単純に _tmp というモノへのリンクを貼っているだけなイメージ。

じゃあどうすればええのん?

いじる前にコピることで id が変わる

>>> import copy
>>> 
>>> _tmp = dict()
>>> _list = list()
>>> 
>>> _tmp['a'] = 'hoge'
>>> _list.append(_tmp)
>>> 
>>> _tmp = copy.deepcopy(_tmp)
>>> _tmp['b'] = 'huga'
>>> _list.append(_tmp)
>>> 
>>> _list
[{'a': 'hoge'}, {'a': 'hoge', 'b': 'huga'}]
>>> id(_list[0])
139797555681536
>>> id(_list[1])
139797556515248
>>> 

というか……

使いまわしつつ途中で append とか普通ありえないけど、ちゃんと使った後は初期化しようねっていう話。

>>> _tmp = dict()
>>> _list = list()
>>> 
>>> _tmp['a'] = 'hoge'
>>> _list.append(_tmp)
>>> 
>>> _tmp = dict(_tmp)
>>> _tmp['b'] = 'huga'
>>> _list.append(_tmp)
>>> 
>>> _list
[{'a': 'hoge'}, {'a': 'hoge', 'b': 'huga'}]
>>> 

Debianでrootだけlocaleが間違っていると思ったらとってもギルティだった話。

聞いてくれや、事の顛末を。

python

print('あいうえお')

とかやってみようとすると

root@sv1:~# python
Python 3.5.3 (default, Sep 27 2018, 17:25:39)
[GCC 6.3.0 20170516] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> print('あいうえお')
  File "<stdin>", line 0

    ^
SyntaxError: 'ascii' codec can't decode byte 0xe3 in position 7: ordinal not in range(128)
>>>
root@sv1:~#

こんなことになる。

同じ構成にしているはずの別サーバーでは

root@sv2:~# python
Python 3.5.3 (default, Sep 27 2018, 17:25:39)
[GCC 6.3.0 20170516] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> print('あいうえお')
あいうえお
>>>
root@sv2:~#

普通にうまく行く。

「なんでやねん!!!」と思って標準出力のエンコーディングを見て見ると

root@sv1:~# python
Python 3.5.3 (default, Sep 27 2018, 17:25:39)
[GCC 6.3.0 20170516] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import sys
>>> print(sys.stdout.encoding)
ANSI_X3.4-1968
>>>
root@sv1:~#

こりゃまたどうしてこんなことに…

もしやと思い、おもむろにlocaleと叩いてみると

root@sv1:~# locale
LANG=C
LANGUAGE=
LC_CTYPE="C"
LC_NUMERIC="C"
LC_TIME="C"
LC_COLLATE="C"
LC_MONETARY="C"
LC_MESSAGES="C"
LC_PAPER="C"
LC_NAME="C"
LC_ADDRESS="C"
LC_TELEPHONE="C"
LC_MEASUREMENT="C"
LC_IDENTIFICATION="C"
LC_ALL=
root@sv1:~#

∩(´;ヮ;`)∩ンヒィ~~~~~~~~~~~~~~~~~~~~

そういえばこのサーバー、インストールの時間違えてen_US.utf8を選ぶつもりがCを選んじゃったんだった…

でもその後直した気がするのでもう少し調べてみる。

root@sv1:~# cat /etc/default/locale
LANG=en_US.UTF-8
root@sv1:~#

既に設定されている。

現に一般ユーザーでは問題ない

【ひみつだよ】$ locale
LANG=en_US.UTF-8
LANGUAGE=
LC_CTYPE="en_US.UTF-8"
LC_NUMERIC="en_US.UTF-8"
LC_TIME="en_US.UTF-8"
LC_COLLATE="en_US.UTF-8"
LC_MONETARY="en_US.UTF-8"
LC_MESSAGES="en_US.UTF-8"
LC_PAPER="en_US.UTF-8"
LC_NAME="en_US.UTF-8"
LC_ADDRESS="en_US.UTF-8"
LC_TELEPHONE="en_US.UTF-8"
LC_MEASUREMENT="en_US.UTF-8"
LC_IDENTIFICATION="en_US.UTF-8"
LC_ALL=
【ひみつだよ】$

…は?

たねあかし。

rootだけということは、きっとrootのbash profileあたりがおかしいんだろう…

root@sv1:~# cat ~/.profile
# ~/.profile: executed by Bourne-compatible login shells.

if [ "$BASH" ]; then
  if [ -f ~/.bashrc ]; then
    . ~/.bashrc
  fi
fi

mesg n || true
# Installed by Debian Installer:
#  no localization for root because C
#  cannot be properly displayed at the Linux console
LANG=C
LANGUAGE=C
root@sv1:~#

コラコラコラコラ~ッ!(`o´)
おまえなんでそんなとこに書いとんねん!!

ちなみに問題ないサーバーではこんなの無い。

root@sv2:~# cat ~/.profile
# ~/.profile: executed by Bourne-compatible login shells.

if [ "$BASH" ]; then
  if [ -f ~/.bashrc ]; then
    . ~/.bashrc
  fi
fi

mesg n || true
root@sv2:~#

マジで何のために書いてあるんこれ…

と言うわけで直すよ。

root@sv1:~# vi ~/.profile
root@sv1:~# cat ~/.profile
# ~/.profile: executed by Bourne-compatible login shells.

if [ "$BASH" ]; then
  if [ -f ~/.bashrc ]; then
    . ~/.bashrc
  fi
fi

mesg n || true
root@sv1:~# logout
【ひみつだよ】@sv1:~$ su -
Password:
root@sv1:~# locale
LANG=en_US.UTF-8
LANGUAGE=
LC_CTYPE="en_US.UTF-8"
LC_NUMERIC="en_US.UTF-8"
LC_TIME="en_US.UTF-8"
LC_COLLATE="en_US.UTF-8"
LC_MONETARY="en_US.UTF-8"
LC_MESSAGES="en_US.UTF-8"
LC_PAPER="en_US.UTF-8"
LC_NAME="en_US.UTF-8"
LC_ADDRESS="en_US.UTF-8"
LC_TELEPHONE="en_US.UTF-8"
LC_MEASUREMENT="en_US.UTF-8"
LC_IDENTIFICATION="en_US.UTF-8"
LC_ALL=
root@sv1:~# python
Python 3.5.3 (default, Sep 27 2018, 17:25:39)
[GCC 6.3.0 20170516] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> print('あいうえお')
あいうえお
>>>
root@sv1:~#

直った。

結局あれ、なんのためやったん?

~/.profileにはこんなことが書いてあった。

# Installed by Debian Installer:
#  no localization for root because C
#  cannot be properly displayed at the Linux console

翻訳すると、こう。

Debianインストーラによってインストールされます。
Cのため、root用のローカライゼーションはありません。
Linuxコンソールに正しく表示されない

要はシステムがCになってるとコンソールがちゃんと表示されないからその対策ってこと…?

┐(´ー`)┌ さっぱりわかんないよ

Apacheのアクセスログでホスト名が出る時。

confで HostNameLookupson にすると逆引きしてからログを記録するが、そうじゃなくてもホスト名で記録されるときがある。

わざわざ逆引きするとパフォーマンス悪くなりそうなのになんでだろーって思ってるとこんな記事を発見。

hacknote.jp

原因は .htaccess の「allow from」部分でした。
order deny,allow
deny from all
allow from {hostname}
とやっていると結局逆引きしているのでアクセスログにホスト名が載るようです。

つまり、htaccessにホスト名で記述すると逆引きしないと評価できないから、ログに逆引きされた結果で書かれるっていう事らしい。

なるほどなー

自分なりのpythonのロギング作法

「自分なり」?

自分なり。

いつもコーディングするときに「ロギングどないしてたんやったかな」ってなるから、メモ。

多分Python本来のお作法とは外れてるけど、んなもん知るかいな。さてはアンチだなオメー。

スタンス

  • 基本的に画面には出力しないが、ロートロガーに流れてきたWARNING以上は出力
  • 設定ファイル(yamlを使うことが多い)でログファイルを指定できるようにしておいて、そこにはINFO以上を出力
  • 実行時に--debugを付けたら、画面に全てのログが出るようにする。

コードはよ。

import logging

# ログ用変数設定
logger = logging.getLogger(__name__)
logformat = logging.Formatter('%(asctime)s %(module)s[%(lineno)d] [%(levelname)s]: %(message)s', '%Y-%m-%d %H:%M:%S')

# ロギング設定
logger.setLevel(logging.DEBUG)
logging.getLogger('').handlers.clear()
disp_handler = logging.StreamHandler()
disp_handler.setLevel(logging.WARNING)
disp_handler.setFormatter(logformat)
logging.getLogger('').addHandler(disp_handler)


def main():
    ~~~ここでごねごね~~~

    nomal_handler = logging.FileHandler(filename=configs['system']['logfile'])
    nomal_handler.setLevel(logging.INFO)
    nomal_handler.setFormatter(logformat)
    logger.addHandler(nomal_handler)

    ~~~しょり~~~


if __name__ == '__main__':

    if '--debug' in sys.argv:
        disp_handler.setLevel(logging.DEBUG)

    try:
        main()
    except:
        logger.critical('********** Exception **********\n%s', traceback.format_exc())
        sys.exit(1)

Apacheで特定IPをdenyしたりallowしたりするときのconf

職業柄だとは思うけど

あっ!!
今すぐこのIPを弾きたい!!!
今すぐ!!!!

みたいな時がたまにあるので、そんな時にどんなconfを書けばいいかすぐにわかるようにまとめておく。

絶対他の人の役には立たないと思うけど、僕の備忘録だからそれでよし。

特定IPからのリクエストを拒否

allowを明示的に書く
<FilesMatch "\.php">
<LIMIT POST GET>
Order Allow,Deny
Allow from all
Deny from 8.8.8.8
</LIMIT>
</FilesMatch>
denyだけでOK
<FilesMatch "\.php">
<LIMIT POST GET>
Order Deny,Allow
Deny from 8.8.8.8
</LIMIT>
</FilesMatch>

特定IPからのリクエストを許可

denyを明示的に書く
<FilesMatch "\.php">
<LIMIT POST GET>
Order Deny,Allow
Deny from all
Allow from 8.8.8.8
</LIMIT>
</FilesMatch>
allowだけでOK
<FilesMatch "\.php">
<LIMIT POST GET>
Order Allow,Deny
Allow from 8.8.8.8
</LIMIT>
</FilesMatch>

どゆことですのん?

感覚的にはOrderが逆なんだよなぁ…

Order Allow,Denyのとき
  1. 全てのIPが一旦拒否対象になる
  2. Allowで指定したIPが許可対象になる
  3. 例外としてDenyで設定したIPを拒否する
Order Deny,Allowのとき
  1. 全てのIPが一旦許可対象になる
  2. Denyで指定したIPが拒否対象になる
  3. 例外としてAllowで設定したIPを拒否する
つまり…?

Order ①,②だと、①がメイン、ただし例外として②を適用する」という感じだろうか。

bindする関数(PHP編)

時々PHPでテキストファイルに変数をbindして使いたいって思うことがあるから、そんなときに便利な関数を作ってみた。

いわゆるテンプレートエンジンってやつかな。

$str = "bbbbb__test__c __aaa__ ccc";
 
echo bindTemplate($str, array('test'=>'うわーい'));
echo bindTemplate($str, array('test'=>'ぽぷて', 'aaa'=>'ぴぴっく'));
 
function bindTemplate($template, $param, $pattern='/__(.+?)__/')
{
    return preg_replace_callback(
        $pattern,
        function ($matches) use($param){
            $original = $matches[0];
            $key = $matches[1];
 
            if ( array_key_exists($key, $param) ){
                return $param[$key];
            }else{
                return $original;
            }
        },
        $template
    );
}
//?>

↓実行結果はこうなる↓

bbbbbうわーいc __aaa__ cccbbbbbぽぷてc ぴぴっく ccc

余談だけど

これを試している環境でeAcceleratorが動いていて、そのせいで無名関数がまともに動かなかった。 まじくそっすわー凸(☎︎ω☎ )凸

Let’s Encrypt でワイルドカード証明書を自動更新させたらえらい目にあった話

Let’s Encryptワイルドカード証明書がどうしても使ってみたかった(ついでに自動更新にも対応させたかった)ので、いろいろ頑張ってみた話。

*** おことわり ***
前のブログからほぼそのままコピペしてきてます。
この後なんか色々変えたような覚えがあるけど、記憶を探ってみないと何を変えたか覚えてないので、とりあえずこのまま記事にしました。
いつかちゃんと書き直そうと思います。いつか。

認証用ゾーンの準備

Let’s Encryptワイルドカード証明書は、該当するドメインのTXTレコードを見に来る。

つまり、Certbotが勝手にTXTレコードを書き換えればよいわけだが、他のホスト名とかが書いてあるゾーンファイルをCertbotに直接いじらせてシリアルも変えさせるのは気持ち悪かったのでゾーンを分けた。

[root@sv1 ~]# cat /var/named/zone/_acme-challenge.yuicho.net
$TTL 1M
$ORIGIN _acme-challenge.yuicho.net.
@       IN      SOA     sv1.yuicho.net. hostmaster.yuicho.net. (
                                        2018050304      ; serial
                                        1D      ; refresh
                                        1H      ; retry
                                        4W      ; expire
                                        1M )    ; minimum
        IN      NS      sv1.yuicho.net.
        IN      NS      sv2.yuicho.net.
 
@       IN      TXT     "TXT-Record-Test"
[root@sv1 ~]#
[root@sv1 ~]# tail -6 /etc/named.conf
zone "_acme-challenge.yuicho.net" {
        type master;
        file "zone/_acme-challenge.yuicho.net";
        allow-query { any; };
        notify yes;
};
[root@sv1 ~]#
[root@sv2 ~]# tail -7 /etc/named.conf
zone "_acme-challenge.yuicho.net" {
        type slave;
        file "slaves/_acme-challenge.yuicho.net";
        allow-query { any; };
        allow-transfer { none; };
        masters { 133.18.169.101; };
};
[root@sv2 ~]#
[root@sv1 ~]# host -t TXT _acme-challenge.yuicho.net sv1
Using domain server:
Name: sv1
Address: 133.18.169.101#53
Aliases:
 
_acme-challenge.yuicho.net descriptive text "TXT-Record-Test"
[root@sv1 ~]#

余談だが、ゾーンファイルに権限がないとrndc reloadさせてくれなかった。

本来は

[root@sv1 ~]# rndc reload acme-challenge.yuicho.net
rndc: 'reload' failed: permission denied
[root@sv1 ~]#

となるはずなのだが、先頭に_が入っていると

[root@sv1 ~]# rndc reload acme-challenge.yuicho.net
rndc: 'reload' failed: not found
[root@sv1 ~]#

となった。

しかし、不思議なことに再度権限を戻して

[root@sv1 ~]# ls -la /var/named/zone/_acme-challenge.yuicho.net
-rw-r-----. 1 root root 264  5月  4 01:28 /var/named/zone/_acme-challenge.yuicho.net
[root@sv1 ~]#

rndc reloadしても通る。

[root@sv1 ~]# rndc reload _acme-challenge.yuicho.net
zone reload queued
[root@sv1 ~]#

なぜだ………(´・ω・`)

Certbotで使うフックの作成

CertbotでできるDNS-01認証(今もこの名前でいいのかな)は、基本インタラクティブな動作になる

qiita.com

でも今回は自動更新に対応させたかったので、それだと困る。

そのため、Certbotが認証の時に叩くフックスクリプトが必要になる。

tech.fjct.fujitsu.com

そんなわけで、以下のスクリプトを作成。

[root@sv1 ~]# cat /usr/local/bin/dns-auth.sh
#!/usr/bin/env bash
ZONENAME="_acme-challenge.${CERTBOT_DOMAIN}"
 
cp -av /var/named/zone/$ZONENAME{,.backup}
 
sed -i -r -e "s/@\s+IN\s+TXT\s+\".+?\"/@ IN TXT \"${CERTBOT_VALIDATION}\"/g" -e "s/[0-9]{10}\s+;\s+serial/`date "+%s"` ; serial/g" /var/named/zone/$ZONENAME
 
rndc reload $ZONENAME
 
sleep 90;
[root@sv1 ~]#

かなりお粗末スクリプトだけど動きゃいいんだよ!!!!!(`Д´)

ちなみに、sleep 90を入れているのはとても苦肉の策。

当初は*.yuicho.netのみで認証していたからこんなもの入れてなかったけど、それだとyuicho.netでこの証明書が使えなくなってしまう。

SANsを使ってyuicho.net*.yuicho.netの両方を兼ね備えた証明書にしなければいけないけど、そうすると2ドメイン分の認証が走ってしまうので、TTL 1Mでも間に合わなくなってしまう。

そこで仕方なく、TTL分以上(90秒)待ってLet’s Encrypt側のキャッシュが消えるようにしている。

(というか、認証サーバーがキャッシュ持つなよなー)

Certbotに証明書を発行させる

[root@sv1 ~]# certbot certonly -n -d yuicho.net -d *.yuicho.net --agree-tos --email register.letsencrypt@yuicho.net --manual-public-ip-logging-ok --manual --preferred-challenges dns-01 --manual-auth-hook /usr/local/bin/dns-auth.sh --server https://acme-v02.api.letsencrypt.org/directory --post-hook '/bin/systemctl reload httpd; /bin/systemctl reload postfix; /bin/systemctl reload dovecot;'
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Plugins selected: Authenticator manual, Installer None
Starting new HTTPS connection (1): acme-v02.api.letsencrypt.org
Obtaining a new certificate
Performing the following challenges:
dns-01 challenge for yuicho.net
dns-01 challenge for yuicho.net
Output from dns-auth.sh:
`/var/named/zone/_acme-challenge.yuicho.net' -> `/var/named/zone/_acme-challenge.yuicho.net.backup'
zone reload queued

Output from dns-auth.sh:
`/var/named/zone/_acme-challenge.yuicho.net' -> `/var/named/zone/_acme-challenge.yuicho.net.backup'
zone reload queued
 
Waiting for verification...
Cleaning up challenges
Running post-hook command: /bin/systemctl reload httpd; /bin/systemctl reload postfix; /bin/systemctl reload dovecot;
 
IMPORTANT NOTES:
 - Congratulations! Your certificate and chain have been saved at:
   /etc/letsencrypt/live/yuicho.net/fullchain.pem
   Your key file has been saved at:
   /etc/letsencrypt/live/yuicho.net/privkey.pem
   Your cert will expire on 2018-08-01. To obtain a new or tweaked
   version of this certificate in the future, simply run certbot
   again. To non-interactively renew *all* of your certificates, run
   "certbot renew"
 - If you like Certbot, please consider supporting our work by:
 
   Donating to ISRG / Let's Encrypt:   https://letsencrypt.org/donate
   Donating to EFF:                    https://eff.org/donate-le

[root@sv1 ~]#

無事ワイルドカード証明書をげっちゅ!!! f:id:yuich0:20181113005930p:plain

これで、次からはcertbot renewで良いはず。

まとめ

もっと簡単にCertbotでとれるようにしとけよなー!!!

っていうか、キャッシュ持つなよなー!!!