技術メモ

神奈川在住のITエンジニアの備忘録。おもにプログラミングやネットワーク技術について、学んだことを自分の中で整理するためにゆるゆると書いています。ちゃんと検証できていない部分もあるのでご参考程度となりますが、誰かのお役に立てれば幸いです。

Dockerのコンテナ内の「OS」について

Dockerのコンテナは「アプリケーションの動作環境を提供する」という説明を良く見かける。この「動作環境」について、コンテナ内にはOSの機能の一部があるのか、それとも、仮想マシンのゲストOSのように、コンテナ内には普通のOS(全体)が再現されているのか、ちゃんと理解できずモヤモヤした状態だった。

 

そこで、ネットでいろいろ調べたところ、

なぜDockerではホストOSと違うOSベースのコンテナイメージが動くのか - Qiita

の記事を読んで、だいぶ理解が進んだ。

# 素晴らしい記事をありがとうございました m(_ _)m

 

ざっくり纏めると、コンテナは、OSのカーネル部分は独自で持たずホストOSのものを利用し、ファイルシステムとライブラリは独自のものを持つ、ということ。なので、最初の疑問点に戻ると、Dockerでは「コンテナ内にはOSの機能の一部がある」ということになると思っている。

JavaでSNMPトラップ送信

JavaSNMPトラップを送信するコードを作成した。snmp4j (https://www.snmp4j.org/) を使用しているので、コンパイル&実行には snmp4j-x.x.x.jar が必要。(実機検証は Java1.8 + snmp4j-2.6.2.jar で実施した)


実行方法
java TrapSender [-v {1|2c|2}] [-a agent-addr] [-o trapOid] [-e enterpriseID] destHost


実行例 (SNMPv1 トラップ送信)
java TrapSender -v 1 -a 192.168.0.1 -e .1.2.3.4.5.6 192.168.0.2

実行例 (SNMPv2c トラップ送信)
java TrapSender -v 2c -o .1.2.3.4.5.6 192.168.0.2

import org.snmp4j.*;
import org.snmp4j.mp.SnmpConstants;
import org.snmp4j.smi.*;
import org.snmp4j.transport.DefaultUdpTransportMapping;
import java.io.IOException;

public class TrapSender {

    // Set default value.
    private String destAddr = "127.0.0.1";
    private String agentAddr = "127.0.0.1";
    private int destPort = 162;
    private String trapOid = ".1.2.3.4.5";
    private String enterpriseId = ".1.2.3.4.5";
    private String commName = "public";
    private int snmpVersion = SnmpConstants.version2c;
    private int varBindNum = 3;

    void sendTrap () throws IOException {
        if (snmpVersion == SnmpConstants.version1) {
            sendV1Trap();
        }
        else {
            if (snmpVersion == SnmpConstants.version2c) {
                sendV2cTrap();
            }
        }
    }

    void sendV1Trap () throws IOException {

        // Construct a trap pdu.
        PDUv1 trapPdu = new PDUv1();
        trapPdu.setType(PDU.V1TRAP);
        trapPdu.setEnterprise(new OID(enterpriseId));
        trapPdu.setGenericTrap(PDUv1.ENTERPRISE_SPECIFIC);
        trapPdu.setSpecificTrap(1);
        trapPdu.setAgentAddress(new IpAddress(agentAddr));
        addVarBindsTo(trapPdu, varBindNum);

        // Specify a target(dest info).
        CommunityTarget target = createTarget(commName, SnmpConstants.version1, destAddr, destPort);

        // Send a snmp trap.
        System.out.println("Sending an SNMPv1 trap to " + destAddr + " ...");
        sendPdu(trapPdu, target);
    }

    void sendV2cTrap () throws IOException {

        // Construct a trap pdu.
        PDU trapPdu = new PDU();
        trapPdu.setType(PDU.NOTIFICATION);
        trapPdu.add(new VariableBinding(SnmpConstants.sysUpTime, new TimeTicks(1000)));
        trapPdu.add(new VariableBinding(SnmpConstants.snmpTrapOID, new OID(trapOid)));
        addVarBindsTo(trapPdu, varBindNum);

        // Specify a target(dest info).
        CommunityTarget target = createTarget(commName, SnmpConstants.version2c, destAddr, destPort);

        // Send a snmp trap.
        System.out.println("Sending an SNMPv2c trap to " + destAddr + " ...");
        sendPdu(trapPdu, target);
    }

    private CommunityTarget createTarget(String commName,
                                         int snmpVersion,
                                         String destIpAddr,
                                         int destPort) throws IOException {
        CommunityTarget target = new CommunityTarget();
        target.setCommunity(new OctetString(commName));
        target.setVersion(SnmpConstants.version2c);
        target.setAddress(new UdpAddress(destIpAddr + "/" + destPort));
        return target;
    }

    private void sendPdu (PDU trapPdu, CommunityTarget target) throws IOException {
        TransportMapping transport = new DefaultUdpTransportMapping();
        Snmp snmp = null;
        try {
            transport.listen();
            snmp = new Snmp(transport);
            snmp.send(trapPdu, target);
            snmp.close();
        } catch (IOException ex) {
            throw ex;
        } finally {
            snmp.close();
        }
    }

    private void addVarBindsTo (PDU trapPdu, int varBindNum) {
        if (trapPdu == null || varBindNum == 0) {
            return;
        }
        for (int i=1; i<=varBindNum; i++) {
            String oid = ".1.2.3.4.5." + i;
            String val = "val" + i;
            trapPdu.add(new VariableBinding(new OID(oid), new OctetString(val)));
        }
    }

    void setParams(String[] args) throws IllegalArgumentException {

        for (int i=0; i<args.length; i++) {
            if ("-v".equals(args[i])) {
                i++;
                if ("1".equals(args[i])) {
                    snmpVersion = SnmpConstants.version1;
                }
                else if ("2".equals(args[i]) || "2c".equals(args[i])) {
                    snmpVersion = SnmpConstants.version2c;
                }
                else {
                    throw new IllegalArgumentException("Specified SNMP version is invalid...");
                }
            }
            if ("-a".equals(args[i])) {
                i++;
                if (!isValidIpv4Addr(args[i])) {
                    throw new IllegalArgumentException("Specified agent address is invalid...");
                }
                agentAddr = args[i];
            }
            if ("-o".equals(args[i])) {
                i++;
                if (!isValidOid(args[i])) {
                    throw new IllegalArgumentException("Specified trap oid is invalid...");
                }
                trapOid = args[i];
            }
            if ("-e".equals(args[i])) {
                i++;
                if (!isValidOid(args[i])) {
                    throw new IllegalArgumentException("Specified enterprise id is invalid...");
                }
                enterpriseId = args[i];
            }
            else{
                destAddr = args[i];
            }
        }
        if(destAddr == null) {
            throw new IllegalArgumentException("Trap dest is not specified...");
        }
    }

    private boolean isValidIpv4Addr(String addrStr) {
        String[] octets = addrStr.split("\\.");
        if (octets.length != 4) {
            return false;
        }
        for (String octet : octets) {
            int octetNum;
            try {
                octetNum = Integer.parseInt(octet);
            }
            catch (NumberFormatException ex) {
                return false;
            }
            if (octetNum < 0 || 255 < octetNum) {
                return false;
            }
        }
        return true;
    }

    private boolean isValidOid(String oidStr) {
        final int SUB_ID_CNT_MAX = 128;
        final double SUB_ID_MAX = Math.pow(2,32)-1;
        if (oidStr.startsWith(".")) {
            oidStr = oidStr.substring(1);
        }
        String[] subIds = oidStr.split("\\.");
        if (SUB_ID_CNT_MAX < subIds.length) {
            return false;
        }
        for (int i=0; i<subIds.length; i++) {
            int subIdNum;
            try {
                subIdNum = Integer.parseInt(subIds[i]);
            }
            catch (NumberFormatException ex) {
                return false;
            }
            if (i == 0) {
                if (subIdNum != 0 && subIdNum != 1 && subIdNum != 2) {
                    return false;
                }
            }
            if (subIdNum < 0 || SUB_ID_MAX < subIdNum) {
                return false;
            }
        }
        return true;
    }

    public static void main(String[] args) {
        TrapSender trapsender = new TrapSender();
        try {
            trapsender.setParams(args);
            trapsender.sendTrap();
        } catch (IOException ex) {
            System.err.println("Exception happened while sending an snmp trap...");
            System.err.println(ex.getMessage());
            ex.printStackTrace();
            System.exit(1);
        } catch (Exception ex) {
            System.err.println(ex.getMessage());
            ex.printStackTrace();
            System.exit(1);
        }
    }
}

上記で送信したSNMPトラップをwiresharkで見て、意図した内容になっていたので問題ないはず。
現時点では、トラップに含まれるvarbindなど、ハードコードしている部分もあるので、今後はそれらも引数で変更できるようエンハンスしたい。

ファイルから指定した行間だけを抜き出すスクリプト

ファイルから指定した行間だけを抜き出すスクリプト pickup.pl を作成した。ログファイルなど、非常にサイズの大きいファイルをエディタで見ると、エディタがメモリ不足でまともに動かないことがあるので、必要な行間だけをピックアップして見たい時に使用すると良いと思う。

使い方は、例えば以下のように、ピックアップの開始行、終了行、対象のファイルパスを指定する。
perl pickup.pl -s 1000 -e 2000 bigSize.log
(bigSize.logの1000~2000行目をピックアップする)

ピックアップ結果は、bigSize.log があるディレクトリ配下に pickuped_bigSize.log として作成される。

pickup.pl

use strict;
use warnings;
use Getopt::Long 'GetOptions';
use File::Basename;

my $start;
my $end;
GetOptions(
    'start=s' => \$start,
    'end=s'   => \$end,
);

unless (($start =~/\d+/) and ($end =~/\d+/)) {
    die "Start and end should be digit...";
}

if ($end < $start) {
    die "End should be bigger than start...";
}

my $inFile = $ARGV[0];
if (! -f $inFile) {
    die "There is not $inFile file...";
}
my $outFile = createOutputFilePath($inFile, "pickuped_");

open(my $inFh,  "< $inFile")  or die("Error :$!");
open(my $outFh, "> $outFile") or die("Error :$!");

my $lineNum = 1;
foreach my $line (<$inFh>) {
    if($start <= $lineNum and $lineNum <= $end) {
        print($outFh $line);
    }
    $lineNum++;
}

close($inFh);
close($outFh);

sub createOutputFilePath {
    my ($inputFilePath, $suffix) = @_;
    my ($name, $dir) = fileparse($inputFilePath);
    my $outputFilePath = $dir . $suffix . $name;
}

tarコマンドの戻り値

Linuxの tar コマンドの戻り値には、0, 1, 2 があり、それぞれの意味は以下。

tar(1) - Linux manual page

 

バックアップ・スクリプトなどで tar コマンドによるアーカイブ処理を実行する場合、tarコマンドの戻り値「1」を成功として扱うか失敗として扱うか、迷うかもしれない。

これについては、アーカイブ中にアーカイブ対象のファイルが変更される可能性があり、それが許容される場合は「1」は成功として扱い、そうではない場合は失敗と扱うのが良いと思っている。

例えば、アーカイブ対象の中にログファイルがあり、アーカイブ中にそれが更新されることがある場合は、通常、「1」は成功として扱った方が良いと思う。(そうしないと、バックアップ・スクリプトは頻繁に失敗扱いになってしまうので・・)

Windowsのシャットダウン・スクリプト

Windowsシャットダウン時に確実にサービスを停止させるための手段として「シャットダウン・スクリプト」というものがある。これについて、簡単に纏める。

  • OSの通常のシャットダウン処理 (Service Control Managerによる処理) の前に実行される。
  • シャットダウン・スクリプトの処理が完了するまで、OSの通常のシャットダウン処理に移らない。(ただし、タイムアウトはある)
  • タイムアウトはデフォルトで10分。この時間内に終了しなかったら、強制終了される。

例えば、DBMSのように、停止に時間がかかり、かつOSから強制停止されるとファイルが壊れてその後に障害が起きる可能性があるものなど、停止処理をなるべくちゃんと行いたいサービスについて、このシャットダウン・スクリプトの使用は有効だと思う。

 

以下を参考にさせて頂きましたm(_ _)m

https://tech.nikkeibp.co.jp/it/free/NT/WinReadersOnly/20040415/61/

http://www.atmarkit.co.jp/ait/articles/0407/31/news026.html

依存関係逆転の原則

オブジェクト指向の「依存関係逆転の原則 (Dependency Inversion Principle)」について簡単に纏めてみる。

 

例えば、パッケージAとパッケージBあり、パッケージAのクラスがパッケージBのクラスを使用しているとする。ここで、パッケージBの方が変更されることが多く、リビルドされることが多いとすると、通常、それに依存するパッケージAもその都度リビルドが必要になる。

そこで、パッケージB内のクラスのインタフェースをパッケージA側で持ち、パッケージB内のクラスはそのインタフェースを実装するよう設計する。こうすれば、そのインタフェースが変わらない限り、パッケージA側はパッケージB側の変更のたびにリビルドせずに済む。(型=クラスの情報としては、パッケージAはパッケージBに依存していないので。) 機能としては、パッケージAはパッケージBに依存したままだが、ビルドの際の依存関係については、パッケージBがパッケージAに依存するかたちになる。

 

いろいろ書いたがざっくり言うと、あまり変更されない方(パッケージA)が良く変更される方(パッケージB)のインタフェースを持って規定し、良く変更される方はそれに合わせて実装するというアーキテクチャにすると、開発の際にリビルドする回数が減らせるのではないか、ということ。

CSRFについて

セキュリティ脆弱性の一つであるCSRF (クロス・サイト・リクエスト・フォージェリ) について簡単に纏めてみた。

CSRFを一言で言うと、攻撃者により、webサイトの利用者が意図しないリクエストがwebサイトに対して実行されてしまうセキュリティ脆弱性のこと。

 

攻撃のメカニズム

  1. ユーザはwebサイトA(正規のサイト)にログインしている状態。(前提条件)
  2. ユーザは攻撃者が用意したwebサイトB(悪意のあるサイト)にアクセスする。
  3. webサイトBからダウンロードされたJavaScriptがユーザのwebブラウザ上で動作し、そのJavaScriptがwebサイトAに対して、ユーザの意図しないリクエストを行う。これにより、例えば、webサイトAが掲示板の場合、ユーザが意図しない書き込み等が行われてしまう。

この攻撃は、ユーザはwebサイトAにログインしている状態であり、webサイトA側からすると正規のユーザからのリクエストに見えてしまうので、webサイトAでの権限チェックなどでは防げない。

なお、webサイトB由来のJavaScriptがwebサイトAに対してリクエストを出すのは「同一生成元ポリシー」に反するので、webブラウザ側でブロックできるのは?と思った。しかし、調べたところ、上記の 3. の攻撃で使用されるであろう form タグの action 属性には同一生成元ポリシーが適用されず、任意のURLを指定できるようなので、ブロックできないらしい。

 

対策

webサイト利用者側

攻撃の前提条件はwebサイトにログインしていることなので、利用が終わったwebサイトからはこまめにログインする。

webサイト側

フォームが入ったページをクライアント側に返す時はページにCSRFトークンを含めて、クライアントからフォームデータが来たら、それに含まれているトークンをチェックする。