技術的な話題 に関する投稿を表示しています

fail2ban で ban した IP が firewalld に登録されているか確認する

設定の仕方によっても表示される内容は違う、とおもう。
これは sshd を firewallcmd-ipset の action にしたときのもの。

$ sudo firewall-cmd --direct --get-all-rules
ipv4 filter INPUT 0 -p tcp -m multiport --dports ssh -m set --match-set fail2ban-sshd src -j DROP

$ sudo ipset list
Name: fail2ban-sshd
Type: hash:ip
Revision: 4
Header: family inet hashsize 1024 maxelem 65536 timeout 86400
Size in memory: 312
References: 1
Number of entries: 2
Members:
xxx.xxx.xxx.xxx timeout 86317
xxx.xxx.xxx.xxx timeout 86316

WordPressのメタデータをつか

WordPress にはお手軽便利に関連データを出し入れできる、メタデータとよばれる仕組みがある。
メタデータ API - WordPress Codex 日本語版

これを使うと、例えば、投稿に対するリアクションをつけるようにしたり WordPress の標準にはないデータを乗せることができるようになる。

ちなみに、似たような…というか大体同じ認識のカスタムフィールドなるものもある。これもメタデータ。こっちは登録時?に設定できるようなもの、と思っている。管理画面で入れると、テンプレート側でもその値に合わせて何か表示できる、みたいな。
カスタムフィールドの使い方 - WordPress Codex 日本語版

メタデータにいれるものは serialize に通せるものならなんでもよいようで、配列はもちろん、__sleep()と__wakeup()メソッドを実装していればクラスでも問題なくいけるっぽい。たぶん。
add_metadata() | Function | WordPress Developer Resources
PHP: serialize - Manual

で、この post_meta 系の関数、それそのままだとちょっと大変になる場合があったので簡単にラッパークラスを作った。
wputil/PostMeta.php at master · sters/wputil

こんな感じでつかえる。

$reaction = new PostMeta(get_the_ID(), 'reaction');
echo 'Smile! ' . $reaction->get()['smile'] . ' times';

前後の記事

Next:
Prev:

PHPのクラスをたどって図で出すツール

あまり知らないクラスのコードを読んでいると、あっちのクラスにいったり、こっちのクラスに行ったり、コレ何だったっけ?、がたくさんでて大変。で、それを説明するのも大変。
というわけで、サクッと簡単にクラスの繋がり方を可視化するような何かを書いた。

GitHub - sters/php-class-graph

書いたはいいんだけど、あんまり考えずにえいやーで作ったのでこれ以上拡張出来ない感じになってしまった…
テストもちょっと書き方な〜〜〜となってしまったのでリファクタ余地は大いにあり…。

PHP のコードをパースするために nikic/PhpParser を使っている。
GitHub - nikic/PHP-Parser: A PHP parser written in PHP

それを使ってクラス呼び出しっぽいのを辿っている。このあたり。

php-class-graph/Visitor.php at master · sters/php-class-graph · GitHub

これで、あるクラスから、どのクラスを呼ぼうとしているかを記録。その記録を整理して図形データとして出力できるようになっている。

クラスっぽいのを辿る際に、クラス名からファイル名を解決するために、プロジェクトルートディレクトリを指定し、その composer autoloader を読み込んでいるのがひとつ工夫したポイント。このあたり。
php-class-graph/SourceList.php at master · sters/php-class-graph · GitHub

おしごとコードや自分のいくつかで試したけど、ちゃんとできてそう…?
たとえば sters/cakephp3-aws-s3-datasource: AWS S3 datasource in CakePHP3 でやるとこういう。いやでもどこからともなく親ノードが発生するのはちょっと違う気がするなあ…、間違ってそう…。

このまま使うと、開始地点から永遠に深ぼってしまうので、どこまで辿るのが指定したほうがいい vendor 下は無視するとか。といったのも example ディレクトリに作ってみたのでお試しどうぞ。

CREATE TABLE 文から Markdown のテーブル表記をつくる

ドキュメントにテーブルの説明を書くのがちょっと苦しかったので CREATE TABLE 文から Markdown のテーブル表記を作れるようにした。

GitHub - sters/createtable2Markdown

CREATE TABLE 文をパースできれば勝てるので別に MySQL 限定とかではない。特殊すぎる記載じゃなければ動くと思う。

GitHub - xwb1989/sqlparser: SQL Parser implemented in Go

これがすごくて、若干の挙動が怪しい不要な要素をスルーするようにしてるけれども、あとはパース結果を組み立てる以外なにもしてない。

標準入力で SQL な文字列を受け取って、標準出力で Markdown が出せるようにしているので、ダンプしたものをそのまま流したり、クリップボードを活用したり、コマンドラインでサクサク使うのにめちゃいい。
Readme にも書いたけどこういうことができる。

mysqldump -u database_user -p -d -n --compact database_name | createtable2Markdown

ごみばこいんのjQuery依存をなくそうとした

ごみばこいんの jQuery 依存をなくそうとした。
背景としては jQuery の読み込みに 30KB くらい使っていて、でも jQuery をそんなに大活用しているわけでもなくて。
じゃあいらんか、と。

zepto にすることも考えた。が、せっかくなのでまるっと消すことにした。
GitHub - madrobby/zepto: Zepto.js is a minimalist JavaScript library for modern browsers with a jQuery-compatible API


自分で書いていたやつから jQuery な記載を消していった。
$は document.querySelector とか document.querySelectorAll とかで。
XHR がちょっと面倒だなあ…とか思ったけど fetch API あるやん!で解決した、便利。

Fetch API - Web API | MDN

あとは Chrome の DevTool でコンソールで試しながら変数あるかなあとか見たりしながら書いてた。


画像の表示に使っていた lity という lightbox なライブラリが jQuery 依存していたので別のものに切り替える必要があった。

GitHub - jsor/lity: Lightweight accessible and responsive lightbox.

cdnjs を lightbox で探していたらよさそうなのがあった。
luminous という lightbox なライブラリで lity と容量もあまりかわらない(3KB くらい)っぽい。

GitHub - imgix/luminous: A simple lightweight no-dependencies JavaScript lightbox.

imgix 画像加工ができる CDN サービスをやっている会社が提供している lightbox なライブラリとのこと。

imgix • Real-time image processing and image CDN

余談にはなるが日本で同様のものだと ImageFlux が有名だろうか。むしろ画像 CDN でパラメータで扱うのはこれしかしらなかった。。
画像変換サービス ImageFlux|さくらインターネット


あと jQuery に依存していたわけではないが Chrome の DevTool から Performance タブで Load イベントまでの様子を測ってみると Highlight.js がそこそこに時間を使っていてなんとかしたいなあと。

GitHub - highlightjs/highlight.js: Javascript syntax highlighter

これまたいくつか調べてみると prism なるものが見つかった。
テーマを切り替えることもできるし、様々な言語のハイライトに対応しているし、プラグインで拡張できるし、でっかいハイライトするなら WebWorker を使っての asyncRender も有効に使えそうな感じがする。

GitHub - PrismJS/prism: Lightweight robust elegant syntax highlighting.

実際にいれてみると、言語検出機能がないので、特に言語を指定せずにざっくり使っていた部分についてハイライトが効かなかった。のでそれっぽいものを自分でざっくり書いた WordPress で使うので、投稿についたタグの情報とか、コードの中身とかからざっくり判断でわりかし十分。多分間違っていても、そんなに気にならない。


そのほか、処理順を入れ替えたり、遅いやつをもっと遅延させて影響なくしたり requestAnimationFrame を使ってみるなどした。
変更する前と比べて、表示されるまでがだいぶ早くなった、と思う。(主観)

Google App Script で Gmail の掃除をする

久しぶりに Google App Script を見たらものすごいアップデートされているような気がする。

こんな管理画面的なものあったっけ…。。
自分のプロジェクト - Apps Script

本題 Github からくる通知メールや、各種メルマガ、その他さまざまな通知系のメールが Gmail の受信箱の大半を圧迫しており、まともに見れなくなってしまった。
フィルタを設定したものの、すでに受信したメールを一括削除することが、どうにも Gmail 上からはうまく動いていないっぽい。

というわけで Google App Script を使って不要なメールをドカッと削除する。

こんな感じのお手軽な関数で十分。

function myFunction() {
  var query = 'label:xxx subject:yyy'; // ここに任意の検索クエリを書く
  while(true) {
    var threads = GmailApp.search(query, 0, 500);
    if (threads.length <= 0) {
      return
    }
    for (var i = 0; i < 5; i++) {
      GmailApp.moveThreadsToTrash(threads.slice(i*100, i*100 + 100));
    }
  }
}

API ドキュメントはここ。
Class GmailApp | Apps Script | Google Developers

初回だけ手動で実行し Gmail に関する権限を取得する必要がある。

あとは自動トリガーに設定 2 時間に 1 回実行くらいで。もっと早い頻度で動かすこともできるのだが、早すぎると Gmail の API で制限に引っかかってしまう。気長にやるのがよさそう。
しばらく放置したのちに見るといらないメールがすべて消え去ってすっきり。

htpasswdをちゃんと使う

なんだか htpasswd で長いパスワードを設定してもちゃんと機能していないっぽい…、いやいや Basic 認証だから短い文字列しか使えない、なんていう制約はない、のでは…?ということで調べた。結果、ドキュメントかコマンドのヘルプ表示に書いてあることがすべてだった。

まずは、おや??と思ったサーバが使っていたのが Apache 2.2 だったので、そちらのドキュメントを見てみる。

htpasswd - Manage user files for basic authentication - Apache HTTP Server Version 2.2

-m
Use MD5 encryption for passwords. This is the default (since version 2.2.18).

-d
Use crypt() encryption for passwords. This is not supported by the httpd server on Windows and Netware and TPF.
This algorithm limits the password length to 8 characters. This algorithm is insecure by today's standards. It used to be the default algorithm until version 2.2.17.

ち、ちしきもソフトウェアも更新されていない。。。
古い httpd を使っていたので、そのまま更新してパスワードファイルを作り直し。

後述するがデフォルトになっている MD5 を利用するのがいいっぽい。

$ sudo yum update httpd

$ htpasswd -inm foo-user

ついでに Apache 2.4 を使っている別サーバの様子も確認。

htpasswd - Manage user files for basic authentication - Apache HTTP Server Version 2.4

こちらは bcrypt が使えたり SHA が insecure だよといった情報も増えている。

-m
Use MD5 encryption for passwords. This is the default (since version 2.2.18).

-B
Use bcrypt encryption for passwords. This is currently considered to be very secure.

-C
This flag is only allowed in combination with -B (bcrypt encryption). It sets the computing time used for the bcrypt algorithm (higher is more secure but slower default: 5 valid: 4 to 17).

-d
Use crypt() encryption for passwords. This is not supported by the httpd server on Windows and Netware. This algorithm limits the password length to 8 characters. This algorithm is insecure by today's standards. It used to be the default algorithm until version 2.2.17.

-s
Use SHA encryption for passwords. Facilitates migration from/to Netscape servers using the LDAP Directory Interchange Format (ldif). This algorithm is insecure by today's standards.

なんで SHA が insecure なのかは下の Security Considerations トピックに書いてある。
SHA を指定するとパスワードに対してソルトが設定されず毎回同じ値になるのがよろしくないとのこと。

というわけでこちらの Apache 2.4 がいるサーバでは bcrypt を使う、ついでにコストパラメータもちょっと上げてみる。

$ htpasswd -nb -C 10 bar-user
...

実際に各種作って比べるとこんな感じ。
いままでのそれよりだいぶ雰囲気がちがう。。。

$ cat htpasswd_test.sh
#!/bin/sh

password=test-foo-bar

echo 'raw'
echo $password | htpasswd -inp test
echo 'crypt()'
echo $password | htpasswd -ind test
echo 'sha'
echo $password | htpasswd -ins test
echo 'md5'
echo $password | htpasswd -inm test
echo 'bcrypt()'
echo $password | htpasswd -inB test
echo 'bcrypt() with cost=10'
echo $password | htpasswd -inB -C 10 test


$ htpasswd_test.sh

raw
Warning: storing passwords as plain text might just not work on this platform.
test:test-foo-bar

crypt()
Warning: Password truncated to 8 characters by CRYPT algorithm.
test:19KcNn65a0Ovo

sha
test:{SHA}FsxBAWUFKrRlyJof3T+q9X6QfNE=

md5
test:$apr1$/nZjVA4i$5.tcV5puEwC6t0xQzuBfK1

bcrypt()
test:$2y$05$W4354fiJgesqn4/EXJhOsemadtHoX.PevbZA76fptomx09NAvbQTa

bcrypt() with cost=10
test:$2y$10$A6DDhPBzUOQA3aRui6acK.IaRpe0bqL9m5dcER4Gp0Bwk/SzEYhNa

Google Cloud Build + kaniko を試す

Cloud Build のドキュメントを見ていたら、ビルドを高速化するのに kaniko を使うソリューションがあるよ、と書かれていたので、試してみる。

Kaniko キャッシュの使用 | Cloud Build | Google Cloud
GitHub - GoogleContainerTools/kaniko: Build Container Images In Kubernetes

kaniko っていうのはすごいざっくりと理解したのは Docker なしに k8s 上で Docker イメージをビルドできるよ、というもの。
で、そのとき対象の Dockerfile に関して、レイヤーごとに全部キャッシュを取ってくれるので、差分があった部分以降をビルドするだけになり docker build よりも高速化できる、という話っぽい。通常 docker build するときも、レイヤーごとの情報を持っていて、変わった部分以降をビルドすることができる。できるが、そのキャッシュのバックエンドとか、マルチステージにしたときのビルドステップの進め方とかをいい感じにしたっていう話なんじゃあないかなあ。(それ以上の理解はできていない)

さて、ビルドする対象となる Dockerfile はこういう具合。
マルチステージで go で書かれたアプリケーションサーバのビルド、からのそのアプリケーションコンテナのイメージを作る。

FROM golang:1.12 as builder
ARG GITHUB_TOKEN
ARG VERSION
ENV GO111MODULE=on

WORKDIR /go/src/github.com/foo/bar

COPY go.mod go.sum ./
RUN echo "machine github.com login ${GITHUB_TOKEN}" > ~/.netrc
RUN go mod download

COPY . ./
RUN CGO_ENABLED=0 GOOS=linux go install -v -ldflags "-X main.version=${VERSION}" github.com/foo/bar/cmd/server

FROM alpine:latest

RUN apk add --update --no-cache ca-certificates tzdata
COPY --from=builder /go/bin/server /bin/server
CMD ["/bin/server"]

それでもとの cloudbuild.yaml はこうなっている。
変数をバケツリレーしていく。

steps:
- name: gcr.io/cloud-builders/docker
  args:
  - build
  - -t
  - $_IMAGE
  - --build-arg
  - GITHUB_TOKEN=$_GITHUB_TOKEN
  - --build-arg
  - VERSION=$_VERSION
  - -f
  - $_DOCKERFILE_PATH
  - .
images:
  - $_IMAGE

で、これを kaniko に対応させるとこうなる。

steps:
- name: 'gcr.io/kaniko-project/executor:latest'
  args:
  - --destination=$_IMAGE
  - --dockerfile=$_DOCKERFILE_PATH
  - --cache=true
  - --cache-ttl=6h
  - --build-arg=GITHUB_TOKEN=$_GITHUB_TOKEN
  - --build-arg=VERSION=$_VERSION

その他 GCP 設定や Dockerfile などなどはそのままで実行することはできた。

できたが結果はちょっと微妙で、めっちゃ早くなったかというとそうでもない。。。

ビルド時、主に時間がかかるのが go mod download と go install (実際は go build が時間かかる)のふたつで、アプリケーションのコードを変えただけの場合は go mod のキャッシュが効いてくれるが、レイヤーを持ってくる(?)のにやっぱり時間がかかるので、プラスマイナスみて、ややマイナス、みたいな状態。。

具体的な時間としては kaniko を使う前の元々ので 5 分くらい。 kaniko してフルキャッシュ状態(再実行しただけ)で 1-2 分。 go mod のキャッシュが効いてて 2-4 分くらい。キャッシュがまったくなし = go.mod が変更された場合で 5-7 分くらい。ちゃんと測ってなくて数回ためした程度で、実はもっと早いかもしれない。そこは試す余地がある。

あとは単純にお財布で戦って、マシンサイズを上げて強い子にすると早くなるんじゃないかなあ~。


そもそも、コンテナイメージの作成を早くするのが目的なら CI 上でクロスコンパイルしたバイナリを Docker にいれちゃうのがよさそう。
というか CI 上でも golang:1.12 なイメージでビルドするならば別にクロスコンパイルでもないのか。

マルチステージな Dockerfile でアプリケーションのビルドもまるっと!と Cloud Build でビルドすると、余計なゴミが入りにくい、という良い点はありそう。

とはいえ、ワークスペースをアタッチしない、かつ go mod だけ共有してビルドしちゃえば Cloud Build でやるのとほぼ同じ環境になりそうな気がする。そうしてできたバイナリをコンテナ内に COPY して、コンテナレジストリに登録しちゃえばいいんじゃないかな。

WordPress 上で定義した API に nonce による制約を入れる

wp_verify_nonce() | Function | WordPress Developer Resources

wp_create_nonce() | Function | WordPress Developer Resources

wp_create_nonce をしたら nonce な値が生成されるが WordPress にログインされているユーザによって異なる、というのがちょっと気になった。未ログインだと未ログインユーザとして一緒の扱いになり、未ログインでも〜〜みたいな制約をつけようとしたらちょっと弱い感じがする。

例えばリクエスト元 IP(スマホなんかだとコロコロ変わるので難しい)だったり UserAgent(被る可能性が高い)だったり、なんとかフロントエンド、クライアント側にユニークになるような値を作ってもらって nonce を生成するのがよさそう。とはいえ、未ログインユーザとして出来ること、持っている権限は、異なる未ログインユーザであっても基本的には同じになるだろうので、なんか、まあ、やりたいことに合わせて、よしなにしたらいいんじゃないかなあ………。

前回の API を作る記事にあわせて nonce を作って、検証する API も書いてみるとこんな感じ。

class PostAPI
{
    private $namespace;
    private $nonceKey;

    public function __construct(string $namespace, string $nonceKey)
    {
        $this->namespace = $namespace;
        $this->nonceKey = $nonceKey;
        $this->registerNonce();
    }

    protected function registerNonce()
    {
        register_rest_route(
            $this->namespace,
            'nonce',
            [
                'methods' => 'POST',
                'callback' => function(WP_REST_Request $req) {
                    $response = new WP_REST_Response;
                    $response->set_status(200);
                    $response->set_data([
                        'result' => 'success',
                        'nonce' => wp_create_nonce($this->nonceKey),
                    ]);
                    return $response;
                },
            ]
        );
    }

    public function register(string $endpoint, \Closure $handler)
    {
        register_rest_route(
            $this->namespace,
            $endpoint,
            [
                'methods' => 'POST',
                'callback' => function (WP_REST_Request $req) use ($handler) {
                    if (!wp_verify_nonce($req['nonce'], $this->nonceKey)) {
                        $response = new WP_REST_Response;
                        $response->set_status(401);
                        $response->set_data([]);
                        return $response;
                    }

                    return $handler($req);
                },
            ]
        );
    }
}

add_action('rest_api_init', function() {
    $api = new PostAPI('myapi/v1', 'myapi');
    $api->register('/say', function(WP_REST_Request $req) {
        $message = $req['message'];

        $response = new WP_REST_Response;
        $response->set_status(200);
        $response->set_data([
            'result' => 'success',
            'message' => $message,
        ]);
        return $response;
    });
});

うん、うごいていそうだ。 

$ curl -X POST -H 'Content-Type:application/json' -D - '.../wp-json/myapi/v1/say?message=hello'
HTTP/2 401
...

[]


$ curl -X POST -H 'Content-Type:application/json' -D - '.../wp-json/myapi/v1/nonce'
HTTP/2 200
...

{"result":"success","nonce":"a0519f891d"}


$ curl -X POST -H 'Content-Type:application/json' -D - '.../wp-json/myapi/v1/say?message=hello&nonce=a0519f891d'
HTTP/2 200
...

{"result":"success","message":"hello"}

前後の記事

Next:
Prev:

WordPress 上で API を新たに定義する

WordPress で API を定義するには WordPress の REST API の仕組みに乗れる register_rest_route というのを使う。
メソッドも処理もその他差し込み動作も指定し放題。

テンプレート下に php おいて$_POST でできます!はまちがい。これだと WordPress がもってる様々な恩恵に授かれない。

ちなみに環境によっては WordPress のプラグインで API を指定して無効にしたりするものが入っているかもしれないが register_rest_route した API はちゃんと出てくる、はず…。

さてこの register_rest_route の公式説明をみると、プラグイン名で名前をつけろ、とある。
register_rest_route() | Function | WordPress Developer Resources

特に配布するようなものでないとか規模が小さいとかなら api/v1 とかつければいいと思う。
機能や振る舞いに合わせてちゃんと名付けするのがいいとは思うけど。

というわけで基本形の使い方はこういう感じ。
クロージャーでシュッと書けばいいし、別にラップするクラスを作るなどしてよしなにしてもいいのではと思った。

add_action('rest_api_init', function() {
    register_rest_route( 'myapi/v1', '/foo', [
        'methods' => 'POST',
        'callback' => function(WP_REST_Request $req) {
            $message = $req['message'];

            $response = new WP_REST_Response;
            $response->set_status(200);
            $response->set_data([
                'result' => 'success',
                'message' => $message,
            ]);
            return $response;
        },
    ]);
});

クラスで簡単にラップする、例えばこういう感じ。やり方は無限。

class PostAPI
{
    private $namespace;

    public function __construct(string $namespace)
    {
        $this->namespace = $namespace;
    }

    public function register(string $endpoint, \Closure $handler)
    {
        register_rest_route(
            $this->namespace,
            $endpoint,
            [
                'methods' => 'POST',
                'callback' => $handler,
            ]
        );
    }
}

add_action('rest_api_init', function() {
    $api = new PostAPI('myapi/v2');
    $api->register('/foo', function(WP_REST_Request $req) {
        $message = $req['message'];

        $response = new WP_REST_Response;
        $response->set_status(200);
        $response->set_data([
            'result' => 'success',
            'message' => $message,
        ]);
        return $response;
    });
});

リクエストパラメータを受け取るには、$req が WP_REST_Request になっていて、これを使う。
WP_REST_Request | Class | WordPress Developer Resources

ArrayAccess の機能を持っていて、パラメータをよしなに取得できるようになっていてお手軽度が高い。

そしてレスポンスは WP_REST_Response を使う。
これもお手軽度が高く、勝手に json フォーマットに変換してくれたりする。
WP_REST_Response | Class | WordPress Developer Resources

こうして作った API は、とくに認証や制限を書けていないならすぐにでも curl や Javascript での Ajax から呼び出すことができる。

$ curl -X POST -H 'Content-Type:application/json' '{{WordPress url}}/wp-json/myapi/v1/foo?message=its_query'
{"result":"success","message":"its_query"}

$ curl -X POST -H 'Content-Type:application/json' -d '{"message":"its json"}' '{{WordPress url}}/wp-json/myapi/v1/foo'
{"result":"success","message":"its json"}

こういう具合。

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15