haproxy(mode tcp)配下のnginxでグローバルIPを知る

目次

(注意) この記事はゴミです

この記事はなんの問題解決にもなっていません。失敗です。 後日問題を解決しましたが、メモをとっていなかったのでどうやって解決したのか具体的な方法がわかりません…

背景

haproxyとはリバースプロキシなのですが、レイヤー4で動かすことができます。
どういうことかというと、http以外の通信でもリバースプロキシと動くことができるということです。 したがって、私はsoftetherの通信とnginxの通信を同じIPアドレスであってもドメイン名ごとに裏で異なる サーバーへアクセスするようにコントロールしています。softetherのSSL通信を剥かれると困りますから レイヤー4のTCPモードで動作して頂けるhaproxyは都合がいいわけです。

しかし、一つ問題がありました。nginx側のログにSSL通信をしてきたホストIPがhaproxyサーバーのものになってしまうということです。 これでは治安の悪いアクセスが来てもどこのIPからその攻撃が来たのか知ることができません。 今回の記事ではhaproxyとnginxのログを照らし合わせるスクリプトを作成することによっておおよそ問題を解決します。

ログの場所

ログを照合するにはそれぞれどこにログが配置されているか知る必要があります。

haproxyのログ

$ less /var/log/haproxy.log

以上のコマンドで確認できます。

nginxのログ

$ less /var/log/nginx/access.log

以上のコマンドで確認できます。

スクリプトを書く

まずは完成したスクリプトをお見せします。

全体のスクリプト

bash用に書きました。したがって、shでは動かないでしょう。

#!/bin/bash

IFS=$'\n'

HAPROXY_LOGS=(`cat /var/log/haproxy.log`)
NGINX_LOGS=(`cat /var/log/nginx/access.log`)
MONTH=()
DATE=()
TIME=()

for ((i = 0; i < ${#HAPROXY_LOGS[@]}; i++))
do
  MONTH+=( `echo ${HAPROXY_LOGS[${i}]} | awk '{ print $1 }'` )
  DATE+=( `echo ${HAPROXY_LOGS[${i}]} | awk '{ print $2 }'` )
  TIME+=( `echo ${HAPROXY_LOGS[${i}]} | awk '{ printf "%s", $7 }' | awk -F ':' '{ printf "%s:%s:%s", $2, $3, $4 }'` )
done

TIME=$( printf "%s\n" "${TIME[@]}" | sort -u )

PROGRESS_J=0

for (( i=0; i < ${#TIME[@]}; i++))
do
  HAPROXY_IP=`echo ${HAPROXY_LOGS[${i}]} | awk '{ print $6 }'`
  for ((j = ${PROGRESS_J}; j < ${#NGINX_LOGS[@]}; j++))
  do
    NGINX_IP=`echo ${NGINX_LOGS[${j}]} | awk '{ print $1 }'`
    if [[ ${NGINX_LOGS[${j}]} =~ ${MONTH} ]] && [[ ${NGINX_LOGS[${j}]} =~ ${DATE} ]]; then
      if [[ ${HAPROXY_IP} =~ "172.16" ]]; then
        continue
      elif [[ ${NGINX_LOGS[${j}]} =~ ${TIME[${i}]:0:5} ]]; then
        echo -n "${MONTH[${i}]} ${DATE[${i}]} `echo ${NGINX_LOGS[${j}]} | awk -F ":" '{ printf "%s:%s:%s ", $2, $3, $4 }' | awk '{ printf "%s ", $1 }'`"
        if [[ ${NGINX_IP} == "127.0.0.1" ]]; then
          echo -n "${HAPROXY_IP} "
        else
          echo -n "${NGINX_IP} "
        fi
        echo ${NGINX_LOGS[${j}]} | awk '{ printf "%s %s %s %s %s %s %s %s %s\n", $6, $7, $8, $9, $10, $11, $12, $13, $14 }'
        PROGRESS_J=`expr ${j} + 1`
      fi
    fi
  done
done

長いですが長くないとも言えるでしょう(笑)

各ログの取得

ログはリスト変数に格納します。

IFS=$'\n'

HAPROXY_LOGS=(`cat /var/log/haproxy.log`)
NGINX_LOGS=(`cat /var/log/nginx/access.log`)

かっこで代入を行うことによってリスト変数として定義できます。 ログの取得は雑にcatコマンドを用いています。コマンドの出力結果を代入したいので バッククォートでコマンドを囲っています。これがなければ正しく動作しません。

IFS=$'\n'

この部分に関してですが、リストの要素一つ分の区切りは改行によって行うという取り決めをしています。

haproxyログから日時情報を取得

このあたりからはややこしいのでコメントによる解説も取り入れます。

# 日時用リスト変数を宣言
MONTH=()
DATE=()
TIME=()

# ログを一行ずつ読み込みリスト変数へ格納
for ((i = 0; i < ${#HAPROXY_LOGS[@]}; i++))
do
  MONTH+=( `echo ${HAPROXY_LOGS[${i}]} | awk '{ print $1 }'` )
  DATE+=( `echo ${HAPROXY_LOGS[${i}]} | awk '{ print $2 }'` )
  TIME+=( `echo ${HAPROXY_LOGS[${i}]} | awk '{ printf "%s", $7 }' | awk -F ':' '{ printf "%s:%s:%s", $2, $3, $4 }'` )
done

# 重複した時刻を削除
TIME=$( printf "%s\n" "${TIME[@]}" | sort -u )

awkコマンドはデフォルトのオプションを付けない状態において、空白区切りで何番目の文字列を抜き出すかを指定することができます。 一番目の文字列は月で二番目の文字列は日にち、三番目の文字列が時間であるというわけです。

ログの照合

この部分が今回のスクリプトでコアとなる部分です。

# nginxで同じ行を読まないようにどこまで読む行が進んだか記録する変数
PROGRESS_J=0

# haproxyログから取得した時間の分だけループして照らし合わせる
for (( i=0; i < ${#TIME[@]}; i++))
do
	# haproxyログに残っているグローバルIPを変数に格納します
  HAPROXY_IP=`echo ${HAPROXY_LOGS[${i}]} | awk '{ print $6 }'`

	# nginxのログを一行ずつ読み込みます(既に読んでいる行は読みません PROGRESS_Jのおかげです)
  for ((j = ${PROGRESS_J}; j < ${#NGINX_LOGS[@]}; j++))
  do
		# nginxログに残っているIPアドレスを変数に代入(httpだった場合グローバルIPが入っている)
    NGINX_IP=`echo ${NGINX_LOGS[${j}]} | awk '{ print $1 }'`

		# 月日を照合
    if [[ ${NGINX_LOGS[${j}]} =~ ${MONTH} ]] && [[ ${NGINX_LOGS[${j}]} =~ ${DATE} ]]; then
			# LAN内からのアクセスは排除
      if [[ ${HAPROXY_IP} =~ "172.16" ]]; then
        continue
			# 時間を十分単位で照合(1分単位のグローバルIPアドレスは取りこぼしてしまう)
      elif [[ ${NGINX_LOGS[${j}]} =~ ${TIME[${i}]:0:5} ]]; then
        echo -n "${MONTH[${i}]} ${DATE[${i}]} `echo ${NGINX_LOGS[${j}]} | awk -F ":" '{ printf "%s:%s:%s ", $2, $3, $4 }' | awk '{ printf "%s ", $1 }'`"
				# haproxyサーバからのアクセスならグローバルIPアドレスを出力
        if [[ ${NGINX_IP} == "127.0.0.1" ]]; then
          echo -n "${HAPROXY_IP} "
				# httpでアクセスしてきたログはnginxのログに残ったグローバルIPをそのまま出力
        else
          echo -n "${NGINX_IP} "
        fi
				# その他こまやかな情報を出力(どのパスにアクセスしようとしたかなどが分かる)
        echo ${NGINX_LOGS[${j}]} | awk '{ printf "%s %s %s %s %s %s %s %s %s\n", $6, $7, $8, $9, $10, $11, $12, $13, $14 }'
        PROGRESS_J=`expr ${j} + 1`
      fi
    fi
  done
done

十分単位で時間を照合しているのは、HTTPの方がどうしてもログが多くなってしまうからです。 TCPとしてログは一つしかなくてもHTTPはTCPの中で何度もやり取りが発生するので一つのTCPログに対して 複数のHTTPログが残ると考えるべきです。したがって、十分単位にしなければ取得できるログが極端に少なくなってしまいます。 また、副作用として照合が大雑把になっている分本来アクセス元として存在するはずのグローバルIPがいくつか見えなくなります。 それでも治安の悪いIPアドレスをいくつか判定できることには大きな意義があります。

その他こまやかな情報と書きましたが、あの部分でwp-adminへアクセスしようとしているかどうかなどを確認できます。 管理画面へのアクセスを試みるような輩は悪ですからIPのブロックも検討できるわけです。本来であらばiptablesなどを使ってそのあたりも自動化 したいですが、今回はそこまでやっておりません。

まとめ

bashスクリプトが非コンパイル型の言語であったため期待した動作が得られずイライラし続けたのですが、なんとかある程度希望をかなえる物ができました。 同じ問題で悩んでいる方がおられましたら、上記スクリプトを参考にして頂けると幸いです。最後まで読んで頂きありがとうございました。




Archives

2022 (7)
2021 (3)
2020 (4)

Writer

筆者のイメージ画像
kusshie

情報系学部に所属していた社会人1年生です。大学ではネットワークを学んでいましたがまだまだです。 最近は友達に誘われてISPごっこに足を踏み入れました (AS63791)。