日々の備忘録

技術について日々学んだことを書きます。

自宅LANから特定Webサービスへのアクセスを遮断する

SNSなどを見ているといつのまにか時間が経ってしまうので、ネットワークに関しては素人ながら対策を施しました。

自分で設定したものなので当然ながら解除もできるのですが、とにかく手間を増やすことで何となく見てしまう頻度を減らすことを目的としています。

試した対策

ルーターでフィルタリング

LANの根っこを抑えるということで、ルーターの設定から着手しました。

ISP(NURO)のONU(ZXHN F660A)をそのまま利用しており、中継機は挟んでいません。

IPフィルタ

設定項目を確認したところ、宛先がドメイン指定できず、IPアドレスの変更があった場合に手動で追従させる手間が増えるため、不採用としました。

URLフィルタ

こちらが使用できそうだったのですが、有効にした上で https://example.com などとしてブラックリストとして登録しても該当URLへのアクセスを遮断できず、取扱説明書にも入力フォーマットの例がないためこちらの使用は断念しました。

(そもそもhttpsの場合、TLS復号しなければhostnameとport以外を知ることができないはずなので、hostname[:port]フォーマットでなければ判定できないような気もしますが詳細は不明です...)

Windows Hostsファイルで設定

ルーター単体で設定できなかったため、ひとまずメインで利用しているPCのhostsファイルを編集して対応しました。

対処方法としては0コストで楽ではあるのですがWindowsに限られますし、他の端末からは見れてしまうのであまり効果的ではありませんでした。

DNS Serverを構築

Synology NASがあり、こちらでDNS Serverが簡単に構築できたため、こちらを採用しました。

シンプルな構成ですが、図は以下のとおりです。

f:id:ryoshimada746:20210927235622p:plain

遮断したいものだけNAS DNSにダミー登録しておき、他は元々のDNS(ルーター)にフォールバックするようにしています。

HTTP Proxy Serverを構築

Synology NASでHTTP Proxyを建ててみたのですが、アクセス先によっては体感での速度劣化が激しかったため、DNSを利用してみています。 端末ごとにProxyを通すか設定が容易なため、用途によってはこちらのほうが利便性が高いと思われます。

まとめ

私の自宅環境依存ではありますが、DNSでフィルタリングする方式を取りました。

今のところ許容範囲ではありますが、Webブラウジング全般が若干遅くなっているように感じます。 もしかしたらクライアント含めDNSキャッシュが上手く効いていないのかもしれません。

時間があればDNS Serverの設定を見直すか、他の専用サーバを用意しようと思います。

Hyper-Vのポート予約について

はじめに

Hyper-Vの使用方法とは関係ありません。Dockerを使用している最中に気づいた件です。

Hyper-Vがポート予約をしていることを今回初めて知りました。

Dockerで特定ポートを使用できない

ローカル環境のhttpサーバに8080を使うことはよくあると思いますが、以下のエラーで使用できないと出ていました。

Error response from daemon: Ports are not available: listen tcp 0.0.0.0:8080: bind: An attempt was made to access a socket in a way forbidden by its access permissions.

他の用途で8080を使用している記憶はなかったのでいろいろ調べたところ、以下のリンクが見つかりました。

Desktop 3.0.0: listen tcp: bind: An attempt was made to access a socket in a way forbidden by its access permissions. · Issue #10008 · docker/for-win · GitHub

sql server - Docker SQL bind: An attempt was made to access a socket in a way forbidden by its access permissions - Stack Overflow

どうやらHyper-Vによってかなりの範囲が予約されているようだったので、stackoverflowに提示されている方法で調整したところ、無事8080ポートが使用できるようになりました。

Docker DesktopはWSL2 Backendで使用しており、Hyper-Vを久しく使っていないので無効化しておくとこういったことがなくなるかもしれませんが、いわゆる仮想マシンが欲しくなったときに不便なので悩ましいところです。

Windows Sandbox と chocolatey でアプリケーション検証環境を作成する

Windows Sandbox と chocolatey でアプリケーション検証環境を作成する方法を記載します。

Windows Sandbox

Windows 10 Pro では Windows Sandbox という機能が利用できます。

docs.microsoft.com

ざっくり説明すると、ホストマシンと隔離された軽量なデスクトップ環境を一時的に構築する機能です。

ホストマシンと隔離されるので、安全かわからないアプリケーションをインストールして試したりすることができるとのこと。

一時的なものなので、稼働中のSandboxウィンドウを閉じれば削除されます。

chocolatey

Windows における、いわゆるパッケージマネージャソフトウェアです。

chocolatey.org

choco install <package> といった具合にインストールをコマンドとして実行できます。

Community Repositoryだけでもかなりの数のパッケージが用意されているため、大変便利です。

自動化

先に chocolatey を用いたインストールスクリプトを用意しておきます。

# install chocolatey
[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072

Invoke-Expression ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))

choco install -y "googlechrome"

これでGoogle chromeをインストールするスクリプトができます。

次にWindows Sandbox側の準備をします。

まず、Windows Sandboxを利用可能なように一度だけ下記コマンドを実行する必要があります。

Start-Process powershell {Enable-WindowsOptionalFeature -FeatureName "Containers-DisposableClientVM" -All -Online} -Verb RunAs

Windows Sandbox の起動ファイルとして、wsbという拡張子のxmlファイルが利用できます。

docs.microsoft.com

下記のように設定することで、ホストマシンのC:\ShareOnHostがSandbox側にC:\Sharedとして共有され、foo.ps1を自動で実行します。

<Configuration>
  <MappedFolders>
    <MappedFolder>
      <HostFolder>C:\ShareOnHost</HostFolder>
      <SandboxFolder>C:\Shared</SandboxFolder>
      <ReadOnly>true</ReadOnly>
    </MappedFolder>
  </MappedFolders>
  <LogonCommand>
    <Command>powershell.exe -ExecutionPolicy RemoteSigned C:\Shared\foo.ps1</Command>
  </LogonCommand>
</Configuration>

あとはこのwsbファイルをダブルクリックするなり、powershell Start-Process path\to\box.wsb とするだけでSandboxが構築できます。

注意点

実は、上記と同じようにLogonCommandにchocolateyを使ったインストールスクリプトを指定するだけではパッケージインストールが上手くいきません。

私自身これにかなりはまってしまったのですが、chocolateyのlogを確認することで糸口が見つかりました。

chocolateyのlogは、C:\ProgramData\chocolatey\logs にあります。

  • ERROR: The handle is invalid. などと表記されており、LogonCommandがウィンドウなしで起動されていることをタスクマネージャで確認している
  • Sandbox内で直接インストールスクリプトpowershell host上で実行させると問題なく動作する

以上のことから、chocolateyの動作にはpowershell hostをコンソールウィンドウありで起動する必要があると疑いを持ちました。

そして下記リンクの通りにLogonCommandを変更すると問題なく動作するようになりました。

Windows Sandbox PowerShell logon command window not visible - Stack Overflow

<LogonCommand>
powershell.exe -ExecutionPolicy Unrestricted -Command "start powershell {{ -NoExit -File C:\path\to\install.ps1 }}"
</LogonCommand>

ということで、簡易的にアプリケーションの検証環境が構築できました。

ちなみに、chocolateyのcommunity repositoryにはRate Limitが設けられており、短期間で何度も実行することはできないのでご注意ください。(ライセンスを購入すれば無視できるかもしれません)

Github アカウントとgit commit autherの紐づけについて

Githubアカウントは前から作ってあったのですが、閲覧やissue記載が主でコミットは今まであまりしていなかったので改めて気づいたことを記載します。

git pushは正常に受け付けられるため、何も問題ないと思っていたのですが、今になってコミット履歴のauthorがghostになっており、紐づけがされていないがわかりました。

結論から言うと、githubに登録しているメールアドレスとgit config user.emailを合致させる必要がありました。

docs.github.com

メールアドレスを下手に公開したくなかったので、git config user.emailには適当なダミーアドレスを設定していたため、ghostになっていたというわけです。

Githubには個人メールアドレスを秘匿する機能があるため、これを有効にしている場合はnoreply専用のアドレスが用意されるのでこちらをuser.emailに設定すれば、紐づけが上手くいきます。 docs.github.com

また、メールアドレスの秘匿をより強固にする場合、以下のリンクの通りに設定しておいたほうがよさそうです。

docs.github.com

Centos7 Journald と rsyslog で/dev/logの管理が競合する

経緯

rsyslogを弄っているときに /dev/log が消えてしまうことがありました。

Centos7 では /dev/log はjournaldの管理下となり (systemd-journald.socket)、rsyslogはjournaldから順次読み出しをするようになったようです。 にも関わらず、rsyslogも/dev/logを管理してしまうようです。(元々syslogの管理するものだったので当然かもしれませんが)

発覚した経緯としては、rsyslogのrainerscriptを調整する際、適宜systemctl restartが必要なのですが、稀に /dev/log が消失してしまうことがあり、systemd-journald.socketを再起動するまでそのままになってしまいます。

systemd / rsyslog 双方の具体的なバージョンは失念しましたがcentos 7.8にシステムアップデートした後で確認しています。

解消方法

centos7 標準yumリポジトリのrsyslogが古いため、rsyslogの公式リポジトリから新しいバージョンにアップグレードする必要があります。 rsyslog rhelcentos-rpms

v8.2010にアップグレードしたところ、何度かrestartをしましたが、今のところ再発していないので解消されたようです。

ディストリビューションにも依ると思いますが、本元で解消されていても取り込まれないのは若干厄介ではありますね。 その代わりに新しいバグが混入することもないので一長一短だとは思いますが。

Vagrant + Hyper-V 静的IP設定 + SMB設定

せっかくWindows 10 Proを使っているのでHyper-Vvagrantで操作してみたいと思い、試行錯誤しました。

本来であれば、以下の項目を全て満たしたかったのですが、どうしても3だけは解決できませんでした。

  1. 静的IPの設定
  2. ホスト、ゲスト間フォルダ共有
  3. vagrant up時のインタラクティブ操作の排除

一応使用する目線で許容範囲にはなったので記事にしたいと思います。

環境

また、Hyper-V で利用可能な仮想ネットワークが既にあることを前提としています。(Hyper-V自体は何年か前に利用可能にしておいたのでこの辺りの流れは覚えておらず...)

静的IPアドレスの指定

まず、Hyper-VIPアドレスを固定するには以下の方法があります。

ちなみに、vagrant 公式ドキュメントに記載の通り、Hyper-V providerを選択するとVagrantfileに記載するネットワーク設定のほとんどが無視されるので、自分でどうにかする必要があります。

DHCPサーバを立てたところで、VMMACアドレスVagrantで指定できないので必然的に後者を選択することになります。

まずHyper-V NATにはホストに対して一つのNATネットワークしか構築できない制約があり、実態に応じて広い範囲で構築する必要があります。

VM構築の前にNATネットワークを構築する必要があるので、triggerを使います。Hyper-Vを使用する場合は管理者権限で起動したPowershellが大前提となるので、これのために権限周りで何か気にする必要はないと思います。

# Vagrantfile
# ... 中略
config.trigger.before :up do |trigger|
  trigger.run = {
    path: "create-nat-hyperv-switch.ps1"
  }
end
# create-nat-hyperv-switch.ps1
$switch_name = "VagrantNATSwitch"
$ip_addr = "192.168.100.1"
$ip_prefix = "24"
$ip_addr_prefix = "192.168.100.0/24"
$network_name = "VagrantNATNetwork"

if ($switch_name -notin (Get-VMSwitch | Select-Object -ExpandProperty Name)) {
    New-VMSwitch -Name $switch_name -SwitchType Internal
} else {
    Set-VMSwitch -Name $switch_name -SwitchType Internal
}

$vmnet_adapter = Get-VMNetworkAdapter -ManagementOS -SwitchName $switch_name

$network_adapter = Get-NetAdapter | ? { $_.DeviceID -eq $vmnet_adapter.DeviceID }

if ($network_adapter | Get-NetIPAddress -IPAddress $ip_addr -ErrorAction SilentlyContinue) {
    $network_adapter | Disable-NetAdapterBinding -ComponentID ms_server -ErrorAction SilentlyContinue
    $network_adapter | Enable-NetAdapterBinding -ComponentID ms_server -ErrorAction SilentlyContinue
} else {
    $network_adapter | Remove-NetIPAddress -Confirm:$false -ErrorAction SilentlyContinue
    $network_adapter | Set-NetIPInterface -Dhcp Disabled -ErrorAction SilentlyContinue
    $network_adapter | New-NetIPAddress -AddressFamily IPv4 -IPAddress $ip_addr -PrefixLength $ip_prefix -ErrorAction Stop | Out-Null
}

$interface_alias = $network_adapter.Name
Set-NetConnectionProfile -InterfaceAlias $interface_alias -NetworkCategory Private

if ($ip_addr_prefix -notin (Get-NetNAT | Select-Object -ExpandProperty InternalIPInterfaceAddressPrefix)) {
    New-NetNAT -Name $network_name -InternalIPInterfaceAddressPrefix $ip_addr_prefix
} else {
    Write-Host ("{0} for static IP configuration already exists. skipping" -F $ip_addr_prefix)
}

以上で vagrant up 時に先にこちらのNAT構築、仮想スイッチ作成が完了します。

私の環境の問題かもしれませんが、Set-NetConnectionProfile を毎回実行しないとホストマシンが再起動するごとにネットワークプロファイルがPublic扱いに戻されてしまい、SMBなどがファイアウォールで許可されない状況になります。

識別できないネットワーク扱いになっているのを解消すればいいようですが、ローカルセキュリティポリシーの設定を緩くするか、powershell - Windows 8.1 Hyper-V network adapter is set to public and won't save as private - Server Fault によれば、レジストリを弄ることでPublic扱いに変更されることを回避可能なようです。試していませんが、ローカルセキュリティポリシーを緩くするのはあまり気が進みませんね。

Vagrantを介している場合は毎回Set-NetConnectionProfileを上記スクリプトで実行するので問題にはなりませんが、Hyper-V自体をきちんと使おうとすると真面目にやらなければならないんだろうと思います。

ウィルス対策ソフトがインストールされている場合、Set-NetConnectionProfileでもSMBが通らないことがあるので、その場合はウィルス対策ソフトのファイアウォール設定を確認してください。

あとはこれをVMに如何に設定するかなのですが、ssh接続が必要な処理の前に仮想スイッチに切り替えてしまうと処理が進まなくなるため、切り替えタイミングが重要です。

reload provisionerとの組み合わせでネットワーク設定

# configure-static-ip.sh
STATIC_IP=...
IPV4_PREFIX=...
IPV4_ADDR_GATEWAY=...
IPV4_DNS=...

CONNECTION_UUID=$(nmcli -g uuid con)

nmcli con mod "${CONNECTION_UUID}" ipv4.addresses "${STATIC_IPV4}/${IPV4_PREFIX}"
nmcli con mod "${CONNECTION_UUID}" ipv4.gateway ${IPV4_ADDR_GATEWAY}
nmcli con mod "${CONNECTION_UUID}" ipv4.dns "${IPV4_DNS}"
nmcli con mod "${CONNECTION_UUID}" ipv4.method manual
nmcli con mod "${CONNECTION_UUID}" connection.autoconnect yes
nmcli con reload
# set-hyperv-switch.ps1
$vmname = "..."
$switch_name = ...
Get-VM $vmname | Get-VMNetworkAdapter | Connect-VMNetworkAdapter -SwitchName $switch_name
config.vm.provision "shell", preserve_order: true do |shell|
  shell.path = "./configure-static-ip.sh"
end
config.vm.provision :reload, preserve_order: true

config.trigger.before :reload do |trigger|
    trigger.info = "Setting Hyper-V switch"
    trigger.run = {
        path: "set-hyperv-switch.ps1"
        args: [
            "-vmname #{vm_name}",
            "-switch_name #{switch_name}"
        ]
    }
end

ゲストOS側で静的IPを設定しつつ、reload providerで再起動。triggerでreload直前に仮想スイッチの入れ替えを行っています。

幸い、再起動時(シャットダウン時)にネットワーク疎通ができない場合でもvagrantは強制再起動してくれるのでこれで上手くいきます。

SMBでのフォルダ共有

SMBでのフォルダ共有時、アカウント入力を求められます。

毎回入力するのは面倒ですがVagrantfileにベタ書きするのはよろしくないので、vagrant-envを使って.envを読み込ませるようにします。

SMB_HOST(ホストOSのIP)は必須ではないはずですが、私のマシンでホストOSのIPを特定できていないような挙動を示したことがあるので、念のため設定しています。(問題なければルータでホストマシンのIPを固定しておくといいです。)

# .env
SMB_USERNAME=...
SMB_PASSWORD=...
SMB_HOST=...
# Vagrantfile
config.vagrant.plugins = [
  "vagrant-env",
  ...
]
config.env.enable
config.vagrant.sensitive = [
  ENV['SMB_USERNAME'],
  ENV['SMB_PASSWORD'],
  ENV['SMB_HOST'],
]

config.vm.synced_folder ".", "/vagrant", type: "smb",
  smb_username: ENV['SMB_USERNAME'],
  smb_password: ENV['SMB_PASSWORD'],
  smb_host: ENV['SMB_HOST']

これでSMBによるフォルダ共有は完了です。

vagrant upだけでインタラクティブな操作なしに仮想マシン起動が完結すること

これがどうしても実現できませんでした。

まず、VM未作成の場合に接続可能な仮想スイッチを選択することが必須となります。

Vagrantfile内でrubyの関数が実行可能なので、以下のようにVMの作成状況を確認してネットワーク設定を有効化することができます。(ネットワーク設定はほとんどが無視されるのですが、何故かbridgeはうまいこと読み取ってくれます)

vm_exists = system("pwsh.exe", "-Command", "Get-VM -VMName foo -ErrorAction SilentlyContinue | Out-Null")
if !vm_exists
  config.vm.network "public_network", bridge: "default_switch_name"
end

ただ、これではreload provisionerで仮想スイッチの入れ替えをしたところでスイッチが切り替わらず、静的IP設定だけが残ってしまって疎通不能なマシンができてしまいます。おそらくreload時に改めてVagrantfileを読み直さないので、スイッチ設定が強制で戻されているのかと思います。

次にtyped_triggerも試してみたのですが、Import直後のスイッチの設定はHyper-Vで確認する限りではできているものの、結局入力を求められてしまいました。

# Vagrantfile

ENV['VAGRANT_EXPERIMENTAL'] = "typed_triggers"
...

config.trigger.after :"VagrantPlugins::HyperV::Action::Import", type: :action do |trigger|
  trigger.info = "Setup Default Switch"
  trigger.run = {
    path: "set-default-switch.ps1"
  }
end

以上の通り、VM作成時にインタラクティブな操作が必要になってしまいました。

また、NATを他の用途で使っている場合はそれも考慮する必要があり、少し気を使います。

結論としては、よほどの事情がない限りは素直にVirtualBoxを使ったほうが圧倒的に楽です。

もし真面目にHyper-Vを使いたいのであれば、VagrantHyper-Vネットワーク対応を待つか、こちらからPull requestを送るくらいの気概が必要かもしれません。

参考URL networking - HyperV - Static Ip with Vagrant - Super User

Systemd で multi instance service を作る際の注意点

表題の通り、Systemdを使っている際に遭遇した問題について記載してみます。


Systemdを使う際に、一時ディレクトリとしてRuntimeDirectoryを定義し、そこをpid、sockファイル置き場にすることはよくあると思います。

サービス停止時に不要なものをクリーンしてくれるので便利なのですが、特定条件において注意が必要です。

まず、Systemd では一つのserviceファイルをテンプレートのように使用することで、複数のサービスインスタンスを管理することが可能です。

# foo@.service
[Unit]
Description=foo service %i

[Service]
Type=simple
RuntimeDirectory=/var/run/foo
PIDFile=/var/run/foo/foo_%i.pid
...

上記のようにファイル名を○○@.service としておくことで、systemctl start foo@baz.service としてテンプレートのように扱うことができます。

この際、serviceファイル内の %i の部分が、baz で置換されるイメージです。

実際には %i 以外にも使用できる指定子はあるようなので、興味のある方は調べてみてください。systemd man page

# systemctl start foo@baz.service
[Unit]
Description=foo service baz

[Service]
Type=simple
RuntimeDirectory=/var/run/foo
PIDFile=/var/run/foo/foo_baz.pid
...

こうすることで、特定のサービスを用途ごとに比較的簡単に切り分けることができます。

察しの良い方は既にお気づきかもしれませんが、RuntimeDirectoryを %i などで分けておかないと、複数起動しているインスタンスの内、一つでも停止してしまうと、他のインスタンスの一時ファイルを巻き込んで全て削除してしまいます。上記の例であれば、PIDファイルが該当します。

なので、以下のように分けておく必要があります。

[Service]
...
RuntimeDirectory=/var/run/foo_%i
PIDFile=/var/run/foo_%i/%i.pid
...

こうすることで、RuntimeDirectory がインスタンスごとに分割でき、それぞれのインスタンスの停止に応じて削除されるようになります。

また、具体的なバージョンは失念してしまったのですが、比較的新しめのSystemdであれば以下のようにディレクトリ名に %i を定義することも可能です。(複数インスタンスの問題というより、fooが先に存在しないとディレクトリが作成できない制約か何かだったかもしれません)

RuntimeDirectory=/var/run/foo/%i

よくよく考えれば事前に予想できそうなものなのですが、踏むまでは気づけないものですね...

複数インスタンスの管理は便利な機能なので、ご存知なかった方は是非使ってみてください。