という話

技術ブログにしたい

Route53+DNS-01のLet'sEncryptを自動更新する

ググると日本語の記事も結構出てくるんですが、どれも私の環境ではうまく動かなかったので自分なりにやりました。

前提

  • CentOS7
  • certbotcertbot-external-authインストール済み
  • awsコマンドがインストール済み
  • aws configure 済みで、Route53の権限を持ってる

certbot-external-authは下記でインストール

https://github.com/EnigmaBridge/certbot-external-auth
上のURLを参考に

pip install certbot
pip install certbot-external-auth

でインストールできる。

コード

renew.sh

#!/bin/sh

certbot certonly \
  -d sub1.example.com \
  -d sub2.example.com \
  --email test@example.com \
  --agree-tos \
  --preferred-challenges dns \
  --keep-until-expiring \
  --text \
  --configurator certbot-external-auth:out \
  --certbot-external-auth:out-public-ip-logging-ok \
  --certbot-external-auth:out-handler /path/to/hook.sh

# apacheなら
systemctl restart httpd

# nginxなら
# systemctl restart nginx


hook.sh

#!/bin/sh

set -e
cmd="$1"
shift

case "$cmd" in
    perform)
        HOSTED_ZONE_ID="/hostedzone/YOUR_HOSTED_ZONE_ID"
        FILENAME=`date "+%Y%m%d%H%M%S"`.json
        DIR=/path/to/json_dir/

        # 環境変数チェック
        if [ -z "$domain" ] || [ -z "$validation" ]; then
                echo "Undefined environment variable"
                exit 1
        fi

        # 設定用jsonファイルを書き出し
        cat <<EOT > $DIR$FILENAME
{
  "Changes": [
    {
      "Action": "UPSERT",
      "ResourceRecordSet": {
        "Name": "_acme-challenge.$domain",
        "Type": "TXT",
        "TTL": 60,
        "ResourceRecords": [
          {
            "Value": "\"$validation\""
          }
        ]
      }
    }
  ]
}
EOT

        # jsonファイルをアップロードしてTXTレコードを追加
        aws route53 change-resource-record-sets --hosted-zone-id "$HOSTED_ZONE_ID" --change-batch file://$DIR$FILENAME

        # DNS反映待ち
        sleep 60
        ;;
    *)
        ;;
esac

使い方

renew.shをcronに登録して毎月1回実行すれば更新されるはず。

説明

certbot-external-authってやつがDNS-01のチャレンジを行うときに色々なタイミングでフックしてくれます。
そのフックを利用してチャレンジ前にTXTレコードを書き換えて認証してもらうって流れです。

どのタイミングでフックされるかや、どういう環境変数を渡してくれるかは下記を参考にしてください。
github.com

hook.shの方はどのフックタイミングかを見極めてperformのときにTXTレコードを書き換えます。
hosted_zone_idはRoute53にアクセスしてHosted zonesを見ればわかります。
f:id:ichiy:20180730145427p:plain

awsコマンドでRoute53の変更を行うにはjsonを使うので、設定用jsonを書き出します。
書き出したjsonawsコマンドでアップロードします。
チェレンジ前にDNSの設定が反映されてる必要があるのでTTLの60秒待ちます。

その後はcertbotが勝手にやってくれます。
最後にWebサーバーを再起動すれば新しい証明書になります。

注意点

本番でやる想定なので「--keep-until-expiring」というオプションをつけてます。
これは証明書の更新が必要ない場合は発行しないというオプションです。

証明書の有効期限にかかわらず発行させる場合は「--force-renewal」というオプションをつけます。
このオプションをつけて実行する場合Let's Encryptのレート制限に引っかからないように注意しましょう。
Rate Limits - Let's Encrypt - Free SSL/TLS Certificates

法律の刑罰を比較出来るサービスを作った

compalex.site

きっかけはなんかの過労死ニュースで見た、企業への罰金が50万だったという記事。
もちろん損害賠償があるんだけど法律的には罰金50万で済むということに衝撃を受けた。

それと同時期に漫画村とかで著作権違反のことが話題になってたときに調べたら、こちらは10年以下の懲役または1000万円以下の罰金だそうな。

つまり間違って人が死んじゃうより違法アップロードとかしたほうがめちゃくちゃ罪が重いということ。
どういう経緯があってこういう法律になったのかはわからないけど、直感的にはおかしくないか?これを比較出来るサービスを作ったら面白くないか?
ということで作りました。



技術的な話

特に面白い・新しい技術は使ってないです。

イーガブっていう政府がやってるサイトから法律をダウンロード出来ます。
XML形式なんでパースしてDBに突っ込みました。結構XMLの中身のツリーがバラバラで苦労しました。

環境は、さくらのVPS1台にPHPとVue.jsで作って、前段にCloudFlare噛ましてるだけです。
CSSもベタで書いてます。
驚くほど枯れた技術しか使ってないですが、自分一人で早く、かつお金をかけずに作ろうと思うとこんな感じ。

UIは自分なりに頑張ったつもりだけどデザインの基礎を知らんのでわからんです。

ヘッドレスChrome+Selenium+Pythonでファイルダウンロード

業務に必要なレポートを毎日ダウンロードして加工して別レポート作成する、
みたいな作業を自動化して欲しいとのことでSeleniumでサクッと作ろうと思ったんですが、ヘッドレスChromeだとデフォルトではファイルダウンロードができなくて半日費やしました。

その時の解決方法のメモ。

環境

CentOS7.2
Python2.7.5 (小学生に笑われるやつ)
Selenium
Google Chrome 66.0.3359.117

現象

vaaaaaanquish.hatenablog.com
上記の記事を参考にさせてもらって普通にページの取得とかはできたんですが、ダウンロードボタンを押しても何も起こりませんでした。
正確に言うと、処理の時間としてはダウンロードしてるのとほぼ同じ時間かかっているものの実際のファイルはどこにも無い、かつエラーも出てないという現象でした。

CentOSで使ってたソースをMacに持ってきてヘッドレスじゃないChrome+Seleniumだと普通にダウンロード出来たのでヘッドレスChrome特有の問題かなと当たりをつけました

結論 & 解決

調べてみるとヘッドレスChromeではセキュリティのためデフォルトでファイルのダウンロードが出来ないようになってるみたいでした
stackoverflow.com

上記のstackoverflowに載ってる解決方法とほぼ同じなんですが以下の様にすることでファイルをダウンロード出来ました。
以下の例はヘッドレスChromeChromeをダウンロードしてます。

# -*- coding: utf-8 -*-
from selenium import webdriver
from time import sleep

chromeOptions = webdriver.ChromeOptions()
chromeOptions.add_argument('--headless')
chromeOptions.add_argument('--window-size=1280,1024')

driver = webdriver.Chrome(chrome_options=chromeOptions)

# ヘッドレスChromeでファイルダウンロードするにはここが必要だった
driver.command_executor._commands["send_command"] = ("POST", '/session/$sessionId/chromium/send_command')
driver.execute("send_command", {
    'cmd': 'Page.setDownloadBehavior',
    'params': {
        'behavior': 'allow',
        'downloadPath': './' # ダウンロード先
     }
})

# ヘッドレスChromeでChromeをダウンロードしてみる
driver.get('https://www.google.co.jp/chrome/index.html')
sleep(3)

# Chromeをダウンロードボタンをクリック
driver.find_element_by_css_selector('#marquee > a').click()
sleep(1)

# 同意してインストールボタンをクリック
driver.find_element_by_css_selector('#eula-accept').click()
sleep(15)

driver.close()


ヘッドレスChromeでファイルダウンロードできた!
ちなみにChrome62から追加されたっぽい?機能なのでそれ未満はダメかもです

AMP対応するまでに失敗したこと

自社サービスをAMP対応したので、その最中に失敗した事のメモです。

AMPとは

www.ampproject.org


失敗したこと

  • レスポンシブで縦幅固定の枠に画像を表示したいなら amp-img は使わない
  • サードパーディで専用のコンポーネントがある場合がある
  • #development=1 は完璧ではない
  • AMPページへのリンクはPCページに置かないといけない


レスポンシブで縦幅固定の枠に画像を表示したいなら amp-img は使わない

AMPページで画像を表示したい場合amp-imgという専用のタグを使います。
画像をレスポンシブで表示したい場合は「layout="responsive"」を指定します。
このlayoutを設定しちゃうとwidthとかheightとかガン無視して表示されちゃいます。
この状態でCSSをいじってもどうにもなりませんでした。

解決策はamp-imgを使わずに CSSでbackgroun-imageを使うことです。
HTML

<div class="responsive-imge"></div>


CSS

.responsive-imge {
  width: 100%;
  height: 200px;
  position: relative;
  box-sizing: border-box;
  background-size: cover;
  background-repeat: no-repeat;
  background-position-x: 50%;
  background-position-y: 50%;
  background-image:url(https://ampbyexample.com/img/amp.jpg);
}

こんな感じです。よくある手法ですね。
ただAMP対応しよとすると、画像はamp-imgを使わなきゃ駄目って記述が散見されるので、てっきりCSSで指定は出来ないもんだと思いこんでました。


サードパーディで専用のコンポーネントがある場合がある

これはリファレンスちゃんと読めって結論に至るんですが・・・。
下記のページにはAMP専用のタグとコンポーネントが羅列されてます

Accelerated Mobile Pages Project

YoutubeInstagram見たいな大きい所以外にも色々使えるコンポーネントがあります。
規約で保存が禁止されてるサードパーティAPIが多いので、javascriptを使えないAMPではこういうコンポーネントを使わないと表示できません。
これを知るまでは頑張ってサーバーでAPI取得してcron回して作ってました。


#development=1 は完璧ではない

チュートリアルでAMPのページを確認するときはChromeでURLに「#development=1」を付けてDeveloperToolsを使って確認しろと書いてあります。
ここで出たエラーを修正すればOKだよ見たいな書き方してますが、このモードは完璧ではないです。(今後解消されるかも)
このモードで修正してOKが出ても、実際に配信するとエラーが出ることがあります。

ちゃんとしたバリデーションは下記の2つのツールを使って検証する必要があります。

https://validator.ampproject.org/
構造化データ テストツール

前者はAMP記法として正しいかをチェックしてくれます。
後者は構造化データが正しいかをチェックしてくれます。

<script type="application/ld+json">
~~~
</script>

こんな感じで構造化データをマークアップしてるやつです。
この2つのツールでOKをもらわないとエラーと認識されて掲載されません。

AMPページへのリンクはPCページに置かないといけない
<link rel="amphtml" href="http://amphtml/index.html">

こんなやつです。

これに一番ハマりました。
書いてある通りなんですが、AMPページへのリンクはPCで見るページに設置しないとGoogleには認識してもらえません。
レスポンシブサイトとかですと問題ないですが、PCとスマホとかでテンプレートを切り分けてる場合は注意が必要です。

僕が確認した限りではリファレンスにはPCページに設置してね見たいな記述は無いです。
バイルで見たときに早く表示するためのものなのに何故!と思いましたね。



これからAMP対応しようと思う方の参考になれば。

PHPでベースラインJPEGをプログレッシブJPEGに変換する

fa-works.com

上記の記事を見るまでそもそもベースラインjpegプログレッシブjpegの2つがあること自体知らなかった・・・。
とにかくプログレッシブjpegのほうが有利なようなのでPHPでベースラインjpegプログレッシブjpegに変換してみます。


ほぼここに書いてある通りなんですが

// 画像のインスタンスを作成します
$image = imagecreatefromjpeg('path/to/test.jpg');

// インターレースを有効にします
imageinterlace($image, true);

// 画像を保存します
imagejpeg($image, 'path/to/progressive_test.jpg');
imagedestroy($image);

これでプログレッシブjpegとして保存出来ます。

PAKUTASOさんのこの画像で試してみます。

元々の画像(ベースラインjpeg
https://tty-i.com/assets/img/ISHIIMG_7059.jpg

変換後の画像(プログレッシブjpeg
https://tty-i.com/assets/img/progressive_ISHIIMG_7059.jpg


元々の画像は上から徐々に読み込まれてるのに対し、変換後の画像はすぐに全体が読み込まれ徐々にキレイになっていきます。