Segmentation Faultぐ

Segmentation Fault

コアダンプの数だけ強くなれるよ。

C/C++のメモリ破壊を3種の方法で検出してみる

C/C++のプログラマーなら一度は悩ませられるであろうメモリ関連のバグ。その内の1つであるメモリ破壊は作りこむのは簡単ですが、後々見つける事が難しくなる場合も多くやっかいな奴です。

今回はそんなメモリ破壊検出の助けとなるツールをいくつか試してみます。

メモリ破壊を起こすソースコード


まず、メモリ破壊を起こすサンプルとしてバッファオーバーフローを起こすoverflow.cを準備します。

overflow.c

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>

int main(void)
{
    uint8_t *ptr = NULL;

    // 64byte分のメモリ領域を取得
    ptr = (uint8_t *)malloc(64);
    if (NULL == ptr) {
        fprintf(stderr, "out-of-memory\n");
        exit(EXIT_FAILURE);
    }

    // メモリ破壊箇所
    // メモリ領域を1byteはみ出した位置に適当なデータを挿入
    ptr[64] = 0x5a;
    free(ptr);

    return 0;
}



Electric Fenceによる検出


Electric Fenceはmalloc()関数を使用する際にプログラマーがやりがちなバグの検出を手助けするライブラリーです。

通常、malloc()で取得したメモリ領域を超えて不正アクセスを行っても、そこがアクセス可能な領域である場合はプログラムは起動し続けてしまいバグに気づかないことがあります。

Electric Fenceを使用するとそういったケースでも不正メモリアクセスを検出し即時にコアダンプするようになります。

ライブラリーのインストールとコンパイル時に-lefenceを指定することで使用できます。


# ElectricFenceのインストール
[user@localhost ef]$ sudo yum -y install ElectricFence


# コアダンプ時にコアファイルを出力するように設定
[user@localhost ef]$ ulimit -c unlimited


# プログラムのコンパイル
[user@localhost ef]$ gcc -g -O0 overflow.c -lefence


# プログラムの実行
[user@localhost ef]$ ./a.out

  Electric Fence 2.2.2 Copyright (C) 1987-1999 Bruce Perens <bruce@perens.com> 
Segmentation fault (core dumped)


# gdbによる解析
[user@localhost ef]$ gdb a.out core.2396
...(途中略)
[New LWP 2396]
Core was generated by `./a.out'.
Program terminated with signal 11, Segmentation fault.
#0  0x0000000000400802 in main () at overflow.c:17
17          ptr[64] = 0x5a; ★ここでコアダンプ
Missing separate debuginfos, use: debuginfo-install ElectricFence-2.2.2-39.el7.x86_64 glibc-2.17-157.el7_3.5.x86_64
(gdb)



Valgrindによる検出


Varlgrindはメモリリーク検出やメモリデバッグを行うことができるツールです。解析の速度は低速ですがデバッグ対象のプログラムを再コンパイルしなくても使えるというメリットがあります。


# Valgrindのインストール
[user@localhost ef]$ sudo yum -y install valgrind


# プログラムのコンパイル
[user@localhost ef]$ gcc -g -O0 overflow.c


# プログラムの実行
[user@localhost ef]$ valgrind --leak-check=full ./a.out
==2430== Memcheck, a memory error detector
==2430== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==2430== Using Valgrind-3.12.0 and LibVEX; rerun with -h for copyright info
==2430== Command: ./a.out
==2430==
==2430== Invalid write of size 1 ★ここで検出
==2430==    at 0x4006A2: main (overflow.c:17)
==2430==  Address 0x51f6080 is 0 bytes after a block of size 64 alloc'd
==2430==    at 0x4C28BE3: malloc (vg_replace_malloc.c:299)
==2430==    by 0x400666: main (overflow.c:10)
==2430==
==2430==
==2430== HEAP SUMMARY:
==2430==     in use at exit: 0 bytes in 0 blocks
==2430==   total heap usage: 1 allocs, 1 frees, 64 bytes allocated
==2430==
==2430== All heap blocks were freed -- no leaks are possible
==2430==
==2430== For counts of detected and suppressed errors, rerun with: -v
==2430== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)



Mudflapによる検出


MudflapはGCC4に実装されているデバッグ機能です。バッファオーバーフローやメモリリークをプログラム実行時に検出することが可能です。

ライブラリーのインストールとコンパイル時に-fmudflap -lmudflapを指定することで使用できます。


# ライブラリーのインストール
[user@localhost ef]$ sudo yum -y install  libmudflap libmudflap-devel


# プログラムのコンパイル
[user@localhost ef]$ gcc -g -O0 -fmudflap -lmudflap overflow.c


# プログラムの実行
[user@localhost ef]$ ./a.out
*******
mudflap violation 1 (check/write): time=1508643754.888009 ptr=0x1864e10 size=1
pc=0x7f475e654308 location=`overflow.c:17:13 (main)'
      /lib64/libmudflap.so.0(__mf_check+0x18) [0x7f475e654308]
      ./a.out(main+0xb6) [0x400bb3]
      /lib64/libc.so.6(__libc_start_main+0xf5) [0x7f475e2abb35]
Nearby object 1: checked region begins 1B after and ends 1B after
mudflap object 0x1864e70: name=`malloc region'
bounds=[0x1864dd0,0x1864e0f] size=64 area=heap check=0r/0w liveness=0
alloc time=1508643754.887851 pc=0x7f475e654718
      /lib64/libmudflap.so.0(__mf_register+0x18) [0x7f475e654718]
      /lib64/libmudflap.so.0(__real_malloc+0xbf) [0x7f475e65523f]
      ./a.out(main+0x2e) [0x400b2b]
      /lib64/libc.so.6(__libc_start_main+0xf5) [0x7f475e2abb35]
number of nearby objects: 1



まとめ


各ツールの比較

ツール名 デバッグ対象の
再コンパイル
実行速度 動的メモリの
チェック機能
静的メモリの
チェック機能
分かりやすさ
Electric Fence 必要 ×
Valgrind 不要
Mudflap 必要


各ツールの使用感を比較するとこんな感じでしょうか。 どれも一長一短あるので状況に応じて使い分けるのが良さそうです。



gdbで動作中のプロセスをデバッグしてみる

gdbを使ってLinux上で既に動作しているアプリケーションをattachしてデバッグしてみる。

今回は例としてsnmpdをほんの少しだけ解析します。環境はLinux(CentOS7)です。

gdbのインストール

gdbが無いと始まらないのでyumでインストールします。

[user@localhost gdb]$ sudo yum install gdb


SNMPエージェントのビルド

解析用のsnmpdをソースコードからインストールします。インストール方法はこちらを参照。

www.segmentation-fault.xyz

[user@localhost net-snmp-5.7.3]$ /usr/local/sbin/snmpd --version

NET-SNMP version:  5.7.3
Web:               http://www.net-snmp.org/
Email:             net-snmp-coders@lists.sourceforge.net


シンボルの抽出と削除

インストールしたsnmpdをfileコマンドで調べるとシンボルがそのまま残っているのでstripコマンドでシンボルを削除します。

[user@localhost net-snmp-5.7.3]$ file /usr/local/sbin/snmpd
/usr/local/sbin/snmpd: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, BuildID[sha1]=e0b96f955caa14b8ad97c44cbc2f292f31e72caf, not stripped
[user@localhost net-snmp-5.7.3]$
[user@localhost net-snmp-5.7.3]$ sudo strip /usr/local/sbin/snmpd
[user@localhost net-snmp-5.7.3]$
[user@localhost net-snmp-5.7.3]$ file /usr/local/sbin/snmpd
/usr/local/sbin/snmpd: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, BuildID[sha1]=e0b96f955caa14b8ad97c44cbc2f292f31e72caf, stripped
[user@localhost net-snmp-5.7.3]$


また、ビルドしたsnmpdからデバッグ用のシンボルファイルを取り出しておきます。

[user@localhost net-snmp-5.7.3]$ objcopy --only-keep-debug agent/.libs/snmpd snmpd.debug


SNMPエージェント起動してgdbでattach

準備が出来たのでSNMPエージェントを起動してgdbで補足してみます。

attach後にdirectoryでソースコードを場所、symbol-fileでシンボル情報をgdbに教えてあげます。

そうしたら、snmpdが周期的に実行するループの処理にブレークポイントを張って動きを見てみましょう。

[user@localhost net-snmp-5.7.3]$ sudo /usr/local/sbin/snmpd -c /usr/local/etc/snmpd.conf -p /var/run/snmpd.pid -M /usr/local/share/snmp/mibs
[user@localhost net-snmp-5.7.3]$
[user@localhost net-snmp-5.7.3]$ ps aux |grep snmpd
root     11089  0.0  0.3 132640  4000 ?        S    09:47   0:00 /usr/local/sbin/snmpd -c /usr/local/etc/snmpd.conf -p /var/run/snmpd.pid -M /usr/local/share/snmp/mibs
[user@localhost net-snmp-5.7.3]$
[user@localhost net-snmp-5.7.3]$ sudo gdb --pid=11089
GNU gdb (GDB) Red Hat Enterprise Linux 7.6.1-100.el7
Copyright (C) 2013 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>

...(途中略)...

(gdb)
(gdb) directory agent/
Source directories searched: /home/user/gdb/net-snmp-5.7.3/agent:$cdir:$cwd
(gdb)
(gdb) symbol-file snmpd.debug
Load new symbol table from "/home/user/gdb/net-snmp-5.7.3/snmpd.debug"? (y or n) y
Reading symbols from /home/user/gdb/net-snmp-5.7.3/snmpd.debug...done.
(gdb)
(gdb) break snmpd.c:1217
Breakpoint 1 at 0x40383c: file snmpd.c, line 1217.
(gdb)
(gdb) continue
Continuing.
Breakpoint 1, receive () at snmpd.c:1217
(gdb) p reconfig
$1 = 0
(gdb) l
1212
1213        /*
1214         * Loop-forever: execute message handlers for sockets with data
1215         */
1216        while (netsnmp_running) {
1217            if (reconfig) {
1218    #if HAVE_SIGHOLD
1219                sighold(SIGHUP);
1220    #endif
1221                reconfig = 0;
(gdb) p netsnmp_running
$2 = 1
(gdb)
(gdb) detach
Detaching from program: /usr/local/sbin/snmpd, process 11089
(gdb) q



ちゃんと期待した箇所でブレークしてますね。detachするとsnmpdがgdbの補足から解放されます。 これで起動中のプロセスをgdbでデバッグできることが分かりました。


自動化してみる

実際にやってみるとgdbを起動してから実際に確認するまでにコマンドを打つ操作が多いことが分かります。1回だけなら良いですが何回も同じことをするのはとても面倒です。こういうところは自動化していきましょう。

gdbは-xオプジョンで指定したファイルからgdbのコマンドを入力することができるのでこれを利用します。 外部入力のファイルとしてsnmp_gdb.shを作成します。

snmp_gdb.sh

#
# snmpdをgdbでデバッグしてみる
#

# ログ出力
set logging file gdb.log
set logging on


# ページャー機能をOFF
set pagenation off

# ソースコードの所在を指定
directory agent/

# デバッグの為のシンボルファイルの読み込み
symbol-file snmpd.debug


# ブレークポイントの設定
break snmpd.c:1217
commands
  # ブレークポイント到達時に実行するコマンド
  printf "#### Break Point Start ###\n"
  list
  printf "reconfig = %d\n", reconfig
  printf "netsnmp_running = %d\n", netsnmp_running
  continue
  printf "#### Break Point End   ###\n"
  printf "\n"
end



commands~endの句はbreakポイントに到達した際に自動で実行するコマンドを定義しています。 commandsの書式はcommands <ブレークポイント番号>ですが、引数を省略すると直前に張ったブレークポイントのさ番号になります。

ファイルが作成できたら実際にやってみましょう。

[user@localhost net-snmp-5.7.3]$ sudo gdb --pid=11089 -x snmp_gdb.sh
...(略)
Breakpoint 1 at 0x40383c: file snmpd.c, line 1217.
Missing separate debuginfos, use: debuginfo-install glibc-2.17-157.el7_3.5.x86_64 openssl-libs-1.0.1e-60.el7_3.1.x86_64 zlib-1.2.7-17.el7.x86_64
(gdb)
(gdb) continue
Continuing.

Breakpoint 1, receive () at snmpd.c:1217
1217            if (reconfig) {
#### Break Point Start ###
1212
1213        /*
1214         * Loop-forever: execute message handlers for sockets with data
1215         */
1216        while (netsnmp_running) {
1217            if (reconfig) {
1218    #if HAVE_SIGHOLD
1219                sighold(SIGHUP);
1220    #endif
1221                reconfig = 0;
reconfig = 0
netsnmp_running = 1
#### Break Point End   ###

...(以降繰り返しのため省略)...



これで期待通りの動作を確認できました。キチンと作ればテストの自動化も出来そうですね。



TCPのURGフラグの謎

普段からネットワークを扱う人にとってはお馴染みのTCPという技術。

ネットワークを学ぼうとするビギナーが最初に教わるであろう基礎的で重要なプロトコルですが、ずっとTCPヘッダにあるURGフラグ(緊急フラグ)とUrgenポインタ(緊急ポインタ)の使いどころが疑問でした。

参考書やネットで調べて出てくる記事にはURGフラグとUrgentポインタの意味は書いてあるのですが、具体的にどんなシーンで使うのかは書いてない事が多いので良く分かっていませんでした。

今回は疑問を解消するべくURGフラグについて少し調べてみました。

URGフラグ(緊急フラグ)とは



URGフラッグとは図で言うところの赤で囲ったところにある1bit幅のフィールドです。

大抵は"0"になっていますが、これに"1"が立っているとUrgentポインタ(緊急ポインタ)のフィールドにデータの在り処が設定されます。...みたいな説明をよく見かけます。


TCPヘッダ フォーマット
f:id:segmentation-fault:20171014113429p:plain

出典:RFC 793 - Transmission Control Protocol


URGフラグを使う一般的なプログラム



実際にURGフラグを使ってるプログラムは無いだろうかと探したところ、Telnetで使われているらしいです。というかTelnet以外見つけられなかった。

説明を見ると端末から実行したコマンドを<Ctrl-C>でキャンセルすれば発生するみたい。

参考
TCP Flags: PSH and URG - PacketLife.net
3 Minutes Networking No.54


実際にキャプチャして確認



コマンドを<Ctrl-C>でキャンセル
f:id:segmentation-fault:20171014120524p:plain


キャプチャ
f:id:segmentation-fault:20171014120715p:plain


確かにフラグが立っている!


プログラムを作って確認



実際にURGフラグが立ったデータを受信するとアプリケーションからはどう見えるのでしょうか。 簡単なプログラムを作って確認してみます。

Windowsをクライアント、LinuxをサーバとしてクライアントからURGフラグ無し、有りのデータを送信します。

URGフラグを付けるには送受信時(recv, send)実行時のフラグに"MSG_OOB"を設定すればよく、受信側にはOSからアプリに対しシグナル SIGURG が発行されるぽい。

クライアント

// TCP_Client.cpp : コンソール アプリケーションのエントリ ポイントを定義します。
//

#include "stdafx.h"

#include <stdio.h>
#include <winsock2.h>
#include <windows.h>

#define PORT_NO_SERVER (12345)
#define IP_ADDRESS_SERVER "192.168.100.101"

int main(void)
{
    WSADATA wsaData;
    struct sockaddr_in server;
    SOCKET sfd = -1;
    char buf[32] = { 0 };
    int rc = 0;
    int ws = 0;

    // winsock2の初期化
    WSAStartup(MAKEWORD(2, 0), &wsaData);

    // ソケットの作成
    sfd = socket(AF_INET, SOCK_STREAM, 0);

    // 接続先指定用構造体の準備
    server.sin_family = AF_INET;
    server.sin_port = htons(PORT_NO_SERVER);
    server.sin_addr.S_un.S_addr = inet_addr(IP_ADDRESS_SERVER);

    // サーバに接続
    rc = connect(sfd, (struct sockaddr *)&server, sizeof(server));
    if (rc < 0) {
        printf("connect() failed(%d)\n", rc);
        exit(EXIT_FAILURE);
    }

    do  {
        // サーバへデータを送信
        // おはよう
        memset(buf, 0, sizeof(buf));
        snprintf(buf, sizeof(buf) - 1, "GOOD MORNING.\n");
        ws = send(sfd, buf, sizeof(buf), 0);
        printf("send %d byte, %s\n", ws, buf);
        
        // こんにちは
        memset(buf, 0, sizeof(buf));
        snprintf(buf, sizeof(buf) - 1, "GOOD AFTERNOON.\n");
        ws = send(sfd, buf, sizeof(buf), 0);
        printf("send %d byte, %s\n", ws, buf);

        // こんばんは
        memset(buf, 0, sizeof(buf));
        snprintf(buf, sizeof(buf) - 1, "GOOD EVENING.\n");
        ws = send(sfd, buf, sizeof(buf), 0);
        printf("send %d byte, %s\n", ws, buf);

        // HELLO
        memset(buf, 0, sizeof(buf));
        snprintf(buf, sizeof(buf) - 1, "HELLO.\n");
        ws = send(sfd, buf, sizeof(buf), MSG_OOB);
        printf("send %d byte, %s\n", ws, buf);

        Sleep(3000);

    } while (0);

    // winsock2の終了処理
    WSACleanup();

    return 0;
}


サーバ

シグナルハンドラ内で、やってはイケない事をしてますが、サンプルなので許してください。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <errno.h>
#include <signal.h>
#include <fcntl.h>

#define MAX_CLIENTS (1)
#define MSGBUF_SIZE (1024)

int gsfd = -1;

static void sig_urg(int signo)
{
    printf("SIGURG received\n");

    char msgbuf[MSGBUF_SIZE] = {0};
    ssize_t rs = 0;

    rs = recv(gsfd, msgbuf, sizeof(msgbuf)-1, MSG_OOB);

    printf("recv OOB %d bytes \n", rs, msgbuf);
    int i = 0;
    for (i = 0; i < rs; i++) {
        printf("%c", msgbuf[i]);
    }

    printf("\n");
    signal(SIGURG, sig_urg);
}

int main(void)
{
    int sfd = -1;
    struct sockaddr_in client;
    int socklen = sizeof(client);
    int ac_sfd = -1;
    int rc = 0;

    struct sockaddr_in addr = {
        .sin_family = AF_INET,
        .sin_port = htons(12345),
        .sin_addr = {
            .s_addr = INADDR_ANY,
        },
    };

    /* ソケットの作成 */
    sfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sfd < 0) {
        perror("socket");
        goto error_end;
    }

    rc = bind(sfd, (struct sockaddr *)&addr, sizeof(addr));
    if (rc < 0) {
        perror("bind");
        goto close_sfd_end;
    }

    printf("Listening...\n");

    rc = listen(sfd, MAX_CLIENTS);
    if (rc < 0) {
        perror("listen");
        goto close_sfd_end;
    }

    /* 要求受付 */
    ac_sfd = accept(sfd, (struct sockaddr *)&client, &socklen);
    if (ac_sfd < 0) {
        perror("accept");
        goto close_sfd_end;
    }

    printf("Connected!\n");

    gsfd = ac_sfd;
    fcntl(ac_sfd, F_SETOWN, getpid());
    signal(SIGURG, sig_urg);
    sleep(3);

    while (1) {
        /* メッセージ受信 */
        char msgbuf[MSGBUF_SIZE] = {0};
        ssize_t rs = 0;
        rs = recv(ac_sfd, msgbuf, sizeof(msgbuf)-1, 0);
        if (rs < 0) {
            perror("write");
            break;
        } else if (rs == 0) {
            printf("Connection Lost\n");
            break;
        } else {
            printf("recv %d bytes \n", rs);
            int i = 0;
            for(i = 0; i < rs; i++)
                printf("%c", msgbuf[i]);
        }
    }

    close(ac_sfd);
 close_sfd_end:
    close(sfd);
 error_end:

    return 0;
}


実行結果

実際にプログラムを実行して、送受信データをキャプチャしてみます。

f:id:segmentation-fault:20171014121546p:plain

f:id:segmentation-fault:20171014121600p:plain


確かにクライアントからはデータがURGフラグ付きで送信されていて、サーバ側ではSIGURGを受信しました。

ただ、SIGURG受信時にMSG_OOBフラグ付きでrecvしたらデータを先読み出来ると思ったのですが、違うみたいです。(実装が間違ってるだけかもですが)


まとめ



またまだ謎は多いのですが、実際にURGフラグを使っている例があると分かっただけで良い収穫でした。

telnetのソースコードを読んでもう少し理解を深めたいと思います。



ownCloud+nginx+さくらVPSでプライベートクラウド構築

前回、さくらVPS+nginx環境にSSL証明書を導入してHTTPSによる通信ができるようになりました。今回はownCloudを導入して自分専用のファイルサーバを構築していきます。

www.segmentation-fault.xyz

はじめに

ownCloudはファイル共有サーバー・ソフトウェアです。ownCloud社によりオープンソースの無償版と機能拡張+商用サポートがついた有償版(Enterprise版)が提供されています。

公式はこちら。 owncloud.org

また、ownCloudを開発していたメンバーによって作られたNextcloudというものも存在します。こちらはオープンソースのソフトウェアとして提供することに注力しているようです。 ownCloudとNextcloudの関係については下記が参考になります。

qiita.com

どちらも簡単に言うとDropbox風のファイル共有サーバーを自前で作れますよという機能になります。今回はownCloudの無償版を使います。

ownCloudの導入

概要

使用するVPSは既にWordpressを運用している状態ですので、今回はサブディレクトリにownCloudをインストールします。 具体的にはLinux(CentOS7)に下記ディレクトリ構成でインストールします。

/var/www/wordpress
/var/www/owncloud

それぞれ、
https://"サーバのドメイン"/で接続 ⇒ Wordpressにアクセス、
https://"サーバのドメイン"/owncloudで接続 ⇒ ownCloudにアクセス

という感じに住み分けます。


事前準備

まずは下記を用意します。

  1. Linuxサーバ(VPS等)
  2. 独自ドメイン
  3. SSL証明書

Linuxサーバには事前に必要なソフト(nginx, php, mysql(mariaDB))をインストールしておきます。 PHPのバージョンが5.6.0以上である必要があるので注意です。

$ sudo yum install epel-release
$ sudo yum install http://rpms.famillecollet.com/enterprise/remi-release-7.rpm
$ sudo yum install --enablerepo remi-php70 php php-fpm php-gmp php-mbstring php-mcrypt php-mysqlnd php-opc
ache php-pear-Net-Curl php-pecl-redis php-pecl-zip php-soap php-intl php-ldap php-gd

$ sudo yum install nginx
$ sudo yum install mysql

$ sudo systemctl enable nginx.service
$ sudo systemctl enable php-fpm.service
$ sudo systemctl enable mariadb.service

下記所有者をnginxに変更します。

$ sudo chown nginx:nginx /var/lib/php/ -R

念のため装置をリブートしておきましょう。

$ sudo reboot


ownCloudのダウンロード

こちらから必要なファイルをダウンロードしてきます。

https://owncloud.org/install/

f:id:segmentation-fault:20171007135910p:plain

f:id:segmentation-fault:20171007135914p:plain


ダウンロードが完了したら/var/wwwに展開して所有者をnginxに変更します。

# ファイルのダウンロード
$ wget https://download.owncloud.org/community/owncloud-10.0.3.tar.bz2

# /var/wwwに展開
$ sudo tar jxvf owncloud-10.0.3.tar.bz2 -C /var/www

# 所有者をnginxに変更します
$ sudo chown nginx:nginx /var/www/wordpress/owncloud/ -R


各種設定

ownCloudにアクセスするために下記の設定ファイルを編集していきます。

(1) /etc/php-fpm.d/www.conf
(2) /etc/nginx/conf.d/myserver.conf


(1) /etc/php-fpm.d/www.conf


ユーザとグループがapacheになっている場合はnginxに変更します。 また、nginxとphp-fpm間で通信する為のUNIXドメインのソケットを追記します。

$ sudo vim /etc/php-fpm.d/www.conf

#下記を記載する 
listen = /var/run/php-fpm/php-fpm.sock
listen.mode = 0666
user = nginx
group = nginx


(2) /etc/nginx/conf.d/myserver.conf


nginxの設置ファイルを編集します。 "location ^~ /owncloud { ... }"でくくられた部分でWordpressとownCloudを住み分けてます。


$ sudo vim /etc/nginx/conf.d/myserver.conf


# 下記を記載する
upstream php-handler {
    server unix:/var/run/php-fpm/php-fpm.sock;
}

server {
    listen       80;
    server_name  .<あなたのドメイン>;
    rewrite     ^ https://<あなたのドメイン>$request_uri?;
}

server {
    listen       443 ssl http2;
    #listen       [::]:443 ssl http2;
    server_name  <あなたのドメイン>;
    root         /var/www/wordpress/;

    ssl_certificate "<公開鍵のパス>";
    ssl_certificate_key "<秘密鍵のパス>";

    ssl_session_cache shared:SSL:1m;
    ssl_session_timeout  5m;

    ssl_protocols TLSv1.1 TLSv1.2;

    ssl_prefer_server_ciphers on;
    ssl_ciphers 'kEECDH+ECDSA+AES128 kEECDH+ECDSA+AES256 kEECDH+AES128 kEECDH+AES256 kEDH+AES128 kEDH+AES256 DES-CBC3-SHA +SHA !aNULL !eNULL !LOW !kECDH !DSS !MD5 !EXP !PSK !SRP !CAMELLIA !SEED';

    index index.php;

    location ~* /wp-config.php {
        deny all;
    }

    #location ~* /wp-login\.php|/wp-admin/((?!admin-ajax\.php).)*$ {
    #  auth_basic "Please enter your name and password";
    #  auth_basic_user_file  "/var/www/wordpress/.htpasswd";
    #}

    # For OwnCloud
    # Protecting sensitive files from the evil outside world
    location ~ ^/owncloud/(data|config|\.ht|db_structure.xml|README) {
        deny all;
    }

    location ^~ /owncloud {

        root /var/www/;

        # set max upload size
        client_max_body_size 10G;
        fastcgi_buffers 64 4K;

        # Disable gzip to avoid the removal of the ETag header
        gzip off;

        # Uncomment if your server is build with the ngx_pagespeed module
        # This module is currently not supported.
        #pagespeed off;

        index index.php;

        error_page 403 /owncloud/core/templates/403.php;
        error_page 404 /owncloud/core/templates/404.php;

        location ~ ^/owncloud/(?:\.htaccess|data|config|db_structure\.xml|README) {
            deny all;
        }

        rewrite ^/owncloud/caldav(.*)$ /owncloud/remote.php/caldav$1 redirect;
        rewrite ^/owncloud/carddav(.*)$ /owncloud/remote.php/carddav$1 redirect;
        rewrite ^/owncloud/webdav(.*)$ /owncloud/remote.php/webdav$1 redirect;

        rewrite ^/owncloud/core/doc/([^\/]+)(?:$|/) /owncloud/core/doc/$1/index.html;

        try_files $uri $uri/ /owncloud/index.php;

        location ~ \.php(?:$|/) {
            fastcgi_split_path_info ^(.+\.php)(/.+)$;
            include fastcgi_params;
            fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
            fastcgi_param PATH_INFO $fastcgi_path_info;
            fastcgi_param HTTPS on;
            fastcgi_pass php-handler;
        }

        # Optional: set long EXPIRES header on static assets
        location ~* \.(?:jpg|jpeg|gif|bmp|ico|png|css|js|swf)$ {
            expires 30d;
            # Optional: Don't log access to assets
            access_log off;
        }

    }

    location ~ \.php$ {
       fastcgi_split_path_info ^(.+\.php)(/.+)$;
       include  fastcgi_params;
       fastcgi_index index.php;
       fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
       fastcgi_param PATH_INFO $fastcgi_script_name;
       fastcgi_pass php-handler;
    }
}


編集が完了したらngint -tでコンフィグの確認をします。

$ sudo nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

ファイルの編集が終ったらnginx, php-fpmを再起動します。

$ sudo systemctl restart nginx.service
$ sudo systemctl restart php-fpm.service


データベースの作成

ownCloud用にデータベースを作成します。

$ sudo mysql -u root -p

MariaDB [(none)]>

# ユーザの作成
MariaDB [(none)]> create user 'abcdefg'@localhost IDENTIFIED BY 'sukeke1234';
Query OK, 0 rows affected (0.12 sec)

# データベースの作成
MariaDB [(none)]> create database owncloud;
Query OK, 1 row affected (0.00 sec)

# データベースに作成したユーザによる操作権限を付与
MariaDB [(none)]> GRANT ALL ON ownclound.* TO 'abcdefg'@'localhost' WITH GRANT OPTION;
Query OK, 0 rows affected (0.00 sec)

# 念のため
MariaDB [(none)]> FLUSH PRIVILEGES;
Query OK, 0 rows affected (0.00 sec)

MariaDB [(none)]> exit
Bye

$ 

これで事前準備は完了です。


接続確認

https://"サーバのドメイン"/owncloudで接続して下記画面が出ればOKです。

https://"サーバのドメイン"/の場合はWordpressが起動すること事も確認しましょう。

f:id:segmentation-fault:20171007150821p:plain

管理者アカウント(ownCloudへのログイン用のアカウント)とデータベースファイルを指定してセットアップを行います。 (管理者アカウントにadminを指定するとエラーになるので注意。)

f:id:segmentation-fault:20171007151003p:plain

セットアップ後に下記画面が表示されるので作成した管理者アカウントでログインします。

f:id:segmentation-fault:20171007151204p:plain

無事ログインまで出来れば完了です。

f:id:segmentation-fault:20171007151332p:plain


スマホからアクセス

ownCloud環境を構築できましたのでスマホアプリからアクセスしてみましょう。 私はAndroidユーザなのでAndroidアプリ(有料)を使います。

f:id:segmentation-fault:20171007153553j:plain

f:id:segmentation-fault:20171007153555j:plain


ファイルの参照、追加、削除が出来るか確認しましょう。



ハマったこと

作業の過程で色々なエラーで苦しめられました(^-^;)

PHPのバージョンが古い

現象:こんな画面が表示される。

f:id:segmentation-fault:20171007152225p:plain

対策:PHPのバージョンアップ

$ sudo yum install epel-release
$ sudo yum install http://rpms.famillecollet.com/enterprise/remi-release-7.rpm
$ sudo yum install --enablerepo remi-php70 php php-fpm php-gmp php-mbstring php-mcrypt php-mysqlnd php-opc
ache php-pear-Net-Curl php-pecl-redis php-pecl-zip php-soap php-intl php-ldap php-gd


/var/run/php-fpm/php-fpm.sockにアクセス不可

現象:こんな画面が表示される。

f:id:segmentation-fault:20171007152934p:plain

原因:/var/run/php-fpm/php-fpm.sockにアクセス権が足りない。

対策:www.confにソケットの権限を追記する。

$ sudo vim /etc/php-fpm.d/www.conf

#下記を記載する 
listen = /var/run/php-fpm/php-fpm.sock
listen.mode = 0666 # defaultだと 0660のためnginxがアクセスできない


データベースのユーザ名にハイフン"-"があるとAccess deniedになる。

現象:こんな画面が表示される。

f:id:segmentation-fault:20171007152329p:plain

対策:試しにデータベースのユーザ名を"-"無しにするとエラーが出なくなった。


セットアップ実行で404 Not Foundになる

現象:

公式のマニュアルでExample Configurationsにある例に従って設定してみるも、セットアップ画面までは行くが、セットアップ実行で404 Not Foundになってしまう。

対策:

原因は分からずじまいですが、nginxの設定ファイルに下記を記載したら動作した。

    location ^~ /owncloud {

        root /var/www/;

        # set max upload size
        client_max_body_size 10G;
        fastcgi_buffers 64 4K;

        # Disable gzip to avoid the removal of the ETag header
        gzip off;

        # Uncomment if your server is build with the ngx_pagespeed module
        # This module is currently not supported.
        #pagespeed off;

        index index.php;

        error_page 403 /owncloud/core/templates/403.php;
        error_page 404 /owncloud/core/templates/404.php;

        location ~ ^/owncloud/(?:\.htaccess|data|config|db_structure\.xml|README) {
            deny all;
        }

        rewrite ^/owncloud/caldav(.*)$ /owncloud/remote.php/caldav$1 redirect;
        rewrite ^/owncloud/carddav(.*)$ /owncloud/remote.php/carddav$1 redirect;
        rewrite ^/owncloud/webdav(.*)$ /owncloud/remote.php/webdav$1 redirect;

        rewrite ^/owncloud/core/doc/([^\/]+)(?:$|/) /owncloud/core/doc/$1/index.html;

        try_files $uri $uri/ /owncloud/index.php;

        location ~ \.php(?:$|/) {
            fastcgi_split_path_info ^(.+\.php)(/.+)$;
            include fastcgi_params;
            fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
            fastcgi_param PATH_INFO $fastcgi_path_info;
            fastcgi_param HTTPS on;
            fastcgi_pass php-handler;
        }

        # Optional: set long EXPIRES header on static assets
        location ~* \.(?:jpg|jpeg|gif|bmp|ico|png|css|js|swf)$ {
            expires 30d;
            # Optional: Don't log access to assets
            access_log off;
        }

    }

まとめ

セキュリティ的に見直すところもあるとは思いますが、ひとまずownCloudを使って自分だけのファイルサーバを作ることができました。 エラーには苦しめられましたが、それもまた良い勉強になったということで。



PowerShellでメールを受信してみる

とあるソフトウェアの試験で対象の装置からSMTPサーバへログが記載されたメールが飛ぶかどうか確認する必要がありました。フリーのSMTPサーバを使えれば良いのですが、試験環境にはインターネットから切り離されたWindowsのPCしか無い状況。しかも勝手にソフトは入れられない。

どうしようかと悩んでいた時、前にPowerShellでSyslogを受信して確認した事があったのを思いだしメールの受信もできるはず、ということでPowerShellで動く簡易的なSMTPサーバを作ってみました。

SMTPサーバのスクリプト

動けば良いやで作ったのでアホみたいな実装ですがご容赦ください。

# SMTP_S.ps1

function Start-Smtp-Server([int]$port=25, [string]$IPAdress="127.0.0.1", [switch]$Echo=$false){
    $listener = new-object System.Net.Sockets.TcpListener([System.Net.IPAddress]::Parse($IPAdress), $port)
    $listener.start()

    write-host "Waiting for a connection on port $port..."
    $client = $listener.AcceptTcpClient()
    write-host "Connected from $($client.Client.RemoteEndPoint)"
    
    $stream = $client.GetStream()
    
    Start-Sleep -m 500
    
    # 送信 220
    $text = "220 localhost testshell ready `r`n"
    $Sendbytes = [System.Text.Encoding]::UTF8.GetBytes($text)
    $stream.Write($Sendbytes, 0, $Sendbytes.Length)
    write-host "SERVER ==> CLIENT : SEND 220"
    
    # 受信 EHLO or HELO
    [byte[]]$Readbytes = 0..255|%{0}
    $stream.Read($Readbytes, 0, $Readbytes.Length)
    $text = [System.Text.Encoding]::UTF8.GetString($Readbytes)
    write-host "CLIENT ==> SERVER : $text"
    
    # 送信 250 色々
    $text = "250-Nice to meet you.`r`n"
    $text = $text + "250-8BITMIME`r`n"
    $text = $text + "250-STARTTLS`r`n"
    $text = $text + "250-AUTH=CRAM-MD5 PLAIN LOGIN ANONYMOUS`r`n"
    $text = $text + "250 SIZE`r`n"
    $Sendbytes = [System.Text.Encoding]::UTF8.GetBytes($text)
    $stream.Write($Sendbytes, 0, $Sendbytes.Length)
    write-host "SERVER ==> CLIENT : SEND 250"
    
    # 受信 MAIL
    [byte[]]$Readbytes = 0..255|%{0}
    $stream.Read($Readbytes, 0, $Readbytes.Length)
    $text = [System.Text.Encoding]::UTF8.GetString($Readbytes)
    write-host "CLIENT ==> SERVER : $text"
    
    # 送信 250 OK
    $text = "250 Okey dokey`r`n"
    $Sendbytes = [System.Text.Encoding]::UTF8.GetBytes($text)
    $stream.Write($Sendbytes, 0, $Sendbytes.Length)
    write-host "SERVER ==> CLIENT : SEND 250 OK"
    
    # 受信 RCPT
    [byte[]]$Readbytes = 0..255|%{0}
    $stream.Read($Readbytes, 0, $Readbytes.Length)
    $text = [System.Text.Encoding]::UTF8.GetString($Readbytes)
    write-host "CLIENT ==> SERVER : $text"
    
    # 送信 250 OK
    $text = "250 Recipient accepted`r`n"
    $Sendbytes = [System.Text.Encoding]::UTF8.GetBytes($text)
    $stream.Write($Sendbytes, 0, $Sendbytes.Length)
    write-host "SERVER ==> CLIENT : SEND 250 Recipient accepted"
    
    # 受信 DATA
    [byte[]]$Readbytes = 0..255|%{0}
    $stream.Read($Readbytes, 0, $Readbytes.Length)
    $text = [System.Text.Encoding]::UTF8.GetString($Readbytes)
    write-host "CLIENT ==> SERVER : $text"
    
    # 送信 354 End message with period
    $text = "354 End message with period`r`n"
    $Sendbytes = [System.Text.Encoding]::UTF8.GetBytes($text)
    $stream.Write($Sendbytes, 0, $Sendbytes.Length)
    write-host "SERVER ==> CLIENT : SEND 354 End message with period"
    
    
    # 分割受信(fragment)に対応するのは面倒なので受信しきるまで適度にwait...
    Start-Sleep -m 1000
    
    # 受信 DATA
    [byte[]]$Readbytes = 0..2048|%{0}
    $stream.Read($Readbytes, 0, $Readbytes.Length)
    $text = [System.Text.Encoding]::UTF8.GetString($Readbytes)
    write-host "CLIENT ==> SERVER : $text"
    
    
    # 送信 250 Mail accepted
    $text = "250 Mail accepted`r`n"
    $Sendbytes = [System.Text.Encoding]::UTF8.GetBytes($text)
    $stream.Write($Sendbytes, 0, $Sendbytes.Length)
    write-host "SERVER ==> CLIENT : SEND 250 Mail accepted"
    
    write-host "`nGoodbye! See You!`n"
    
    $client.Close()
    $listener.Stop()
    write-host "Connection closed."
}

Start-Smtp-Server -ip $Args[0] -port $Args[1]


動作確認用のMail送信スクリプト

# Mail.ps1

# 送信先メールアドレス
$to = "you@sukekeke.com"
 
# 送信元メールアドレス
$from = "me@sukekeke.com"

# メール件名
$subject = "Powershell Message"

# 本文
$body += "Mail to Server From Powershell`n"
$body += "Hello World !!"

# SMTPサーバのIPアドレス
$smtp = "192.168.2.101"


# メール送信
Send-MailMessage -To $to -From $from -SmtpServer $smtp -Subject $subject -Body $body -Encoding UTF8


結果

# サーバ起動

PS C:\ > .\SMTP_S.ps1 192.168.2.101 25
Waiting for a connection on port 25...
# メール送信

PS C:\ > .\Mail.ps1


キャプチャして確認


f:id:segmentation-fault:20170927233903p:plain

f:id:segmentation-fault:20170927233913p:plain


キャプチャを見ると無事にメールを受信できていました。 ただ、このSMTPサーバはクライアントから受信した内容はガン無視してシーケンスを進めるので少しでも想定と違うパケットを受信するとアウトです。

今回はちょっとした確認用途なので、これ以上は頑張りません。

まとめ

Google先生に聞いても、PowerShellからMailを送信するサンプルコードは沢山ありますが、受信する例は中々見つからなくて苦戦しました。(私がWindows系のプログラミングに疎いというのもありますが...)

あまり必要になるケースが無いの仕方ないですね(^-^;)


参考

下記を参考にさせていただきました。

Simple TCP Listener – Null Payload

Powershellでメールを送信する最も簡単な方法 | いろんなサーバー設定研究所



さくらVPS + nginx + SSL証明書でHTTPS通信

さくらVPSにnginx + wordpressの環境を構築してちょこちょこ遊んでおります。近いうちにHTTPSで接続できるようにもしたいなぁと思ってたのですが、手つかずのまま大分時間が経過してしまいました。

www.segmentation-fault.xyz

今回はSSL証明書を導入してHTTPSで接続できるように設定していきます。

環境(前提)

  • CentOS 7 (さくらVPS)
  • Wordpressの環境を設定済み(httpでアクセス可能)


全体の流れ

作業的には下記3ステップとなります。

 1.サーバ上で秘密鍵とCSRの作成
 2.SSL証明書の購入
 3.サーバに証明書を設置


事前準備

まずは事前準備です。下記ソフトをインストールします。

$ sudo yum install openssl
$ sudo yum install mod_ssl


秘密鍵とCSRの作成

ココでは下記作業を行います。

  1. 秘密鍵作成のための擬似乱数ファイル(rand.dat)を生成
  2. 疑似乱数ファイルから秘密鍵を生成
  3. 秘密鍵からCSRを作成
# 疑似乱数ファイルを生成
$ cd /etc/pki/nginx
$ sudo openssl md5 /usr/bin/* > rand.dat
# 秘密鍵を生成
$ sudo openssl genrsa -rand rand.dat -des3 2048 > newkey.pem
61643 semi-random bytes loaded
Generating RSA private key, 2048 bit long modulus
.......................................+++
........................................................+++
e is 65537 (0x10001)
Enter pass phrase: <任意のパスワードを入力>
Verifying - Enter pass phrase: <再度同じパスワードを入力>
# CSRを生成 (入力内容は仮だよ)
$ sudo openssl req -new -key newkey.pem -out newcsr.pem
Enter pass phrase for newkey.pem: <秘密鍵生成時のパスワードを入力>
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [XX]:JP
State or Province Name (full name) []:Tokyo
Locality Name (eg, city) [Default City]:Arakawa
Organization Name (eg, company) [Default Company Ltd]:Sakura
Organizational Unit Name (eg, section) []:VPS
Common Name (eg, your name or your server's hostname) []:centos7.com
Email Address []:

Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
An optional company name []:
$
$ cat newcsr.pem
-----BEGIN CERTIFICATE REQUEST-----
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAA            AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAA  秘密だよ  AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAA            AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAA
-----END CERTIFICATE REQUEST-----


CSR生成時の設定項目は下記です。下記以外の項目については入力は任意です(Enter連打でOK)。

CSR生成時の入力項目 記載内容 設定例
Country Name (2 letter code) [XX]: 国別番号 JP
State or Province Name (full name) : 都道府県名 Tokyo
Locality Name (eg, city) [Default City]: 市区町村名 Arakawa
Organization Name (eg, company) [Default Company Ltd]: 組織名 Sakura
Organizational Unit Name (eg, section) : 部門名 VPS
Common Name (eg, your name or your server’s hostname) []: コモンネーム(FQDN) centos7.com


入力内容はご自身の環境に合わせて変更ください。作成した秘密鍵はなくさないように。
ここで生成したnewcsr.pemの中身がSSL証明書の購入時に必要になりますのでtextファイル等にコピペしましょう。

SSL証明書の購入

こちらからSSL証明書を購入します。今回はRapidSSLを選択します。

ssl.sakura.ad.jp

f:id:segmentation-fault:20170923232146p:plain

f:id:segmentation-fault:20170923232318p:plain

プランと決済方法を選択します。私は3年プランを選択しました。

f:id:segmentation-fault:20170923232457p:plain

続いて先ほど生成したCSRの内容を入力します。

f:id:segmentation-fault:20170923234858p:plain


後はさくらインターネットさんからメールが来るのを待ちます。

サーバー証明書の設置

サーバー認証


SSL証明書の購入手続きが完了すると下記のようなメールが届きます(抜粋)。会員ページより認証ファイルを取得して指定のパスに配置しましょう。

お申込みいただきましたSSLサーバ証明書の申請が完了いたしましたので、お知
らせいたします。

  ================================================================
  《 SSLサーバ証明書の情報 》

     サーバ証明書種別:SSL ラピッドSSL(3年)
     サービスコード  :xxxxxxxxxxxx
     コモンネーム    :xxxxxxxxxxxxxxxxx
  ================================================================

引き続き、認証局より申請いただいたドメインの使用権の確認がございますので、
以下の手順をご確認の上、認証ファイルのアップロードをお願いいたします。



    ※認証ファイルは以下のいずれかのURLに配置
      認証ファイル名は、fileauth.txtです

    例)
      http://<申請時に指定したFQDN(コモンネーム)>/.well-known/pki-validation/fileauth.txt

まず、認証ファイルを取りに行きます。

f:id:segmentation-fault:20170924101124p:plain

f:id:segmentation-fault:20170924100038p:plain

続いて認証ファイルを設置します。

# 環境により変わる

$ sudo mkdir -p /var/www/wordpress/.well-known/pki-validation/
$ sudo mv fileauth.txt /var/www/wordpress/.well-known/pki-validation/


設置後にブラウザからアクセスして中身が見えたらOKです。この状態でしばらく待つと認証が完了し再度メールが届きます。認証が終ったらサーバー上からファイルは削除しましょう。

証明書の設置


認証が完了すると下記のような、SSLサーバ証明書が発行された旨のメールが届きます。

お申込みいただきましたSSLサーバ証明書の発行が完了いたしましたので、以下
の通りお知らせいたします。

  ================================================================
  《 SSLサーバ証明書の情報 》

     サーバ証明書種別:SSL ラピッドSSL(3年)
     サービスコード  :xxxxxxxxxxxx
     コモンネーム  :xxxxxxxxxxxxxxxxxxx
     証明書有効期限 :2020年09月23日
  ================================================================

つきましては、以下に手順を記載いたしますので、お客様にてご利用サーバへ
の導入作業を行っていただきますよう、お願いいたします。
  • SSLサーバ証明書
  • 中間CA証明書

メールの記載に従って上記2点を取ってきます。会員ページよりSSLサーバ証明書、メールに記載されているリンクから中間CA証明書をそれぞれ取得します。



SSLサーバ証明書

f:id:segmentation-fault:20170924102936p:plain

f:id:segmentation-fault:20170924113101p:plain

認証が完了していると状態が変わり利用期間が表示されています。


中間CA証明書

f:id:segmentation-fault:20170924103442p:plain

取得した証明書をサーバー上で結合し配置します。

$ sudo cat server.crt ca.crt > serverca.crt
$ vim serverca.crt # 証明書の間に改行を入れる
$ cat serverca.crt
-----BEGIN CERTIFICATE-----

SSLサーバ証明書

-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----

中間CA証明書

-----END CERTIFICATE-----


これで必要なファイルはそろいました。

nginxの設定


秘密鍵とSSLサーバ証明書をnginxのコンフィグに合わせて配置します。

# 環境により異なる

$ sudo mkdir -p /etc/pki/nginx/private/
$ sudo cp serverca.crt /etc/pki/nginx/server.crt
$ sudo cp newkey.pem /etc/pki/nginx/private/server.key
$
$ cat /etc/nginx/conf.d/wordpress.conf
server {
    listen       80;
    server_name  .<あなたのサーバ>;
    rewrite     ^ https://<あなたのサーバ>$request_uri?;
}

server {
    listen       443 ssl http2;
    #listen       [::]:443 ssl http2;
    server_name  <あなたのサーバ>;
    root         /var/www/wordpress;

    ssl_certificate "/etc/pki/nginx/server.crt";
    ssl_certificate_key "/etc/pki/nginx/private/server.key";

    ssl_session_cache shared:SSL:1m;
    ssl_session_timeout  5m;

    ssl_protocols TLSv1.1 TLSv1.2;

    ssl_prefer_server_ciphers on;
    ssl_ciphers 'kEECDH+ECDSA+AES128 kEECDH+ECDSA+AES256 kEECDH+AES128 kEECDH+AES256 kEDH+AES128 kEDH+AES256 DES-CBC3-SHA +SHA !aNULL !eNULL !LOW !kECDH !DSS !MD5 !EXP !PSK !SRP !CAMELLIA !SEED';

    index index.php;

    location ~* /wp-config.php {
        deny all;
    }


    location ~ \.php$ {
        fastcgi_pass    127.0.0.1:9000;
        fastcgi_index   index.php;
        fastcgi_param   SCRIPT_FILENAME /var/www/wordpress$fastcgi_script_name;
        fastcgi_param   PATH_INFO $fastcgi_script_name;
        include         fastcgi_params;
    }
}


このままnginxを再起動すると秘密鍵のパスワード入力が出来ずに失敗します。なので秘密鍵からパスフレーズを削除します。

$ sudo cd /etc/pki/nginx/private/
$ sudo cp server.key server.key.orig
$ sudo openssl rsa -in server.key -out server.key


nginxの設定が完了したらコンフィグ内容を確認して再起動します。

$ sudo nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
$
$ sudo systemctl restart nginx.service 



接続確認

nginxを再起動後にhttps付きでサイトにアクセスし、緑色の鍵マークが表示されればOKです。

f:id:segmentation-fault:20170924110532p:plain


鍵マークが表示されない(ブラウザに怒られる)、アクセスできない等が発生した場合はnginxのコンフィグ、ファイアウォールの設定、Wordpressの設定を見直してみてください。



coincheckでビットコインを自動で定期購入してみる

今回はcoincheckのAPIを使ってビットコインを購入するスクリプト(python)を作ってみます。 cronと組み合わせれば定期購入もできそうです。実行環境はLinux(CentOS 7)です。

取引用のAPIキーを作成

APIを利用して取引を操作するにはAPIキーを作成する必要があるため先にやっておきます。coincheckにログイン後、設定からAPIキーを選択して下方の「新たにAPIキーを追加する」を選択しAPIキーを作成します。

f:id:segmentation-fault:20170922155233p:plain

許可する操作を選択できますので、必要なものにチェックを入れましょう。

f:id:segmentation-fault:20170922154946p:plain

ソースコード

取引用のAPIキーが用意できたので、下記のドキュメントを参考にスクリプトを作成していきます。 10000円分のビットコインを成り行きで購入するスクリプトです。

coincheck.com

# -*- coding: utf-8 -*-
import sys
import json
import requests
import time
import hmac
import hashlib

class ccapi:
    def __init__(self, key, key_secret, endpoint):
        self.key = key
        self.key_secret = key_secret
        self.endpoint = endpoint

    def get(self, path):
        nonce = str(int(time.time()))
        text = nonce + self.endpoint + path

        signature = hmac.new(
                bytes(self.key_secret.encode('ascii')),
                bytes(text.encode('ascii')),
                hashlib.sha256).hexdigest()

        return requests.get(
                self.endpoint + path ,
                headers = self.__get_header(self.key, nonce, signature))

    def post(self, path, payload):
        payload = json.dumps(payload)
        nonce = str(int(time.time()))
        text = nonce + self.endpoint + path + payload

        signature = hmac.new(
                bytes(self.key_secret.encode('ascii')),
                bytes(text.encode('ascii')),
                hashlib.sha256).hexdigest()

        return requests.post(
                self.endpoint+path,
                data = payload,
                headers = self.__get_header(self.key, nonce, signature))

    def delete(self,path):
        nonce = str(int(time.time()))
        text = nonce + self.endpoint + path

        signature = hmac.new(
                bytes(self.key_secret.encode('ascii')),
                bytes(text.encode('ascii')),
                hashlib.sha256).hexdigest()

        return requests.delete(
                self.endpoint+path,
                headers = self.__get_header(self.key, nonce, signature))

    def __get_header(self, key, nonce, signature):
        headers = {
            'ACCESS-KEY': key,
            'ACCESS-NONCE': nonce,
            'ACCESS-SIGNATURE': signature,
            'Content-Type': 'application/json'
        }

        return headers

if __name__ == '__main__':

    argv = sys.argv
    argc = len(argv)

    if (argc != 3):
        print('usage: python %s <key> <secret key>' % argv[0])
        quit()

    key = argv[1]
    key_secret = argv[2]
    endpoint = 'https://coincheck.com'

    cc = ccapi(key,key_secret,endpoint)

    # 販売レートの確認(特に意味は無い)
    salerate = '/api/rate/btc_jpy'
    rate = cc.get(salerate).json()
    print('BTC-JPY : \%s' % rate['rate'])

    # \10000分の成り行き買い注文
    order = '/api/exchange/orders'
    payload = {
       "pair": "btc_jpy",
       "order_type": "market_buy",
       "market_buy_amount": 10000,
    }
    result = cc.post(order, payload).json()
    print(result)

    # 注文キャンセル
    #order = '/api/exchange/orders/[id]'
    #result = cc.delete(order)
    #print(result)


実行結果

恐る恐る実行してみるとリクエストは成功した模様。早速、coincheckにログインして結果を確認します。

$ python3 ccauto.py 'アクセスキー' 'シークレットアクセスキー'
BTC-JPY : \414875.0
{'stop_loss_rate': None, 'id': xxxxxxxx, 'rate': None, 'amount': None, 'pair': 'btc_jpy', 'order_type': 'market_buy', 'market_buy_amount': '10000.0', 'success': True, 'created_at': '2017-09-22T05:53:30.127Z'}


スマホアプリで取引履歴と残高を確認。

f:id:segmentation-fault:20170922160459j:plain

f:id:segmentation-fault:20170922160509j:plain


キッチリ10000円分の購入が出来ていました。(一安心)

cronと組み合わせて定期購入

ビットコインが欲しいけど価格変動のリスクは抑えたい... そんな時に頼りになるのがドル・コスト平均での定期購入(積立)ですね。

cronと組み合わせれば簡単に実現できます。

まぁ自分で作らずとも他社の取引所には仮想通貨の積立サービスはあったりしますが趣味の世界なのでDon't care。

zaif.jp

まとめ

実際にやってみると、驚くほど簡単にビットコインの購入が出来ることが分かります。(もちろん売却も) 入力する桁を間違えたりすると大変なことになってしまうのでチェック機能はキチンと作りたいですね。