作業中のメモ

よく「計算機」を使って作業をする.知らなかったことを中心にまとめるつもり.

【Node.js】axiosで CSRF 対策への対応

どうも,筆者です.

今回は,axios を用いて,外部サーバと通信する際の CSRF 対策への対応方法を示す。 ここでは,Django で構築された外部サーバ http://hogehoge.com/login にログイン後,同一サーバの http://hogehoge.com/loggedin/sample にアクセスすることを考える.前提として、対象の URL はログイン済みの時のみアクセス可能とする.

python の場合

普段,python を利用しているので,requestsモジュールによる実装を示しておく.今回は,ここに示す内容を Node.js で実現する.

準備

pip install requests

実装

import requests

base_url = 'http://hogehoge.com'

with requests.session() as session:
    # step1: login
    login_url = '{}/login'.format(base_url)
    session.get(login_url)
    csrf_token = session.cookies['csrftoken']
    data = {
        'csrfmiddlewaretoken': csrf_token,
        'username': 'username',
        'password': 'password',
     }
     session.post(login_url, data=data, headers=dict(Referer=login_url))
     # Step2: access to loggedin/sample
     response = session.get('{}/loggedin/sample'.format(base_url))
     print(response.text) # loggedin/sampleのページの内容が表示される

Nodejs

Node.js による実装を示す.

準備

npm install axios
npm install axios-cookiejar-support
npm install tough-cookie
npm install querystring

実装

const querystring = require('querystring');
const axios = require('axios');
const axiosCookieJarSupport = require('axios-cookiejar-support').default;
const tough = require('tough-cookie');
axiosCookieJarSupport(axios);

class CustomAxios {
    constructor(baseURL) {
        const Cookie = tough.Cookie;
        const cookieJar = new tough.CookieJar();
        const csrfHeaderName = 'X-CSRFToken';       // 外部サーバの設定に合わせる
        const csrfCookieName= 'csrftoken';          // 外部サーバの設定に合わせる
        this.postTokenName = 'csrfmiddlewaretoken'; // 外部サーバの設定に合わせる
        this.csrfToken = null;
        this.api = axios.create({
            baseURL: baseURL,
            jar: cookieJar,
            withCredentials: true,
            xsrfHeaderName: csrfHeaderName,
            xsrfCookieName: csrfCookieName,
            headers: {
                'x-requested-with': 'XMLHttpRequest',
            },
        });

        // Add cookie support for Node
        this.api.interceptors.request.use((config) => {
            if (this.csrfToken) {
                config.headers[csrfHeaderName.toLowerCase()] = this.csrfToken;
            }
            cookieJar.getCookies(config.baseURL, (err, cookies) => {
                if (!err && cookies) {
                    config.headers['cookie'] = cookies.join('; ');
                }
            });

            return config;
        });
        this.api.interceptors.response.use((response) => {
            const header = response.headers['set-cookie'];

            if (header) {
                const cookies = (header instanceof Array) ? header.map(Cookie.parse) : [Cookie.parse(header)];

                cookies.forEach((cookie) => {
                    cookieJar.setCookie(cookie, response.config.baseURL, (err, targetCookie) => {
                        if (!err && targetCookie) {
                            // Store previous csrfToken
                            if (targetCookie.key === csrfCookieName) {
                                this.csrfToken = targetCookie.value;
                            }
                        }
                    });
                });
            }

            return response;
        });

        // bind
        this.get = this.get.bind(this);
        this.post = this.post.bind(this);
    }

    async get(linkName, params) {
        const data = params || {};
        // get request
        const response = await this.api.get(linkName, {params: data});

        return response;
    }

    async post(linkName, sendData) {
        let response;
        // get csrfToken
        response = await this.api.get(linkName);
        const data = Object.assign({[this.postTokenName]: this.csrfToken}, JSON.parse(JSON.stringify(sendData)));
        const headers = {
            Referer: `${response.config.baseURL}${linkName}`,
        };
        // post request
        response = await this.api.post(linkName, querystring.stringify(data), {headers: headers});

        return response;
    }
}

実行例

(async () => {
    const session = new CustomAxios('http://hogehoge.com');
    let response;

    // Step1: login
    const data = {
        username: 'username',
        password: 'password',
    };
    response = await session.post('/login', data);
    // Step2: access to loggedin/sample
    response = await session.get('/loggedin/sample');
    console.log(response.data);
})().catch((err) => console.log(err));

Raspberry Pi と FreePBX と brastel(My 050) で VoIP 環境を構築する②

どうも,筆者です.

続いて,FreePBX で設定をしていく.今回も,以下のサイトを参考に進める.

digi-78.blogspot.com

全体構成

後で,具体的な設定が出てくるが,先に全体構成を示しておく.IAX や PJSIP は VoIP 通信を行う際のプロトコルであると認識している.

全体構成

上記の図にあるように,それぞれの以下のような方法で通信する.

経路 通信プロトコル
スマホから asterisk IAX2
asterisk から brastel server PJSIP

必ず,この方法で通信しないといけないわけではないが,ほかの方法でうまく実現ができなかった.このため,今回は上記の構成で進める.

FreePBXの設定

基本的な操作は,参考サイトをもとに進めていただきたい.ここでは,設定した結果を示すに留める.

アカウントの設定

参考サイトをもとに,アカウントを設定し, apply config を実行する.FreePBX を LAN 外からアクセス可能な構成にする人は,推測可能なユーザ名やパスワードは避けること.

具体的な設定

具体的な設定となるが,すべての画像データを格納できないため,詳細情報を Google Drive 上に残しておく.こちらの PDF を参考に設定していただきたい.

ファイル名:RasPBX_setting.pdf

drive.google.com

また,各設定項目では,設定が完了したら,右下の「送信」を押下後に,右上の「設定適用」を押下すること.

内線の設定 Extensions

  • General の設定.ディスプレイ名は,好きな名前を記入すること.また,初回追加時の内線番号は以降では変更できないため,3 桁から 4 桁程度の値を任意で設定すること.
  • 高度な設定.ここも特に設定することはない.type が friend,port が 4569,qualify が yes となっていることを確認しておくこと.type に関しては,friend のほか,peer と user がある.それぞれの違いは以下を参照のこと.
type 役割
peer 発信専用(電話を受けることはできない)
user 着信専用(電話をかけることはできない)
friend 発信・着信の双方が可能(通常はこちらを使用)

外線設定 Trunks

外線設定となる.外部へ電話をかける際は,ここの設定が重要となる.リンク先の内容に従って設定すること.

発信設定 Outbound Routes

リンク先の内容に従って設定すること.

内線グループ設定 Ring Groups

リンク先の内容に従って設定すること.

着信設定 Inbound Routes

リンク先の内容に従って設定すること.

外部から使いたい人向け

出先から使いたい人向けの設定も載せておく.理解していない人が設定を行うと非常に危険なため,ポート開放の設定など,具体的な方法は示していない.どうしても外から利用したい場合は,VPN の利用などを検討すること.

また,4569のポートをそのまま開放するとすぐに攻撃が来るため,公開するポートは推測しづらいものにすること.

ファイル名:RasPBX_public.pdf

drive.google.com

スマホ側の設定

参考サイトと同じように Zoiper を利用する.これは,IAX2 に対応したソフトウェアが Zoiper 位しかないためである.

LAN 内から利用する場合

アカウント作成時,hostname or providerRaspberry Pi の IP アドレスを入力する.

# 例
192.168.11.2

外部から利用する場合

アカウント作成時,hostname or providerFQDN か 固定 IP アドレスを入力する.また,基本的にポート番号を変更しているため,hostname の後ろに公開用のポート番号を指定する.

# 例
www.exmaple.com:12345

上記以外は,参考サイトの内容に従って設定すればよい.

Raspberry Pi と FreePBX と brastel(My 050) で VoIP 環境を構築する①

どうも,筆者です.

今回は,Rasbperry Pi と FreePBX と brastel(My 050) を用いて,VoIP 環境を構築していこうと思う. LINE 等が普及して,今更感はあるが,気にせず進める.

参考サイト

環境構築にあたり,以下の 2 つのサイトを参考にした.今回は,こちらをベースに進める.

digi-78.blogspot.com

環境構築

概要を述べるに留め,具体的な作業手順は省略する.

イメージの取得と書き込み

参照サイトにもあるように,下記のリンクからイメージファイルをダウンロードし,SD カードに書き込む.

www.raspberry-asterisk.org

2021/9/5 時点の最新版は,以下のようになっている.ここで,Asterisk や FreePBX という用語が出てくるが,これは以降の対応関係を示すときに説明する.

RasPBX のイメージ

raspbx-upgrade の失敗

先に断っておくと,今記載の内容は,筆者の記憶をもとに記載している.一部間違っている箇所があるかもしれないので,出力内容を確認した上で実行して欲しい.

raspbx-upgrade コマンドを実行しても,以下のようなメッセージが表示され,途中で失敗する.

署名照合中にエラーが発生しました。リポジトリは更新されず、過去のインデックスファイルが使われます。GPG エラー: https://packages.sury.org/php buster InRelease: 以下の署名が無効です

このエラーに関しては,以下のサイトが参考になった.

arakoki70.com

参考先のサイトに記載があるように,apt-key list コマンドで GPG 署名状態を確認する.期限切れの署名があるため,署名を削除後,最新のものに更新する.

# GPG 署名状態を確認
apt-key list

# 該当する署名を削除
rm /etc/apt/trusted.gpg.d/php.gpg

# DEB.SURY.ORG の key を削除
apt-key del [表示された key の値]

# DEB.SURY.ORG の GPG ファイルをダウンロード
wget -O /etc/apt/trusted.gpg.d/php.gpg https://packages.sury.org/php/apt.gpg

他にもエラーが残っていたかもしれないが,これで,raspbx-upgrade コマンドが実行できた(と記憶している).

対応関係

次回,FreePBX を用いて Asterisk の設定を行っていく.その際に,筆者が理解している範囲で,対応関係を示す.

対応関係

それぞれの位置づけとしては,以下のようになると認識している.

  • asterisk

    • IP-PBX(IP 型の電話交換機)の実体.このプログラム群が PBX の役割をする.
    • 大量の設定ファイルが存在し,内容も複雑.
  • FreePBX

    • asterisk の設定ファイルを Web 上から GUI で追加・更新するためのプログラム.
    • Web 経由で設定できるため,asterisk の設定ファイルを直接書かなくて良い.

今回も FreePBX に頼るため,基本的に FreePBX の使い方を覚える方針となる.具体的な設定内容は次回以降で説明する.

IPv4 と IPv6 の併用環境下におけるルーティング情報の設定

どうも,筆者です.

最近,v6プラスを導入した.v6プラスの導入により,ルータに搭載されている VPN サーバ機能が利用できなくなった.このため,IPv4 のネットワークを構築し,VPN 環境を作成する方針とした. ここまでは良かったのだが,IPv4 から IPv6 のネットワークにアクセスできない.ネットワークが異なるため,アクセスできないのは当然であるが,これでは非常に不便である.

ここでは,構築した IPv4IPv6 の併用環境下において,IPv4 のネットワークから IPv6 のネットワーク上のサーバにアクセスできるように設定を行う.

前提

ネットワーク構成

IPv4IPv6 のネットワークを以下に示す.ここで,Router A は,IPv6 のネットワークが,Router B は,IPv4 のネットワークが構築されているものとする.また,PC のデフォルトゲートウェイは,Router B,Raspberry Piデフォルトゲートウェイは,Router A に設定されているものとする.

ネットワーク構成

ネットワーク機器の設定情報

  • 192.168.0.x(IPv6)側の設定
対象機器 NIC IP アドレス
Router A - 192.168.0.1
Raspberry Pi X eth0 192.168.0.3
Raspberry Pi Y eth0 192.168.0.5
  • 192.168.100.x(IPv4)側の設定
対象機器 NIC IP アドレス
Router B - 192.168.100.1
Raspberry Pi Y eth1 192.168.100.5
PC eth0 192.168.100.10

目的

ここでは,以下に示すように,PC から Router B,Raspberry Pi Y,Router A を経由し,Raspberry Pi X 内にある IRC サーバにアクセスする場合を考える.

通信経路

通信のための設定

目的に示した通信を実現するためには,以下の 2 点について設定を行う.

  1. Router B におけるルーティング情報の登録
  2. Raspberry Pi X におけるルーティング情報の登録

Router B におけるルーティング情報の登録

Router B の設定画面を開き,「Static Route」や「静的経路情報」の設定画面を開く.その設定画面で,以下の情報を追加後,設定を有効にする.

項目 内容
Destination IP 192.168.0.0
Subnet Mask 255.255.255.0
Next Hop 192.168.100.5
Interface LAN
Metric 0

上記の設定の意味としては,「192.168.0.0/24 宛のパケットを 192.168.100.5 に渡す」という内容になる.

これにより,PC から Raspberry Pi X 宛の通信は,PC -> Router B -> Raspberry Pi Y -> Router A -> Raspberry Pi X の順に渡されることになる.

ただ,このままでは,Raspberry Pi X は,192.168.100.0/24 からの通信をどこに渡せばよいかが分からない.すなわち,応答時のルーティング情報がないため,応答が返ってこない状態となる.

Raspberry Pi X におけるルーティング情報の登録

今度は,Raspberry Pi X に 192.168.100.0/24 宛の通信のルーティング情報を登録する.

Raspberry Pi X において,以下のコマンドを実行し,ルーティングテーブルを更新する.

sudo ip route add 192.168.100.0/24 via 192.168.0.5 dev eth0

上記の意味としては,「192.168.100.0/24 宛のパケットを 192.168.0.5 に渡す」という内容になる.

ただし,このままでは再起動後に設定内容が消えてしまうため,/lib/dhcpcd/dhcpcd-hooks/40-route というファイルを作成し,以下の内容を書き込む.

ip route add 192.168.100.0/24 via 192.168.0.5 dev eth0

以上で,設定は完了となる.一部,理解できていない部分もあるが現時点で期待通りの動作をしているため,しばらく様子見をする.

【TeraTerm】公開鍵認証方式による SSH 接続の自動化

どうも,筆者です.

最近,TeraTerm で LAN 内のサーバにアクセスすることが増えたが,毎回,サーバの指定とパスワードの入力が必要となる.

ここでは,TeraTerm の機能を利用し,ショートカットキーで TeraTerm のマクロを呼び出し,自動的に SSH 接続できるような仕組みを構築する.

本来は,安全のためにパスフレーズを入力するが,今回は,LAN 内の使用を前提とするため,パスフレーズの設定は省略する.

使用する TeraTerm に関する情報

  • TeraTerm は,ポータブル版を利用する.
  • 使用する TeraTerm のバージョンは,4.102 である.
  • TeraTerm の配置場所は以下のようにする.

    C:\Users\user\OneDrive\デスクトップ\apps\teraterm-4.102

参考サイト

以下のサイトを参考に,作業を行った.

www.j-oosk.com

qiita.com

ttssh2.osdn.jp

自動化の準備

公開鍵と秘密鍵の生成

TeraTerm の機能を用いて,公開鍵と秘密鍵を生成する.今回は,C:\Users\user\OneDrive\デスクトップ\apps\ssh_keyfile 以下に公開鍵と秘密鍵を保存する.

  1. 「Setup」→「SSH KeyGenerator」を押下する.

    生成画面
    公開鍵と秘密鍵の生成

  2. 「Key type」を「RSA」,「Key Bits」を「4096」とする(Key Bits:鍵の長さは 4096 以外でもよいが,なるべく長い方が良い).また,「bcrypt KDF format」のチェックを外し,「Generate」を押下する.

    鍵の設定

  3. 今回はパスフレーズなしのため,「Key passphrase」と「Confirm passphrase」は空欄のままとし,「Save public key」と「Save private key」を順に押下する.

    鍵の生成

    「Save private key」を押下時に,以下のようなメッセージが表示される.これは,「はい」を押下する.今回はパスフレーズなしのため,このような警告が表示される.

    パスフレーズなしのため表示される警告

公開鍵の設置

生成した公開鍵を SSH 接続先のサーバに配置する.

  1. 「File」→「New connection ...」を押下する.表示されたダイアログから接続先のサーバを指定し,「OK」を押下する.

    接続先のサーバの指定

  2. いつも通り「username」と「password」を入力し,「OK」を押下する.ここでは,ラズパイに接続するため,username を pi とする.

    ユーザ名とパスワードの入力

  3. ログイン後,「File」→「SSH SCP」を押下し,公開鍵「id_rsa.pub」をサーバのホームディレクトリにコピーする.ここでは,「From」には,C:\Users\user\OneDrive\デスクトップ\apps\ssh_keyfile\id_rsa.pub を,「To」には,~/ を指定した.

    SSH SCP

    公開鍵をサーバにコピー

  4. ファイルを転送が完了したら,コマンド操作で公開鍵(~/id_rsa.pub)を authorized_keys に追記する.

# 作業ディレクトリの確認
pwd
# 出力結果:/home/pi

# ファイルの確認
ls
# 出力結果:id_rsa.pub

# .ssh ディレクトリの生成とパーミッションの設定
mkdir .ssh
chmod 700 .ssh

# 公開鍵の追記
cat id_rsa.pub >> .ssh/authorized_keys
# authorized_keys のパーミッションの設定(初回生成時のみ)
chmod 600 .ssh/authorized_keys

# サーバから切断
exit

公開鍵認証方式によるログイン

再度,TeraTerm を立ち上げ,接続先を選択し,ユーザ名とパスワードを入力する画面を表示させる.その状態で,「Use RSA/DSA/ECDSA/ED 25519 key to log in」を選択し,「Private key file:」を押下し,先ほど生成した秘密鍵を選択する.今回の場合,秘密鍵C:\Users\user\OneDrive\デスクトップ\apps\ssh_keyfile\id_rsa となる.

公開鍵認証方式によるログイン

上記において,username を入力後に「OK」を押下して,サーバに接続できることを確認する.

TeraTerm マクロによる自動化

本題に入る.ここでは,TeraTerm マクロを用いて,以下を実現する.

  • ショートカットキーによる接続先の選択
  • SSH 接続に対し,有効なショートカットキーの一覧の表示
  • ショートカットキー押下後に自動接続

TeraTerm マクロの作成

サーバへ自動接続するために,マクロを作成する.ここでは,C:\Users\user\OneDrive\デスクトップ\apps\teraterm_macros というディレクトリを作成し,その中にマクロファイルを格納する.

サーバへの自動接続時に使用するマクロを以下に示す.ここでは,このファイルを「nas_server.ttl」として保存する.

; ===== setting =====
; usrename
username = 'pi'
; port
port = 22
; secret file path
keyfile = '"..\ssh_keyfile\id_rsa"'
; hostname
hostname = 'nas.example.com'
; ===== end =====

; create connection command
getdir current_dir
sprintf2 private_key_fullpath '%s\%s' current_dir keyfile
sprintf2 msg '%s:%d /ssh2 /auth=publickey /user=%s /keyfile=%s' hostname port username private_key_fullpath

; connect to server
connect msg

作成したファイルをダブルクリックし,パスフレーズを入力することなくログインできることを確認する.

ショートカットキーの登録

teraterm-4.102 内にある KEYBOARD.CNF を修正し,ショートカットキーを登録する.ここでは,Ctrl + F1 を押下時に,先ほど作成した「nas_server.ttl」が呼び出されるように設定する.

上記を実現するために,ファイルの末尾にある [User keys] 部分に以下を追記する.

; === User key definitions ===
[User keys]
;  Ctrl + F1 key: connect nas server
User1=1083,2,..\teraterm_macros\nas_server.ttl

上記を設定後,TeraTerm を開き,ショートカットキー(Ctrl + F1)を押下すれば,指定したマクロが呼び出され,サーバに接続できる.ショートカットキーを押下する際は,事前に「New connection」の画面を閉じること.

TeraTerm 起動時の設定の変更

上記の方法でショートカットキーによりサーバへ接続できるようになった.しかし,毎回表示される「New connection」を閉じる必要があることとショートカットキーを覚えておく必要がある.ここでは,この 2 点について改善する.

ショートカットキーの一覧を表示するマクロの作成

先ほど追加したショートカットキーを表示するマクロを作成する.ここでは,このマクロは teraterm-4.102 内に「startup_dialog.ttl」として保存する.

作成したマクロを以下に示す.

; refer to user key definitions
sprintf2 msg ''
strconcat msg 'Ctrl + F1: nas server\n' ; 今後追加する際は,この行を複製する
; convert special characters
strspecial msg
; show status dialog
statusbox msg 'server list'
; wait [sec]
pause 3
; close status dialog
closesbox

設定ファイルの更新

teraterm-4.102 内にある TERATERM.INI を開き,以下を修正する.

  • HostDialogOnStartup=onHostDialogOnStartup=off に修正.

    これにより,「New connection」のダイアログが非表示になる.

  • StartupMacro=StartupMacro=startup_dialog.ttl に修正.

    これにより,上記で作成した「ショートカットキーを表示するマクロ」が起動時に実行される.

起動時に表示される画面を以下に示す.このダイアログは 3 秒程度経過すると表示されなくなる.

サーバ一覧