【Wireshark】ルータを経由する HTTPS 通信の内容を tcpdump でキャプチャして復号してみる

こんにちは。
サーバの秘密鍵が漏洩すると HTTPS 通信の内容が復号されてしまうことは知識として理解していますが、いざ実際に復号するには、どのようにすればいいのか調べてみました。

今回はクライアント ( Client ) 、ルータ ( Router ) 、サーバ ( Server ) の 3 ホストを仮想環境に用意して、順番に検証してみようと思います。

検証方法

以下の図のように、Router でキャプチャした暗号化されたパケットを、Server の秘密鍵、Client のセッションキーを用いて復号化することをゴールとします。
パケットキャプチャには tcpdump を、パケットの復号化には Wireshark を用います。

環境構築

各種ホストは VirtualBox 上に起動した AlmaLinux9 にて構築します。

RoleIP addressOSSupplement
Router192.168.33.11AlmaLinux 9
Client192.168.33.12AlmaLinux 9
Server192.168.33.13AlmaLinux 9vhosts: verienv.com

Router

Router は iptables を用いて構築し、且つ、192.168.33.0/24 ネットワークからのパケットをフォワードするように設定します。

systemctl stop firewalld
systemctl disable firewalld
dnf -y install iptables-services tcpdump
iptables -t nat -A POSTROUTING -s 192.168.33.0/24 -j MASQUERADE
iptables -A FORWARD -s 192.168.33.0/24 -j ACCEPT
iptables -A FORWARD -d 192.168.33.0/24 -j ACCEPT
iptables-save > /etc/sysconfig/iptables
echo "net.ipv4.ip_forward = 1" >> /etc/sysctl.conf
sysctl -p

Client

Client では、元々のルーティングテーブルのエントリを削除し、先の Router をデフォルトゲートウェイとするエントリを追加します。
また、verienv.com の名前解決先が Server に向くように hosts ファイルにエントリを追加します。

dnf -y install net-tools
route delete default
route add default gw 192.168.33.11
route del -net 192.168.33.0 netmask 255.255.255.0
echo "192.168.33.13 verienv.com" >> /etc/hosts

Server

Server には特別な設定は必要なく、一般的な 443 を Listen する WEB サーバを構築します。
設定した証明書の秘密鍵は手元に落としておきます。
※ 本検証では verienv.com という FQDN のバーチャルホストを切り使用します。
※ ありふれた設定なので、本記事では割愛します。

検証

キャプチャ

Router にて以下コマンドを実行し、192.168.33.0/24 からのパケットをキャプチャして capture.pcap というファイルに出力します。

tcpdump -i eth1 -s 0 net 192.168.33.0/24 -w capture.pcap

Client にて以下コマンドを実行し、Server に対して JSON の POST リクエストを発行します。
※ SSLKEYLOGFILE 環境変数には、鍵情報 ( セッションキー ) のダンプ先となるファイルパスを指定しており、今回はカレントディレクトリの key.log というファイルに出力します。

SSLKEYLOGFILE=./key.log curl -k -X POST -H "Content-Type: application/json" -d '{"name": "snkk1210", "age": 100, "city": "New York"}' https://verienv.com

Router のフォアグラウンドで実行されている tcpdump を 「Ctrl + c」 で終了させ、Router の capture.pcap と Client の key.log を手元に落とします。

復号化

暗号化されたパケットの復号には Wireshark を使います。
一旦、手元に落とした capture.pcap を Wireshark で開いてみます。

この状態だとパケットは暗号化されたままで内容が分からないですね…。
以下の手順で Wireshark に先の鍵情報を登録します。

Edit > Preferences > Protocols > TLS > RSA keys list [Edit]

KeyValue
IP address192.168.33.13
Port443
Protocolhttp
Key File< Server の秘密鍵のパス >
Password

実は Perfect Forward Secrecy ( PFS ) が有効である場合や、暗号スイートに DHE を使っている場合、秘密鍵だけだと復号ができません。

同じく (Pre)-Master-Secret log filename に手元に落とした key.log ( セッションキー ) を指定します。

この状態でパケットの中身を覗いてみます。


新たに Decrypted TLS というタブが追加されており、こちらを選択することでリクエストの中身を確認することができました。

終わりに

今回、復号処理は全て Wireshark で行いましたが、この辺りを深く理解して独自に実装できるようになりたいものです。
もっと勉強しなくちゃですね。

【Terraform】 ECR 拡張スキャン通知用の Lambda をデプロイしてみる

こんにちは。
先日に投稿した 「Inspector V2 の ECR 拡張スキャン結果を Slack に通知する」 ですが、Lambda のみ手動で作成していたので、こちらも Terraform でデプロイできるように実装します。

【Terraform】Inspector V2 の ECR 拡張スキャン結果を Slack に通知する

Terraform

Terraform で Lambda をデプロイするには、予めソースコードを zip 化しておく必要があるようです。
ついては archive_file という Data Source で対応します。
ソースコードの更新も、zip ファイルの hash 値で判断してくれるようで便利ですね。

/** 
# NOTE: Lambda
*/

// Python スクリプト ZIP 化
data "archive_file" "ecr_enhanced_scanning_finding" {
  type        = "zip"
  source_file = "${path.module}/lambda/source/ecr_enhanced_scanning_finding_notice.py"
  output_path = "${path.module}/lambda/bin/ecr_enhanced_scanning_finding_notice.zip"
}

// Lambda 
resource "aws_lambda_function" "ecr_enhanced_scanning_finding" {
  filename                       = data.archive_file.ecr_enhanced_scanning_finding.output_path
  function_name                  = "${var.common.project}-${var.common.environment}-ecr-enhanced-scan-finding-notice-function"
  description                    = "${var.common.project}-${var.common.environment}-ecr-enhanced-scan-finding-notice-function"
  role                           = aws_iam_role.lambda_role.arn
  handler                        = "ecr_enhanced_scanning_finding_notice.lambda_handler"
  source_code_hash               = data.archive_file.ecr_enhanced_scanning_finding.output_base64sha256
  reserved_concurrent_executions = -1
  runtime                        = "python3.7"
  environment {
    variables = {
      channelName         = var.channel_name
      kmsEncryptedHookUrl = var.kms_encrypted_hookurl
    }
  }

  lifecycle {
    ignore_changes = [
      environment
    ]
  }

}

// SNS
resource "aws_lambda_permission" "ecr_enhanced_scanning_finding" {
  statement_id  = "AllowExecutionFromSNS"
  action        = "lambda:InvokeFunction"
  function_name = aws_lambda_function.ecr_enhanced_scanning_finding.function_name
  principal     = "sns.amazonaws.com"
  source_arn    = aws_sns_topic.ecr_enhanced_scanning_finding.arn
}

// SNS Subscription
resource "aws_sns_topic_subscription" "ecr_enhanced_scanning_finding" {
  topic_arn = aws_sns_topic.ecr_enhanced_scanning_finding.arn
  protocol  = "lambda"
  endpoint  = aws_lambda_function.ecr_enhanced_scanning_finding.arn
}

Lambda

Lambda のソースコード ( Python ) は、以下のようなディレクトリ構成で配置します。

.
├── eventbridge.tf
├── lambda
│   ├── bin
│   │   └── ecr_enhanced_scanning_finding_notice.zip
│   └── source
│       └── ecr_enhanced_scanning_finding_notice.py
├── lambda.tf
└── variables.tf

現状、Terraform には環境変数を KMS キーで暗号化する機能が見当たりませんでした。
そのため、手動で暗号化するか、暗号化しないかのどちらでも対応できるように実装しておきます。
import boto3
import json
import logging
import os

from base64 import b64decode
from urllib.request import Request, urlopen
from urllib.error import URLError, HTTPError
from urllib.parse import quote

logger = logging.getLogger()
logger.setLevel(logging.INFO)

def lambda_handler(event, context):

    CHANNEL_NAME = os.environ['channelName']

    if  "hooks.slack.com" in os.environ['kmsEncryptedHookUrl']:
        logger.info("kmsEncryptedHookUrl: " + str(os.environ['kmsEncryptedHookUrl']))
        logger.info("kmsEncryptedHookUrl is not Encrypted")
        UNENCRYPTED_HOOK_URL = os.environ['kmsEncryptedHookUrl']
        HOOK_URL = "https://" + quote(UNENCRYPTED_HOOK_URL)
    else:
        logger.info("kmsEncryptedHookUrl is Encrypted")
        logger.info("kmsEncryptedHookUrl: " + str(os.environ['kmsEncryptedHookUrl']))
        ENCRYPTED_HOOK_URL = os.environ['kmsEncryptedHookUrl']
        HOOK_URL = "https://" + boto3.client('kms').decrypt(
            CiphertextBlob=b64decode(ENCRYPTED_HOOK_URL),
            EncryptionContext={'LambdaFunctionName': os.environ['AWS_LAMBDA_FUNCTION_NAME']}
        )['Plaintext'].decode('utf-8')

    logger.info("Event: " + str(event))
    message = json.loads(event['Records'][0]['Sns']['Message'])
    logger.info("Message: " + str(message))

    resources = message['resources'][0]
    title = message['title']
    severity = message['severity']
    inspector_score = message['inspectorScore']
    source_url = message['sourceUrl']

    # MEMO: <!channel>, <@user_id>
    slack_message = {
        "channel": CHANNEL_NAME,
        "icon_emoji": ":rotating_light:",
        "attachments": [
            {
                "color": "#FF0000",
                "title": "ECR イメージスキャン結果に脆弱性が存在します",
                "title_link": "https://ap-northeast-1.console.aws.amazon.com/ecr/repositories?region=ap-northeast-1",
                "text": "<!here> \n *Resources* : %s \n" % (resources),
                "fields": [
                    {
                        "title": "Title",
                        "value": title,
                        "short": True
                    },
                    {
                        "title": "Severity",
                        "value": severity,
                        "short": True
                    },
                    {
                        "title": "Inspector_score",
                        "value": inspector_score,
                        "short": True
                    },
                    {
                        "title": "Source_url",
                        "value": source_url,
                        "short": True
                    }
                ]

            }
        ]
    }

    logger.info("HOOK_URL: " + str(HOOK_URL))
    logger.info("CHANNEL_NAME: " + str(CHANNEL_NAME))

    req = Request(HOOK_URL, json.dumps(slack_message).encode('utf-8'))

    try:
        response = urlopen(req)
        response.read()
        logger.info("Message posted to %s", slack_message['channel'])
    except HTTPError as e:
        logger.error("Request failed: %d %s", e.code, e.reason)
    except URLError as e:
        logger.error("Server connection failed: %s", e.reason)

Terraform ですが思っていたよりも Lambda 周りのデプロイツールとして優秀なんじゃないかと思いました。
わあい。

【GCP】Booting from Hard Disk 0… から復旧させた話【P2V】

こんにちは。
先日に投稿した下記方法で GCP に P2V (RHEL6) を試みました。
…が、上手くVM が起動しなかったため、頑張って復旧させた際の備忘録です。

【V2V】dd コマンドでデータを丸ごと吸い出して GCP にサーバを移設してみる


シリアルコンソールから確認すると、出力が Booting from Hard Disk 0… にて止まっている模様。
仕方がないので、レスキュー用の VM を起動し、正常にブートするディスク (復旧ディスク) に対して、問題の (起動しない) ディスクデータを移行し VM の起動を目指します。

1. 復旧ディスクを用意

GCP では既に RHEL6 / CentOS6 のイメージを提供していません。
ついては、AWS にて CentOS6 のイメージを起動し、dd コマンドでディスクのイメージを取得してから GCP にて VM を作成します。
※ 詳細については こちら を参照ください。

※ 必要に応じてディスクを拡張しておきます。

sed -i -e "s/^mirrorlist=http:\/\/mirrorlist.centos.org/#mirrorlist=http:\/\/mirrorlist.centos.org/g" /etc/yum.repos.d/CentOS-Base.repo
sed -i -e "s/^#baseurl=http:\/\/mirror.centos.org/baseurl=http:\/\/vault.centos.org/g" /etc/yum.repos.d/CentOS-Base.repo
yum install epel-release
yum install cloud-utils-growpart
growpart /dev/sda 1
reboot
resize2fs /dev/sda1

2. データ移行

レスキュー用の VM を起動し、「1」にて作成したディスクと、問題の (起動しない) ディスクをアタッチし、それぞれ適当なディレクトリにマウントします。

※ /mnt/source01 → 問題の (起動しない) ディスク
※ /mnt/dest01 → 復旧ディスク

データ移行には rsync を使います。
※ 予め同期させないデータのリストファイルを作成してから実行します。

vim /var/tmp/exclude_list.txt 
=============================
# necessary-file exclude
- /boot/*
- /dev/*
- /sys/*
- /proc/*
- /etc/fstab
- /etc/passwd
- /etc/shadow
- /run/*

nohup rsync -av --exclude-from=/var/tmp/exclude_list.txt /mnt/source01/* /mnt/dest01/ &

※ OS ユーザはエントリの重複が発生しないように /etc/passwd 、/etc/shadow に手動で追記します。

データ同期が完了したら、ディスクをアンマウントし VM を起動します。

3. ネットワーク周り調整

VM 起動後はシリアルコンソールから接続します。
※ ログインプロンプトが表示されたら成功です。
P2V だとネットワーク周りの設定が競合して疎通ができない状態になっていると思うので適宜調整します。

※ たいていは network-scripts 配下のファイルを調整し、75-persistent-net-generator.rules を退避(削除)すれば良いかと。

調整後は VM を再起動して下さい。

vim /etc/sysconfig/network-scripts/ifcfg-eth0
=============================================
IPV6INIT="no"
DHCP_HOSTNAME="localhost"
BOOTPROTO="dhcp"
DEVICE="eth0"
ONBOOT="yes"
MTU=1460
PERSISTENT_DHCLIENT="y"
IPV6INIT=yes

mv /etc/udev/rules.d/75-persistent-net-generator.rules /var/tmp/

※ GATEWAY は VPC のゲートウェイですね。
vim /etc/sysconfig/network
==========================
NETWORKING=yes
HOSTNAME=hogehoge
NETWORKING_IPV6=no
NOZEROCONF=YES
GATEWAY=hogehoge

4. 終わりに

VM が起動しない時は手詰まりで泣きそうになりました…。
一か八かでデータの同期を試みたところ正常に起動したようで何よりです。
この案件、色々と勉強になりました…。

Jenkins + Selenium + Chrome で テストを実行する CI/CD 環境 を作ってみる。

こんにちは。
最近、WSL に Selenium をインストールして遊んでいました。
そこで、ふと思ったのですが、これを CI/CD パイプラインに組み込んだら面白いのではないかと考えました。
Jenkinsの勉強も兼ねて、環境を構築してみることにします。
具体的には、以下のようなパイプラインを作成します。

1. サーバにコンテンツをデプロイする。
2. デプロイ後に Selenium でテストを行う。
3. テスト結果を Slack に通知する。

この一連の流れを自動化し、効率的に運用できる環境を目指します。

Jenkins 構築

CentOS7 に Jenkins / Selenium / Chrome のパッケージを導入します。

yum install wget unzip git
rpm --import https://pkg.jenkins.io/redhat-stable/jenkins.io.key
yum install epel-release
yum install java-11-openjdk-devel
wget -O /etc/yum.repos.d/jenkins.repo https://pkg.jenkins.io/redhat-stable/jenkins.repo
yum install jenkins
systemctl start jenkins.service
systemctl enable jenkins.service

cat << "EOF" >> /etc/yum.repos.d/google.chrome.repo
[google-chrome]
name=google-chrome
baseurl=http://dl.google.com/linux/chrome/rpm/stable/$basearch
enabled=1
gpgcheck=1
gpgkey=https://dl-ssl.google.com/linux/linux_signing_key.pub
EOF

yum install xorg-x11-server-Xvfb
yum install google-chrome-stable
yum install ipa-gothic-fonts ipa-mincho-fonts ipa-pgothic-fonts ipa-pmincho-fonts

yum install python3
pip3 install selenium

cd /usr/local/src/
wget https://chromedriver.storage.googleapis.com/93.0.4577.15/chromedriver_linux64.zip
unzip chromedriver_linux64.zip
mv chromedriver /usr/local/bin/

Jenkins の UI から Xvfb / Slack Notification のプラグインを導入します。
※ Slack 側で Jenkins CI を予め追加しておいてください。
Jenkinsの管理 > プラグインの管理 > 利用可能 > Xvfb > Download now and install after

Jenkinsの管理 > Global Tool Configuration > Xvfb installation追加 > Name に Xvfb を入力 > Save

Jenkinsの管理 > プラグインの管理 > 利用可能 > Slack Notification > Download now and install after

Jenkinsの管理 > システムの設定 > Slack > Workspace に チームサブドメインを入力

Jenkinsの管理 > システムの設定 > Slack > Credential 追加 > Jenkins > 種類 Secret text 

Secret : インテグレーション用トークン認証情報 ID
ID : 任意

> 保存

デプロイジョブの作成

デプロイはシンプルに、ターゲットサーバに SSH 接続して develop ブランチを git pull するだけとします。
Jenkins は jenkins ユーザとして実行されるため、ホームディレクトリ配下にターゲットサーバに設定した公開鍵と対になる秘密鍵を配置しておきます。

mkdir /var/lib/jenkins/.ssh
vi /var/lib/jenkins/.ssh/id_rsa
chown -R jenkins:jenkins /var/lib/jenkins/.ssh
chmod 700 /var/lib/jenkins/.ssh
chmod 600 /var/lib/jenkins/.ssh/id_rsa

新規ジョブ作成 > フリースタイル・プロジェクトのビルド

から

ビルド手順の追加 > シェルの実行
で下記のワンライナーを実行するだけです。

ssh -oStrictHostKeyChecking=no target@xxx.xxx.xxx.xxx "cd /path/to/hoge; git pull origin develop"

ビルド後の処理の追加 > 他のプロジェクトのビルド
で 後述の テストジョブを指定します。

テストジョブの作成

Jenkins サーバの適当なディレクトリに Selenium のスクリプトを配置しておきます。
※ 今回は個人的に作った以下 WEB ツール上で、適当に画面を偏移するスクリプトを用意しました。
https://github.com/snkk1210/ease

#!/usr/bin/python3

import time
import datetime
from selenium import webdriver

URL='http://xxx.xxx.xxx.xxx'
USER="xxx@localhost"
PASSWD="xxx"

def clickHref(link):
    driver.find_element_by_link_text(link).click()
    time.sleep(1)
    return 0

def clickBtn(xpath):
    driver.find_element_by_xpath(xpath).click()
    time.sleep(1)
    return 0

def inputForm(element, input):
    driver.find_element_by_name(element).send_keys(input)
    time.sleep(1)
    return 0

def dialogboxThrough():
    driver.switch_to_alert().accept()
    time.sleep(1)
    return 0

driver = webdriver.Chrome(executable_path='/usr/local/bin/chromedriver')

driver.get(URL)

# login check

inputForm("email", USER)

inputForm("password", PASSWD)

clickBtn("//button[@class='btn btn-block btn-flat btn-primary']")

# sidemenu check

side_menus = ['Playbooks','Make Playbook','Authentications','Make Auth','Archives','Upload Files','Profile','Members']
for side_menu in side_menus:
    clickHref(side_menu)

# make playbook check

clickHref("Make Playbook")

inputForm("name", "test-playbook" + str(time.time()))

inputForm("private_key", "aaa")

inputForm("inventory", "bbb")

clickBtn("//input[@class='btn btn-success']")

# edit playbook check

clickHref("Playbooks")

clickBtn("//input[@class='btn btn-success']")

inputForm("private_key", "aaa")

inputForm("inventory", "bbb")

clickBtn("//input[@class='btn btn-success']")

# archive playbook check

clickHref("Playbooks")

clickBtn("//input[@class='btn btn-warning']")

# delete playbook check

clickHref("Archives")

clickBtn("//input[@class='btn btn-danger']")

dialogboxThrough()

# exit

time.sleep(5)
driver.quit()

新規ジョブ作成 > フリースタイル・プロジェクトのビルド

から

ビルド環境 > Start Xvfb before the build, and shut it down after.
に チェックを入れます。

ビルド手順の追加 > シェルの実行
で上記のスクリプトを実行するように指定してあげるだけで OK です。

ビルド後の処理の追加 > Slack Notifications
を設定しておけば、ジョブの実行結果を Slack に通知することが可能です。

【EASE】Laravel で Chatwork 通知機能を実装してみる【Ansible】

こんにちは。
先日に Laravel で作った Ansible の管理ツールですが、プロビジョニング時に通知を行う機能が欲しいなー、と。
Slack でもよいのですが、会社で主に使っている Chatwork に通知してみます。

【Ansible】playbookをWEB上で管理/実行できるツールを作ってみた【Laravel】

事前準備

Chatwork の API トークンが必要です。
https://developer.chatwork.com/ja/

環境変数(.env)から API トークンと、通知先エンドポイントを読めるようにしておきます。

config/chatwork.php 
==========================================
<?php

# Chatworkのtokenを環境変数から取得
return [
    'cw-token' => env('CW_TOKEN'),
    'cw-endpoint' => env('CW_ENDPOINT'),
];

メソッド

Laravel では Guzzle HTTP クライアントで簡単に http リクエストを発行できるようです。
ヘッダに API トークン、ボディに通知内容のメッセージを指定するんですが、URL エンコードする必要があるので、リクエストを作成する前に asForm メソッドを呼び出します。
※プロビジョニング実行時、完了時で合計 2 つのメソッドを作成します。

use Illuminate\Support\Facades\Http;

    /**
     * 実行時 Chatwork 通知メソッド
     */
    public static function notify2ChatworkStart($user){
        $token = config('chatwork.cw-token');
        $endpoint = config('chatwork.cw-endpoint');
        $date = date("Y/m/d H:i:s");

        $message = "[info][title]EASE Ansible MG started provisioning at " . $date . "[/title]\n" . " by " . $user->name . "[/info]";
        $response = Http::asForm()->withHeaders([
            'X-ChatWorkToken' => $token,
        ])->post($endpoint, [
            'body' => $message
        ]);
        return $response;
    }

    /**
     * 終了時 Chatwork 通知メソッド
     */
    public static function notify2ChatworkEnd($ansible_output){
        $token = config('chatwork.cw-token');
        $endpoint = config('chatwork.cw-endpoint');
        $date = date("Y/m/d H:i:s");

        $message = "[info][title]EASE Ansible MG ended provisioning at " . $date . "[/title]\n" .  $ansible_output . "[/info]";
        $response = Http::asForm()->withHeaders([
            'X-ChatWorkToken' => $token,
        ])->post($endpoint, [
            'body' => $message
        ]);
        return $response;
    }

使い方

環境変数が設定されている時にメソッドを呼び出すだけです。
とてもお手軽。

# Chatworkへの通知(実行時)
if (config('chatwork.cw-token')){ Playbook::notify2ChatworkStart($user); };

# Chatworkへの通知(完了時)
if (config('chatwork.cw-token')){ Playbook::notify2ChatworkEnd($ansible_output[count($ansible_output)-2]); };  

CentOS7 を入れた Raspberry Pi に Laravel8 環境を構築する

こんにちは。
CentOS7 を入れたラズパイに Laravel8 環境を構築してみます。
デフォルトのリポジトリからだと、古い PHP と MariaDB が入っちゃうので、それぞれ工夫して導入します。

PHP

デフォルトのリポジトリだと PHP5 系が入っちゃいます。
armv7l 用の remi リポジトリがあるけども PHP7.2 なので、こちらもダメです。
ついては phpenv でソースから PHP7.4 をビルドします。

# EPEL リポジトリ導入
cat > /etc/yum.repos.d/epel.repo << EOF
[epel]
name=Epel rebuild for armhfp
baseurl=https://armv7.dev.centos.org/repodir/epel-pass-1/
enabled=1
gpgcheck=0
EOF

# 関連パッケージ 導入
yum groupinstall "Development tools"
yum install libicu libicu-devel oniguruma oniguruma-devel cmake cmake3
yum install gcc libxml2 libxml2-devel libcurl libcurl-devel libpng libpng-devel libmcrypt libmcrypt-devel libtidy libtidy-devel libxslt libxslt-devel openssl-devel bison libjpeg-turbo-devel readline-devel autoconf sqlite-devel bzip2-devel nginx

# phpenv 導入
cd /usr/local/src/
git clone https://github.com/CHH/phpenv.git
cd phpenv/bin/
./phpenv-install.sh
git clone https://github.com/CHH/php-build.git ~/.phpenv/plugins/php-build

vi ~/.bashrc
======================================
export PATH="/root/.phpenv/bin:$PATH"
eval "$(phpenv init -)"
======================================

source ~/.bashrc

# libzip 導入
cd /usr/local/src/
wget https://libzip.org/download/libzip-1.7.3.tar.gz
tar xzvf libzip-1.7.3.tar.gz 
cd libzip-1.7.3
cmake3 -DCMAKE_INSTALL_PREFIX=/usr/local/libzip
make
make install

export PKG_CONFIG_PATH="$PKG_CONFIG_PATH:/usr/local/libzip/lib/pkgconfig"

# PHP 導入
phpenv install --list
phpenv install 7.4.14
phpenv global 7.4.14

# PHP-FPM 設定
vi ~/.phpenv/versions/7.4.14/etc/php-fpm.d/www.conf
==========================================================
[www]
user = nginx
group = nginx
listen = /var/run/www.sock
listen.owner = nginx
listen.group = nginx
==========================================================

vi ~/.phpenv/versions/7.4.14/etc/php-fpm.conf
==========================================================
pid = /var/run/php-fpm.pid
==========================================================

ln -s /root/.phpenv/versions/7.4.14/sbin/php-fpm /usr/local/bin/php-fpm

# Unit ファイル作成
vi /usr/lib/systemd/system/php-fpm.service
==============================================================
[Unit]
Description=The PHP FastCGI Process Manager
After=syslog.target network.target

[Service]
Type=forking
PIDFile=/var/run/php-fpm.pid
ExecStartPre=/usr/bin/rm -f /run/php-fpm.pid
ExecStart=/usr/local/bin/php-fpm -D
ExecReload=/bin/kill -USR2 $MAINPID
PrivateTmp=true
RuntimeDirectory=php-fpm
RuntimeDirectoryMode=0755

[Install]
WantedBy=multi-user.target
==============================================================

# PHP-FPM 起動
systemctl daemon-reload
mkdir -p /var/run/php
systemctl enable php-fpm
systemctl start php-fpm

MariaDB

こちらもデフォルトのリポジトリからだと 5.5 系が入っちゃうのでパスです。
一旦 yum で 5.5 系を導入して初期設定を行って、その後にソースから最新版をビルドし、Unit ファイルの書き換えで最新版にアップデートします。

# 5.5 系の MariaDB 導入
yum install mariadb-server
systemctl start mariadb
mysql_secure_installation

# 10.5.8 の MariaDB ソースをビルド
cd /usr/local/src/
wget https://downloads.mariadb.org/interstitial/mariadb-10.5.8/source/mariadb-10.5.8.tar.gz/from/https%3A//mirror.yongbok.net/mariadb/
mv index.html mariadb.tar.gz
tar -zxvf mariadb.tar.gz
cd mariadb-10.5.8/
cmake . -DCMAKE_INSTALL_PREFIX=/usr/local/mariadb
make
make install

# Unit ファイル書き換え
systemctl stop mariadb
vi /usr/lib/systemd/system/mariadb.service
================================================
#ExecStart=/usr/bin/mysqld_safe --basedir=/usr
ExecStart=/usr/local/mariadb/bin/mysqld_safe
================================================

# 10.5.8 にて MariaDB 起動
systemctl daemon-reload
systemctl start mariadb

Composer

Composer は公式に則って普通に導入です。

php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
php -r "if (hash_file('sha384', 'composer-setup.php') === '756890a4488ce9024fc62c56153228907f1545c228516cbf63f885e036d37e9a59d27d63f46af1d4d07ee0f76181c7d3') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;"
php composer-setup.php
php -r "unlink('composer-setup.php');"

補足

EASE の Nginx 設定ファイル例も記載しておきます。
自分用の備忘録ですしお寿司。

upstream php-fpm-ease {
  ip_hash;
  server unix:/var/run/www.sock;
}

server {
     listen 80;
     server_name ease.local;
     root         /var/www/vhosts/ease/public;
     index        index.php index.html index.htm;
     access_log   /var/log/nginx/ease.local_access.log;
     error_log    /var/log/nginx/ease.local_error.log;

     fastcgi_read_timeout 999999;
     proxy_read_timeout 999999;

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    location ~ \.php$ {
        fastcgi_pass  php-fpm-ease;
        fastcgi_index  index.php;
        fastcgi_param  SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param PATH_INFO $fastcgi_path_info;
        fastcgi_param PATH_TRANSLATED $document_root$fastcgi_path_info;
        fastcgi_buffer_size  128k;
        fastcgi_buffers  256 16k;
        fastcgi_busy_buffers_size  256k;
        fastcgi_temp_file_write_size  256k;
        include  fastcgi_params;
    }

}

【Ansible】Playbook を WEB で管理できるツールを作ってみた【Laravel】

こんにちは。
Ansible の Playbook 管理ツールといえば、 Ansible Tower / AWX / Ansible Semaphore 等々がございます。
これらを業務フローに組み込めないかな、と試してみたのですが、いずれもあまりピンとこず。

ないなら自分で作ってしまえと思い、年末の勢いでツールを作ってみました。
https://github.com/snkk1210/ease

実現したかったこと

ツールを作成するに辺り、実現したかったポイントは下記 7 点です。

・WEB ブラウザ上で Playbook が管理できる。
・WEB ブラウザ上で Playbook に定義された処理を実行できる。
・処理実行 ( プロビジョニング ) 履歴を保持できる。
・既に存在する Playbook ( リポジトリ ) の資産を流用できる。
・ユーザ毎に細かな権限設定が可能であり、組織的に管理できる。
・プロビジョニング時に公開鍵認証/パスワード認証の両方が使用できる。
・外部ファイルをアップロードし、処理実行時に利用することができる。

社内でのユースケースとしては、下記のようなことを実現したく実装しました。

だいたいの使い方

1. プロビジョニング対象への接続認証を登録

サイドメニューから「Make Auth」を選択し、認証名、パスワード、秘密鍵を登録

2. Playbook を作成

サイドメニューから「Make Playbook」を選択し、下記項目をそれぞれ登録

・Playbook
→ Playbook の名前

・repository
→ Playbookに使うリポジトリ名

・認証
→ 作成した接続認証

・private_key
→ 認証に使う秘密鍵
※ここで設定した秘密鍵情報が優先される(空にすると接続認証の情報を使用)

・inventory
→ インベントリファイル

・vars
→ 変数ファイル

・main
→ レポジトリに存在する role を include する Playbook

3. Playbook を選択

サイドメニューから「Playbooks」を選択し、一覧の中から使用する Playbook の「Run」を選択

4. Playbookを実行

セレクトボックスから「鍵認証」「パスワード認証」を選択して、「ドライラン」または「実行」を選択

権限周り

実装する権限は下記 3 ロールに絞りました。
※ もっと細かく調整できるように改修したいですね。

admin → 全てのリソースの操作が可能/ユーザの作成が可能
read-only → 全てのリソースの「閲覧」のみ可能
他ユーザ → 自分で作成したリソースのみ操作が可能

終わりに

余力があれば、アカウント権限操作周りの機能と、WEB ベースでのリポジトリ追加機能を実装する予定です。
始めて Laravel 使いましたが、こんな感じに実装できるんですね。
色々と勉強になりました。

【fdisk】LVM 環境を構築した後に拡張してみる【備忘録】

こんにちは。
fdisk にあまり馴染みがなかったので、勉強がてら LVM 構築/拡張の検証をしてみました。

“【fdisk】LVM 環境を構築した後に拡張してみる【備忘録】” の続きを読む

【Raspberry Pi】CentOS7 に Zabbix をソースからインストールしてみる

ラズベリーパイで Zabbix を構築しようと思ったんですが、CentOS7 用のパッケージがなかったんですよね…
なんで、ソースからインストールしなきゃ、ってことで備忘録です。
ついでに Nginx と php-fpm もソースから最新版を導入してみます。

“【Raspberry Pi】CentOS7 に Zabbix をソースからインストールしてみる” の続きを読む

【Ansible】Ansible で AWS の3層ネットワークを構築する【IaC】

こんにちは。
表題の通り AWS の 3層ネットワークを構築する playbook を用意してみます。
※ 詳細は README を参照ください。
https://github.com/keisukesanuki/aws-vpc-3layer

作るもの

・VPC
・SUBNET
・INTERNETGATEWAY
・NATGATEWAY
・ROUTETABLE

ディレクトリ構造

.
├── README.md
├── ansible.cfg
├── hosts
├── roles
│   └── aws_vpc
│       ├── tasks
│       │   └── main.yml
│       └── vars
│           └── main.yml
└── vpc_create.yml

playbook

---
# tasks file for aws_vpc
- name: create_vpc
  ec2_vpc_net:
    name: "{{ vpc_name }}"
    cidr_block: "{{ vpc_cidr }}"
    region: "{{ region }}"
    profile: "{{ profile }}"
    dns_hostnames: yes
    dns_support: yes
  register: vpc_info

# PUBLIC_SUBNET 作成
- name: create_public_subnet
  ec2_vpc_subnet:
    vpc_id: "{{ vpc_info.vpc.id }}"
    cidr: "{{ item.pub_subnet_cidr }}"
    az: "{{ item.subnet_az }}"
    region: "{{ region }}"
    resource_tags: { "Name":"{{ item.pub_subnet_name }}" }
    profile: "{{ profile }}"
  register: pubsub_info
  with_items:
    - "{{ pub_subnet }}"

# DMZ_SUBNET 作成
- name: create_dmz_subnet
  ec2_vpc_subnet:
    vpc_id: "{{ vpc_info.vpc.id }}"
    cidr: "{{ item.dmz_subnet_cidr }}"
    az: "{{ item.subnet_az }}"
    region: "{{ region }}"
    resource_tags: { "Name":"{{ item.dmz_subnet_name }}" }
    profile: "{{ profile }}"
  register: pubsub_info
  with_items:
    - "{{ dmz_subnet }}"

# PRIVATE_SUBNET 作成
- name: create_private_subnet
  ec2_vpc_subnet:
    vpc_id: "{{ vpc_info.vpc.id }}"
    cidr: "{{ item.pri_subnet_cidr }}"
    az: "{{ item.subnet_az }}"
    region: "{{ region }}"
    resource_tags: { "Name":"{{ item.pri_subnet_name }}" }
    profile: "{{ profile }}"
  register: prisub_info
  with_items:
    - "{{ pri_subnet }}"

# IGW 作成
- name: create_igw
  ec2_vpc_igw:
    vpc_id: "{{ vpc_info.vpc.id }}"
    region: "{{ region }}"
    tags: { "Name":"{{ igw_name }}" }
    profile: "{{ profile }}"
  register: igw_info

# ROUTETABLE 作成(IGW)
- name: create_route_table
  ec2_vpc_route_table:
    vpc_id: "{{ vpc_info.vpc.id }}"
    subnets: "{{ atache_igw_subnet }}"
    routes:
      - dest: 0.0.0.0/0
        gateway_id: "{{ igw_info.gateway_id }}"
    region: "{{ region }}"
    profile: "{{ profile }}"
    resource_tags: { "Name":"{{ rttable_pub_name }}" }

# NGW の ID を取得
- name: get_subnet_id
  shell: aws ec2 describe-subnets --region {{ region }} --profile {{ profile }} --output text | grep -B 1 {{ ngw_subnet_name }} | awk 'NR==1 {print $12}'
  register: ngw_subnet_id

#- name: show
#  debug:
#    msg: "{{ ngw_subnet_id.stdout }}"

# NGW 作成
- name: create_ngw
  ec2_vpc_nat_gateway:
    subnet_id: "{{ ngw_subnet_id.stdout }}"
    region: "{{ region }}"
    profile: "{{ profile }}"
  register: ngw_info

#- name: show
#  debug:
#    msg: "{{ ngw_info.nat_gateway_id }}"

# NGW 作成まで待つ
- name: wait_for_ngw
  pause:
    minutes: 5

# ROUTETABLEの作成(NGW)
- name: create_route_table2
  ec2_vpc_route_table:
    vpc_id: "{{ vpc_info.vpc.id }}"
    subnets: "{{ atache_ngw_subnet }}"
    routes:
      - dest: 0.0.0.0/0
        gateway_id: "{{ ngw_info.nat_gateway_id }}"
    region: "{{ region }}"
    profile: "{{ profile }}"
    resource_tags: { "Name":"{{ rttable_dmz_name }}" }

NATGATEWAY の ID が上手く取得できなかったので awscli の結果をパースして ngw_subnet_id に渡しています。

変数定義

---
# vars file for aws_vpc

# REGION
  region: "ap-northeast-1"

# PROFILE
  profile: "default"

# VPC
  vpc_name: "sanuki-wd-vpc2"
  vpc_cidr: "10.10.0.0/16"

# IGW
  igw_name: "sanuki-igw2"

# NGW
  ngw_name: "sanuki-ngw2"


# NGWを作成するサブネット名
  ngw_subnet_name: "sanuki-wd-public-subnet2-a"

# ROUTETABLE(PUBLIC)
  rttable_pub_name: "sanuki-pub-rt2"

# ROUTETABLE(DMZ)
  rttable_dmz_name: "sanuki-dmz-rt2"

# PUBLIC_SUBNET
  pub_subnet:
    - { pub_subnet_cidr: "10.10.10.0/24" ,subnet_az: "ap-northeast-1a" ,pub_subnet_name: "sanuki-wd-public-subnet2-a" }
    - { pub_subnet_cidr: "10.10.20.0/24" ,subnet_az: "ap-northeast-1c" ,pub_subnet_name: "sanuki-wd-public-subnet2-c" }


# DMZ_SUBNET
  dmz_subnet:
    - { dmz_subnet_cidr: "10.10.30.0/24" ,subnet_az: "ap-northeast-1a" ,dmz_subnet_name: "sanuki-wd-dmz-subnet2-a" }
    - { dmz_subnet_cidr: "10.10.40.0/24" ,subnet_az: "ap-northeast-1c" ,dmz_subnet_name: "sanuki-wd-dmz-subnet2-c" }


# PRIVATE_SUBNET
  pri_subnet:
    - { pri_subnet_cidr: "10.10.50.0/24" ,subnet_az: "ap-northeast-1a" ,pri_subnet_name: "sanuki-wd-private-subnet2-a" }
    - { pri_subnet_cidr: "10.10.60.0/24" ,subnet_az: "ap-northeast-1c" ,pri_subnet_name: "sanuki-wd-private-subnet2-c" }

# IGWに紐付けるサブネット
  atache_igw_subnet:
    - "10.10.10.0/24"
    - "10.10.20.0/24"

# NGWに紐付けるサブネット
  atache_ngw_subnet:
    - "10.10.30.0/24"
    - "10.10.40.0/24"

NatGateway が片方の AZ にしかないため、冗長性の観点からは ? となりますが、まぁいいでしょう。

↓ 2層の playbook はこちら

【Ansible】AnsibleでAWSのネットワークを構築する【IaC】