🚥

RubyでVLANとアクセスリストを作成する

2023/10/17に公開

概要

私の前職がネットワークに関わる仕事だったので、プログラミングでTCP/IPを表現することに興味がありました。

(いきなり余談ですが、下記の記事はかなり面白いです)

https://zenn.dev/satoken/articles/golang-tcpip

ネットワークの概念にも様々あり、そして私はそのほとんどを忘れていましたが、その中でもなんとなく覚えていたVLANとアクセスリストを作りました。

アクセスリストはWebでいうバリデーションに若干似ており(通していいかどうかを判断する)、AWSなどだとセキュリティグループという名称で同様な概念があるので、なんとなく覚えていました。

用語

VLAN

VLAN( Virtual LAN )とは、物理的な接続形態とは独立して、仮想的なLANセグメントを作る技術です。
VLANはスイッチ内部で論理的にLANセグメントを分割するために使用されます。VLANを使用することでルータやL3スイッチと同じようにL2スイッチでもブロードキャストドメインの分割を行うことができます。

https://www.infraexpert.com/study/vlanz1.html

アクセスリスト(ACL)

アクセスコントロールリストとは、通信アクセスを制御するためのリストのことです。一般的には略して
 ACLと呼ばれています。ネットワーク管理者は通信要件に従ってACL(Access Control List)を定義して
 ルータを通過するパケットに対して、通過を許可するパケット、通過を拒否するパケットを決められます。

https://www.infraexpert.com/study/aclz1.html

開発

1. Deviseクラスの作成

class Device
  attr_accessor :ip_address, :vlan

  def initialize(ip_address, vlan)
    @ip_address = ip_address
    @vlan = vlan
  end
end

まず、サーバーやPC、スマホなどのインスタンスを後で作るために、Deviceというクラスを作成しておきます。持つのはIPアドレスとどのVLANに属するかという情報です。

2. VLANクラスの作成

class VLAN
  attr_reader :id, :name, :subnet

  def initialize(id, name, subnet)
    @id = id
    @name = name
    @subnet = subnet
  end
end

次にVLANクラスを作ります。持つのはVLAN_ID、VLANの名前、そのVLANのサブネットです。
サブネットは、ここでは192.168.100.0/24のような形式のデータを指していて、ネットワークアドレスとサブネットマスクで構成されるものです。

3. アクセスリストクラスの作成

class AccessList
  def initialize
    @entries = []
  end

  def add_entry(action, source, destination)
    @entries << AccessListEntry.new(action, source, destination)
  end

  def access_allowed?(source_ip, dest_ip)
    if @entries.any? { |entry| entry.action == "deny" && entry.matches_traffic?(source_ip, dest_ip) }
      return false
    end

    @entries.any? { |entry| entry.action == "permit" && entry.matches_traffic?(source_ip, dest_ip) }
  end

  def report_access_result(source_ip, dest_ip)
    if access_allowed?(source_ip, dest_ip)
      p "この通信は通す!"
    else
      p "この通信は通さない!"
    end
  end

  class AccessListEntry
    require 'ipaddr'
    attr_accessor :action, :source, :destination

    def ip_within_range?(ip, cidr)
      ip_addr = IPAddr.new(ip)
      network = IPAddr.new(cidr)
      network.include?(ip_addr)
    end

    def initialize(action, source, destination)
      @action = action
      @source = source
      @destination = destination
    end

    def matches_traffic?(source_ip, dest_ip)
      ip_within_range?(source_ip, @source) && ip_within_range?(dest_ip, @destination)
    end
  end
end

AccessListEntryからいくと、この人はアクセスリスト本体(AccessList)に追加して、どのアクセスは許可、どのアクセスは拒否、というのを判断するための1つのルールです。
matches_traffic?で、与えられた出発点IPと、目的地IPが、アクセスリストのエントリーの条件に合致するかを見ています。

AccessListの方は、matches_traffic?を使用したaccess_allowed?で、出発点IPと、目的地IP、そしてエントリー(ルール)をもとに、このアクセスを許可するかどうかを判断しています。

そのboolの結果をもとに、report_access_resultで通すか否かを出力しています。

4. 実際にシミュレーションを走らせる

require_relative 'device'
require_relative 'access_list'
require_relative 'vlan'

# VLANの作成
vlan_server = VLAN.new(10, "admin", "10.10.10.0/24")
vlan_engineer = VLAN.new(20, "engineer", "10.10.20.0/24")
vlan_marketing = VLAN.new(30, "marketing", "10.10.30.0/24")
vlan_guest = VLAN.new(40, "guest", "10.10.40.0/24")

# デバイスの作成
db_server = Device.new("10.10.10.1", vlan_server)
http_server = Device.new("10.10.10.2", vlan_server)
engineer_pc = Device.new("10.10.20.1", vlan_engineer)
marketing_pc = Device.new("10.10.30.1", vlan_marketing)
guest_sp = Device.new("10.10.40.1", vlan_guest)

# アクセスリストの作成
access_list = AccessList.new
access_list.add_entry("permit", "10.10.20.0/24", "10.10.10.0/24") # エンジニア部門からサーバー用セグメントへのアクセスを許可
access_list.add_entry("permit", "10.10.30.0/24", "10.10.10.1/32") # マーケティング部門からDBへのアクセスを許可
access_list.add_entry("deny", "10.10.30.0/24", "10.10.10.2/32") # マーケティング部門からHTTPサーバーへのアクセスをブロック

# エンジニア部門からDBへのアクセスは成功
access_list.report_access_result(engineer_pc.ip_address, db_server.ip_address)

# エンジニア部門からHTTPサーバーへのアクセスは成功
access_list.report_access_result(engineer_pc.ip_address, http_server.ip_address)

# マーケティング部門からHTTPサーバーへのアクセスは失敗
access_list.report_access_result(marketing_pc.ip_address, http_server.ip_address)

# マーケティング部門からDBサーバーへのアクセスは成功
access_list.report_access_result(marketing_pc.ip_address, db_server.ip_address)

# ゲストのスマホからDBへのアクセスは失敗
access_list.report_access_result(guest_sp.ip_address, db_server.ip_address)

作成したクラスたちをもとに、実際にシミュレーションを走らせてみます。
登場人物はDBサーバー、HTTPサーバー、エンジニア部門用PC、マーケティング部門用PC、ゲスト用スマホです。

まず、サーバー、エンジニア部門、マーケティング部門、ゲストでVLANを分けます。
そして登場する端末たちにIPを振り、各VLANに所属させます。

アクセスリストのエントリーには、以下の3つを追加します。

  1. エンジニア部門からサーバー用セグメントへのアクセスを許可
  2. マーケティング部門からDBへのアクセスを許可
  3. マーケティング部門からHTTPサーバーへのアクセスはブロック

各種設定が終わったので、最後に、以下パターンの出力結果を確認します。

  • エンジニア部門からHTTPサーバー
    → ルール1で許可してるので、「この通信は通す!」

  • エンジニア部門からDB
    → ルール1で許可してるので、「この通信は通す!」

  • マーケティング部門からHTTPサーバー
    → ルール3で拒否しているので、「この通信は通さない!」

  • マーケティング部門からDB
    → ルール2で許可してるので、「この通信は通す!」

  • ゲストのスマホからDB
    → どのルールにも明記されていないので、「この通信は通さない!」

ロジック的にホワイトリスト方式にしているので、最後のスマホ→DBも弾いています。

所感

とてもかんたんにですが、再現できてよかったです。

Discussion