Mac OS X Serverでサンドボックスを使用してカスタムWebアプリケーションを分離する

少し叙情的な紹介



どういうわけか、奇妙なもの、つまりユーザーがCampingマイクロフレームワークに基づいてWebアプリケーションを単独でダウンロードして実行できる管理しやすいホスティングが必要な顧客がいました。 そして、nginx、thinサーバー、およびezjailをjail管理ツールとして使用して、FreeBSD 9.0を実行する仮想サーバーで提案しました(すべてが非常に簡単ですが、興味のある方は説明します)。 1週間後、顧客は実際にAppleソリューションのファンであり、Mac OS Xを実行しているメインサーバーで同じシステムが実行されていることを望んでいることを認めました。そして、私は以前に触れた喜びがなかったため、ソリューションを適応させることに喜んで同意しましたこのシステムと少なくとも少しそれを勉強したかった。 「しかし」たった1つしかありませんでした-MacOS X Serverには刑務所はありません(8)。 そこで、ユーザーがロードしたアプリケーションを可能な限り安全に実行するソリューションを探して(多くの理由でchrootを使用することはできなかったし、したくありませんでした)、システムに非常に柔軟で完全に統合されたツールSandboxを見つけました。



ホスティングの基盤を構築する



サンドボックス


サンドボックスは素晴らしいツールであることが判明しました。 AppArmorを連想させるもの、多少SeLinuxですが、実際に動作するために必要以上の機能を提供せず、アプリケーションをチェックし続ける完全にユニークな方法です。 サンドボックスポリシーを適用する方法は、サンドボックスでアプリケーションを起動し、このアプリケーション用に以前に記述されたプロファイル(セキュリティポリシーの説明を含むテキストファイル)へのパスをオプションとして渡すことです。 残念ながら、Sandboxは私が慣れているよりも文書の質がやや劣ります (FreeBSDハンドブックの詳細が壊れています)が、ネットワーク上で特定のプロファイルを記述する多くの例があり、それがタスクをはるかに容易にしました。 軽量のThin rubyアプリケーションサーバーのプロファイル、その使用方法を記述する必要がありました。以下で説明します。 すべてのプロファイルは、マークアップ言語バージョンの宣言で始まり、できればデフォルトポリシー(この場合は明らかに禁止されています)で始まります。 すべてのディレクティブまたはそのセットは括弧で囲まれています。 ポリシー名(または「操作」-操作)はマスク(ワイルドカード-*)をサポートし、ルールの範囲を拡大します。 フィルター(パスネットワークファイルモードxattrマッハ信号の6つのみ)は、ルールに従って設定されます(詳細については、構文を参照してください )。 たとえば、文字列、正規表現(regex)、文字列でパスを指定できます。英語をトレースすることを許してください、「サブパス」。 すべてのコメントは「;」で始まります。

; ; Sandbox profile for application owned by virtual (non-system) user XXXXXX ; (version 1) ;     (deny default) ;           ; (,      unix-).  ;  ,       ; unix- (allow network-bind) ;   Thin       (. ) ;  ,   fork() (allow process-fork) ;      DirectoryService       ;  ,      . (allow mach-lookup (global-name "com.apple.system.DirectoryService.libinfo_v1") (global-name "com.apple.system.DirectoryService.membership_v1") ) ;       Thin-,   ruby ;  - -  ,    ;-) (allow process-exec (regex "^/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr") (regex "^/usr/bin/thin$") ) ;   ,        file-read   ; ,   regex ^/opt/sandbox/apps/XXXXXX ;    -  ,    -  (allow file-read-metadata (literal "/opt/sandbox/apps/XXXXXX/log") (literal "/opt/sandbox/apps/XXXXXX/tmp") ) ;     gem',         (allow file-read* (literal "/usr/bin/thin") (regex "^/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr") (regex "^/System/Library/PrivateFrameworks/TrustEvaluationAgent.framework/Versions/A/TrustEvaluationAgent") (regex "^/Library/Ruby/Gems/1.8/") (regex "^/usr/lib") (regex "^/opt/sandbox/apps/XXXXXX") ) ;       , -  . (allow file* (regex "^/opt/sandbox/apps/XXXXXX/tmp/thin.sock$") (regex "^/opt/sandbox/apps/XXXXXX/tmp/thin.pid$") (regex "^/opt/sandbox/apps/XXXXXX/log/thin.log$") )
      
      







薄い


Thinは、カスタムCampingアプリケーションを実行するために選択されました。 なぜMongrel、Passenger、uWSGIなどではなく、Thinですか? 彼は必要なすべての機能をサポートし、リソースをあまり要求しませんでした(ただし、深刻な研究はしませんでした)。 さらに、どういうわけかアプリケーションを単独で実行するような方法でPassengerを準備する方法を見つけることができませんでしたが、おそらく何らかの方法で可能だったでしょう(異なるユーザーに代わってnginxの多くのコピーを実行するオプションを取りません、このオプションは考慮されましたが、マークされていました)、コメントの誰かが実用的なソリューションを提案している場合、私は見て喜んでいます。 私のお気に入りの収穫機-最後のヒントからのuWSGI-は、FreeBSD(開発者に通知され、数日以内にすべてが修理されましたが、残念ながら、列車は去りました)およびMacOS Xのラックアプリケーションで正しく動作することを拒否しましたまったく何もしません。 Mongrelは試す時間はなく、Thinに立ち止まって、本当にうまくいきました。 そのため、ThinコンテナでのCampingラックベースアプリケーションの起動ラインは次のとおりです。



 cd /opt/sandbox/apps/XXXXXX && \ sandbox-exec -f /opt/sandbox/profiles/XXXXXX.sb \ /usr/bin/thin --socket /opt/sandbox/apps/XXXXXX/tmp/thin.sock \ --rackup /opt/sandbox/apps/XXXXXX/approot/config.ru \ --environment production --timeout 4 --chdir /opt/sandbox/apps/XXXXXX/approot \ --log /opt/sandbox/apps/XXXXXX/log/thin.log \ --daemonize --pid /opt/sandbox/apps/XXXXXX/tmp/thin.pid \ --user thinbot --group thinbot --tag XXXXXX start
      
      







「タグ」オプションを使用すると、すべてのリソースを正確に食べたトップとpsを見ることができます(システムユーザーはすべての起動に単独で使用されます)。



Nginx


すべてが簡単です。 統計なし。 ホスティングの仮想「ユーザー」の名前は、それに割り当てられたサブドメインと同等です。

  server { server_name ~(.+).domain.tld; set $user $1; location / { proxy_pass http://unix:/opt/sandbox/apps/$user/tmp/thin.sock:/; } }
      
      







スクリプトバインディング


私はshを使用してバインディングを設計しました。シンプルでポータブルなものが好きだからです。 批判は大歓迎です。スクリプトはかなり湿っています。 スクリプトはルート(root)として実行されることを前提としています。

仮想ユーザー管理-users_management.sh:

 #!/bin/sh # Mike Kuznetsov 2012 mike4gg@gmail.com user=$1 action=$2 usage() { echo "Usage: `basename $0` <username> <create|remove|list>" exit } if [ "${action}x" = "x" ]; then usage fi sb_app_dir=/opt/sandbox/apps/${user} sb_app_root=${sb_app_dir}/approot sb_profile=/opt/sandbox/profiles/${user}.sb thin_sock=${sb_app_dir}/tmp/thin.sock thin_pid=${sb_app_dir}/tmp/thin.pid thin_log=${sb_app_dir}/log/thin.log thinuser=thinbot thingroup=thinbot create_sandbox() { cat <<EOF > ${sb_profile} ; ; Sandbox profile for application owned by virtual (non-system) user ${user} ; (version 1) (deny default) (allow network-bind) (allow process-fork) (allow mach-lookup (global-name "com.apple.system.DirectoryService.libinfo_v1") (global-name "com.apple.system.DirectoryService.membership_v1") ) (allow process-exec (regex "^/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr") (regex "^/usr/bin/thin$") ) (allow file-read-metadata (literal "${sb_app_dir}/log") (literal "${sb_app_dir}/tmp") ) (allow file-read* (literal "/usr/bin/thin") (regex "^/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr") (regex "^/System/Library/PrivateFrameworks/TrustEvaluationAgent.framework/Versions/A/TrustEvaluationAgent") (regex "^/Library/Ruby/Gems/1.8/") (regex "^/usr/lib") (regex "^${sb_app_dir}") ) (allow file* (regex "^${thin_sock}$") (regex "^${thin_pid}$") (regex "^${thin_log}$") ) EOF mkdir ${sb_app_dir} mkdir ${sb_app_root} mkdir ${sb_app_dir}/tmp mkdir ${sb_app_dir}/log chown -R ${thinuser}:${thingroup} ${sb_app_dir} } case ${action} in create) if [ -d ${sb_app_dir} ]; then echo "User's application directory ${sb_app_dir} exists. Exiting" usage elif [ -f ${sb_profile} ]; then echo "User's sandbox profile ${sb_profile} exists. Exiting" usage fi printf "Creating sandbox for user ${user}... " create_sandbox echo "done" ;; remove) printf "Removing sandbox for user ${user}... " if [ -f ${thin_pid} ]; then /usr/bin/thin --pid ${thin_pid} stop > /dev/null 2>&1 fi if [ -d ${sb_app_dir} ]; then rm -r ${sb_app_dir}; fi if [ -f ${sb_profile} ]; then rm ${sb_profile}; fi echo "done" ;; list) printf "Username\tApplication state\tPID\tMemory usage\n" echo "-----------------------------------------------------------------" total_mem=0 for user_ in `ls /opt/sandbox/apps` do if [ -f /opt/sandbox/apps/${user_}/tmp/thin.pid ]; then pid_=`cat /opt/sandbox/apps/${user_}/tmp/thin.pid` ps ax | grep ^${pid_} > /dev/null if [ $? -eq 0 ]; then mem_=`ps -p ${pid_} -o rss | tail -1 | awk '{ print $1 }'` mem=`expr ${mem_} \/ 1024` total_mem=`expr ${total_mem} + ${mem}` printf "${user_}\t\trunning\t\t${pid_}\t\t${mem}Mb\n" else printf "${user_}\t\tnot running\n" fi else printf "${user_}\t\tnot running\n" fi done echo "-----------------------------------------------------------------" printf "Total memory usage: ${total_mem}Mb\n" ;; *) usage ;; esac
      
      







ユーザーアプリケーション管理-application_management.sh:

 #!/bin/sh # Mike Kuznetsov 2012 mike4gg@gmail.com user=$1 action=$2 sb_app_dir=/opt/sandbox/apps/${user} sb_app_root=${sb_app_dir}/approot sb_profile=/opt/sandbox/profiles/${user}.sb thin_sock=${sb_app_dir}/tmp/thin.sock thin_pid=${sb_app_dir}/tmp/thin.pid thin_log=${sb_app_dir}/log/thin.log thinuser=thinbot thingroup=thinbot exitcode=0 usage() { echo "Usage: `basename $0` <username> <start|stop|restart>" exit 0 } start_thin() { if [ -f ${thin_pid} ]; then pid_=`cat ${thin_pid}` ps ax | grep ^${pid_} > /dev/null if [ $? -eq 0 ]; then echo "Thin instance for user ${user} is already running. Maybe try restart?" usage fi fi printf "Starting thin instance for user ${user}..." if [ -f ${thin_pid} ]; then rm -f ${thin_pid} fi cd ${sb_app_dir} sandbox-exec -f ${sb_profile} /usr/bin/thin --socket ${thin_sock} --rackup ${sb_app_root}/config.ru \ --environment production --timeout 4 --chdir ${sb_app_root} --log ${thin_log} --daemonize --pid ${thin_pid} \ --user ${thinuser} --group ${thingroup} --tag ${user} start cd - > /dev/null sleep 1 pid_=`cat ${thin_pid}` ps ax | grep ^${pid_} > /dev/null if [ $? -eq 0 ]; then echo "done" else echo "FAILED!" echo "Last 20 lines of logfile ${thin_log}:" tail -20 ${thin_log} exitcode=10 fi } stop_thin() { if [ -f ${thin_pid} ]; then pid_=`cat ${thin_pid}` ps ax | grep ^${pid_} > /dev/null if [ $? -ne 0 ]; then echo "Thin instance for ${user} user is already stopped or died. Maybe try start?" usage fi else echo "Pid file ${thin_pid} not found. Nothing to stop." usage fi printf "Stopping thin instance for user ${user}..." /usr/bin/thin --pid ${thin_pid} stop > /dev/null if [ $? -eq 0 ]; then echo "done" else echo "FAILED!" echo "Last 20 lines of logfile ${thin_log}:" tail -20 ${thin_log} exitcode=20 fi } if [ "${action}x" = "x" ]; then usage fi if [ ! -d ${sb_app_dir} ]; then echo "User's application directory ${sb_app_dir} doesn't exist. Exiting" usage elif [ ! -f ${sb_profile} ]; then echo "User's sandbox profile ${sb_profile} doesn't exist. Exiting" usage fi case ${action} in start) start_thin ;; stop) stop_thin ;; restart) stop_thin start_thin ;; *) usage ;; esac exit ${exitcode}
      
      





おわりに



Sandboxはかなり強力なサンドボックスであり、Mac OS Xをサーバープラットフォームとして普及させるのに役立つと思います。



PS: Habrahabrサイトの管理に感謝します。これにより、ネガティブなカルマでも投稿を公開できました。 この記事に対する聴衆の態度があまり厳しくないことを本当に願っています-これがHabréでの私の最初の実際の投稿です-そして、私は書き続けることを望みます。 近い将来、私はそのようなトピックについて書くと思います。DjangoアプリケーションのパッチのインスタントWebプレゼンテーションのためのgitフックと帝国のuWSGIモードの使用。 uWSGIは、柔軟性のある1つのホスティング言語に限定されない、柔軟なWebアプリケーションコンテナを作成します。 Informix DBMSの全国展開の機能。 しかし、あなたが思いとどまれば、私はしません。

みんなありがとう。



All Articles