ゲーム業界エンジニアの独り言

クライアント、サーバ、インフラなどなど興味あることなんでも紹介

ゲームプログラマのためのデザインパターン(ビジター)

今、「C++のためのAPIデザイン」を読んでます。 APIをラポン具デザインパターンについてまとめてみました。

ゲームプログラマのためのC++

ゲームプログラマのためのC++

デザインパターンとは・・・

デザインパターンはプログラム設計でよく見られる問題の特性と、 このような問題に対して最も一般的に採用されている解決策の両方の 両方をカプセル化した抽象構造です。

自分なりに解釈すると、 オブジェクト指向で設計する際に起こりやすい問題の解決策だったり、 再利用性や可読性を上げるための設計の考え方だと思ってます。

ゲーム開発において特に有効なデザインパターン

  • シングルトン
  • ファザード
  • オブザーバ
  • ビジター

今回はビジターを紹介します。

ビジターとは・・・

ビジターとは英語で訪問者を意味します。 ビジターは機能を訪問者であるビジターに記述し処理の追加を簡単にします。

ゲームプログラマーが直面する苛立たしい問題として、 ある特定の機能を実装し、複数の場所でその機能が必要となり コード内に同じような機能の複製が作成されてしまうことがあります。

この解決策になるのがビジターパターンです。

例:オブジェクト位置に関連する処理

オブジェクト位置に関係する処理で例えばで以下のようなものがあります。

  • プレイヤーから一定範囲内に存在する敵を全て検索する
  • プレイヤーから一定範囲内に存在するアイテムを全て検索する
  • プレイヤーから一番近くにあるアイテムを検索する
  • プレイヤーから一定範囲内に存在する敵の数を取得する

このようなオブジェクトの位置に対する処理は挙げればきりがありません。 他にもイベント発生トリガーや、セーブポイントなどゲームのほとんどが 同じような性質を持っています。

ビジターを使用しない実装例

// 敵クラス
class Enemy
{
public:
    Vector3D GetPosition(); // 位置を取得
    // その他、敵に関する処理
}
// アイテムクラス
class Item
{
public:
    Vector3D GetPosition(); // 位置を取得
    // その他、アイテムに関する処理
}
// セーブポイントクラス
class SavePoint
{
public:
    Vector2D GetLocation(); // 位置を取得(これは2Dで扱っている)
    // その他、セーブポイントに関する処理
}

これらのクラスはある場所に対するパブリックなアクセサを持っています。 しかし、位置の扱いが異なる場合もあります。(Vector3DとVector2Dなど) 他にも、アクセサの名前が異なる場合もあるでしょう。

こういった違いのためか位置に対する処理は、 以下のような形で記述されることが多いと思います。

// 敵管理クラス(敵の検索など処理を記述)
class EnemyManager
{
public:
    // 指定位置から範囲内の敵を全て検索
    vector<Enemy*> FindEnemyInRange(const Vector3D& pos, const float range);
    // 指定位置から範囲内の敵の数を取得
    int GetEnemyInRangeCount(const Vector3D pos, const float range);
}

コード内を見てみると、他にも ItemManager や SavePointManager など、 似たような処理の複製が存在していることに気づくと思います。

プロジェクトを開始したばかりなら、これらのオブジェクトクラスを 位置を扱う基底クラスから派生させる事で、複製を回避できます。 しかし、プロジェクトがかなり進行してからこの問題が発生した場合は そのような大幅なインターフェイスの変更は危険です。

この解決策になるのがビジターパターンです。 ビジターを使用するとクラスのインターフェイスは変更せず拡張が可能になります。

ビジターの実装

ビジターの実装には2つの主要なコンポーネントがあります。

1.訪問する全てのクラスに対して、訪問メカニズムを提供するために  ビジタークラスを記述します。  さまざまな複数のクラスを横断するビジターを、実装したい共通機能の  カテゴリ一つにつき一つ持つことができます。

class ObjectVisitor
{
public:
    // 訪問されるオブジェクトで使用される明示的な訪問メソッド
    virtual void VisitEnemy(Enemy *enemy) = 0;
    virtual void VisitItem(Item*item) = 0;
    virtual void VisitSavePoint(SavePoint*savepoint) = 0;
}

2.訪問を受ける各クラスのインターフェイスには  訪問を受け入れるメソッドを追加します。  ビジターはパブリックインターフェイスにアクセスできるようになります。

// 敵クラス
class Enemy
{
public:
    Vector3D GetPosition(); // 位置を取得
    // 訪問を受け入れるメソッド
    void AcceptVisitor(ObjectVisitor *visitor)
    {
        visitor->VisitEnemy(this);
    }
}
// アイテムクラス
class Item
{
public:
    Vector3D GetPosition(); // 位置を取得
    // 訪問を受け入れるメソッド
    void AcceptVisitor(ObjectVisitor *visitor)
    {
        visitor->VisitItem(this);
    }
}
// セーブポイントクラス
class SavePoint
{
public:
    Vector2D GetLocation(); // 位置を取得
    // 訪問を受け入れるメソッド
    void AcceptVisitor(ObjectVisitor *visitor)
    {
        visitor->VisitSavePoint(this);
    }
}

実際の使用例

上で記述したインターフェイスを使用し、 範囲内のオブジェクトを検索する例を見てみます。 (ビジター関係ない実装は省きます)

// 範囲内にオブジェクトが存在するか判定するビジタークラス
class FindRangeInObjectVisitor
{
public:
    // 検索位置を設定
    void SetPosition(const Vector3D pos) { pos_= pos; }
    // 検索範囲を設定
    void SetRange(const float range) { range_ = range; }
    // 結果を取得
    bool GetResult() { retutn result_; }

    // 訪問されるオブジェクトで使用される明示的な訪問メソッド
    void VisitEnemy(Enemy *enemy);
    void VisitItem(Item*item);
    void VisitSavePoint(SavePoint*savepoint);

private:
    Vector3D pos_; // 検索位置
    float range_; // 検索範囲
    bool result_; // 判定結果

    // 範囲内に位置が存在するか判定する処理
    bool IsRangeIn(const Vector3D pos);
}
// 敵の訪問メソッド
void FindRangeInObjectVisitor::VisitEnemy(Enemy *enemy)
{
    // enemy にアクセスし範囲内か判定
    if (IsRangeIn(enemy->GetPosition()))
        result_ = true;
}

// アイテムの訪問メソッド
void FindRangeInObjectVisitor::VisitItem(Item *item)
{
    // item にアクセスし範囲内か判定
    if (IsRangeIn(enemy->GetPosition()))
        result_ = true;
}

// セーブポイントの訪問メソッド
void FindRangeInObjectVisitor::VisitItem(SavePoint *savepoint)
{
    // savepoint にアクセスし範囲内か判定
    // Vector2D → Vector3D への変換も吸収
    Vector3D pos;
    pos.x = savepoint->GetLocation().x;
    pos.y = savepoint->GetLocation().y;
    pos.z = 0.f;
    if (IsRangeIn(pos))
        result_ = true;
}
Enemy *target_enemy = GetTargetEnemy(); // 範囲内判定を行う敵のインスタンス

// 範囲内検索のビジタークラス
FindRangeInObjectVisitor visitor;
visitor.setPosition(GetPlayerPosition());
visitor.setRange(10.0f);

// ビジターが敵インスタンスを訪問し範囲内判定
target_enemy->AcceptVisitor(visitor);

if (visitor->GetResult())
{
    // 指定の敵は範囲内に存在
}

各オブジェクトに訪問メソッドを追加し、ビジターを迎えることで ビジターはオブジェクトのパブリックインターフェイスにアクセスし、 情報を収集することが出来ます。 検索機能もビジターに実装するため、機能の複製も避けられます。

ゲームプログラマのためのデザインパターン(オブザーバ)

今、「ゲームプログラマのためのC++」を読んでます。 ゲームプログラムで使えるデザインパターンについてまとめてみました。

ゲームプログラマのためのC++

ゲームプログラマのためのC++

デザインパターンとは・・・

デザインパターンはプログラム設計でよく見られる問題の特性と、 このような問題に対して最も一般的に採用されている解決策の両方の 両方をカプセル化した抽象構造です。

自分なりに解釈すると、 オブジェクト指向で設計する際に起こりやすい問題の解決策だったり、 再利用性や可読性を上げるための設計の考え方だと思ってます。

ゲーム開発において特に有効なデザインパターン

  • シングルトン
  • ファザード
  • オブザーバ
  • ビジター

今回はオブザーバを紹介します。

オブザーバとは・・・

あるオブジェクトに何らかの変更が生じた場合に、 それに関係する別のオブジェクトに通知を行うメカニズムを提供します。

例:オブジェクトの参照保持

ゲーム中開発中に多く見られる問題の一つに、 オブジェクト(ObjectA)を保持している別のオブジェクト(ObjectB)が存在するのに オブジェクト(ObjectA)の削除を行ってしまう事が上げられます。

// 小さなクラス
class ObjectA
{
public:
    void ObjectA(void) {}
    ~ObjectA(void) {}

    void Update(void) {}
}

// ObjectAの参照を持つクラス
class ObjectB
{
public:
    void ObjectB(ObjectA *pObjectA) : m_pObjectA(pObjectA)
    {
    }
    ~ObjectB(void) {}

    void Update(void)
    {
        if (m_pObjectA != NULL)
            m_pObjectA->Update();
    }

private:
    ObjectA* m_pObjectA;
}
ObjectA *pObjectA = new ObjectA();
ObjectB *pObjectB = new ObjectB(pObjectA);
pObjectB->Update(); // pObjectB を更新、問題なし
delete pObjectA;    // pObject Aを削除
pObjectB->Update(); // pObjectB を更新、失敗

2つの目の pObjectB->Update() が失敗する理由は pObjectA が削除され、 pObjectB が持つ pObjectA への参照が無効になりアクセスに失敗するからです。

問題のある解決策

解決策の一つに、ObjectA が ObjectB を知っているというものがあります。

// 小さなクラス
class ObjectA
{
public:
    ~ObjectA(void)
    {
        m_pObjectB->NotifyObjectADestruction();
    }

private:
    ObjectB* m_pObjectB;
}

// ObjectAの参照を持つクラス
class ObjectB
{
public:
    void NotifyObjectADestruction(void)
    {
        // m_pObjectA が削除された時に通知を貰い参照を無効化
        m_pObjectA = NULL;
    }
}

しかし、この解決策は問題点があります。

  • 参照を無効化だけでなく、他にも通知を行いたい
  • ObjectA だけではなく 他のオブジェクトの参照も保持したい

などの変更が起こった場合に、ソースは冗長になり複雑さを増します。

オブザーバの実装

この解決策はオブザーバです。 オブザーバはサブジェクトと呼ばれる別のオブジェクトの変化を観察します。 1つのサブジェクトは複数のオブザーバを持つことが出来ます。 そして、全てのオブザーバに自分の変化を通知します。

// 基本のオブザーバクラス
class Observer
{
public:
    virtual ~Observer(void);
    virtual void Update(void) = 0;

    void SetObject(Subject* pSubject)
    {
        m_pSubject = pSubject;
    }

protected:
    Subject* m_pSubject; 
}

// 基本のサブジェクトクラス
class Subject
{
public:
    virtual ~Subject(void)
    {
        std::list<Observer *>::iterator it;
        for (it = m_observers.begin(); it != m_observers.end(); it++)
        {
            (*it)->SetObject(NULL);
        }
    }

    virtual void Update(void)
    {
        std::list<Observer *>::iterator it;
        for (it = m_observers.begin(); it != m_observers.end(); it++)
        {
            (*it)->Update();
        }
    }

    virtual  void AddObserver(Observer* pObserver)
    {
        m_observers.push_back(pObserver);
        pObserver->SetObject(this);
    }

protected:
    std::list<Observer *> m_observers;
}

Observer クラスは単純で監視するサブジェクトを知っているだけです。 Subject クラスは少し複雑で、オブザーバのリストを保持していて 自分に何か状態の変化があると保持しているオブザーバ達に通知を送ります。 ここでも自分が死んだこと(デストラクタ)自分が更新された事(Update メソッド)を オブザーバ達に通知します。

実際の使用例

実際に使用されている例を見てみます。

ここでは、STG でよく見る、 「メインウエポンの攻撃時に、サブウェポンも一緒に攻撃」を実装してみます。 (グラディウスみたいなやつです) ここではメインウェンポンが複数のサブウェポンを保持できる想定です。

// サブウェポン基礎クラス(オブザーバ)
class SubWeaponBase
{
public:
    virtual ~SubWeaponBase(void) {}
    virtual void Attack(void) = 0;

    void SetObject(MainWeapon* pMainWeapon)
    {
        m_pMainWeapon= pMainWeapon;
    }

protected:
    MainWeapon* m_pMainWeapon; 
}

// 実際のサブウェポンA(オブザーバ実装)
class SubWeaponA : public SubWeaponBase
{
public:
    SubWeaponA(void) {}
    virtual ~SubWeaponA(void) {}

    virtual void Attack(void)
    {
        // 攻撃処理
    }
}

// メインウェポン基礎クラス(サブジェクト)
class MainWeaponBase
{
public:
    MainWeaponBase(void) {}

    virtual ~MainWeaponBase(void)
    {
        std::list<SubWeaponBase*>::iterator it;
        for (it = m_subweapons.begin(); it != m_subweapons.end(); it++)
        {
            (*it)->SetObject(NULL);
        }
    }

    virtual void Attack(void)
    {
        // サブウェポンも攻撃
        std::list<SubWeaponBase*>::iterator it;
        for (it = m_subweapons.begin(); it != m_subweapons.end(); it++)
        {
            (*it)->Attack();
        }
    }

    virtual void AddSubweapon(SubWeaponBase* pSubweapon)
    {
        m_subweapons.push_back(pSubweapon);
        pObserver->SetObject(this);
    }

protected:
    std::list<SubWeaponBase*> m_subweapons;
}

// 実際のメインウェポンAクラス(サブジェクト実装)
class MainWeaponA : public MainWeaponBase
{
public:
    MainWeaponA(void) {}

    virtual ~MainWeaponA(void) {}

    virtual void Attack(void)
    {
        // 攻撃処理
        
        // 基礎クラスの攻撃処理を呼び出し(サブウェポンも攻撃)
        MainWeaponBase::Attack();
    }
}
MainWeaponA *pMainWeaponA = new MainWeaponA();
SubWeaponA *pSubWeaponA = new SubWeaponA();
// pMainWeaponA にオブサーバ(pSubWeaponA)追加
pMainWeaponA->AddSubweapon(pSubWeaponA);
// メインウェポン攻撃、オブサーバに追加したサブウェポンも攻撃
pMainWeaponA->Attack();
// メインウェポンを破棄、オブサーバのサブウェポンも攻撃
delete pMainWeaponA;

ゲームプログラマのためのデザインパターン(シングルトン)

今、「ゲームプログラマのためのC++」を読んでます。 ゲームプログラムで使えるデザインパターンについてまとめてみました。

ゲームプログラマのためのC++

ゲームプログラマのためのC++

デザインパターンとは・・・

デザインパターンはプログラム設計でよく見られる問題の特性と、 このような問題に対して最も一般的に採用されている解決策の両方の 両方をカプセル化した抽象構造です。

自分なりに解釈すると、 オブジェクト指向で設計する際に起こりやすい問題の解決策だったり、 再利用性や可読性を上げるための設計の考え方だと思ってます。

ゲーム開発において特に有効なデザインパターン

  • シングルトン
  • ファザード
  • オブザーバ
  • ビジター

今回はシングルトンを紹介します。

シングルトンとは・・・

その名前から分かるとおり、インスタンスを一つしか持っていないクラスです。 一般にシングルトンの目的は、ある一連の機能を集中管理する場所の提供で、 残りのコードはグローバルにその場所にアクセスできます。

例:ファイルマネージャ

ほとんどのゲームは、ファイルの検索、オープン、クローズ、修正するシステムを持っています。 このようなシステムはファイルマネージャと呼ばれ、通常は一つのゲームに一つしかありません。

シングルトンを使わない場合のファイルマネージャ実装例

class FileManager
{
public:
    FileManager();
    ~FileManager();
    bool FileExists(const char* strName) const;
    File* OpenFile(const char* strName, eFileOpenMode mode);
    bool CloseFile(File* pFile);
protected:
};
FileManager fileManager;
File *pFP = fileManager.OpenFIle("test.txt". eReadOnly);

このコードは問題なく動作しますが、大きなプロジェクトでは多少の問題が起こります。

まず複数のファイルマネージャを作成を防止する機能がついていません。 ファイル管理は通常、特定のクラスやシステムの仕様に左右されないので、 ファイルマネージャが複数に存在する理由はないし、リソースの無駄です。

さらに、マルチスレッドを使用している場合は無分別にファイルアクセスを行うのは危険です。

本当に必要なのは、ファイルアクセスに責任を単独で負うただ1つのマネージャーです。 ここで、「じゃあインスタンスを一つ作るようにするよ!」と思っていても、 他のプログラマが2つめのインスタンスを作らない保証はありません。

アクセスの問題もあります。 ファイルマネージャの唯一のインスタンスはどこにおけばいいでしょうか? グローバル空間に置いておくだけというのは考えにくいですね・・・

そこで、以上の解決策としてシングルトンを使用します。

シングルトンの実装

class Singleton
{
public:
    static Singleton *GetInstance()
    {
        return s_pInstance;
    }

   static void Create();
   static void Destroy();

protected:
    static Singleton *s_pInstance;
    Singleton(): // 隠しコンストラクタ
};
// 唯一のインスタンスをNULLで初期化
Singleton *Singleton::s_pInstance = NULL;

// Create()
static  void Singleton::Create()
{
    if (!s_pInstance)
    {
        s_pInstance = new Singleton;
    }
}

// Destroy()
static  void Singleton::Destroy()
{
    delete s_pInstance;
    s_pInstance  = NULL;
}

コンストラクタを外部公開しないことで、外部からのインスタンス作成を禁止して 複数のインスタンス作成を不可能にします。

インスタンスの取得を static関数 にすることでグローバルアクセスを提供します。

インスタンスへのアクセスをクラス管理しているためカプセル化も行えています。

ここでは明示的に Create() と Destroy() を呼んでいます。 GetInstance() でインスタンスの作成を行う実装もありますが、 明示的に作成と破棄を行うほうが管理しやすいためこうしています。

ファイルマネージャーでの使用例

起動時にファイルマネージャーのインスタンスを作成します。

FileManager::Create();

この機能はこれまでと同じ方法でアクセスします。

FileManager::GetInstance()->OpenFile("test.txt", eReadOnly);

プログラムのシャットダウン時にインスタンスを破棄します。

FileManager::Destroy();

テンプレート化

コードの再利用性も考えてテンプレート化してみます。

template<class T>
class Singleton
{
public:
    static inline T& GetInstance()
    {
        static T instance;
        return instance;
    }

protected:
    Singleton() {} // 外部でのインスタンス作成は禁止
    virtual ~Singleton() {}

private:
    void operator=(const Singleton& obj) {} // 代入演算子禁止
    Singleton(const Singleton &obj) {} // コピーコンストラクタ禁止
};

使用例

class FileManager : public Singleton<FileManager>
{
public:
    friend class Singleton<FileManager>; // Singleton でのインスタンス作成は許可

public:
    bool FileExists(const char* strName) const;
    File* OpenFile(const char* strName, eFileOpenMode mode);
    bool CloseFile(File* pFile);

protected:
    FileManager(); // 外部でのインスタンス作成は禁止
    virtual ~FileManager();
};

CentOS を Jenkins のスレイブマシーンに追加する方法

CentOS を Jenkins のスレイブマシーンに追加する方法

ノードの追加

Jenkinsの管理 -> ノードの管理 -> 新規ノード作成 -> ノード名を入力 -> ダムスレーブを選択 -> OK

以下のノード情報を入力してOK ノード名、説明、ラベルは都度変更

ノード名:test
説明:
同時ビルド数:1
リモートFSルート:/var/lib/jenkins
ラベル:test
用途:このマシーンを特定ジョブ専用にする
起動方法:JNLP経由でスレーブを起動
可用性:可能な限りオンラインのままにする

スレーブマシーン側の設定

スレーブマシーンにログイン後、

起動ユーザーを作成

# useradd jenkins

ワークディレクトリを作成

# mkdir -p /var/lib/jenkins
# chown -R jenkins:jenkins /var/lib/jenkins

設定ファイルを作成  JENKINS_URL は Jenkins のアクセスURL  JENKINS_NODENAME は 作成したノード名  JENKINS_SECRET は 作成したノードページに書いてある -secret の値

# vi /etc/sysconfig/jenkins-slave

JENKINS_WORKDIR="/var/lib/jenkins"
JENKINS_USER="jenkins"
JENKINS_URL="https://210.0.0.0:41001/jenkins"
JENKINS_NODENAME="test"
JENKINS_SECRET="9f1819708de5b9ddd1033847172e9157b152e509f404e4274f10ef8b63c30346"
JENKINS_OPTION="-noCertificateCheck"
 # chmod 600 /sysconfig/jenkins-slave

起動サービスの作成

# vi /etc/init.d/jenkins-slave

#!/bin/sh
#
# jenkins-slave:    Launch a Jenkins BuildSlave instance on this node
#
# chkconfig:    - 99 01
# description:  Enable this node to fulfill build jobs
#
 
# Source function library.
. /etc/rc.d/init.d/functions
 
[ -f /etc/sysconfig/jenkins-slave ] && . /etc/sysconfig/jenkins-slave
 
[ -n "$JENKINS_URL" ] || exit 0
[ -n "$JENKINS_WORKDIR" ] || exit 0
[ -n "$JENKINS_USER" ] || exit 0
[ -n "$JENKINS_NODENAME" ] || exit 0
[ -n "$JENKINS_SECRET" ] || exit 0
[ -n "$JENKINS_OPTION" ] || exit 0
[ -x /usr/bin/java ] || exit 0
 
download_jar()
{
    curl -s -o slave.jar $JENKINS_URL/jnlpJars/slave.jar || exit 0
}

start()
{
  cd $JENKINS_WORKDIR
  [ -f slave.jar ] || download_jar
 
  echo -n $"Starting $prog: "
 
  su $JENKINS_USER sh -c "\
    java -jar slave.jar -jnlpUrl $JENKINS_URL/computer/$JENKINS_NODENAME/slave-agent.jnlp -secret $JENKINS_SECRET $JENKINS_OPTION >slave.log 2>&1 &"

  if [ $? = 0 ]; then echo '[OK]'; else echo '[NG]'; fi
}
 
stop()
{
  echo -n $"Shutting down $prog: "
 
  PID=`ps -ef | grep '[j]ava -jar slave.jar' | awk '{print $2}'`
  [ -n "$PID" ] && kill $PID
 
  if [ $? = 0 ]; then echo '[OK]'; else echo '[NG]'; fi
}
 
# See how we were called.
case "$1" in
  start)
    start
    ;;
  stop)
    stop
    ;;
  restart|reload)
    stop
    start
    ;;
  status)
    status java
    ;;
  *)
    echo $"Usage: $0 {start|stop|restart|reload}"
    exit 1
esac
 
exit 0
# chmod 755 /etc/init.d/jenkins-slave

起動サービスに登録

# chkconfig --add jenkins-slave
# chkconfig jenkins-slave on
# chkconfig --list jenkins-slave

起動

# service jenkins-slave start

slave.jar がダウンロードが失敗した場合は手動で配置 slave.jar は 作成したノードページからダウンロード可能

# mv slave.jar /var/lib/jenkins/

改訂新版Jenkins実践入門 ――ビルド・テスト・デプロイを自動化する技術 (WEB+DB PRESS plus)

改訂新版Jenkins実践入門 ――ビルド・テスト・デプロイを自動化する技術 (WEB+DB PRESS plus)

Jenkins実践入門 ?ビルド・テスト・デプロイを自動化する技術 (WEB+DB PRESS plus)

Jenkins実践入門 ?ビルド・テスト・デプロイを自動化する技術 (WEB+DB PRESS plus)

CentOSでサーバー公開するためのセキュリティ設定メモ

セキュリティ設定メモ

全パッケージのアップデート

# yum –y update

root アカウントにメールアドレス設定

# vi /etc/aliases

root:           メールアドレス

設定更新

newaliases

テスト送信

echo test|mail root

ssh 設定

リモートからの root ログインを無効

# vi /etc/ssh/sshd_config

PermitRootLogin no
PermitEmptyPasswords no

root になれるユーザーを限定

root になれるユーザーは wheel グループのみ。

# usermod -G wheel ggc
# visudo

%wheel ALL=(ALL) ALL

ポート番号を変更

# vi /etc/ssh/sshd_config

Port 10022

ssh 再起動。

/etc/init.d/sshd restart

iptables 設定

INPUT, FORWARD, OUTPUTを一旦全て閉じる。 使用するIPのポートのみ空ける。 今回は アクセス許可IPの https(443) とローカルのみ。

# vi /etc/sysconfig/iptables

# Generated by iptables-save v1.4.7 on Thu May  1 20:30:59 2014
*filter
:INPUT DROP [0:0]
:FORWARD DROP [0:0]
:OUTPUT DROP [0:0]
-A INPUT -i lo -j ACCEPT
-A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
-A INPUT -s 200.000.000.000 -p tcp -m tcp --dport 443 -j ACCEPT
-A INPUT -s 192.168.36.0/24 -j ACCEPT
COMMIT
# Completed on Thu May  1 20:30:59 2014

再起動

# /etc/init.d/iptables restart

ログ監視設定

ログ監視ツールとして logwatch をインストールする。 1日一回ログの内容を整形して root にメールを送ってくれる。

# yum  install logwatch

ウィルス対策ソフト設定

保険的な対策も含めて導入。 オープンソースのウィルス対策ソフトである Clam AntiVirus を導入する。

EPEL リポジトリ導入

# rpm -Uvh  http://dl.fedoraproject.org/pub/epel/6/x86_64/epel-release-6-8.noarch.rpm

Clam AntiVirus 導入

# yum –y --enablerepo=epel install clamd

root 権限で動作するように変更。

# vi /etc/clamd.conf

#User clamav

起動と自動起動設定。

# /etc/rc.d/init.d/clamd start
# chkconfig clamd on

ウィルス定義ファイル最新化。 ウィルス定義ファイル更新機能の有効化。

# vi /etc/freshclam.conf

#Example

ウィルススキャンテスト。

# clamscan --infected --remove --recursive

デイリーでのチェック設定

結果を root のメールアドレスに送信。 /etc/cron.daily に実行スクリプトを配置すると 毎日、AM 3:00 ごろに実行される。

# vi /etc/cron.daily/virusscan 
#!/bin/bash

PATH=/usr/bin:/bin

# clamd update
yum -y update clamd > /dev/null 2>&1

# excludeopt setup
excludelist=/root/clamscan.exclude
if [ -s $excludelist ]; then
    for i in `cat $excludelist`
    do
        if [ $(echo "$i"|grep \/$) ]; then
            i=`echo $i|sed -e 's/^\([^ ]*\)\/$/\1/p' -e d`
            excludeopt="${excludeopt} --exclude-dir=^$i" 
        else
            excludeopt="${excludeopt} --exclude=^$i" 
        fi
    done
fi

# virus scan
CLAMSCANTMP=`mktemp`
clamscan --recursive --remove ${excludeopt} / > $CLAMSCANTMP 2>&1
[ ! -z "$(grep FOUND$ $CLAMSCANTMP)" ] && \

# report mail send
grep FOUND$ $CLAMSCANTMP | mail -s "Virus Found in `hostname`" root
rm -f $CLAMSCANTMP

実行権限付加。

# chmod +x virusscan

rootkit 検知ツールを導入

chkrootkit というrootkit 検知ツールを導入。 rootkitLinuxサーバーにインストールされてしまっていないかチェック。

rootkit とは クラッカーが遠隔地のコンピュータに不正に侵入した後に利用するソフトウェアをまとめたパッケージ。

chkrootkit インストール

# yum --enablerepo=epel install chkrootkit

chkrootkit 存在を確認

# chkrootkit | grep INFECTED

上記chkrootkit実行結果として"INFECTED"という行が表示されなければ問題なし

デイリーでのチェック設定

結果を root のメールアドレスに送信。 /etc/cron.daily に実行スクリプトを配置すると 毎日、AM 3:00 ごろに実行される。

# vi /etc/cron.daily/chkrootkit 
#!/bin/bash

PATH=/usr/bin:/bin

TMPLOG=`mktemp`

# chkrootkit実行
chkrootkit > $TMPLOG

# ログ出力
cat $TMPLOG | logger -t chkrootkit

# SMTPSのbindshell誤検知対応
if [ ! -z "$(grep 465 $TMPLOG)" ] && \
   [ -z $(/usr/sbin/lsof -i:465|grep bindshell) ]; then
        sed -i '/465/d' $TMPLOG
fi

# upstartパッケージ更新時のSuckit誤検知対応
if [ ! -z "$(grep Suckit $TMPLOG)" ] && \
   [ -z $(rpm -V `rpm -qf /sbin/init`) ]; then
        sed -i '/Suckit/d' $TMPLOG
fi

# rootkit検知時のみroot宛メール送信
[ ! -z "$(grep INFECTED $TMPLOG)" ] && \
grep INFECTED $TMPLOG | mail -s "chkrootkit report in `hostname`" root

rm -f $TMPLOG

実行権限付加。

# chmod +x virusscan

改ざん監視ソフト設定

AIDE オープンソースのホスト型侵入検知システム(HIDS)。 これを使うことで、ファイルの改ざん監視を行える。

AIDE のインストール

# yum install aide

設定ファイルの編集

必要のある場合は修正。

# vi /etc/aide.conf

データベースファイルの作成

# aide --init
# cp /var/lib/aide/aide.db.new.gz /var/lib/aide/aide.db.gz

改ざんチェック

# aide --update

改ざんが検知されると以下のような結果が出力される。

AIDE found differences between database and filesystem!!
Start timestamp: 2009-11-30 14:39:45
Summary:
Total number of files=11247,added files=0,removed files=0,changed files=1
Changed files:
changed:/bin/ls
Detailed information about changes:
File: /bin/ls
Mtime : 2009-11-30 14:26:18 , 2009-11-30 14:39:39
Ctime : 2009-11-30 14:26:18 , 2009-11-30 14:39:39

データベースファイルの更新

# cp /var/lib/aide/aide.db.new.gz /var/lib/aide/aide.db.gz

監視ポリシー(aide.conf)

監視ポリシーは、インストール時の標準のものでも、例えば/etcや/bin配下などの広範囲にチェックを行う。 ただし、標準では、/var/logや/root配下もチェックするので ユーザーのログイン、rootがコマンドを実行しただけで、改ざんとして通知される。 こうした検知が煩わしい、もしくは独自ファイルの監視を行いたい場合、カスタマイズが必要。

例えば、以下の例の場合、全てのファイルに関して、 パーミッション、ユーザー、グループに変更があった場合、改ざんとして検知されます。

/ p+u+g

デイリーでのチェック設定

結果を root のメールアドレスに送信。 /etc/cron.daily に実行スクリプトを配置すると 毎日、AM 3:00 ごろに実行される。

# vi /etc/cron.daily/aide
#!/bin/bash

# These settings are mainly for the wrapper scripts around aide,
# such as aideinit and /etc/cron.daily/aide

# This is the email address reports get mailed to

MAILTO=root

# Set this to suppress mailings when there's nothing to report

#QUIETREPORTS=1

# This parameter defines which aide command to run from the cron script.
# Sensible values are "update" and "check".
# Default is "check", ensuring backwards compatibility.
# Since "update" does not take any longer, it is recommended to use "update",
# so that a new database is created every day. The new database needs to be
# manually copied over the current one, though.

COMMAND=update

# This parameter defines how many lines to return per e-mail. Output longer
# than this value will be truncated in the e-mail sent out.

LINES=1000

# This parameter gives a grep regular expression. If given, all output lines
# that _don't_ match the regexp are listed first in the script's output. This
# allows to easily remove noise from the aide report.

NOISE="" 

# This parameter defines which options are given to aide in the daily
# cron job. The default is "-V4".

AIDEARGS="" 

PATH="/sbin:/usr/sbin:/bin:/usr/bin" 
LOGDIR="/var/log/aide" 
LOGFILE="aide.log" 
CONFFILE="/etc/aide.conf" 
ERRORLOG="error.log" 
ERRORTMP=`mktemp -t "$ERRORLOG".XXXXXXXXXX`

[ -f /usr/sbin/aide ] || exit 0

AIDEARGS="-V4" 

if [ -f /etc/default/aide ]; then
        . /etc/default/aide
fi

FQDN=`hostname -f`
DATE=`date +"at %Y-%m-%d %H:%M"`

# default values

MAILTO="${MAILTO:-root}" 
DATABASE="${DATABASE:-/var/lib/aide/aide.db.gz}" 
LINES="${LINES:-1000}" 
COMMAND="${COMMAND:-check}" 

if [ ! -f $DATABASE ]; then
        (
        echo "Fatal error: The AIDE database does not exist!" 
        echo "This may mean you haven't created it, or it may mean that someone has removed it." 
        ) | /usr/bin/mail -s "Daily AIDE report for $FQDN" $MAILTO
        exit 0
fi

aide $AIDEARGS --$COMMAND >"$LOGDIR/$LOGFILE" 2>"$ERRORTMP" 
RETVAL=$?

if [ -n "$QUIETREPORTS" ] && [ $QUIETREPORTS -a \! -s $LOGDIR/$LOGFILE -a \! -s $ERRORTMP ]; then
        # Bail now because there was no output and QUIETREPORTS is set
        exit 0
fi

(cat << EOF;
This is an automated report generated by the Advanced Intrusion Detection
Environment on $FQDN ${DATE}.

EOF

# include error log in daily report e-mail

if [ "$RETVAL" != "0" ]; then
        cat > "$LOGDIR/$ERRORLOG" << EOF;

*****************************************************************************
*                    aide returned a non-zero exit value                    *
*****************************************************************************

EOF
        echo "exit value is: $RETVAL" >> "$LOGDIR/$ERRORLOG" 
else
        touch "$LOGDIR/$ERRORLOG" 
fi
< "$ERRORTMP" cat >> "$LOGDIR/$ERRORLOG" 
rm -f "$ERRORTMP" 

if [ -s "$LOGDIR/$ERRORLOG" ]; then
        errorlines=`wc -l "$LOGDIR/$ERRORLOG" | awk '{ print $1 }'`
        if [ ${errorlines:=0} -gt $LINES ]; then
                cat << EOF;

****************************************************************************
*                      aide has returned many errors.                      *
*           the error log output has been truncated in this mail           *
****************************************************************************

EOF
                echo "Error output is $errorlines lines, truncated to $LINES." 
                head -$LINES "$LOGDIR/$ERRORLOG" 
                echo "The full output can be found in $LOGDIR/$ERRORLOG." 
        else
                echo "Errors produced  ($errorlines lines):" 
                cat "$LOGDIR/$ERRORLOG" 
        fi
else
        echo "AIDE produced no errors." 
fi

# include de-noised log

if [ -n "$NOISE" ]; then
        NOISTEMP=`mktemp -t aidenoise.XXXXXXXXXX`
        NOISTEMP2=`mktemp -t aidenoise.XXXXXXXXXX`
        sed -n '1,/^Detailed information about changes:/p' "$LOGDIR/$LOGFILE" | \
        grep '^\(changed\|removed\|added\):' | \
        grep -v "^added: THERE WERE ALSO [0-9]\+ FILES ADDED UNDER THIS DIRECTORY" > $NOISETMP2

        if [ -n "$NOISE" ]; then
                < $NOISETMP2 grep -v "^\(changed\|removed\|added\):$NOISE" > $NOISETMP
                rm -f $NOISETMP2
                echo "De-Noised output removes everything matching $NOISE." 
        else
                mv $NOISETMP2 $NOISETMP
                echo "No noise expression was given." 
        fi

        if [ -s "$NOISETMP" ]; then
                loglines=`< $NOISETMP wc -l | awk '{ print $1 }'`
                if [ ${loglines:=0} -gt $LINES ]; then
                        cat << EOF;

****************************************************************************
*   aide has returned long output which has been truncated in this mail    *
****************************************************************************

EOF
                        echo "De-Noised output is $loglines lines, truncated to $LINES." 
                        < $NOISETMP head -$LINES
                        echo "The full output can be found in $LOGDIR/$LOGFILE." 
                else
                        echo "De-Noised output of the daily AIDE run ($loglines lines):" 
                        cat $NOISETMP
                fi
        else
                echo "AIDE detected no changes after removing noise." 
        fi
        rm -f $NOISETMP
        echo "============================================================================" 
fi

# include non-de-noised log

if [ -s "$LOGDIR/$LOGFILE" ]; then
        loglines=`wc -l "$LOGDIR/$LOGFILE" | awk '{ print $1 }'`
        if [ ${loglines:=0} -gt $LINES ]; then
                cat << EOF;

****************************************************************************
*   aide has returned long output which has been truncated in this mail    *
****************************************************************************

EOF
                echo "Output is $loglines lines, truncated to $LINES." 
                head -$LINES "$LOGDIR/$LOGFILE" 
                echo "The full output can be found in $LOGDIR/$LOGFILE." 
        else
                echo "Output of the daily AIDE run ($loglines lines):" 
                cat "$LOGDIR/$LOGFILE" 
        fi
else
        echo "AIDE detected no changes." 
fi
) | /bin/mail -s "Daily AIDE report for $FQDN" $MAILTO

prelink を無効化

以下の警告が発生

/usr/sbin/prelink: /usr/sbin/luserdel: at least one of file's dependencies has changed since prelinking

prelink 無効化

vi /etc/sysconfig/prelink

PRELINKING=no
/usr/sbin/prelink -ua

プロジェクトを円滑に進めるためのGit環境構築

最近、Gitでの開発管理の活用方法をたくさん聞いたのでまとめ。

ツール

  • Git
  • Git-Flow
  • Git-Lab

以上のツールを活用。みんなフリーで助かる。

Git

分散型バージョン管理システムSVNに比べて複数人での開発で便利。ブランチ管理が高速で簡単。

Git-Flow

Gitではブランチ管理が簡単なため誰でも好きでブランチを作成できる。 そうなってくるとルールを統一しないと管理がしずらい。 (結果としてマージ漏れや巻き戻りなど危険性が増加) そこでGit-Flowを使うと効率的にバージョン管理できるブランチモデルが可能になる。 「A successful Git branching model」 http://keijinsonyaban.blogspot.jp/2010/10/successful-git-branching-model.html (実際の使用法やルールは下のページを見ると理解出来ると思います) http://www.atmarkit.co.jp/ait/articles/1311/18/news017.html

Git-Lab

Git-Hubの機能を持ち自分のサーバーで構築できる感じ。 たくさんのプロジェクトが進行しているとBareレポジトリは複数サーバーに分散しがちだが これを使うと纏めて管理できる。 管理や使用もWebページから可能でエンジニア以外でもとっつきやすい。 詳しくは下のページが分かりやすいです。 http://www.slideshare.net/crooz_techblog/gitlab-web-hooks-git-flowgit

まとめ

この3つを組み合わせて使用すればGitを安全で効率的に分かりすく管理できると思います。 今、開発に導入中ですがGit-Flowはほんとに助かります。 今までGitを有効に使えてなかったんだなと認識しました。 SourceTreeも対応してるのでコマンド使わずでも使用できます。

独習Git

独習Git

ssh-copy-idをmacで使う方法

ssh-copy-id:自身の公開鍵を指定ホストに登録

Linuxでよく使ったてたのでMacでも使いたくなった。

適当なLinuxサーバーに入ってシェルスクリプトをコピーする。

$ ssh user@host
$ which ssh-copy-id
$ cat シェルスクリプトパス

Macの/usr/local/bin/に同じシェルスクリプト作成。

$ vi /usr/loca/bin/ssh-copy-id
$ さっきコピーしたものを貼付け