むろっちのStacking

日々の中で学んだIT知識をメモして置く場所

SSHポートフォワードを悪用する

セキュリティの関係でアクセスしたいサーバーに通信が通らないケースは結構多いと思いと思う
今回はSSHポートフォワードを利用して一時的に直接通信不可能なセグメントへのアクセスを可能にする方法紹介する

状況:ローカルPCセグメントからターゲットサーバーにアクセスしたいが、アクセスができない

f:id:murochi:20210111135925p:plain

 

このような状態でもターゲットや中継ホストからはローカルPCへアクセス可能であるケースは多い。 というのFWはステートフルインスペクションという機能で特定のNWからアクセスを開始した時にのみ通信を許可/遮断する設定であることが多いからである

f:id:murochi:20210111140115p:plain

 

 

FWルールの非対称性を突いて中継サーバーからローカルPCにコネクションを張ってFWを抜けた後にポートフォワードをする方法をとってみる

[shell]

中継ホストで実行

ssh -p -R ローカルPCのポートフォワード対象Port:ターゲット:ポートフォワード先Port ローカルPCユーザー名@ローカルPC

コマンド例

ssh -p -R 80:172.16.0.3:8000 root@192.168.0.2 

f:id:murochi:20210111140145p:plain

 

自己責任の範囲でお使いください

PythonでIPアドレスを簡単に扱う方法

IPアドレスってプログラムから扱おうとするとめんどくさいですよね
数字だけならいいのですが、たいてい192.168.0.2とか間にドットが入ってくる
またサブネットもオクテット刻みであればドットごとに分けて数字として計算すれば加算、減算できますが/23とか 半端な値になると結構めんどくさいです。

2点間のIPレンジの一覧が欲しい時、以下のように書いていました

# 2IPアドレス間の一覧を列挙する
# start_ip '192.168.0.' end_ip '192.168.2.5' subnet '255.255.254.0'
def generate_ipaddr_list(start_ip,end_ip,subnet):
    start_ip_list = [int(i) for i in start_ip.split('.')]
    end_ip_list = [int(i) for i in end_ip.split('.')]
    subnet_list = [int(i) for i in subnet.split('.')]
    int_mask = ip_to_binip(subnet).count('1')
    result_ips = []
    try:
        for oct1_ip in range(start_ip_list[0],end_ip_list[0]+1):
            for oct2_ip in range(start_ip_list[1],end_ip_list[1]+1):
                for oct3_ip in range(start_ip_list[2],end_ip_list[2]+1):
                    for oct4_ip in range(start_ip_list[3],end_ip_list[3]+1):
                        ip_l = [oct1_ip,oct2_ip,oct3_ip,oct4_ip]
                        ip_str = '.'.join([str(i )for i in ip_l])
                        if ip_l <= end_ip_list and ip_to_binip(ip_str)[:int_mask] == ip_to_binip(start_ip)[:int_mask]:
                            result_ips.append(ip_str)
                        else:
                            raise Exception
    except Exception:
        pass
    finally:
        return result_ips

# 10進数のIPをバイナリ形式に変換
def ip_to_binip(str_ip):
    str_ip_list = [int(i) for i in str_ip.split('.')]
    b_str_ip = ''
    for s in str_ip_list:
        b_str_ip += format(s,'b').zfill(8)
    return b_str_ip

一度バイナリ形式に変換してから計算する仕組みです。 Pythonでバイナリ型はIP計算で使いたいような算術演算がないので使いにくいです

計算しにくいなーと思っていたら、そもそもPythonにipaddressってモジュールがありました

docs.python.jp

使い方

import ipaddress as ip
start_ip = ip.IPv4Address('192.168.0.100')
end_ip = ip.IPv4Address('192.168.3.100')

# 特定のIPアドレス間の一覧を表示
i = 0
while start_ip + i <= end_ip:
    now_ip = start_ip + i
    print(now_ip.exploded)
    i += 1 # =< 192.168.0.100 ,192.168.0.101 ... ,192.168.3.100 

# ネットワークセグメントのIP一覧
for addr in ip.IPv4Network('192.168.0.0/23'): # 255.255.254.0でも可
    print(addr.exploded)
# IPアドレスがプライベートか否か
start_ip.is_private
# =>True

# 2進数へ変換
start_ip.packed
# => True

test_ip = ip.IPv4Interface('192.168.2.100')

# IPv4Networkオブジェクトを生成 IPを指定していないと/32
test_ip.network
# => IPv4Network('192.168.2.100/32')

# /XXの形
test_ip.with_prefixlen
# => '192.168.2.100/32'

# /255.255 ~の形
test_ip.with_netmask
# => '192.168.2.100/255.255.255.255'

IPv4InterfaceクラスはIPv4Addressクラスのサブクラスのため、特に問題なければIPv4Interfaceクラスでオブジェクト生成したほうがいいかもしれません

Railsで簡単にMarkdownを使えるようにする

たまにRailsアプリだけど、静的ページだから手間をかけずにメンテしたいとか、 Railsわからない人にもメンテしてほしいページとかがあります そんなときにはMarkdown形式でページが作成されていると便利です

今回はRubyMarkdown用Gem「redcarpet」を利用したRailsアプリでのMarkdown記法利用方法について説明します

今回試した環境

ディレクトリ構成

  • top:今回のviewに対応するコントローラー。top#index => rootとなるように設定

方法

  • redcarpet gemを追加する

  • application_helperにredcarpetを利用するメソッドを作成する
# helpers/application_helper.rb
module ApplicationHelper
  # markdown形式のファイルをhtmlに変換
  # 例ファイル名:"index.md" controller:Top
  # = "#{APP_ROOT}/app/views/top/index.md"
  #
  def render_md(md_path)
    #ファイル名のみ:controller名フォルダ配下を参照
    #ディレクトリ+ファイル名:Viewsルートからのパスを参照
    if File.basename(md_path) == md_path
      path = "#{File.expand_path(Dir::getwd)}/app/views/#{controller.controller_name}/#{md_path}"
    else
      path = "#{File.expand_path(Dir::getwd)}/app/views/#{md_path}"
    end

    return nil unless File.extname(path) == ".md"

    text = File.open(path,"r") do |f|
            f.read
           end
           
    # redcarpetのオプション類
    options = {
      filter_html:     true,
      hard_wrap:       true,
      space_after_headers: true,
    }

    extensions = {
      autolink:           true,
      no_intra_emphasis:  true,
      fenced_code_blocks: true,
    }

    renderer = Redcarpet::Render::HTML.new(options)
    markdown = Redcarpet::Markdown.new(renderer, extensions)

    markdown.render(text).html_safe
  end
end
  • controllerのerbテンプレートにMarkdown用のrenderを記載する
#views/top/index.html.erb
<%= render_md "index.md" %>
<%= render_md "shared/shared.md" %>

できあがり

pysmbの設定とSMBバージョン差異について

pythonでSMB共有をするときは「pysmb」ライブラリが有名かと思います。

使い方はこんな感じです

import platform
from smb.SMBConnection import SMBConnection

if __name__ == &quot;__main__&quot;:
    user = ''
    pw = ''
    remote_host = ''
    ip = ''
    domain_name='' #workgroupの場合はなしorWORKGROUPでOK

    # 設定1
    conn = SMBConnection(
    user,
    pw,
    platform.uname().node,
    remote_host,
    domain=domain_name,
    use_ntlm_v2=True
    )
    conn.connect(ip, 139)

    # 設定2
    conn = SMBConnection(
    user,
    pw,
    platform.uname().node,
    remote_host,
    domain=domain_name,
    use_ntlm_v2=True,
    is_direct_tcp=True)
    conn.connect(ip, 445)

⇛これでTrueが返ってくれば接続OK

ただ使ってみると接続エラーがたくさん出てうまくいかないことがあります

 

なぜつながらないのか

SMBには様々なバージョンが存在し、オプション設定がどのような役割を果たすのか理解していないと共有サーバーの設定と不一致となり、ID、PASSが適切であっても接続できないケースがあります

 

SMBで使うポート

137/UDP 137/TCP 138/UDP 139/TCP 445/UDP 445/TCP

 

SMBの動き(ざっくり)

SMB1/CIFS Netbios名で宛先を解決し、データを送信(Port 137,138,139) SMB1のみ SMB Direct(Port 445)をサポート(CIFSは対象外) SMB2以降 SMB Direct(Port 445)を利用

アクセス時には上位のプロトコル(SMB3)から順に試行してクライアント、サーバー双方が利用できるVerを利用します。

 

最近のSMB事情

Windows2012R2/Window8.1以降はデフォルトでSMB1が無効になっています →Port139でのアクセスは不可

ただ、古い機器等所有している企業などはSMB1系しか対応していない機器もあるためSMB1の対応を設定することも多いようです

f:id:murochi:20210111140801p:plain:w500

ですので、 アクセス対象のサーバーがCIFSにしか対応していない場合
→SMB Directが使えない

SMB1機能を無効化している場合
→CIFS/SMB1の機能であるNetbios名ファイル共有が使えない

 

結論

pysmb等で繋ぐ場合は
古い機器の場合

  • is_direct_tcp=False または書かない(Default:False)
  • connectポート指定は139

新しい機器の場合

  • is_direct_tcp=True
  • connectポート指定は445

仕様を読む限り、is_direct_tcp=Trueのほうが新しいプロトコルを使って共有アクセスするので性能が出るはず。なので設定しておいたほうがいいかと思います

参考 http://www.atmarkit.co.jp/ait/articles/0010/07/news002.html http://www.atmarkit.co.jp/ait/articles/1501/19/news092.html http://www.atmarkit.co.jp/ait/articles/1507/02/news026.html

DockerでMacVlanを使う際に気を付けること

本稼働のDockerを入れようとしたときにちょっとはまったのでメモ。

Dockerに外部からアクセスさせるときに標準ではブリッジネットワーク+ホストIPへのポートフォワードをするが あまりパフォーマンスがよくないとの話を聞き、本稼働用DockerへはMacvlanを試してみることにしました

 

そもそもDocker のMacvlanとは?

 

Dockerの外部ネットワーク設定方法の1つ。別にDocker専用の機能ではなく実態はLinux標準実装の仮想インターフェース機能を用いて物理NICにサブIPを持たせている

イメージ的にはジャンル全然違いますがWEBサーバーのVirtualhostやDNSのCNAMEみたいなものと認識しています

 

で、この仮想IFってのが問題。今時Docker入れるようなところはまず間違いなくDocker on ハイパーバイザだ。

実際に遭遇したのはVMware環境だが、仮想スイッチはデフォルトで大体こんな設定になっている

仮想IFのMACアドレスはスイッチ側で認識されていないMACアドレスのため通常のARPテーブルを使ったスイッチングではパケットが送られない。そのためMacVlanを使う場合には無差別モードというスイッチが受け取ったパケットは配下のポートへMACアドレス見ずに垂れ流しってモードにする必要がある

 

 

私の時には遭遇しませんでしたが、無差別モードはVMNICに対しても同様の設定ができ、e1000の場合は明示的に指定、VMNET2,3系NICは無差別モードにしなくても必要に応じて切り替えてくれるともどこかのサイトに書いてありました。

 

 

vSANの性能について

最近のサーバー周りではHCI(ハイパーコンバージドインフラストラクチャ)が流行っています。

 

ãï½SANãã®ç»åæ¤ç´¢çµæ

今までストレージで処理していた冗長性(RAID)部分を筐体間でRAIDを作るような仕組みです

 

ベンダー資料やメーカーの説明を聞く限り、パフォーマンスに問題がないということだが実際はどうなのだろうか。 気になったので試してみました

 

検証構成

今回は評価版で試せるvSANで実施しています 詳しい構成は省きますが、自宅サーバー組み換えのタイミングでやったのでこんな感じです

物理ホスト1

  • CPU:Xeon 8core
  • MEM:32GB
  • Disk1:SSD 500GB
  • Disk2:HDD 2000GB
  • NIC:1GBps*4

物理ホスト2

  • CPU:Xeon 4core
  • MEM:32GB
  • Disk1:SSD 1000GB
  • Disk2:HDD 2000GB
  • NIC:1GBps*4

物理ホスト3 Withess用 適当なNUC

ãã«ã©ã¼ããªã¨ã¼ã·ã§ã³ã DC3217IYE [Black] ã®è£½åç»å

ディスク構成

 

ネットワーク設定はこんな感じ

 

10GbpsのNICがないのでとりあえずあるだけNIC突っ込んでNICチーミングで負荷分散できないかとこの構成にしました

結果と考察

vSAN上のWindows仮想マシンでCrystal Disk Infoを回した結果がこちら

Writeが遅い。。。。

 

なお画像は取り忘れましたが、同じ構成をSSDオンリーのローカルストレージで回したらWriteは500MB/sくらい出ていました。

 

ここから考察すると以下と考えられる

  • vSANのReadは分散ディスクのうちどちらか一方からのみ応答を行っている
  • Writeは整合性担保の関係で対向ストレージからの応答を待ってからWrite完了を返している
  • NICの負荷分散は物理ホストが1対1である限り意味をなさない。おそらくチーミングやスイッチ側リンクアグリゲーションいずれを使っても、NIC振り分けがPortやIP,Macアドレスに縛られる限り分散はしない。

 

結論

vSAN用のNICは10G必須みたいです。

 

今回の検証前にKVMwith oVirt+Glusterfsも試してたのですがうまく構築できませんでした。環境構築がうまくいったら試してみようと思います