ikasama over technology

忘れたくないことを忘れないために

AWS EC2 に OpenVPN を構築して Chromebook から接続するときにハマったポイント

OpenVPN 2.4によるVPN接続環境をAWSで構築する | Developers.IO

Chromebook から OpenVPN(TLS-Auth、LZO圧縮有り)へ接続してみた

基本はこの2つのあわせ技です。 作業内容のほとんどはそれぞれの記事を参考にしてもらえば。

個人的にハマったところ

インターネット向けのトラフィックを全部 OpenVPN 経由にするとき

OpenVPNserver.conf に以下のような設定を書きます。

push "redirect-gateway def1 bypass-dhcp"
push "dhcp-option DNS 192.168.1.1"

このとき、dhcp-option DNS で指定する DNS サーバのアドレスは、 OpenVPN サーバから到達できるものを指定してください。 何故なら、名前解決も全部 OpenVPN サーバから実行するからです。 最初これがわかってなくて、無限に名前解決に失敗してインターネットに出られなくなった。

AWS EC2 上で構築するなら、AmazonProvidedDNS を指定するのが良いかもしれません。 たとえば VPC のネットワークが 172.30.0.0/16 だったら、AmazonProvidedDNS は 172.30.0.2 になります。 固定で 169.254.169.253 も使えるみたいですけど、試してません。 詳しくはこちら↓。

docs.aws.amazon.com

Chromebook から接続するときはパスワード欄の入力が必須

クライアント証明書をパスワード設定せずに作成した場合でもパスワード欄に何かしらの文字列の入力が必要です。 空欄だと接続しに行ってくれません。

サーバー側の設定と ONC ファイルの設定を合わせる

ONC ファイル作るのが面倒だったんでベースの作成にこれを使いました。

unfix.org

ただ、2018/05/28 時点でそのまま使うのはちょっと微妙で、いくつか手を入れないといけなかったです。

  • "Proto": udp と出力されるので "Proto": "udp" に直す必要がある。 ( udp にダブルクォーテーションがなくて JSON Syntax Error になる)
  • クライアント証明書が埋め込まれてしまっているので、デバイスにインポートしたものを使う場合は要修正
  • "Auth": "SHA512" がサーバ側のデフォルト設定と異なる (サーバ側に auth SHA512 を設定するか、ONC ファイルから "Auth": "SHA512" を消す)

Source IP アドレスを OpenVPN サーバのものにしたい

IP Masquerade しましょう。

$ sudo iptables -t nat -A POSTROUTING -s 10.8.0.0/24 -o eth0 -j MASQUERADE

以上、楽しい VPN ライフを!

他の参考サイト

OpenVPN on ChromeOS Documentation (publish)

Ubuntu 14.04 で OpenVPN - BLOG EX MACHINA

Chromebook で iso イメージを USB フラッシュメモリに焼く

背景

  1. メインマシン (Windows10) がクラッシュを繰り返してとうとうレジストリ破損で起動しなくなった
  2. OS クリーンインストールをしようと思ったけどインストールメディアがない
  3. サブマシンである Chromebook でインストールメディアを作成しよう! ← いまここ

確認した動作環境

手順

1. Ctrl + Alt + T で ターミナルを開く

f:id:ikasamak503:20180430113837p:plain

2. shell でシェルを起動

crosh> shell
chronos@localhost / $ 

3. df で USB フラッシュメモリっぽいのを見つける

chronos@localhost / $ df
Filesystem              1K-blocks    Used Available Use% Mounted on
/dev/root                 1640868 1446944    193924  89% /
devtmpfs                  1031524       0   1031524   0% /dev
tmp                       1031736     176   1031560   1% /tmp
run                       1031736     476   1031260   1% /run
shmfs                     1031736   25656   1006080   3% /dev/shm
/dev/mmcblk0p1           10801712 9147840   1085452  90% /mnt/stateful_partition
/dev/mmcblk0p8              11760      24     11412   1% /usr/share/oem
/dev/mapper/encstateful   3185468  135304   3033780   5% /mnt/stateful_partition/encrypted
media                     1031736       0   1031736   0% /media
none                      1031736       0   1031736   0% /sys/fs/cgroup
imageloader               1031736       0   1031736   0% /run/imageloader
/dev/dm-2                   12476   12476         0 100% /run/imageloader/PepperFlashPlayer
tmpfs                     1031736       4   1031732   1% /run/arc/oem
tmpfs                     1031736       0   1031736   0% /run/arc/sdcard
tmpfs                     1031736       0   1031736   0% /run/arc/obb
tmpfs                     1031736       0   1031736   0% /run/arc/media
tmpfs                     1031736       0   1031736   0% /run/arc/adbd
/dev/fuse                10801712 9147840   1085452  90% /run/arc/sdcard/default/emulated
/dev/fuse                10801712 9147840   1085452  90% /run/arc/sdcard/read/emulated
/dev/fuse                10801712 9147840   1085452  90% /run/arc/sdcard/write/emulated
passthrough               1031736       0   1031736   0% /run/arc/media/removable
/dev/sda                  3704296 3704296         0 100% /media/removable/RHEL-7.3 Server.x86_64

今回は /dev/sda がそれっぽい。前に RHEL7 の起動ディスクにしたやつだった。

4. dd で iso ファイルを焼く

$ sudo dd if=./Win10_1709_Japanese_x64.iso of=/dev/sda

5. お茶でも飲みながら気長に待つ

CakePHP を Docker 上の Nginx + PHP-FPM + MySQL にインストールして動かす

仕事でもりもりつかっている CakePHP ですが、ようやく自宅の環境にもインストールしてみました。

github.com

ハマったところ

intl PHP 拡張がなくて CakePHP のインストールにコケる

エラーメッセージ

$ docker-compose run --rm composer create-project --prefer-dist cakephp/app .
Current working directory: '/home/ikasamak/work/docker-cake'
Installing cakephp/app (3.5.1)
  - Installing cakephp/app (3.5.1)
    Downloading: 100%

Created project in my_app_name
Loading composer repositories with package information
Updating dependencies (including require-dev)
Your requirements could not be resolved to an installable set of packages.

  Problem 1
    - cakephp/cakephp 3.5.9 requires ext-intl * -> the requested PHP extension intl is missing from your system.
    - cakephp/cakephp 3.5.8 requires ext-intl * -> the requested PHP extension intl is missing from your system.
    - cakephp/cakephp 3.5.7 requires ext-intl * -> the requested PHP extension intl is missing from your system.
...

対策

composer, php-fpm イメージに intl PHP 拡張をインストールする

  • Dockerfile (抜粋)
RUN apk update && apk --update add icu-dev
RUN docker-php-ext-install pdo_mysql mysqli mbstring intl

注意: icu-dev パッケージがないと intl PHP 拡張のインストールに失敗するのでインストールしておく。

CakePHP インストール後にアクセスすると 404 エラーが返ってくる

原因

  • Nginx と PHP-FPM を別のコンテナで動作させている
  • Nginx の conf に try_files $uri =404; が設定されている

CakePHP のインストールガイドには Nginx の設定例が載っています。

https://book.cakephp.org/3.0/ja/installation.html#nginx

    location ~ \.php$ {
        try_files $uri =404;
        include fastcgi_params;
        fastcgi_pass 127.0.0.1:9000;
        fastcgi_index index.php;
        fastcgi_intercept_errors on;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    }

しかし、今回のように Nginx と PHP-FPM を別のコンテナで動作させている環境ではうまく動きません。 try_files $uri =404; は Nginx 上で該当のファイルが見つからなければ 404 エラーを返します。 PHP-FPM と別コンテナである Nginx 上には CakePHP のソースはないため、この設定が入っていると 404 エラーになってしまいます。

対策

  • Nginx の conf から try_files $uri =404; を消す

docker-compose で Error processing tar file(exit status 1): unexpected EOF が出るとき

こんなエラーに遭遇しました。

$ docker-compose up -d api
Building api
ERROR: Error processing tar file(exit status 1): unexpected EOF

軽く調べた感じだと、これという決定的な情報は見つからず。 ただどうやら、ワークディレクトリにパーミッションが変なファイルがあると発生するみたい。 今回の環境の場合、 volumes でマウントした MySQL のデータファイルが変なパーミッションで作成されていた。

$ ll -d db-data
drwxr-xr-x. 7 systemd-bus-proxy ssh_keys 4096 Mar 24 23:27 db-data

しかし、類似したパーミッションのファイルを手で作成してもエラーは再現しなかった。 前述のどのファイルが悪さしているのか、はたまた合計ファイルサイズでおかしくなるのか、よくわからないけど、とりあえずファイルを消すかファイルが読める状態にしてあげると動くようになった。

 $ sudo chown -R 1000:1000 db-data
[sudo] password for ikasamak:
$ ll -d db-data
drwxr-xr-x. 7 ikasamak ikasamak 4096 Mar 24 23:27 db-data
$ docker-compose up -d api
Building api
Step 1/10 : FROM php:5.6-apache
5.6-apache: Pulling from library/php
f2b6b4884fc8: Downloading [=============================>                     ] 31.35 MB/52.61 MB
8db887c45800: Download complete
6e0e41c52c70: Downloading [=============>                                     ] 20.49 MB/76.35 MB
(以下略)

MySQL で split, join したいとき

素の SQL で DB にパッチをあてる運用とかがあると*1、こんなことがあります。

  • SQL の条件に変数を使いたい
mysql> SELECT * FROM test;
+----+------+
| id | name |
+----+------+
|  1 | hoge |
|  2 | fuga |
|  3 | foo  |
|  4 | bar  |
+----+------+
4 rows in set (0.00 sec)

mysql> SELECT * FROM test WHERE name IN ('hoge', 'fuga');   # ←ここの IN の中とか!
+----+------+
| id | name |
+----+------+
|  1 | hoge |
|  2 | fuga |
+----+------+
2 rows in set (0.00 sec)
  • でも MySQL の変数は複数の値を持てない
mysql> SET @names = ('hoge', 'fuga');
ERROR 1241 (21000): Operand should contain 1 column(s)
  • そうだ、join して split しよう! ←いまここ

MySQLjoin, split はあるのか

join

  • CONCAT_WS を使います

https://dev.mysql.com/doc/refman/5.6/ja/string-functions.html#function_concat-ws

mysql> SELECT CONCAT_WS(',', 'hoge', 'fuga');
+--------------------------------+
| CONCAT_WS(',', 'hoge', 'fuga') |
+--------------------------------+
| hoge,fuga                      |
+--------------------------------+
1 row in set (0.00 sec)

split

  • 残念ながら、MySQL には split に相当する機能はないです
  • ただし、検索条件として使うなら FIND_IN_SET で代替できます
    • FIND_IN_SET の第二引数はカンマ区切りである必要があります
    • データにカンマが含まれているからパイプで繋いでたりしてるとアウトです、あきらめましょう

https://dev.mysql.com/doc/refman/5.6/ja/string-functions.html#function_find-in-set

mysql> SELECT FIND_IN_SET('fuga', 'hoge,fuga');
+----------------------------------+
| FIND_IN_SET('fuga', 'hoge,fuga') |
+----------------------------------+
|                                2 |
+----------------------------------+
1 row in set (0.00 sec)

mysql> SELECT FIND_IN_SET('foo', 'hoge,fuga');
+---------------------------------+
| FIND_IN_SET('foo', 'hoge,fuga') |
+---------------------------------+
|                               0 |
+---------------------------------+
1 row in set (0.00 sec)

これらを組み合わせて

mysql> SET @names = CONCAT_WS(',', 'hoge', 'fuga');
Query OK, 0 rows affected (0.00 sec)

mysql> SELECT @names;
+-----------+
| @names    |
+-----------+
| hoge,fuga |
+-----------+
1 row in set (0.00 sec)

mysql> SELECT * FROM test WHERE FIND_IN_SET(name, @names);
+----+------+
| id | name |
+----+------+
|  1 | hoge |
|  2 | fuga |
+----+------+
2 rows in set (0.00 sec)

できた!

*1:弊社です

Windows から Docker 実行時に鍵ファイルを渡して SSH したい

問題

$ winpty docker-compose exec proxy ls -l //key
total 4
-rwxr-xr-x    1 root     root          1692 Mar 17 20:42 id_rsa
$ winpty docker-compose exec proxy chmod 0600 //key/id_rsa
$ winpty docker-compose exec proxy ls -l //key
total 4
-rwxr-xr-x    1 root     root          1692 Mar 17 20:42 id_rsa

$ winpty docker-compose exec proxy ssh -i //key/id_rsa $STEP_SERVER_USER@$STEP_SERVER
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@         WARNING: UNPROTECTED PRIVATE KEY FILE!          @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
Permissions 0755 for '/key/id_rsa' are too open.
It is required that your private key files are NOT accessible by others.
This private key will be ignored.
Load key "/key/id_rsa": bad permissions
Permission denied (publickey).

解決方法

  • docker-entrypoint.sh でマウントされた鍵ファイルのコピーを内部で作って chmod する

github.com

#!/bin/sh
KEY_PATH=/root/.ssh
KEY_FILE=key.pem
KEY_FILEPATH=$KEY_PATH/$KEY_FILE
mkdir -p $KEY_PATH
cat /key/$INPUT_KEY_FILENAME > $KEY_FILEPATH
chmod 0600 $KEY_FILEPATH
SSH="ssh -o 'StrictHostKeyChecking no' \
         -o 'ServerAliveInterval 15' \
         -i $KEY_FILEPATH \
         -D 0.0.0.0:$LOCAL_PORT \
         -N \
         $STEP_SERVER_USER@$STEP_SERVER"
echo $SSH
eval $SSH

踏み台サーバ経由でいろいろ (Web, Git, MySQL) アクセスしたい

社外から社内システムに接続しようと奮闘した記録です。 以下の前提でやっていきます。

  • エンドポイントは変えない
    • アプリケーションに手を入れなくて良い
    • ブラウザのブックマークなどがそのまま使える
  • Windows + Git Bash + Docker

Web アクセスしたい

使うものは以下。

  • SSH トンネリング (ダイナミックポートフォワード)
  • プロキシの自動構成

1. トンネルを掘る

ssh stepserver -f -N -D 1080
  • -f: バックグラウンドで実行
  • -N: コマンドを実行しない
  • -D 1080: ダイナミックポートフォワード。localhost:1080 に SOCKS プロキシをたてる。

これでlocalhost:1080 にプロキシとしてアクセスすると、stepserver 経由で通信ができます。

2. プロキシの設定

まずはプロキシの自動構成スクリプト (proxy.pac) を準備します。

function FindProxyForURL(url, host) {
  if (shExpMatch(host, "stg.*.example.com") || dnsDomainIs(host, "git.example.com")) {
    return "SOCKS5 localhost:1080; DIRECT";
  } else {
    return "DIRECT";
  }
}
  • 上の例はステージング環境のアプリケーションと GitLab に Web アクセスしたい場合のサンプルです。
  • stg.*.example.comgit.example.com にマッチする URL は SOCKS プロキシでアクセスし、他は直接アクセスします。
  • 細かい構文なんかは、以下を参考にすると良いです。

docs.microsoft.com

ブラウザに設定

今回は proxy.pac をローカルに置いてますが、踏み台に Web サーバを立てて、そこに置いてもいいかもしれません。

f:id:ikasamak503:20180311195200p:plain

ChromeIE とかは共通ですけど、Firefox は独自の設定です。お使いのブラウザに合わせて設定してください。

Git アクセスしたい

1. SSH でアクセスする場合

SSH フォワーディングします。

~/.ssh/config

Host stepserver
  HostName ec2-XXX-XXX-XXX-XXX.ap-northeast-1.compute.amazonaws.com
  User ec2-user
  Identityfile ~/.ssh/stepserver.pem

Host git.example.com
    HostName git.example.com
    IdentityFile ~/.ssh/git_id_rsa
    ProxyCommand ssh -W %h:%p stepserver

さっきの SOCKS プロキシを使うこともできます。その場合は ProxyCommand を書き換えます。

 ProxyCommand connect -S localhost:1080 %h %p

-S オプションで SOCKS です。

2. HTTP/HTTPS でアクセスする場合

Git にプロキシの設定をします。 SOCKS プロキシ経由でいけるはずなんですが、今回の環境だとうまく動きませんでした。 Git サーバ側の設定が関係しているかも?

$ git config --global http.proxy localhost:1080
$ git config --global https.proxy localhost:1080
$ git config --list | grep proxy
http.proxy=localhost:1080
https.proxy=localhost:1080
$ git clone http://git.example.com/ikasamak/test.git
Cloning into 'test'...
fatal: unable to access 'http://git.example.com/ikasamak/test.git/': Empty reply from server

MySQL アクセスしたい

ちょっと特殊な環境だったので、ちゃんと説明するとこんな感じです。

Windows 上 の Docker コンテナ (複数) のアプリケーションから MySQL 接続したい。

この場合、複数のコンテナからポートフォワーディングするのはしんどいし、 まずアプリケーションに手を入れたくないので、次の構成にしました。

  • Windows ホストからローカルポートフォワードする *1
  • 各コンテナ内のアプリケーションはホスト上のフォワーディングしたポートに MySQL 接続しに行く

やっていきます。

1. コンテナからアクセスするためのホスト側 IP アドレスを確認する

DockerNAT となっているものはコンテナから出てくるインタフェースなので、そこにはコンテナから繋がりません。 それ以外なら何でもいいようです。 今回は 192.168.11.2 とします。

2. トンネルを掘る

ssh stepserver -f -N -L 192.168.11.2:3306:mysql.example.com:3306
  • -L [bind_address:]port:host:hostport: ローカルポートフォワーディング
    • bind_address: ローカルで待ち受けるアドレス。0.0.0.0 にするとどこからでも受け付けるようになる。
    • port: ローカルで待ち受けるポート
    • host: 接続先のホスト。今回の場合は MySQL サーバのエンドポイント。
    • hostport: 接続先のポート。今回の場合は MySQL のデフォルトなので 3306

3. ホストの hosts で無理やり名前解決する

192.168.11.2  mysql.example.com

試しにコンテナから ping を打ってみると、ちゃんと hosts に書いた IP に飛んでいることが分かります。

$ winpty docker-compose exec db ping -c 1 mysql.example.com
PING mysql.example.com (192.168.11.2): 56 data bytes
64 bytes from 192.168.11.2: icmp_seq=0 ttl=37 time=10.231 ms
--- mysql.example.com ping statistics ---
1 packets transmitted, 1 packets received, 0% packet loss
round-trip min/avg/max/stddev = 10.231/10.231/10.231/0.000 ms

このやり方は、Docker の DNS 設定が Automatic じゃないと出来ないかもしれません。

f:id:ikasamak503:20180312015837p:plain

4. コンテナから接続確認

$ winpty docker-compose exec db bash
root@0258fd400317:/# mysql -h mysql.example.com -u user -p
Enter password:
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 376064
Server version: 5.5.34-log MySQL Community Server (GPL) by Remi

Copyright (c) 2000, 2018, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql>

これでうまくいってれば、コンテナ上のアプリケーションもうまく MySQL 接続できると思います!

まとめ

やっていることはどれもだいたい一緒です。

  1. 踏み台に SSHフォワーディング
  2. トラフィックをなんとかフォワーディング先に乗せる

ダイナミックポートフォワーディングは便利なんですが、アプリケーション側が SOCKS プロキシに対応していないと使えないのが難点です。 そこを吸収してくれるソフトウェアもあるっぽいんですが、今回はそこまで踏み込まずにやりました。

あと、Docker for Windows の情報が無さすぎて泣きそうになりました。

参考

*1:ここで SOCKS を使わないのは、アプリケーション側での対応が必要になるからです