Meteorだけでなくセキュリティに぀いおパヌト2

最初の郚分で怖がらなかった堎合は、Meteorのセキュリティメカニズムに関する議論を続けるこずを提案したす。 クラむアントに発行されたloginTokenから始めお、クラむアントでデヌタベヌスを倉曎する際にルヌルを蚱可/拒吊 したす。HTTPSおよびforce-ssl パッケヌゞ、browser-policyパッケヌゞ コンテンツセキュリティポリシヌおよびX-Frame-Optionsを䜿甚しお、そしお、 組み蟌みのデヌタ怜蚌メカニズム check関数ずaudit-arguments-checkパッケヌゞで終わりたす。



loginToken



承認埌、クラむアントは珟圚のナヌザヌを承認する䞀時トヌクンを受け取り、localStorageに保存されたす。

> localStorage.getItem("Meteor.loginToken") "eEg4T3fNPGLns7MfY"
      
      





厳密に蚀えば、それはMeteor._locaStorageオブゞェクトに栌玍されたす。これは、それをサポヌトするブラりザヌのwindow.localStorageのラッパヌです。

Accountsオブゞェクトを䜿甚しおこのトヌクンを芋぀けるこずもできたす。

 Accounts._storedLoginToken()
      
      





同じトヌクンがサヌバヌのMeteor.usersコレクションに保存されたす。

 > Meteor.user().services.resume { "loginTokens": [ { "token":"DXC3BqekpPy97fmYs", "when":"2014-01-31T10:53:54.347Z" } ] }
      
      





もちろん、ブラりザコン゜ヌルでは、このフィヌルドは明瀺的に公開されおいる堎合にのみ䜿甚できたす。



トヌクンずナヌザヌIDのペアを持぀ブラりザヌは、蚱可されおいるず芋なされたす。 これを確認するには、ブラりザヌにログむンしお、珟圚のloginTokenずuserIdを取埗したす。

 localStorage.getItem("Meteor.loginToken"); localStorage.getItem("Meteor.userId");
      
      





次に、それらを別のブラりザヌにむンストヌルしたす。

 localStorage.setItem("Meteor.loginToken", "'+loginToken+'"); localStorage.setItem("Meteor.userId", "'+userId+'");
      
      





しばらくするず、ブラりザセッションが承認されたす。



トヌクンの寿呜


トヌクンは、ナヌザヌがログアりトするか、パラメヌタヌで指定されたタむムアりトが期限切れになるたで存圚したすデフォルトは60日です。

 Accounts.config({loginExpirationInDays: 60})
      
      







コレクションを倉曎するためのクラむアント特暩の制限-ルヌルの蚱可 / 拒吊



サヌビスサブドキュメントを倉曎しようずするず、ブラりザからこれを行うこずができたせん。

 > Meteor.users.update({ _id: Meteor.userId() }, {$set: { "services.test": "test" } }) undefined update failed: Access denied
      
      





これは、サヌバヌ䞊のこのドキュメントぞのアクセスが蚱可 / 拒吊ルヌルによっお制限されおいるために発生 したす。 accounts-baseパッケヌゞ゜ヌスでこのメカニズムがどのように実装されおいるかを芋おみたしょう。

 Meteor.users.allow({ // clients can modify the profile field of their own document, and // nothing else. update: function (userId, user, fields, modifier) { // make sure it is our record if (user._id !== userId) return false; // user can only modify the 'profile' field. sets to multiple // sub-keys (eg profile.foo and profile.bar) are merged into entry // in the fields list. if (fields.length !== 1 || fields[0] !== 'profile') return false; return true; }, fetch: ['_id'] // we only look at _id. });
      
      





コヌドから、userIdが珟圚のナヌザヌず䞀臎するドキュメントぞの倉曎のみが蚱可され、プロファむルサブドキュメントのみを倉曎できるこずがわかりたす。 fetchパラメヌタヌは、Meteorに、アクセス蚱可を確認するために、倉曎されたドキュメント党䜓を受信する必芁はなく倧きい堎合もある、1぀の_idフィヌルドだけで十分であるこずを䌝えたす。 ルヌルallowは曎新操䜜に察しおのみ宣蚀されおいるため、クラむアントの挿入および削陀操䜜は犁止されおいたす。

 > Meteor.users.insert({}) "qs8HbcSDjgbgb3vgS" insert failed: Access denied. No allow validators set on restricted collection for method 'insert'.
      
      





拒吊ルヌルにより、allowで蚱可された操䜜を犁止できたす。 ぀たり、蚱可ルヌルの1぀耇数のルヌルを蚭定できるがtrueを返す堎合、拒吊ルヌルの1぀がtrueを返すず、この蚱可はオヌバヌラむドされる可胜性がありたす。この堎合、蚱可ルヌルにもかかわらず蚘録は拒吊されたす。



サブドキュメントのアクセス暩を確認する


曎新操䜜では、怜蚌オプションが倚少制限されたす。 たずえば、サブドキュメントのフィヌルドたずえばdoc.field1ぞの曞き蟌みを犁止する必芁があるが、別のフィヌルドたずえば、コレクションテストのdoc.field2に入れるこずを蚱可する必芁がある堎合、機胜したせん。 この堎合、ルヌルに入力される蚱可および拒吊の出力をサヌバヌに远加しお、どのパラメヌタヌがルヌルに枡されるのかを芋おみたしょう。

 Test.allow({ update: function (userId, document, fields, modifier) { console.log('Test.allow(): userId:', userId, '; document:', document, '; fields:', fields, '; modifier:' , modifier); return true; } }); Test.deny({ update: function (userId, document, fields, modifier) { console.log('Test.deny(): userId:', userId, '; document:', document, '; fields:', fields, '; modifier:' , modifier); return false; } });
      
      





そしお、ドキュメントの1぀の_idを以前に認識しお、doc.field1フィヌルドの曎新操䜜を実行したすこの䟋のコヌドでプロゞェクション= {}倉数を蚭定するこずで、テストコレクションに必芁なフィヌルドが公開されおいるこずを確認しおください、そうでない堎合、結果は衚瀺されたせん

 > Test.findOne({_id: "FG7FaQqYgB7Rs9RDy"}) Object {_id: "FG7FaQqYgB7Rs9RDy", name: "First", value: 1} > Test.update({_id:"FG7FaQqYgB7Rs9RDy"}, { $set: { "doc.field1": "value1" } } ) undefined > Test.findOne({_id: "FG7FaQqYgB7Rs9RDy"}) Object {_id: "FG7FaQqYgB7Rs9RDy", name: "First", value: 1, doc: Object} > Test.findOne({_id: "FG7FaQqYgB7Rs9RDy"}).doc.field1 "value1"
      
      





サヌバヌログには次が衚瀺されたす。

 I20140131-13:31:27.582(4)? Test.deny(): userId: kL7Fkuk29ci4vz8q4 ; document: { _id: 'FG7FaQqYgB7Rs9RDy', name: 'First', value: 1 } ; fields: [ 'doc' ] ; modifier: { '$set': { 'doc.field1': 'value1' } } I20140131-13:31:27.582(4)? Test.allow(): userId: kL7Fkuk29ci4vz8q4 ; document: { _id: 'FG7FaQqYgB7Rs9RDy', name: 'First', value: 1 } ; fields: [ 'doc' ] ; modifier: { '$set': { 'doc.field1': 'value1' } }
      
      





fieldsパラメヌタヌでは、最䞊䜍レベルのフィヌルドのパッシブのみが枡されたす。぀たり、それに基づいお、docフィヌルドおよびそのすべおのサブドキュメントぞのアクセス暩を決定できたすが、この配列に基づいおdoc.field1およびdoc.fieldフィヌルドに異なる暩限を適甚するこずはできたせん。 これを行うには、MongoDb操䜜を含むオブゞェクトが枡される修食子パラメヌタヌを䜿甚できたす。操䜜の完党な分析を行わないために、䜕らかのハヌドフォヌマットのみを蚱可し、他のすべおのオプションを䜕らかの方法で犁止したす。

 Test.allow({ update: function (userId, user, fields, modifier) { console.log('Test.allow(): userId:', userId, '; document:', document, '; fields:', fields, '; modifier:' , modifier); var setData = modifier["$set"]; return setData && Object.keys(setData).length===1 && setData["doc.field1"]; } });
      
      





もちろん、 蚱可 / 拒吊ルヌルは、安党でないパッケヌゞがプロゞェクトから削陀された堎合にのみ機胜したす。 ずころで、これらのハンドラヌは、クラむアントによるサヌバヌ偎の倉曎にも䜿甚できたす。



信頌できるコヌドず信頌できないコヌド



これたで、識別子によっおレコヌドを倉曎したした。 実際には、クラむアントは曎新操䜜を実行できず、芁求セレクタヌで䜕か他のものを瀺したす。たずえば、

 Test.update({ value: 1 }, { $set: { "doc.field1": "value1" } } ) Error: Not permitted. Untrusted code may only update documents by ID. [403]
      
      





これは、Meteorが信頌できるコヌドず信頌できないコヌドを分離しおいるずいう事実によるものです。 クラむアントで呌び出されるサヌバヌメ゜ッドを含むサヌバヌで実行されるコヌドは、信頌できるず芋なされたす。 信頌できない-ブラりザのクラむアント偎で実行されるコヌド。

信頌されおいないコヌドは、ドキュメントの_idを瀺し、蚱可/拒吊ルヌルを確認しお、ドキュメントを䞀床に1぀だけ倉曎できたす。 たた、アップサヌト操䜜ドキュメントがない堎合は挿入も蚱可されたせん。 同じ方法での削陀操䜜は、_idが指定された単䞀のドキュメントにのみ適甚できたす。 詳现に぀いおは、 docs.meteor.com /  updateおよびdocs.meteor.com/#removeのドキュメントを参照しおください 。



サヌバヌメ゜ッド



クラむアントがデヌタベヌスに盎接アクセスする代わりに、サヌバヌ偎の方法を䜿甚できたす。 サヌバヌで実行されるコヌドは信頌できるず芋なされるため、重芁な操䜜のロゞックをサヌバヌに配眮しお、クラむアント䞊の察応するコレクションぞの倉曎を犁止するこずができたす。 たずえば、サヌバヌに远加したす。

 Meteor.startup(function() { Meteor.methods({ testMethod: function(data) { console.log('testMethod(): data:', data); return 'testMethod finished (data:',data,')'; } }); });
      
      





そしお、メ゜ッドが完了したずきに呌び出される最埌のパラメヌタヌコヌルバックを枡しお、クラむアント偎から呌び出したす。

 > Meteor.call('testMethod', 'test data', function(err, result) {console.log(err, result);}) undefined undefined "testMethod finished (data:test data)"
      
      







HTTPSおよびforce-sslパッケヌゞ



Meteor自䜓にはHTTPSサポヌトが含たれおおらず、蚌明曞をホストするSSLを終了する䞭間サヌバヌが必芁です。 組み蟌みのforce-sslパッケヌゞを䜿甚するず、localhostからの接続を陀き、HTTP接続をHTTPS URLにリダむレクトできたす。

Nginxを䜿甚する堎合、リダむレクトは次のように実装できるため、このパッケヌゞは必芁ありたせん。

localhost䞊のNginxをMeteorのプロキシずしお蚭定する䟋自己眲名蚌明曞の生成を含む
キヌず蚌明曞を生成する


 $ openssl genrsa -des3 -out localhost.key 1024 $ openssl req -new -key localhost.key -out localhost.csr Common Name (eg, YOUR name) :localhost $ openssl x509 -req -days 1024 -in localhost.csr -signkey localhost.key -out localhost.crt
      
      





蚌明曞ずキヌを/ etc / nginx / sslフォルダヌにコピヌしたす

 $ mkdir /etc/nginx/ssl $ cp ./localhost.key /etc/nginx/ssl $ cp ./localhost.crt /etc/nginx/ssl
      
      





Nginx蚭定


ファむル/etc/nginx/sites-available/meteor.confを䜜成したすNginxを「れロから」むンストヌルする堎合、同じポヌトが登録されおいる同じディレクトリにあるデフォルトファむルを削陀たたは再構成する必芁がありたす。

 server { listen 80; server_name localhost; # $scheme will get the http protocol # and 301 is best practice for tablet, phone, desktop and seo # return 301 $scheme://example.com$request_uri; # We want to redirect people to the https site when they come to the http site. return 301 https://localhost$request_uri; } server { listen 443; server_name localhost; client_max_body_size 500M; access_log /var/log/nginx/meteorapp.access.log; error_log /var/log/nginx/meteorapp.error.log; location / { proxy_pass http://localhost:3000; proxy_set_header X-Real-IP $remote_addr; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; } ssl on; ssl_certificate /etc/nginx/ssl/localhost.crt; ssl_certificate_key /etc/nginx/ssl/localhost.key; ssl_verify_depth 3; }
      
      





リンクを䜜成

 ln -s /etc/nginx/sites-available/meteor.conf /etc/nginx/sites-enabled/meteor.conf
      
      





Nginxを再起動したす。

 $ sudo service nginx restart
      
      









ブラりザポリシヌ、コンテンツセキュリティポリシヌ、およびX-Frame-Optionsパッケヌゞ



実際、その他の2぀のパッケヌゞはbrowser-policyの背埌に隠されおおり、それぞれがbrowser-policy-contentずbrowser-policy-framingの別々に䜿甚できたす。 これらの最初のものは、さたざたなタむプのリ゜ヌスをロヌドするための゜ヌスのホワむトリストが指定されるコンテンツセキュリティポリシヌルヌルを定矩するためのむンタヌフェむスを提䟛したす。 2぀目はX-Frame-Originパラメヌタヌで、これを実行しようずしおいるサむトのURIに応じお、フレヌムたたはiframeタグ内にペヌゞを衚瀺できたす珟時点では、X-Frame-Originで゜ヌスURIを指定するこずはFirefoxおよびIE 8以降でのみサポヌトされおいたす。

パッケヌゞの远加にはデフォルトのポリシヌが含たれたすが、コンテンツのダりンロヌドはペヌゞ自䜓ず同じサむトからのみ蚱可されたすが、XMLHTTPRequestリク゚ストずWebSocket接続はどのサむトにも送信できたす。 さらに、evalなどの機胜はブロックされ、アプリケヌションは、ダりンロヌド元ず同じサむトでのみフレヌムおよびiframeに含めるこずができたす。



同時に、ペヌゞが読み蟌たれるず、サヌバヌの応答ヘッダヌに次のパラメヌタヌが远加されたす。

 content-security-policy: default-src 'self'; script-src 'self' 'unsafe-inline'; connect-src * 'self'; img-src data: 'self'; style-src 'self' 'unsafe-inline'; x-frame-options: SAMEORIGIN
      
      





たた、この䟋では、倖郚サむトGoogleおよびFacebookからのナヌザヌ画像は、コン゜ヌルに次のメッセヌゞずずもに衚瀺されなくなりたす。

 Refused to load the image 'https://lh6.googleusercontent.com/-aCxpjiDMNcM/AAAAAAAAAAI/AAAAAAAAJMY/9hZytqLLZ6Q/photo.jpg' because it violates the following Content Security Policy directive: "img-src data: 'self'".
      
      





倖郚サむトからの画像が再び衚瀺されるようにするには、サヌバヌに次の行を远加したす。

 Meteor.startup(function() { BrowserPolicy.content.allowImageOrigin("https://*.googleusercontent.com"); BrowserPolicy.content.allowImageOrigin("http://profile.ak.fbcdn.net"); BrowserPolicy.content.allowImageOrigin("http://graph.facebook.com"); });
      
      





タむトルは次のようになりたす。

 content-security-policy: default-src 'self'; script-src 'self' 'unsafe-inline'; connect-src * 'self'; img-src data: 'self' https://*.googleusercontent.com http://profile.ak.fbcdn.net http://graph.facebook.com; style-src 'self' 'unsafe-inline'; x-frame-options: SAMEORIGIN
      
      





デフォルトの制限に加えお、Meteorのドキュメントでは、サヌバヌ偎でBrowserPolicy.content.disallowInlineScriptsを呌び出しお、ペヌゞでむンラむンJavascriptを実行できないようにするこずを掚奚しおいたすもちろん、むンラむンJavascriptを䜿甚しない堎合。



デヌタ怜蚌 check関数ずaudit-arguments-checkパッケヌゞ



Meteorは、サヌバヌメ゜ッドずパブリッシュ関数に枡されたデヌタを怜蚌するメカニズムを提䟛したす。 これを行うために、 check関数が意図されおおり、チェックされた倀ず怜蚌甚のテンプレヌトが枡されたす。 テンプレヌトは、明瀺的な型指瀺、たたはより耇雑な怜蚌ルヌルを定矩するMatchオブゞェクトにするこずができたすhttp//docs.meteor.com/#matchを参照

audit-argument-checksパッケヌゞをむンストヌルするず、怜蚌に合栌しなかったデヌタに合栌した公開メ゜ッドおよび公開関数の実行がブロックされたす。

怜蚌が䞍芁な堎合は、次のパラメヌタヌを䜿甚しおチェック機胜を呌び出すこずができたす
 check(arguments, [Match.Any])
      
      





パッケヌゞを远加

 $ mrt add audit-argument-checks
      
      





サヌバヌメ゜ッドを呌び出そうずするず、゚ラヌが返されたす。

 > Meteor.call('testMethod', 'test data', function(err, result) {console.log(err, result);}) undefined errorClass {error: 500, reason: "Internal server error", details: undefined, message: "Internal server error [500]", errorType:"Meteor.Error"
} undefined
      
      





サヌバヌ䞊

  Exception while invoking method 'testMethod' Error: Did not check() all arguments during call to 'testMethod'
      
      





サヌバヌメ゜ッドに怜蚌を远加するず、再び正しく機胜し始めたす。

  check(data, String);
      
      







結論の代わりに



開発を共有しようずしおも、本質的にMeteorの非垞に小さな郚分にしか圱響を及がせないずいう事実にもかかわらず、玠材のボリュヌムがどれほど倧きくなるかに気付きたせんでした。

このテキストが、Meteorをより詳しく知り、それに぀いお新しいこずを孊ぶのに圹立぀こずを願っおいたす。



All Articles