20200217のLinuxに関する記事は7件です。

ファイル操作の基本コマンド

Linuxではあらゆる種類の情報をファイルとして扱うため、
ファイルの操作に慣れておくことはLinuxを使う上でとても重要です。
そこで、今回はファイル操作における基本的なコマンドと、その使い方を紹介していきます。

ファイルの操作

1.ファイルの新規作成

touch <作成したいファイル名>

$ touch hoge
# hogeという名前のファイルが新規作成される

また、スペース区切りで複数のファイルを指定することにより、
複数のファイルが作成される。
このように複数のファイルを一度に指定する方法は、これから紹介する
以下のコマンドでも同じように使用できる

  • rm : 複数のファイルが削除される
  • cat : 複数のファイルが連結して表示される

touch <作成したいファイル名1> <作成したいファイル名2> <作成したいファイル名3>

$ touch hoge1 hoge2 hoge3
# 3つのファイルが1回のコマンド実行で作成される。

2.ファイルの削除

rm <削除するファイルパス>

$ rm hoge
# newfileが削除される

なお、rmコマンドを実行してしまうと確認なしにファイルが削除されてしまう。
そのため、削除する前に確認をはさむ場合は-iオプションを付ける。

$ rm -i hoge
rm: 通常ファイル 'hoge' を削除しますか?
# 削除する場合は'y'を、しない場合は'n'を入力する

3.ファイルの上書き

cp <上書き元ファイルパス> <上書きしたいファイルパス>

$ cp hoge1 hoge2
# hoge2がhoge1で上書きされる。

こちらもrmコマンド同様に上書き前に確認をはさむ場合は-iオプションを付ける。

$ cp -i hoge1 hoge2
rm: 'hoge2'を上書きしますか?
# 上書きする場合は'y'を、しない場合は'n'を入力する

4.ファイルの中身確認

cat <中身を確認するファイル>

$ cat hoge
これはテストです。
# hogeに挿入されている内容が表示される。

なお、1でも紹介したとおり、catコマンドでは複数のファイルを指定すると、
ファイルの中身が連結されて表示される。
(これはcatがconcatenate(連結する)の略であることから。)

また、catコマンドではオプションを付けることによって表示形式を
変更することができる

オプション 説明
-n 行数を表示する
-E 行末に$をつけて表示する
-b 空白行以外の行を行数とセットで表示

※注意
catコマンドはファイルの文字コードの取り決めに従って、
数値を文字に置き換えたものを表示している。
そのため、「文字コード以外の数値が含まれているようなファイル」を表示しようとすると
数値を無理やり文字に置き換えようとするため、文字化けが発生する。
これは後で紹介するlessコマンドも同様である。

$ cat /bin/pwd
# 画像はコマンドの実行結果を一部抜粋したものである。
# ご覧のように文字化けが発生しているのが分かる。

スクリーンショット 2020-02-16 10.18.01.png

catコマンドでもファイルの中身を見ることはできるが、
1画面内に収まりきらないファイルの内容を表示する場合は
lessコマンドを使うことをおすすめする。

lessコマンドは長いファイルを1画面ごとに表示し、スクロールバーを
動かさなくても、キーボードだけでファイルを移動して表示することができる。
また、qを押すまではファイル閲覧専用モードなので、ファイルの閲覧だけに
特化しているのもおすすめする理由。

less <ファイルパス>

以下がファイルを移動する際に使うキーの一覧である。

キー 実行結果
f、スペースキー 1画面下に移動する
b 1画面上に移動する
j 1行下に移動する
k 1行上に移動する

5.ディレクトリの作成

mkdir <ディレクトリ>

$ mkdir hogedir
# hogedirというディレクトリが作成される

深いディレクトリを一気に作成したい場合は-pオプションを付ける

$ mkdir -p test/sample/hogedir

6.ディレクトリの削除

ディレクトリの中身が空であるかどうかによって、指定するコマンドが変わってくる。

ディレクトリが空ではない場合

対象となるディレクトリ配下にファイルやディレクトリが存在する場合は
rmコマンドに-rオプションを付けて削除する。

rm -r <削除対象ディレクトリパス>

$ ls hogedir
dir1 dir2 file1.txt
# 削除対象のディレクトリが空ではないことを確認する
$ rm -r hogedir
# ディレクトリに配置されたファイルも含めて、ディレクトリが削除される

ディレクトリが空の場合

rmdirはディレクトリ内にファイル、ディレクトリが配置されていない場合に
使えるコマンドである。
ただし、rmdirは.(ドット)から始まる隠しファイルが含まれていると
実行してもエラーとなる。
そのため、実行前は通常ファイルだけでなく、隠しファイルも含めて
ディレクトリが空であることを確認する。

rmdir <削除するディレクトリパス>

$ ls -a hogedir
# 隠しファイルも含めてディレクトリが空であることを確認する。
$ rmdir hogedir
# 空ディレクトリが削除される

7.ファイル名変更

mv <変更前のファイルパス> <変更後のファイルパス>

2つの引数がどちらともファイルであり、変更後のファイルが存在しない場合は
ファイル名の変更が行われる。
変更後のファイルがすでに存在した場合は、
cpコマンド同様にファイルの上書きが行われる。

$ mv hoge1 hoge2
# hoge1の名前がhoge2に変わる

また、ディレクトリでも同じような方法で名前を変更できる。

mv <変更前のディレクトリパス> <変更後のディレクトリパス>

こちらも変更後のディレクトリが存在しない場合に
ディレクトリ名の変更が行われる。
変更後のディレクトリがすでに存在した場合は
8.のような実行結果となる。


8.ディレクトリへの移動

ここでは7.で紹介したmvコマンドを用いる

mv <移動するファイルパス(スペース区切りで複数指定可)> <移動先ディレクトリパス>

このように最後に指定する引数がファイルではなく、ディレクトリである場合には
名前変更ではなく、ディレクトリへの移動が行われる。

$ mv hoge1 hoge2 hoge3 hogedir
# 3つのファイルがhogedirリに移動する

また、ファイルに限らずディレクトリをディレクトリ配下に移動することも
同じ方法でできる。

mv <移動するディレクトリパス(スペース区切りで複数指定可)> <移動先ディレクトリパス>

$ mv hogedir1 hogedir2
# hogedir2配下にhogedir1が移動する。

参考書籍

新しいLinuxの教科書
入門者のLinux

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

sedコマンドで指定の文字列を削除する方法!!!

Shellscriptを書いていて、sedで指定した完全一致する文字を削除する方法を書きたいと思います。

sed

sed -i -e '/^1.1.1.1$/d' test.txt

結果

元ファイル

1.1.1.100
1.1.1.1

sed後

1.1.1.100

1.1.1.1が消えます。

正規表現の^ $で最初と最後を指定してあげないと1.1.1.1が含まれる文字が消えてしまいます。
そのため、^$の指定がないと上記の1.1.1.100とかも消えます。
  
以下オプション説明
-i ファイルを直接編集する GNU系のみ
-e スクリプト(コマンド)を追加する
/d dコマンドは行を削除する。

正規表現
^ 先頭の文字列
$ 末尾の文字列

コマンド詳細はここをチェック

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AWS Lambda Layersを用いてLambda関数から外部ライブラリを読み込む。Amazon Linux 2のPython環境も整います。(Python3.6、Anaconda)

AWS Consoleにログインし、EC2からAmazon Linux 2を立てます。
image.png
インスタンスタイプはt2.microで、確認と作成ボタンをクリックします。※後述しますが、t2.mediumにしてください。
image.png
セキュリティーグループは各自で設定してください。SSH接続用に22番ポートを開けておきます。
image.png
既存のキーペアを選択、なければ新規作成してください。SSH接続する際に必要です。
image.png
インスタンスの作成中です。インスタンスの表示をクリックします。
image.png
インスタンスの状態がrunning、ステータスチェックが2/2のチェックになったら完了です。
image.png
インスタンスの作成が完了したら、IPv4パブリックIPをコピーして、PuTTYからSSH接続します。
image.png
PuTTYを開いて、Host NameのところにIPアドレスを入力してください。
image.png
Connection->SSH->Authからキーペアを選択します。PuTTYgenを用いて、.pemから.ppkに変換したものを使用します。
image.png
はいをクリックします。
image.png
ec2-userでログインできます。
image.png

ログインしたら、Pythonのバージョンを確認します。いまだにPython2系がデフォルトになっているようです。

$ python
Python 2.7.16 (default, Dec 12 2019, 23:58:22)
[GCC 7.3.1 20180712 (Red Hat 7.3.1-6)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> exit()

Anacondaをインストールします。AnacondaのサイトからダウンロードURLをコピーします。
image.png

ダウンロードURLに対してwgetし、bashでインストールします。

$ wget https://repo.anaconda.com/archive/Anaconda3-2019.10-Linux-x86_64.sh
$ bash Anaconda3-2019.10-Linux-x86_64.sh

Enterで規約を読み、yesを入力。Enterでインストール開始。

Please answer 'yes' or 'no':'
>>> yes

Anaconda3 will now be installed into this location:
/home/ec2-user/anaconda3

  - Press ENTER to confirm the location
  - Press CTRL-C to abort the installation
  - Or specify a different location below
Unpacking payload ...
  0%|   | 0/291 [00:00<?, ?it/s]

で止まったままになってしまった。

t2.mediumでできるらしいので、インスタンスの停止、インスタンスタイプの変更を行います。
current latest miniconda sh installer for linux hangs on Unpacking payload step #9345

Anacondaのインストールができたら、Python3.6環境を作成します。

$ /home/ec2-user/anaconda3/bin/conda create -n py36 python=3.6
$ /home/ec2-user/anaconda3/bin/conda init 

インスタンスを再起動し、py36環境でpythonフォルダにライブラリをインストールします。

$ conda activate py36
$ mkdir python
$ pip install -t ./python requests
$ pip install -t ./python ulid-py
$ pip install -t ./python pillow
$ pip install -t ./python numpy
$ pip install -t ./python opencv-python
$ pip install -t ./python opencv-contrib-python
$ pip install -t ./python pandas
$ pip install -t ./python matplotlib
$ pip install -t ./python folium

pythonフォルダをzip化します。

$ zip -r GachiLayers.zip python

zipファイルをS3へアップロードするために、awscliの設定とboto3をインストールします。

$ pip install awscli
$ pip install boto3

AWS Console の IAM からユーザーを作成し、AWS Access Key と Secret Access Key を取得します。
image.png

AmazonS3FullAccessのアクセス権限を与えておきます。
image.png

タグは特になし。
image.png

確認します。
image.png

作成に成功したら、aws configure に必要なAWS Access Key と Secret Access Key を取得できるので、大事に保管しておきます。
image.png

AWS Configureの設定、AWS Access Key と Secret Access Key を入力します。

$ aws configure
AWS Access Key ID [None]: xxxxxxxxxxxxxxxxxxxx
AWS Secret Access Key [None]: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Default region name [None]: ap-northeast-1
Default output format [None]: json

ZipファイルをS3へアップロードするためのPythonプログラムを作成します。S3バケットを新規作成しておくこと。

$ vi upload.py
import boto3

filename = 'GachiLayers.zip'
s3 = boto3.resource('s3')
obj = s3.Object(<INSERT YOUR BUCKET NAME>, filename)
response = obj.put(Body = open(filename, 'rb'))

実行します。

$ python upload.py

S3にアップロードされていることを確認します。
image.png

AWS Console から Lambda を開き、Layersからレイヤーを作成します。
image.png

作成できました。
image.png

Lambda関数を作成(ランタイムはPython3.6)し、Layerを追加します。
image.png

作成したレイヤーを選択。
image.png

Lamda関数を編集して、ライブラリが読み込まれていることを確認しましょう。

import json
import requests
import ulid
from PIL import Image 
import numpy as np
import cv2
import pandas as pd 
import matplotlib.pyplot as plt
import folium
from folium import plugins

def lambda_handler(event, context):
    # TODO implement
    return {
        'statusCode': 200,
        'body': json.dumps('Hello from Lambda!')
    }

テストイベントを作成します。
image.png

実行できれば、ライブラリが読み込まれているはずです。

Response:
{
  "statusCode": 200,
  "body": "\"Hello from Lambda!\""
}

お疲れ様でした。終わったらインスタンス消してもいいです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Dockerのbridge接続の通信フローをnftablesで追う

はじめに

いまさらながらCentOS8でDockerを勉強しはじめました。で、外部ホスト<--->コンテナのパケットの流れを追ってみたくていろいろ調べました。

けど、リクエストは流れが追えたのですが、レスポンスが追えていません。。。
ひとまずリクエストの経路だけまとめます。

いろいろ前提条件

検証環境

以下の環境でトレースしています。

  • CentOS8(8.1.1911)
  • Docker(19.03.5)
  • firewalld(0.7.0)
  • iptables(1.8.2:nf_tables)
  • nftables(0.9.0)

Docker動作環境

userland proxy(docker-proxy)

iptables / nftables によるdockerネットワークの基本的な動作をみたかったので、docker-pxoryを利用していません。(ヘアピンNAT)
以下のファイルを配置して検証しています。

/etc/docker/daemon.json
{
    "userland-proxy": false
}

参考:https://github.com/nigelpoulton/docker/blob/master/docs/userguide/networking/default_network/binding.md

docker network and containers

検証環境として、以下のエントリで生成されたものを利用します。
CentOS8で構成したdockerホスト 10.254.10.252 、説明はradiusを対象にしていきます。

Docker Composeでネットワークサービス群を5分で作れるようにした(dhcp/radius/proxy/tftp/syslog)

Screenshot from Gyazo

以下のコンテナが生成されます。

server app address listen
proxy squid 172.20.0.2 8080/tcp
syslog rsyslog 172.20.0.3 514/udp
radius freeRADIUS 172.20.0.4 1812/udp
dhcp ISC-Kea 172.20.0.5 67/udp
tftp tftp-server - 69/udp
# docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS                    NAMES
b11308767849        infraserv:proxy     "/usr/sbin/init"    3 minutes ago       Up 3 minutes        0.0.0.0:8080->8080/tcp   proxy
33054f8b7d58        infraserv:tftp      "/usr/sbin/init"    35 hours ago        Up 2 hours                                   tftp
851ea861d04e        infraserv:syslog    "/usr/sbin/init"    35 hours ago        Up 2 hours          0.0.0.0:514->514/udp     syslog
dd3a657cfda2        infraserv:dhcp      "/usr/sbin/init"    35 hours ago        Up 2 hours          0.0.0.0:67->67/udp       dhcp
7249b9c4f11d        infraserv:radius    "/usr/sbin/init"    35 hours ago        Up 2 hours          0.0.0.0:1812->1812/udp   radius

以下のパラメータのネットワークが生成されます。

key value
name infraserv_infranet
subnet 172.20.0.0/24
interface docker1

tftpは --net=host な環境で動作しているため、 docker network は以下のような状態です。

# docker network inspect infraserv_infranet
[
    {
        "Name": "infraserv_infranet",
        "Id": "7ed8face2e4fec3110384fa3366512f8c78db6e10be6e7271b3d92452aefd254",
        "Created": "2020-02-15T05:37:59.248249755-05:00",
        "Scope": "local",
        "Driver": "bridge",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": null,
            "Config": [
                {
                    "Subnet": "172.20.0.0/24",
                    "Gateway": "172.20.0.1"
                }
            ]
        },
        "Internal": false,
        "Attachable": true,
        "Ingress": false,
        "ConfigFrom": {
            "Network": ""
        },
        "ConfigOnly": false,
        "Containers": {
            "7249b9c4f11de1f986892965671086d20957a6021269a5f5bc6dd85263bc0d70": {
                "Name": "radius",
                "EndpointID": "03ae6a9b9ff7817eea101955d2d6ff016982beb65c7dd6631c75c7299682c2dd",
                "MacAddress": "02:42:ac:14:00:04",
                "IPv4Address": "172.20.0.4/24",
                "IPv6Address": ""
            },
            "851ea861d04edeb5f5c2498cc60f58532c87a44592db1f6c51280a8ce27940bd": {
                "Name": "syslog",
                "EndpointID": "d18e466d27def913ac74b7555acc9ef79c88c62e62085b50172636546d2e72bb",
                "MacAddress": "02:42:ac:14:00:03",
                "IPv4Address": "172.20.0.3/24",
                "IPv6Address": ""
            },
            "b11308767849c7227fbde53234c1b1816859c8e871fcc98c4fcaacdf7818e89e": {
                "Name": "proxy",
                "EndpointID": "ffa6479b4f28c9c1d106970ffa43bd149461b4728b64290541643eb895a02892",
                "MacAddress": "02:42:ac:14:00:02",
                "IPv4Address": "172.20.0.2/24",
                "IPv6Address": ""
            },
            "dd3a657cfda211c08b7c5c2166f10d189986e4779f1dfea227b3afe284cbafec": {
                "Name": "dhcp",
                "EndpointID": "7371f4cf652d8b1bdbf2dc1e5e8ae97013a9a70b890c2caa36c2a7cc93b165df",
                "MacAddress": "02:42:ac:14:00:05",
                "IPv4Address": "172.20.0.5/24",
                "IPv6Address": ""
            }
        },
        "Options": {
            "com.docker.network.bridge.enable_ip_masquerade": "true",
            "com.docker.network.bridge.host_binding_ipv4": "0.0.0.0",
            "com.docker.network.bridge.name": "docker1"
        },
        "Labels": {
            "com.docker.compose.network": "infranet",
            "com.docker.compose.project": "infraserv",
            "com.docker.compose.version": "1.25.3"
        }
    }
]

アドレスファミリ

説明を簡略するために、IPv4に絞っています。

Dockerでのパケットの流れについて

通信を追ってみる(radiusの場合)

今回は外部端末(10.254.10.105)から、Dockerホスト(10.254.10.252)あてにradiusのRequestを送付することを例にします。
自ホストに着信した後に転送されるので、注目するchainのhookは prerouting --> forward --> postrouting となります。
そのため、chainのtypeは、filterとnatだけ、に絞って説明します。

ルールは nft list ruleset から 不要なものを除外してますが、あまり有用な情報でもないので、補足にまとめました。

外部端末からのリクエスト(prerouting)

nft list ruleset からhookがpreroutingのものを抽出すると、以下となります。

         table ip nat {
           chain PREROUTING {
(1)          type nat hook prerouting priority -100; policy accept;
(2)->        fib daddr type local COUNTER jump DOCKER
           }
   ->(2)   chain DOCKER {
      ↓      meta l4proto udp udp dport 514 COUNTER dnat to 172.20.0.3:514
      ↓      meta l4proto udp udp dport 67 COUNTER dnat to 172.20.0.5:67
      ↓      meta l4proto tcp tcp dport 8080 COUNTER dnat to 172.20.0.2:8080
     (3)     meta l4proto udp udp dport 1812 COUNTER dnat to 172.20.0.4:1812
           }
         }

現時点での通信は 10.254.10.105:random --> 10.254.10.252:1812 となります。
(1) preroutingをhookしてnatを行うPREROUTINGというchainが選択される
(2) DstAddrはlocalなので、DOCKERというchainに飛ぶ
  addr type localは自ホスト(この場合はDockerホスト)が持つアドレスのことで、
  今回ならlo:127.0.0.1 ens192:10.254.10.252 docker1:172.20.0.1 のことです。
(3) DstPortは1812なので、DstAddrを172.20.0.4:1812にDNATする
引き続きの処理がないため、policy適用 -> accept

この時点の通信は 10.254.10.105:random --> 172.20.0.4:1812 となります。
宛先が172.20.0.4に変更されたため、 routing decision により forward の hook へ進むことになります。

外部端末からのリクエスト(forward)

nft list ruleset からhookがforwardのものを抽出すると、以下となります。

                                table ip filter {
                                  chain FORWARD {
(1)                                 type filter hook forward priority 0; policy drop;
(2)->                               COUNTER jump DOCKER-USER
        ->(3)(4)->                  COUNTER jump DOCKER-ISOLATION-STAGE-1
                    ->(5)           oifname "docker1" ct state related,established COUNTER accept
                      (6)->         oifname "docker1" COUNTER jump DOCKER
                                    iifname "docker1" oifname != "docker1" COUNTER accept
                                    iifname "docker1" oifname "docker1" COUNTER accept
                                  }
               ->(4)              chain DOCKER-ISOLATION-STAGE-1 {
                 (5)->              COUNTER return
                                  }
   ->(2)                          chain DOCKER-USER {
     (3)->                          COUNTER return
                                  }
                         ->(6)    chain DOCKER {
                            ↓       iifname != "docker1" oifname "docker1" meta l4proto udp ip daddr 172.20.0.3 udp dport 514 COUNTER accept
                            ↓       iifname != "docker1" oifname "docker1" meta l4proto udp ip daddr 172.20.0.5 udp dport 67 COUNTER accept
                            ↓       iifname != "docker1" oifname "docker1" meta l4proto tcp ip daddr 172.20.0.2 tcp dport 8080 COUNTER accept
                           (7)      iifname != "docker1" oifname "docker1" meta l4proto udp ip daddr 172.20.0.4 udp dport 1812 COUNTER accept
                                  }                          
                                }
                                table inet firewalld {
                                  chain filter_FORWARD {
                           (8)      type filter hook forward priority 10; policy accept;
                            ↓       ct state established,related accept
                           (9)      ct status dnat accept
                                    iifname "lo" accept
                                    jump filter_FORWARD_IN_ZONES
                                    jump filter_FORWARD_OUT_ZONES
                                    ct state invalid drop
                                    reject with icmpx type admin-prohibited
                                  }
                                  chain filter_FORWARD_IN_ZONES {
                                    iifname "ens192" goto filter_FWDI_public
                                    goto filter_FWDI_public
                                  }
                                  chain filter_FORWARD_OUT_ZONES {
                                    oifname "ens192" goto filter_FWDO_public
                                    goto filter_FWDO_public
                                  }
                                  chain filter_FWDI_public { meta l4proto { icmp, ipv6-icmp } accept }
                                  chain filter_FWDO_public { jump filter_FWDO_public_allow }
                                  chain filter_FWDO_public_allow { ct state new,untracked accept }
                                }

現時点での通信は 10.254.10.105:random --> 172.20.0.4:1812 となります。
(1)forwardのhookの中で最も優先順位が高いので、filterを行うFORWARDというchainが選択される(pri:0)
(2)無条件にDOCKER-USERに飛ぶ
(3)なにもせず戻る
(4)無条件にDOCKER-ISOLATION-STAGE-1に飛ぶ
(5)なにもせず戻る
(6)出力IFはdocker1なので、DOCKERに飛ぶ
(7)入力IFはens192、出力IFはdocker1、DstAddrは172.20.0.4:1812なので、accept
  regular chain のDOCKERはbase chainのFORWARDから呼び出されている。
  DOCKER でacceptした時点で呼び出し元のFORWARDの評価がされ、このchainは終了する。
(8)forwardのhookの中で2番目に優先順位が高いので、filterを行うfilter_FORWARDというchainが選択される(pri:10)
(9)パケットはDNATされているので、accept
この時点の通信は最初と変わらず 10.254.10.105:random --> 172.20.0.4:1812 となります。

外部端末からのリクエスト(postrouting)

nft list ruleset からhookがpostroutingのものを抽出すると、以下となります。

                   table ip nat {
                     chain POSTROUTING {
(1)                    type nat hook postrouting priority 100; policy accept;
 ↓                     oifname "docker1" fib saddr type local COUNTER masquerade
 ↓                     oifname != "docker1" ip saddr 172.20.0.0/24 COUNTER masquerade
 ↓                     meta l4proto udp ip saddr 172.20.0.3 ip daddr 172.20.0.3 udp dport 514 COUNTER masquerade
 ↓                     meta l4proto udp ip saddr 172.20.0.5 ip daddr 172.20.0.5 udp dport 67 COUNTER masquerade
 ↓                     meta l4proto tcp ip saddr 172.20.0.2 ip daddr 172.20.0.2 tcp dport 8080 COUNTER masquerade
 ↓                     meta l4proto udp ip saddr 172.20.0.4 ip daddr 172.20.0.4 udp dport 1812 COUNTER masquerade
                     }
                     table ip firewalld {
                       chain nat_POSTROUTING {
(2)                    type nat hook postrouting priority 110; policy accept;
(3)->                    jump nat_POSTROUTING_ZONES
                       }
   ->(3)               chain nat_POSTROUTING_ZONES {
      ↓                  oifname "ens192" goto nat_POST_public
     (4)->               goto nat_POST_public
                       }
        ->(4)          chain nat_POST_public {
          (5)->          jump nat_POST_public_allow
                       }
             ->(5)     chain nat_POST_public_allow {
               (6)       oifname != "lo" masquerade
                       }
                     }
                   }

現時点での通信は 10.254.10.105:random --> 172.20.0.4:1812 となります。
(1) postroutingのhookの中で最も優先順位が高いのでnatを行うPOSTROUTINGというchainが選択される(pri:100)
  引き続きの処理がないため、policy適用 -> accept
(2) postroutingのhookの中で2番目に優先順位が高いのでnatを行うnat_POSTROUTINGというchainが選択される(pri:110)
(3) 無条件にnat_POSTROUTING_ZONESに飛ぶ
(4) 無条件にnat_POST_publicに飛ぶ
(5) 無条件にnat_POST_public_allowに飛ぶ
(6) 出力IFはdocker1なので、masquerade
  gotoで呼び出された先でchainが終了するため、policy適用 -> accept
  regular chain のnat_POST_public_allowはregular chain のnat_POST_publicから呼び出されている。
  regular chain のnat_POST_publicはregular chain のnat_POSTROUTING_ZONESからgoto命令で呼び出されている。
  goto命令で呼び出されたnat_POST_publicの処理が終了した時点で、呼び出したnat_POSTROUTING_ZONESが終了し
  それを呼び出したnat_POSTROUTINGも終了しpolicy accept が適用される。

masqueradeの処理を受け、最終的には 172.20.0.1:random --> 172.20.0.4:1812 となります。
(docker1から送出されるため、masqueradeで処理されると、送信元アドレスがdocker1になります)

radiusによる認証可否

radiusコンテナが受け取るリクエスト
172.20.0.1:random --> 172.20.0.4:1812

radiusサーバはその可否をチェックし、radiusクライアントに返答を返します。

radiusコンテナが返答するレスポンス
172.20.0.4:1812 --> 172.20.0.1:random

外部端末へのレスポンス

力尽きました。。。
nftablesでカウンタを仕掛けてみると、以下のchainを通過する際のアドレスが見えました。
1回の認証のやり取りなので、各chainで1パケットが見えていました。

type filter hook prerouting  : 172.20.0.4:1812 --> 172.20.0.1:random
type filter hook input       : 172.20.0.4:1812 --> 10.254.10.105:random
type filter hook forward     : 172.20.0.4:1812 --> 10.254.10.105:random
type filter hook postrouting : 172.20.0.4:1812 --> 10.254.10.105:random

radiusコンテナからの返答は、172.20.0.4:1812 --> 172.20.0.1:random であり、
着信時は自分宛の通信に見えるから、hook:inputを通過しているのは分かります。
その後、LocalProcessを通ってforwardに行く、のでしょうか?このあたりからよくわからなくなってしまいました。。。

中途半端になってしまった。。。

radiusからの応答パケットの経路がいまひとつわからない。
なぜどのchainの type:nat も通らないんだろう。。。
なぜ hook:inputhook:forward を同時に通っているんだろう。。。
table bridge filter の type:filter hook:input pri:-200 に入ってるのに
table ip filter の type:filter hook:input pri:0 には入って行ってないんだよなぁ。
L2のブリッジとL3のIPで違う処理をしているとか?

出典

https://knowledge.sakura.ad.jp/22636/
https://ja.wikipedia.org/wiki/Iptables
https://ja.wikipedia.org/wiki/Nftables
https://wiki.archlinux.jp/index.php/Nftables
https://wiki.archlinux.jp/index.php/Iptables
https://wiki.nftables.org/wiki-nftables/index.php/Netfilter_hooks
https://www.frozentux.net/iptables-tutorial/iptables-tutorial.html#TRAVERSINGOFTABLES
https://wiki.archlinux.jp/index.php/Nftables
https://knowledge.sakura.ad.jp/22636/
https://www.codeflow.site/ja/article/a-deep-dive-into-iptables-and-netfilter-architecture

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

netfilterとfirewalldとiptablesとnftablesの関係

はじめに

netfilterについて少し調べてみました。
いろいろと認識の相違があったことがわかったので、記録します。

認識の相違

CentOS8(に限らずだけど)では、以下のことがわかった。
iptablesはiptablesではなくnftablesである
firewall-cmdで設定したルールはiptablesでは表示されない
すべてのルール確認はnftを使う

Linuxでのパケットの流れについて

netfilter

netfilterはLinuxでパケットの処理をする基本ルール
インタフェイスで受信したパケットがどんな処理をされていくか、というやつです。

netfilter.orgによると、

netfilterは、カーネルモジュールがネットワークスタックにコールバック関数を登録できるようにするLinuxカーネル内の一連のフックです。登録されたコールバック関数は、ネットワークスタック内の各フックを通過するすべてのパケットに対してコールバックされます。

だそうです。ありがとうgoogle翻訳!

つまり、パケットがネットワークスタックを通り抜けるときに、netfilterで定義した「フック」というものにより、何らかの処理を挟み込めるようにした仕組み。ということでしょうか。

netfilterのchain

ネットワークスタックを通り抜ける一連の処理のことをchainと呼びます。
chainは、以下のものから構成されます。

名称 意味
hook (ネットワークスタックの)どの部分 で
priority どのような順番 で
type どのような処理をする か

基本は以下の図です。

Screenshot from Gyazo

参考:https://upload.wikimedia.org/wikipedia/commons/3/37/Netfilter-packet-flow.svg

netfilterのchainのhook

「(ネットワークスタックの)どの部分」で、処理が行われるか、を表現したものが、hookです。
つまり、ひっかけるタイミングを表現しています。

上図の中央付近にある、 routing decision の部分で処理が変わっているのがわかると思います。
流入したパケットが「ローカル宛て」か、「ローカル宛てじゃない(=転送する)」かで、使われるhookが変わります。

hookの部分にフォーカスしたものが以下です。

                                             Local
                                            process
                                              ^  |      .-----------.
                   .-----------.              |  |      |  Routing  |
                   |           |-----> input /    \---> |  Decision |----> output \
--> prerouting --->|  Routing  |                        .-----------.              \
                   | Decision  |                                                     --> postrouting
                   |           |                                                    /
                   |           |---------------> forward --------------------------- 
                   .-----------.

参考:https://wiki.nftables.org/wiki-nftables/index.php/Netfilter_hooks

つまり、宛先によって、使われるhookが以下の2通りになります。

ローカルあての場合
prerouting -> input -> output -> postrouting

転送する場合
prerouting -> forward -> postrouting

(このほかにも、ingressというL2へのパケットに対するhookもあるみたいです。)

netfilterのchainのpriority

「どのような順番」で、処理が行われるか、を表現したものが、priorityです。
つまり、各hookの中での処理の順序を表現しています。

priorityは正負の数値で表現されますが、別名が定義されています。
dstnatとscrnatはそれぞれ、preroutingとpostroutingのhookでのみ利用可能です。

ですが、実体は飽くまで数値です。

通称 定義 priority 利用可能なhook
- NF_IP_PRI_RAW_BEFORE_DEFRAG -450 すべて
- NF_IP_PRI_CONNTRACK_DEFRAG -400 すべて
raw NF_IP_PRI_RAW -300 すべて
- NF_IP_PRI_SELINUX_FIRST -225 すべて
- NF_IP_PRI_CONNTRACK -200 すべて
mangle NF_IP_PRI_MANGLE -150 すべて
dstnat NF_IP_PRI_NAT_DST -100 prerouting
filter NF_IP_PRI_FILTER 0 すべて
security NF_IP_PRI_SECURITY 50 すべて
srcnat NF_IP_PRI_NAT_SRC 100 postrouting
- NF_IP_PRI_SELINUX_LAST 225 すべて
- NF_IP_PRI_CONNTRACK_HELPER 300 すべて

参考:http://git.netfilter.org/nftables/tree/include/linux/netfilter_ipv4.h
参考:https://manpages.debian.org/testing/nftables/nft.8.en.html

いわゆる「テーブル」ってやつに似てますね。

netfilterのchainのtype

「どのような処理をする」のか、を表現したものが、typeです。
つまり、具体的にパケットをどうするのか、ということを表現しています。

type 利用可能なhook
filter すべて
nat prerouting,postrouting,input,output
route output

あれ?こっちも「テーブル」ってやつに似てますね。
だから紛らわしいんだよ!

参考:https://www.netfilter.org/projects/nftables/manpage.html

firewalldとiptablesとnftables

netfilterを構成するものについては、上で解説しましたが、じゃあnetfilterをどう操作するの?
ということで、それらを操作するソフトウェアの話です。

firewalld ってなんだ?

firewalld が最上位にいて、バックエンドでiptablesもしくはnftablesが動作している。
バックエンドで動作するiptablesもしくはnftablesがnetfilterの操作をしている。

古いバージョンの firewalld ではバックエンドに iptables が、
新しいバージョンの firewalld ではバックエンドに nftables が、採用されている。

Screenshot from Gyazo

とても分かりやすい。
てか、本稿のタイトルはもはやこれ1枚でいいんではなかろうか。

参考:https://firewalld.org/documentation/concepts.html

iptables ってなんだ?

iptablesはnetfilterを操作できるツールだ。
firewalldが導入される前の古いCentOSでは、iptablesをiptables-serviceというものでデーモン化(サービス化?)していた。
つまり、iptablesコマンドでiptablesのルールを直接変更したり、特定のファイルを読み込ませたりすることで、フィルタリングなりNATなりをしていたんだ。

CentOS8にもiptablesはあるが、実体はnftablesで動作している。

# iptables --version
iptables v1.8.2 (nf_tables)

とはいえ、すでに iptables は iptables ではない。

# ll /usr/sbin/iptables
lrwxrwxrwx. 1 root root 17 11月  9 03:40 /usr/sbin/iptables -> xtables-nft-multi

# man xtables-nft
NAME
       xtables-nft ― iptables using nftables kernel api

DESCRIPTION
       xtables-nft  are versions of iptables that use the nftables
       API.  This is a set of tools to help the system administra‐
       tor  migrate  the  ruleset  from iptables(8), ip6tables(8),
       arptables(8), and ebtables(8) to nftables(8).

iptablesを叩くと、それっぽい書式でルールが見える。
けど、iptablesからは、後述するnftablesで追加したテーブルが見えない。
だから、iptables -L -nv -t nat|filter で表示されるルールと実際の動作が噛み合わないことがある。

例えば、Dockerホストからbridge接続されているコンテナへポート転送をするために firewall-cmd --add-masquerade としたとき。nftでchainを確認すると、以下の動作が見える。

# nft list chain ip firewalld nat_POST_public_allow
table ip firewalld {
        chain nat_POST_public_allow {
                oifname != "lo" masquerade
        }
}

けど、iptablesから firewalld というテーブルは見れない。

# iptables -L -t firewalld
iptables v1.8.2 (nf_tables): table 'firewalld' does not exist
Perhaps iptables or your kernel needs to be upgraded.

man iptablesでTABLESの部分をみてみると、iptablesでは、決められたキーワードのテーブルしか確認ができない。

TABLES
       There  are  currently  five  independent tables (which tables are present at any time depends on the kernel configuration options and which modules are
       present).

       -t, --table table
              The tables are as follows:
              filter:
              nat:
              mangle:
              raw:
              security:

つまり、firewalldのバックでnftablesが動作している場合、 iptablesで表示したルールは正しくない!
ので、ルール確認は、 iptablesでなくnftを使う!

nftables ってなんだ?

nftablesもnetfilterを操作できる、iptabesに置き換わるツールだ。
前述のiptablesと同じような感じで、nftablesもサービス化できるみたいだけど、、、

# systemctl status nftables
● nftables.service - Netfilter Tables
   Loaded: loaded (/usr/lib/systemd/system/nftables.service; disabled; vendor preset: disabled)
   Active: inactive (dead)
     Docs: man:nft(8)

CentOS8ではfirewalldが動作していて、nftablesが裏で動作しているので、nftablesとしてのデーモンは休んでいるみたい。

その中身は、以下のように、 nft コマンドでルールのflush(=除去)と設定ファイルの読み込みをしている。
iptablesをデーモン化したときと似ているね。

# cat /usr/lib/systemd/system/nftables.service  | grep nft
Documentation=man:nft(8)
ExecStart=/sbin/nft -f /etc/sysconfig/nftables.conf
ExecReload=/sbin/nft 'flush ruleset; include "/etc/sysconfig/nftables.conf";'
ExecStop=/sbin/nft flush ruleset

debian/arch/ubuntu/fedora では、nftablesのデーモン動作がデフォルトになっているようだ。

参考:https://wiki.nftables.org/wiki-nftables/index.php/Nftables_from_distributions

基本的なnftablesの使い方

nft

nftablesは nft というコマンドで提供されます。
nftで設定するルールは、処理そのものであるchainと、chainをひとまとめにしたtableとして表現されます。

chainは特定のhookで特定のtypeで表現される処理をまとめたものであり、「(ネットワークスタックの)どの部分」で「どのような順番」で「どのような処理をする」かは、ひとつのchainでまとめられます。
tableは複数のchainをまとめた仮想的なグループとしてとらえます。

設定の確認は nft list というコマンドを使います。

nft list table(s)

設定されているすべてのtableの名称のみ表示します。

# nft list tables
table ip filter
table ip6 filter
(とか、たくさん)

指定したtableの設定されているすべてのchainを表示します。

# nft list table ip nat
table ip nat {
        chain PREROUTING {
                type nat hook prerouting priority -100; policy accept;
                fib daddr type local counter packets 0 bytes 0 jump DOCKER
        }
(table ip natに含まれるchainがすべて表示されます)

nft list chain(s)

設定されているすべてのchainに対して、各chainがどのtableに含まれているか、発動条件である「(ネットワークスタックの)どの部分」で「どのような順番」の処理を行うのか、が表示されます。
(具体的な処理内容は表示されません)

# nft list chains
table ip filter {
        chain INPUT {
                type filter hook input priority 0; policy accept;
        }
        chain FORWARD {
                type filter hook forward priority 0; policy drop;
        }
(たくさん表示されます)

指定したchainを表示します

# nft list chain ip filter FORWARD
table ip filter {
        chain FORWARD {
                type filter hook forward priority 0; policy drop;
                counter packets 0 bytes 0 jump DOCKER-USER
(たくさん表示されます)

すべてのルールの表示

設定されているすべてのルールが表示されます。

# nft list ruleset
table ip filter {
        chain INPUT {
                type filter hook input priority 0; policy accept;
        }

        chain FORWARD {
                type filter hook forward priority 0; policy drop;
                counter packets 0 bytes 0 jump DOCKER-USER

(すごくたくさん表示されます)

さいごに

netfilterはじめ、パケットの取り扱いは奥が深い。
これでもまちがった理解をしているかもしれないし、テキトーに設定してもなんとか動いちゃうこともある。
なるべく正しい知識を取り入れたい。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Docker Composeでネットワークサービス群を5分で作れるようにした(dhcp/radius/proxy/tftp/syslog/dns)

はじめに

CentOS7で各種ネットワークサービスを基本設定したメモ(dhcp/radius/proxy/tftp/syslog)
をCentOS8で再構築しました。今回は各サービスをDockerでコンテナに閉じ込めています。

自分で使う用に docker-compose を利用したところ、5分くらいで環境のリストアができるようになったので、記録に残します。

更新

DHCPはIPマスカレード環境でうまく動作しなかったので、network_mode を host に修正しました。
パケット内にIPアドレスが埋め込まれており、単純なNAPTだとコンテナ内のアドレスがDHCPサーバとなってしまうため。

DNS64とDNSキャッシュの機能を持つ(unbound)も追加しました。

なにができるようになるのか

以下のような環境を作成できます。

Screenshot from Gyazo

docker-compose したホストの同一ポートにバインドしますので、外部端末から ホストのアドレス:サービスのポート にアクセスすると、各コンテナに到着します。
また、各種ログを同一ネットワーク上の syslog を待ち受けているコンテナにログを送信するようにしています。

以下のコンテナが生成されます。

server app address listen
proxy squid 172.20.0.2 8080/tcp
syslog rsyslog 172.20.0.3 514/udp
radius freeRADIUS 172.20.0.4 1812,1813/udp
dns unbound 172.20.0.5 53/udp,tcp
dhcp ISC-Kea - 67,68/udp
tftp tftp-server - 69/udp
# docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED              STATUS              PORTS                                    NAMES
3d7fec4d9ffd        infraserv:dns       "/usr/sbin/init"    About a minute ago   Up 58 seconds       0.0.0.0:53->53/tcp, 0.0.0.0:53->53/udp   dns
c34f033b140c        infraserv:radius    "/usr/sbin/init"    About a minute ago   Up 57 seconds       0.0.0.0:1812-1813->1812-1813/udp         radius
eb32cce0d7b9        infraserv:dhcp      "/usr/sbin/init"    About a minute ago   Up 59 seconds                                                dhcp
fedd69042ec3        infraserv:tftp      "/usr/sbin/init"    About a minute ago   Up 59 seconds                                                tftp
3e033a44c162        infraserv:syslog    "/usr/sbin/init"    About a minute ago   Up 58 seconds       0.0.0.0:514->514/udp                     syslog
d2454fc12ae8        infraserv:proxy     "/usr/sbin/init"    About a minute ago   Up 57 seconds       0.0.0.0:8080->8080/tcp                   proxy

以下のパラメータのネットワークが生成されます。

key value
name infraserv_infranet
subnet 172.20.0.0/24
interface docker1

tftp/dhcpは --net=host な環境で動作しているため、 docker network は以下のような状態です。

# docker network inspect infraserv_infranet
[
    {
        "Name": "infraserv_infranet",
        "Id": "58a9c3da443b49416003d4f3ed5192a521c728270826a18da293000ae4b1004e",
        "Created": "2020-02-19T07:00:15.610028349+09:00",
        "Scope": "local",
        "Driver": "bridge",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": null,
            "Config": [
                {
                    "Subnet": "172.20.0.0/24"
                }
            ]
        },
        "Internal": false,
        "Attachable": true,
        "Ingress": false,
        "ConfigFrom": {
            "Network": ""
        },
        "ConfigOnly": false,
        "Containers": {
            "3d7fec4d9ffd315a6fb087b6a7f28453d7009a8eecd60a5110a3f4f7c4e2564c": {
                "Name": "dns",
                "EndpointID": "537b189764340697c9ea4f727211df450226a09a65aa24f20f275f56346d52fc",
                "MacAddress": "02:42:ac:14:00:05",
                "IPv4Address": "172.20.0.5/24",
                "IPv6Address": ""
            },
            "3e033a44c1625b7dd30a8a358f40c96594f462a6714caf39d726d4cb401ffcfd": {
                "Name": "syslog",
                "EndpointID": "3083640a976bd89e33d0f478eef9e6faa468cecccaea23729cf01f1ba24692f3",
                "MacAddress": "02:42:ac:14:00:03",
                "IPv4Address": "172.20.0.3/24",
                "IPv6Address": ""
            },
            "c34f033b140cee61992bbc3f1d9d652155121a27462aa460413b7be646e0ab6e": {
                "Name": "radius",
                "EndpointID": "f3c7bdc9c08fb283681f412996949126fa5f73213f9a265b6371e028efdc0eb1",
                "MacAddress": "02:42:ac:14:00:04",
                "IPv4Address": "172.20.0.4/24",
                "IPv6Address": ""
            },
            "d2454fc12ae861ff3e240ef26c95c55907382465148e1c4b829d48b5af91d60b": {
                "Name": "proxy",
                "EndpointID": "b81e9ea373b2fb7e0a68f1d24534fc6ebb78c01e24e6eb838ecd06127307a202",
                "MacAddress": "02:42:ac:14:00:02",
                "IPv4Address": "172.20.0.2/24",
                "IPv6Address": ""
            }
        },
        "Options": {
            "com.docker.network.bridge.enable_ip_masquerade": "true",
            "com.docker.network.bridge.host_binding_ipv4": "0.0.0.0",
            "com.docker.network.bridge.name": "docker1"
        },
        "Labels": {
            "com.docker.compose.network": "infranet",
            "com.docker.compose.project": "infraserv_new",
            "com.docker.compose.version": "1.25.3"
        }
    }
]

初期設定

後続の手順 で設定ファイルの修正をしない場合は、以下のパラメータで各サービスが動作します。

radius

初期状態では以下の2ユーザとenableパスワードが利用できるようになります。

ユーザ名 パスワード 備考
foo bar 通常ユーザでのログイン
hoge fuga Cisco機器にログインすると自動で特権に昇格
\$enab15\$ fuga Cisco機器のenableコマンドで遷移する際のパスワード

MACアドレスバイパスでは、以下のMACアドレスの場合に、ダイナミックVLAN用のアトリビュートを付与してAcceptされます。

MACアドレス VLAN文字列
112233445566 default_seg
aabbccddeeff default_seg

DHCP

リース情報

項目
リース時間 10時間
DNSサーバ 8.8.8.8

リース対象セグメント

セグメント レンジ GW
10.1.20.0/24 10.1.20.33 - 10.1.20.62 10.1.20.1
10.1.22.0/24 10.1.22.33 - 10.1.20.230 10.1.22.1

Option43によりアクセスポイントに送付するコントローラアドレス

対象 VCI文字列 コントローラアドレス
Cisco Cisco AP 10.254.10.201,10.254.10.202
Aruba ArubaAP 10.254.10.206

リクエストを受け付けるIFを設定ファイル kea-dhcp4.conf に記載するのですが、デフォルトで ens192 としています。
IFの表記が異なる場合には、設定ファイルの修正が必要です。

事前準備

CentOS と Docker と Docker Compose があれば、ここの手順はスキップで 作業内容 から始めてください。

CentOS8(10分)

CentOS8は最小構成で問題ありません。
もし手元になければ、 ESXi6.7にCentOS8を最小構成で構築 を参照してください。

Docker(5分)

dnf -y update
dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
dnf -y --nobest install docker-ce docker-ce-cli containerd.io
dnf -y update https://download.docker.com/linux/centos/7/x86_64/stable/Packages/containerd.io-1.2.10-3.2.el7.x86_64.rpm
dnf -y update
systemctl enable docker
systemctl start docker

Docker Compose(1分)

curl -L "https://github.com/docker/compose/releases/download/1.25.3/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
chmod +x /usr/local/bin/docker-compose

作業内容

firewall のポリシー追加(10秒)

firewall-cmd --add-service=dns   --zone=public --permanent
firewall-cmd --add-service=tftp   --zone=public --permanent
firewall-cmd --add-masquerade   --zone=public --permanent
firewall-cmd --reload

各サービスのDockerコンテナをつくる(5分)

各サービスのDockerfileと設定ファイル、それらをまとめる docker-compose.yml は GitHub にあるものを使います。

git clone https://github.com/bashaway/infraserv

デフォルトの設定から変更する場合は、以下のエントリなどを参照してください。
設定ファイルの修正なしでもビルドには影響ありません。
ビルド後に修正しても大丈夫ですし。

DHCP
DockerでKea DHCPを構築。CiscoとArubaに同時にOption43が渡せるようになった。

RADIUS
DockerのFreeRADIUSでCiscoのログイン認証+MAC認証+ダイナミックVLANした(CentOS8)

Proxy
Dockerでproxyサーバ

TFTP
Dockerでtftpサーバ

Syslog
Dockerのrsyslogでコンテナ間や他サーバから転送されるログを集約した

設定ファイルの修正が終わったら、コンテナをつくります。

cd infraserv
docker-compose build
docker-compose up -d

いらなくなったら

コンテナ削除
docker-compose stop
docker-compose rm -f

イメージもいらなければ
docker-compose rmi -f

中身の解説

docker-compose.yml

docker-compose.yml は、以下のように構成されています。

docker-compose.yml
version: '3'

services:

  proxy:
    build: ./proxy
    image: infraserv:proxy
    container_name: proxy
    hostname: proxy
    restart: always
    networks:
      infranet:
        ipv4_address: 172.20.0.2
    ports:
      - 8080:8080
    cap_add:
      - SYS_ADMIN
    security_opt:
      - seccomp:unconfined
    volumes:
      - /sys/fs/cgroup:/sys/fs/cgroup:ro
    environment:
      TZ: 'Asia/Tokyo'

  syslog:
    build: ./syslog
    image: infraserv:syslog
    container_name: syslog
    hostname: syslog
    restart: always
    networks:
      infranet:
        ipv4_address: 172.20.0.3
    ports:
      - 514:514/udp
    cap_add:
      - SYS_ADMIN
    security_opt:
      - seccomp:unconfined
    volumes:
      - /sys/fs/cgroup:/sys/fs/cgroup:ro
    environment:
      TZ: 'Asia/Tokyo'

  radius:
    build: ./radius
    image: infraserv:radius
    container_name: radius
    hostname: radius
    restart: always
    networks:
      infranet:
        ipv4_address: 172.20.0.4
    ports:
      - 1812:1812/udp
    cap_add:
      - SYS_ADMIN
    security_opt:
      - seccomp:unconfined
    volumes:
      - /sys/fs/cgroup:/sys/fs/cgroup:ro
    environment:
      TZ: 'Asia/Tokyo'

  dns:
    build: ./dns
    image: infraserv:dns
    container_name: dns
    hostname: dns
    restart: always
    networks:
      infranet:
        ipv4_address: 172.20.0.5
    ports:
      - 53:53/udp
      - 53:53/tcp
    cap_add:
      - SYS_ADMIN
    security_opt:
      - seccomp:unconfined
    volumes:
      - /sys/fs/cgroup:/sys/fs/cgroup:ro
    environment:
      TZ: 'Asia/Tokyo'

  tftp:
    build: ./tftp
    image: infraserv:tftp
    container_name: tftp
    hostname: tftp
    restart: always
    network_mode: host
    cap_add:
      - SYS_ADMIN
    security_opt:
      - seccomp:unconfined
    volumes:
      - /sys/fs/cgroup:/sys/fs/cgroup:ro
    environment:
      TZ: 'Asia/Tokyo'

  dhcp:
    build: ./dhcp
    image: infraserv:dhcp
    container_name: dhcp
    hostname: dhcp
    restart: always
    network_mode: host
    cap_add:
      - SYS_ADMIN
    security_opt:
      - seccomp:unconfined
    volumes:
      - /sys/fs/cgroup:/sys/fs/cgroup:ro
    environment:
      TZ: 'Asia/Tokyo'

networks:
  infranet:
    driver: bridge
    driver_opts:
      com.docker.network.bridge.enable_ip_masquerade: "true"
      com.docker.network.bridge.host_binding_ipv4: "0.0.0.0"
      com.docker.network.bridge.name: "docker1"
    ipam:
      config:
        - subnet: 172.20.0.0/24

さいごに

Dockerでコンテナにしておくと、ホスト機が汚れなくて気持ちいい

出典

http://docs.docker.jp/engine/reference/commandline/toc.html

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Docker Composeでネットワークサービス群を5分で作れるようにした(dhcp/radius/proxy/tftp/syslog)

はじめに

CentOS7で各種ネットワークサービスを基本設定したメモ(dhcp/radius/proxy/tftp/syslog)
をCentOS8で再構築しました。今回は各サービスをDockerでコンテナに閉じ込めています。

自分で使う用に docker-compose を利用したところ、5分くらいで環境のリストアができるようになったので、記録に残します。

なにができるようになるのか

以下のような環境を作成できます。

Screenshot from Gyazo

docker-compose したホストの同一ポートにバインドしますので、外部端末から ホストのアドレス:サービスのポート にアクセスすると、各コンテナに到着します。
また、proxy,radius,dhcpは各種ログを同一ネットワーク上の syslog を待ち受けているコンテナにログを送信するようにしています。

以下のコンテナが生成されます。

server app address listen
proxy squid 172.20.0.2 8080/tcp
syslog rsyslog 172.20.0.3 514/udp
radius freeRADIUS 172.20.0.4 1812/udp
dhcp ISC-Kea 172.20.0.5 67/udp
tftp tftp-server - 69/udp
# docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS                    NAMES
b11308767849        infraserv:proxy     "/usr/sbin/init"    3 minutes ago       Up 3 minutes        0.0.0.0:8080->8080/tcp   proxy
33054f8b7d58        infraserv:tftp      "/usr/sbin/init"    35 hours ago        Up 2 hours                                   tftp
851ea861d04e        infraserv:syslog    "/usr/sbin/init"    35 hours ago        Up 2 hours          0.0.0.0:514->514/udp     syslog
dd3a657cfda2        infraserv:dhcp      "/usr/sbin/init"    35 hours ago        Up 2 hours          0.0.0.0:67->67/udp       dhcp
7249b9c4f11d        infraserv:radius    "/usr/sbin/init"    35 hours ago        Up 2 hours          0.0.0.0:1812->1812/udp   radius

以下のパラメータのネットワークが生成されます。

key value
name infraserv_infranet
subnet 172.20.0.0/24
interface docker1

tftpは --net=host な環境で動作しているため、 docker network は以下のような状態です。

# docker network inspect infraserv_infranet
[
    {
        "Name": "infraserv_infranet",
        "Id": "7ed8face2e4fec3110384fa3366512f8c78db6e10be6e7271b3d92452aefd254",
        "Created": "2020-02-15T05:37:59.248249755-05:00",
        "Scope": "local",
        "Driver": "bridge",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": null,
            "Config": [
                {
                    "Subnet": "172.20.0.0/24",
                    "Gateway": "172.20.0.1"
                }
            ]
        },
        "Internal": false,
        "Attachable": true,
        "Ingress": false,
        "ConfigFrom": {
            "Network": ""
        },
        "ConfigOnly": false,
        "Containers": {
            "7249b9c4f11de1f986892965671086d20957a6021269a5f5bc6dd85263bc0d70": {
                "Name": "radius",
                "EndpointID": "03ae6a9b9ff7817eea101955d2d6ff016982beb65c7dd6631c75c7299682c2dd",
                "MacAddress": "02:42:ac:14:00:04",
                "IPv4Address": "172.20.0.4/24",
                "IPv6Address": ""
            },
            "851ea861d04edeb5f5c2498cc60f58532c87a44592db1f6c51280a8ce27940bd": {
                "Name": "syslog",
                "EndpointID": "d18e466d27def913ac74b7555acc9ef79c88c62e62085b50172636546d2e72bb",
                "MacAddress": "02:42:ac:14:00:03",
                "IPv4Address": "172.20.0.3/24",
                "IPv6Address": ""
            },
            "b11308767849c7227fbde53234c1b1816859c8e871fcc98c4fcaacdf7818e89e": {
                "Name": "proxy",
                "EndpointID": "ffa6479b4f28c9c1d106970ffa43bd149461b4728b64290541643eb895a02892",
                "MacAddress": "02:42:ac:14:00:02",
                "IPv4Address": "172.20.0.2/24",
                "IPv6Address": ""
            },
            "dd3a657cfda211c08b7c5c2166f10d189986e4779f1dfea227b3afe284cbafec": {
                "Name": "dhcp",
                "EndpointID": "7371f4cf652d8b1bdbf2dc1e5e8ae97013a9a70b890c2caa36c2a7cc93b165df",
                "MacAddress": "02:42:ac:14:00:05",
                "IPv4Address": "172.20.0.5/24",
                "IPv6Address": ""
            }
        },
        "Options": {
            "com.docker.network.bridge.enable_ip_masquerade": "true",
            "com.docker.network.bridge.host_binding_ipv4": "0.0.0.0",
            "com.docker.network.bridge.name": "docker1"
        },
        "Labels": {
            "com.docker.compose.network": "infranet",
            "com.docker.compose.project": "infraserv",
            "com.docker.compose.version": "1.25.3"
        }
    }
]

初期設定

後続の手順 で設定ファイルの修正をしない場合は、以下のパラメータで各サービスが動作します。

radius

初期状態では以下の2ユーザとenableパスワードが利用できるようになります。

ユーザ名 パスワード 備考
foo bar 通常ユーザでのログイン
hoge fuga Cisco機器にログインすると自動で特権に昇格
\$enab15\$ fuga Cisco機器のenableコマンドで遷移する際のパスワード

MACアドレスバイパスでは、以下のMACアドレスの場合に、ダイナミックVLAN用のアトリビュートを付与してAcceptされます。

MACアドレス VLAN文字列
112233445566 default_seg
aabbccddeeff default_seg

DHCP

リース情報

項目
リース時間 10時間
DNSサーバ 8.8.8.8

リース対象セグメント

セグメント レンジ GW
10.1.20.0/24 10.1.20.33 - 10.1.20.62 10.1.20.1
10.1.22.0/24 10.1.22.33 - 10.1.20.230 10.1.22.1

Option43によりアクセスポイントに送付するコントローラアドレス

対象 VCI文字列 コントローラアドレス
Cisco Cisco AP 10.254.10.201,10.254.10.202
Aruba ArubaAP 10.254.10.206

事前準備

CentOS と Docker と Docker Compose があれば、ここの手順はスキップで 作業内容 から始めてください。

CentOS8(10分)

CentOS8は最小構成で問題ありません。
もし手元になければ、 ESXi6.7にCentOS8を最小構成で構築 を参照してください。

Docker(5分)

dnf -y update
dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
dnf -y --nobest install docker-ce docker-ce-cli containerd.io
dnf -y update https://download.docker.com/linux/centos/7/x86_64/stable/Packages/containerd.io-1.2.10-3.2.el7.x86_64.rpm
dnf -y update
systemctl enable docker
systemctl start docker

Docker Compose(1分)

curl -L "https://github.com/docker/compose/releases/download/1.25.3/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
chmod +x /usr/local/bin/docker-compose

作業内容

firewall のポリシー追加(10秒)

firewall-cmd --add-service=tftp   --zone=public --permanent
firewall-cmd --add-masquerade   --zone=public --permanent
firewall-cmd --reload

各サービスのDockerコンテナをつくる(5分)

各サービスのDockerfileと設定ファイル、それらをまとめる docker-compose.yml は GitHub にあるものを使います。

git clone https://github.com/bashaway/infraserv

デフォルトの設定から変更する場合は、以下のエントリなどを参照してください。
設定ファイルの修正なしでもビルドには影響ありません。
ビルド後に修正しても大丈夫ですし。

DHCP
DockerでKea DHCPを構築。CiscoとArubaに同時にOption43が渡せるようになった。

RADIUS
DockerのFreeRADIUSでCiscoのログイン認証+MAC認証+ダイナミックVLANした(CentOS8)

Proxy
Dockerでproxyサーバ

TFTP
Dockerでtftpサーバ

Syslog
Dockerのrsyslogでコンテナ間や他サーバから転送されるログを集約した

設定ファイルの修正が終わったら、コンテナをつくります。

cd infraserv
docker-compose build
docker-compose up -d

いらなくなったら

コンテナ削除
docker-compose stop
docker-compose rm -f

イメージもいらなければ
docker-compose rmi -f

中身の解説

docker-compose.yml

docker-compose.yml は、以下のように構成されています。

docker-compose.yml
version: '3'

services:

  proxy:
    build: ./proxy
    image: infraserv:proxy
    container_name: proxy
    hostname: proxy
    restart: always
    networks:
      infranet:
        ipv4_address: 172.20.0.2
    ports:
      - 8080:8080
    cap_add:
      - SYS_ADMIN
    security_opt:
      - seccomp:unconfined
    volumes:
      - /sys/fs/cgroup:/sys/fs/cgroup:ro
    environment:
      TZ: 'Asia/Tokyo'

  syslog:
    build: ./syslog
    image: infraserv:syslog
    container_name: syslog
    hostname: syslog
    restart: always
    networks:
      infranet:
        ipv4_address: 172.20.0.3
    ports:
      - 514:514/udp
    cap_add:
      - SYS_ADMIN
    security_opt:
      - seccomp:unconfined
    volumes:
      - /sys/fs/cgroup:/sys/fs/cgroup:ro
    environment:
      TZ: 'Asia/Tokyo'

  radius:
    build: ./radius
    image: infraserv:radius
    container_name: radius
    hostname: radius
    restart: always
    networks:
      infranet:
        ipv4_address: 172.20.0.4
    ports:
      - 1812:1812/udp
    cap_add:
      - SYS_ADMIN
    security_opt:
      - seccomp:unconfined
    volumes:
      - /sys/fs/cgroup:/sys/fs/cgroup:ro
    environment:
      TZ: 'Asia/Tokyo'

  dhcp:
    build: ./dhcp
    image: infraserv:dhcp
    container_name: dhcp
    hostname: dhcp
    restart: always
    networks:
      infranet:
        ipv4_address: 172.20.0.5
    ports:
      - 67:67/udp
    cap_add:
      - SYS_ADMIN
    security_opt:
      - seccomp:unconfined
    volumes:
      - /sys/fs/cgroup:/sys/fs/cgroup:ro
    environment:
      TZ: 'Asia/Tokyo'

  tftp:
    build: ./tftp
    image: infraserv:tftp
    container_name: tftp
    hostname: tftp
    restart: always
    network_mode: host
    cap_add:
      - SYS_ADMIN
    security_opt:
      - seccomp:unconfined
    volumes:
      - /sys/fs/cgroup:/sys/fs/cgroup:ro
    environment:
      TZ: 'Asia/Tokyo'


networks:
  infranet:
    driver: bridge
    driver_opts:
      com.docker.network.bridge.enable_ip_masquerade: "true"
      com.docker.network.bridge.host_binding_ipv4: "0.0.0.0"
      com.docker.network.bridge.name: "docker1"
    ipam:
      config:
        - subnet: 172.20.0.0/24

さいごに

Dockerでコンテナにしておくと、ホスト機が汚れなくて気持ちいい

出典

http://docs.docker.jp/engine/reference/commandline/toc.html

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む