ゲームプログラマのためのデザインパターン(ビジター)
今、「C++のためのAPIデザイン」を読んでます。 APIをラポン具デザインパターンについてまとめてみました。
- 作者: マイケル・ディックハイザー,三宅陽一郎,田中幸,ホジソンますみ,松浦悦子
- 出版社/メーカー: ソフトバンククリエイティブ
- 発売日: 2011/12/24
- メディア: 大型本
- 購入: 10人 クリック: 232回
- この商品を含むブログ (12件) を見る
デザインパターンとは・・・
デザインパターンはプログラム設計でよく見られる問題の特性と、 このような問題に対して最も一般的に採用されている解決策の両方の 両方をカプセル化した抽象構造です。
自分なりに解釈すると、 オブジェクト指向で設計する際に起こりやすい問題の解決策だったり、 再利用性や可読性を上げるための設計の考え方だと思ってます。
ゲーム開発において特に有効なデザインパターン
- シングルトン
- ファザード
- オブザーバ
- ビジター
今回はビジターを紹介します。
ビジターとは・・・
ビジターとは英語で訪問者を意味します。 ビジターは機能を訪問者であるビジターに記述し処理の追加を簡単にします。
ゲームプログラマーが直面する苛立たしい問題として、 ある特定の機能を実装し、複数の場所でその機能が必要となり コード内に同じような機能の複製が作成されてしまうことがあります。
この解決策になるのがビジターパターンです。
例:オブジェクト位置に関連する処理
オブジェクト位置に関係する処理で例えばで以下のようなものがあります。
- プレイヤーから一定範囲内に存在する敵を全て検索する
- プレイヤーから一定範囲内に存在するアイテムを全て検索する
- プレイヤーから一番近くにあるアイテムを検索する
- プレイヤーから一定範囲内に存在する敵の数を取得する
このようなオブジェクトの位置に対する処理は挙げればきりがありません。 他にもイベント発生トリガーや、セーブポイントなどゲームのほとんどが 同じような性質を持っています。
ビジターを使用しない実装例
// 敵クラス 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++」を読んでます。 ゲームプログラムで使えるデザインパターンについてまとめてみました。
- 作者: マイケル・ディックハイザー,三宅陽一郎,田中幸,ホジソンますみ,松浦悦子
- 出版社/メーカー: ソフトバンククリエイティブ
- 発売日: 2011/12/24
- メディア: 大型本
- 購入: 10人 クリック: 232回
- この商品を含むブログ (12件) を見る
デザインパターンとは・・・
デザインパターンはプログラム設計でよく見られる問題の特性と、 このような問題に対して最も一般的に採用されている解決策の両方の 両方をカプセル化した抽象構造です。
自分なりに解釈すると、 オブジェクト指向で設計する際に起こりやすい問題の解決策だったり、 再利用性や可読性を上げるための設計の考え方だと思ってます。
ゲーム開発において特に有効なデザインパターン
- シングルトン
- ファザード
- オブザーバ
- ビジター
今回はオブザーバを紹介します。
オブザーバとは・・・
あるオブジェクトに何らかの変更が生じた場合に、 それに関係する別のオブジェクトに通知を行うメカニズムを提供します。
例:オブジェクトの参照保持
ゲーム中開発中に多く見られる問題の一つに、 オブジェクト(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++」を読んでます。 ゲームプログラムで使えるデザインパターンについてまとめてみました。
- 作者: マイケル・ディックハイザー,三宅陽一郎,田中幸,ホジソンますみ,松浦悦子
- 出版社/メーカー: ソフトバンククリエイティブ
- 発売日: 2011/12/24
- メディア: 大型本
- 購入: 10人 クリック: 232回
- この商品を含むブログ (12件) を見る
デザインパターンとは・・・
デザインパターンはプログラム設計でよく見られる問題の特性と、 このような問題に対して最も一般的に採用されている解決策の両方の 両方をカプセル化した抽象構造です。
自分なりに解釈すると、 オブジェクト指向で設計する際に起こりやすい問題の解決策だったり、 再利用性や可読性を上げるための設計の考え方だと思ってます。
ゲーム開発において特に有効なデザインパターン
- シングルトン
- ファザード
- オブザーバ
- ビジター
今回はシングルトンを紹介します。
シングルトンとは・・・
その名前から分かるとおり、インスタンスを一つしか持っていないクラスです。 一般にシングルトンの目的は、ある一連の機能を集中管理する場所の提供で、 残りのコードはグローバルにその場所にアクセスできます。
例:ファイルマネージャ
ほとんどのゲームは、ファイルの検索、オープン、クローズ、修正するシステムを持っています。 このようなシステムはファイルマネージャと呼ばれ、通常は一つのゲームに一つしかありません。
シングルトンを使わない場合のファイルマネージャ実装例
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 ノード名、説明、ラベルは都度変更
ノード名: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)
- 作者: 佐藤聖規,和田貴久,河村雅人,米沢弘樹,山岸啓,川口耕介
- 出版社/メーカー: 技術評論社
- 発売日: 2015/06/10
- メディア: 単行本(ソフトカバー)
- この商品を含むブログ (3件) を見る
Jenkins実践入門 ?ビルド・テスト・デプロイを自動化する技術 (WEB+DB PRESS plus)
- 作者: 佐藤聖規,和田貴久,河村雅人,米沢弘樹,山岸啓,川口耕介
- 出版社/メーカー: 技術評論社
- 発売日: 2011/11/11
- メディア: 単行本(ソフトカバー)
- 購入: 26人 クリック: 496回
- この商品を含むブログ (64件) を見る
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 検知ツールを導入。 rootkitがLinuxサーバーにインストールされてしまっていないかチェック。
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
Linuxサーバーセキュリティ徹底入門 オープンソースによるサーバー防衛の基本
- 作者: 中島能和
- 出版社/メーカー: 翔泳社
- 発売日: 2013/10/22
- メディア: 大型本
- この商品を含むブログ (4件) を見る
プロジェクトを円滑に進めるための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も対応してるのでコマンド使わずでも使用できます。
- 作者: リック・ウマリ,吉川邦夫
- 出版社/メーカー: 翔泳社
- 発売日: 2016/02/26
- メディア: 大型本
- この商品を含むブログ (4件) を見る