親バカエンジニアのナレッジ帳

webのエンジニアをやっており、日頃の開発で詰まったことや書き残しておきたいことを載せています。育児のイロハという育児サイト(https://ikujip.jp)の開発も行っているため、その開発で使用されている技術についても掲載しています。

ブロックチェーンってなんだ?

ビットコインを始めとした仮想通貨の登場により、名前を聞くことが多くなったブロックチェーン
ブロックとは?チェーンとは?果たしてこれはどんな仕組みで動いているのでしょう?
そして従来までのサーバ管理システムに比べて、どんなメリットがあるのでしょうか?

ブロックとは?チェーンとは?

仮想通貨の取引を例に挙げますと、それぞれの取引はまとまってブロックと呼ばれる単位になります。
そして、それぞれのブロックは次々に連なっていき、その連なりがブロックチェーンと呼ばれています。

ほんとざっくりとした説明になりましたが、ブロックチェーンでは、このブロックを生成していく技術の奥深さに特徴があり、データが改ざんされにくいという点で信頼を得ているのです。

ブロックチェーンはデータを分散して管理している

従来最も使われているサーバ-クライアントの構成に対して、ブロックチェーンではデータを分散して管理していることに特徴があります。

サーバ-クライアント技術では、クライアント(ブラウザなど)はサーバにリクエストを投げ、サーバはそれに対してレスポンスを返すという流れがありますよね。

この流れではサーバが常に中心となって動くため、サーバがアプリケーションを管理する上でとても大切な役割を担っています。
もしサーバが高負荷になってダウンしてしまうと、ユーザは誰一人としてアプリケーションを利用することができないのです。

対してブロックチェーンでは、特定のサーバがアプリケーションを管理しているわけではなく(一部管理している場合もありますが)、ユーザ同士がネットワークで繋がることでデータを管理し合う仕組みになっています。
これはP2P(ピアツーピア)という仕組みです。
サービスの参加者がみんなで情報を管理し合っている点に特徴があります。

P2Pって具体的になに?どこで動いてるの?

参加者みんなで管理と言われてもピンと来ない方もいますよね。
一時期とても話題になったWinnyなどのファイル交換ソフトを想像してみてください。
あの類のソフトはP2Pの技術が使われているものが多いのですが、使用されたことがあればイメージがつきやすいと思います。

Winnyでは、ある1つのサーバに繋いでいたわけではなく、ユーザ間が繋がっており、欲しいファイルが見つかればダウンロードできる仕組みになっていました。
(著作権問題などで揺れた側面が強かったですが、一旦その話題は置いておいて...)

接続されているユーザー同士でバケツリレーのごとくデータを送受信しており、同じ時間帯にどれだけ多くのユーザーが利用したとしても、1箇所にアクセスが集中することを避けることができるのです。
通信経路をしっかり分散させれば、ユーザが増えれば増えるほどネットワークが拡張していくすごい技術ですね。

十数年前に注目されていた技術がまた注目されることになるのも驚きですね。

すべてのデータは公開されて誰でも見ることができる

ブロックチェーンのデータは、ユーザ同士で送受信をしているため、誰でも見ることができます。
じゃあセキュリティ面がザルなんじゃない?と思われがちですが、データは暗号化されているので心配いりません。
すべてのデータはハッシュ値という意味がわからない文字の並びとなっており、内容を読み取ることはできないのです。

先述しましたが、ブロックチェーンではブロック生成時のマイニングという方法に特徴があり(ここでは詳細は省きます)、基本的には不正なデータを作成できない仕組みがあります。

確かに以下のようなデータを改ざんされた事件もありますが、

www.google.co.jp

基本的には改ざんにはとてつもない計算力を持ったコンピュータが必要であり、一民間人が不正を行うことはできないはずです。

世界の51事例から予見する ブロックチェーン×エネルギービジネス

世界の51事例から予見する ブロックチェーン×エネルギービジネス

ブロックチェーンアプリケーション開発の教科書

ブロックチェーンアプリケーション開発の教科書

safariでエンターによるformのイベントがうまく発火しない時の対処法

formのイベントをエンターで発火

Webサービスにおいて、入力した値を処理するためにformタグを使うことは多いでしょう。

<form>
    <input type="text">
    <button>送信</button>
</form>

基本的に上記のような構造で、buttonをクリックすることでイベントを発火させることになりますが、上記の構造の場合はinputタグにカーソルが当たっているタイミングでエンターボタンをクリックすることでイベントを発火させることができます。

buttonタグにクリックイベントなどを設定し、フォームの送信だけではなくJavascriptによる独自の処理を実行することも可能です。

他のブラウザでは上手くいくのにsafariだと上手く送れない

今回の主題でもありますが、このエンターによるフォームの送信に関してsafariでは上手く発火されないことがあります。
chromeFirefoxでは上手くいくんですが。。。
スマホを使っていてもchromeがデフォルトのAndroidでは動作するのにsafariがデフォルトのiOSでは動作しないということもありえます。

ちなみにこの場合、clickイベントではなく、formにactionを指定してPOSTさせようとする場合は上手くいくことが多いです。
でもやりたいことはPOSTさせることではないですよね。

buttonのスタイルを確認

結論としては、buttonタグに何かスタイルが当たっていないか確認してみてください。
例えばボタンを明示的に表示していない場合、「display:none」などを設定していませんか?
そのようなスタイルを当ててしまうと、chromeでは問題ないですがsafariでは上手くいかないようです。

LOGICOOL ワイヤレストラックボール SW-M570

LOGICOOL ワイヤレストラックボール SW-M570

XSSってどうやって仕込まれる?

XSSについて

このページを閲覧している方ならご存知とは思いますが、まずはXSSについて超簡単に説明します。

この事象は入力フォームがある画面が狙われるものですが、Javascriptのタグを攻撃者によって画面上に埋め込まれ、ユーザ情報やcookieのセッションが盗まれたり(セッションハイジャック)するセキュリティの脅威です。
inputのtextやtextareaに対策を施さないと発生してしまいます。

以下のページが参考になりますね。

情報処理推進機構:情報セキュリティ:脆弱性関連情報の取扱い:知っていますか?脆弱性 (ぜいじゃくせい)/2. クロスサイト・スクリプティング

どうやってタグを入れられる?

XSSは不正なタグを混入されることで発生する脅威ですが、そもそもどのように混入されるのでしょうか?

サーバ情報を盗まれてソースを書き換えられて起きるのでしょうか?
もしソースを書き換えられるくらいであれば、DBにもアクセスできるでしょうし、わざわざこんな回りくどいやり方はしませんよね。

方法としては、GETパラメータにスクリプトを入れる方法やスクリプトをPOSTさせる方法があります。

GETパラメータにスクリプトを入れる方法

Webアプリケーションの設計にもよる部分なのですが、例えばフリーワード検索ができる検索画面を考えてみましょう。
以下のURLの検索画面がそうなのですが、この画面は「freeWord=%5B"居場所"%5D」の箇所に検索したフリーワードが入る仕様になっています。

https://ikujip.jp/father/list/1?type=thread&order=2&freeWord=%5B"居場所"%5D

今回は居場所というワードで検索しているのですが、ここに

freeWord=%5B"<script>alert('テスト')</script>"%5D

のようにスクリプトが混入されたらどうでしょうか?
上記のページでは対策をしているので何も起きませんが、何も対策を施していないサイトであれば、普通に「テスト」と表示されるアラートが出てきてしまいます。

でも攻撃者が自分でそんなスクリプトを入力しても、有用な情報は得られないんじゃない?と思われるかもしれませんが、実際にこのスクリプトが入力された状態でアクセスするのは、攻撃者ではなくサイトのユーザになるのです。
しかもこんな簡単がスクリプトを埋め込むわけではありません。

具体的な例としては、攻撃者がフリーワード欄にスクリプトが入っているリンクを用意し、適当なサイトに貼っておくのです。
何も知らないユーザがそれをクリックすると、スクリプトが埋め込まれた状態で遷移することになり、悪意のあるスクリプトが実行されてしまいます。

もしユーザが遷移先のサイトにログインしたままの状態で、cookieを取得するようなスクリプトが埋め込まれていたらどうでしょう?
cookieにはセッション情報が格納されている場合があるので、セッションが抜き取られてしまいます。(いわゆるセッションハイジャック

また、個人情報を入力するようなページで、他のページに入力値をPOSTするようなスクリプトを埋め込まれてしまったらどうでしょう?
個人情報があっさり取られてしまいますね。

POSTでスクリプトを埋め込まれる方法

次にPOSTでの埋め込み方法ですが、掲示板サイトを想像してみてください。
一般的な掲示板サイトでは、入力した文章は即掲示板に表示されますよね。

例えば以下のようなスクリプトをコメントとして入力した場合も、即スクリプトが表示されます。

<script>alert('テスト')</script>

何も対策がされていないサイトだと、そのスクリプトが実行されてしまうため、今後その掲示板にユーザが訪れる度に悪意のあるスクリプトが実行されてしまうのです。
今回の場合だと、開くたびに「テスト」というスクリプトが表示されますね。

これも上記のようにセッションハイジャックや個人情報の取得などに繋がる可能性があるので要注意です。

まとめ

GETの場合とPOSTの場合のそれぞれでXSSの説明はしましたが、あくまで一例にしか過ぎません。
とにかくユーザがinputやtextareaで何かを入力できるサイトは要注意です。

サーバサイドでもフロントエンドでも、最近のフレームワークを使えば基本的な対策は既にしっかりされているものが多いので、対策を入れるのを忘れてしまいがちになってしまいますが、本来のフレームワークの使い方と外れるような実装を一部使用する場合、未だに脅威は残ります。
例えばSPAサイトなのに一部サーバサイドレンダリングするような使い方とか...

対策方法については、また別で書こうと思いますが(ググればいっぱい出てきますw)、自分が運営しているサイトでこのような被害があると大事になりかねません。
サイト制作時には必ず対策が必要な脅威です。

セキュリティのためのログ分析入門 サイバー攻撃の痕跡を見つける技術

セキュリティのためのログ分析入門 サイバー攻撃の痕跡を見つける技術

イラスト図解式 この一冊で全部わかるセキュリティの基本

イラスト図解式 この一冊で全部わかるセキュリティの基本

SPAサイトのCSRF対策

CSRF対策について

CSRFとは?

このページに来られた方ならもう理解している方も多いとは思いますが、CSRFについて簡単に説明します。
CSRFはリクエスト強要(CSRF:Cross-site Request Forgery)という意味で、クロスサイト(Cross-site)の名の通り、正規のサイトとは別のサイトからリクエストを送らせる偽造工作です。

Webでサーバサイドの開発をしている方ならご存知だとは思いますが、例えばフォームのPOST処理は必要な項目やURLさえわかっていればどこからでもリクエストすることは可能ですよね。(リクエスト先が受け付けてくれるかは別として)

よって、例えば攻撃者が攻撃用サイトを用意し、その中にリンクを設置しておくとします。
何も知らないユーザAがそれをクリックすると、勝手に別なWebアプリケーションのPOST処理が走るように仕込んでおけば、ユーザAがあたかもPOST処理を実行したかのように装ったリクエストを送信できるのです。
以下のサイトの図がわかりやすいです。

クロスサイトリクエストフォージェリ(CSRF) | トレンドマイクロ

CSRFによる被害

ログインを要しないサイトの処理であればユーザAが直接的な被害を被ることはありません。
ただし、掲示板に殺害予告など悪意のある書き込みをするような処理が送られた場合、場合によってはユーザAが誤認逮捕されてしまう恐れなどがあります。
ユーザA自身は何もしていなくても、ユーザAのネットワークのIPアドレスからリクエストが送信されていれば、ユーザAが行ったものと認識されてしまうためです。
実際にそのようなケースでの逮捕されてしまう人は後を絶ちません。

ログインを要するサイトであれば直接的な被害を被ることがあります。
例えばブログサービスなどで、自分は何もしていなくても勝手に投稿を書き込まれてしまうケースなどがあります。
一時期mixiで問題になった「はまちちゃん」などは有名ですね。

www.itmedia.co.jp

また、決済処理が備わっているWebアプリケーションのPOST処理であれば、勝手にものを購入されてしまうなどの被害も起きうる可能性があります。

ユーザ自身が怪しいリンクを踏まないように注意することはもちろん必要ですが、開発する側も別なURLから来たリクエストは弾く仕組み作りをするなど対策は必要になってきます。

通常のCSRF対策

よく行われるCSRF対策としては、トークン認証でしょう。

ページを開いたタイミングでサーバよりランダムのトークンを発行してhidden要素に値を埋め込みます。
POSTのタイミングで他の入力値とともにトークンをサーバに送信し、自サイトのサーバから送信されたトークンと照合する方法です。

外部から攻撃するために適当なトークンを送ってPOSTしようとしても、別ドメインからアクセスされたものと判断されて外部からのPOSTを防ぐことができます。

フォームのCSRF対策としては必ずと言っていいほど行われる対策のため、Webの著名なフレームワーク(例:Ruby on RailsCakePHP)では所定のformタグを埋め込むだけで自動でCSRF対策をしてくれるものが多いです。

さて、それではSPAサイトではどうでしょうか。

SPAサイトのCSRF対策

SPAサイトではページを開いてから非同期で情報を取得してフォームも非同期で作られるため、トークンを仕込むことが難しくなります。
ではSPAサイトではCSRF対策はできず、攻撃されても諦めるしかないのでしょうか?

いえ、そんなことはありません。
HTTPのリクエスト時には、POSTする値以外にも様々な情報を含めて送信できるため、他の方法で自サイトからのアクセスであると照合できれば良いのです。

方法1 リファラで判断

これが一番簡単な方法です。
通常HTTPリクエストでは、リファラ(Referer)を送信することになります。
リファラとは、簡単に言えばアクセス元のページが書かれた情報です。

このリファラが自身のドメインからであれば、正規のリクエストと判断することができます。
ところが、自身のドメインでなければ、外部からの攻撃(CSRF)とみなしてそのリクエストをブロックすればよいのです。
リファラ自体はCSRFの攻撃者では改変できないため、CSRF対策の有効な手段の一つです。

一見容易で問題なさそうに見えるこの方法ですが、弱点も多々あります。
リファラというものは、CSRFの攻撃者からは偽造できませんが、ユーザ自身が別な値にすることは容易です。
例えば以下のようなJavascriptをコンソールで実行するだけで簡単に変更できてしまいます。

Object.defineProperty(document,"referrer",{value:"http://test.com"})


また、ブラウザの設定でリファラを非送信にすることもできるため、不正な利用者でない場合もリファラを送信していないだけではじかれてしまいます。

そしてすべての方法に共通して言えることですが、リファラのチェック部分の実装ミスにも気を使う必要があります。

まずはプロトコルの違いによる部分ですが、httpsからhttpに送信した場合、リファラは送信されません。
API含めた全ページをSSL対応している場合は問題ないですが、対応していない場合はリファラのチェックができない点に注意しましょう。

また、例えば正規表現でチェックをしているとして、「test.comから始まるリファラを通す」という定義をしている場合、test.com.test.comというドメインからリクエストが来た場合も通ってしまいます。
しっかりと「test.com/から始まるリファラを通す」という定義をしましょう。

このように一番簡単に見える方法でも弱点は多いため、この方法を採用するのであれば、リファラのチェックだけで済ませるのではなく、他の対策と併せて使用することをおすすめします。


方法2 ログイン時に発行したトークンを照合

この方法は、ブラウザでPOSTした場合にハッシュ値をチェックするCSRF対策に似ています。

手法としては、ユーザがログインしたタイミングでトークンを発行し、画面表示のタイミングでそのトークンを画面に渡してください。
SPAでステートレスな設計をしており、セッション管理をしていない場合は、トークンを発行したタイミングでユーザとトークンの関係をDBに保存しておくと良いです。

その時、トークンをcookieや、Vue.jsでいうところのstoreなど状態管理のライブラリに保存しておき、POSTのタイミングでサーバに送信します。
サーバ側でトークンを照合し、正しいトークンでなければ処理をブロックすれば良いのです。

この方法で使用するトークンではユーザを特定することもできるため、CSRF対策と同時に、ユーザがログインしているか否かの判別や、ログインユーザの情報取得にも使えます。

この方法も比較的容易ですが、ログインしていることを前提とした方法になっています。
ログインしていなくても、POST処理を使用するようなWebサイトはたくさんあるのではないでしょうか。
匿名の掲示板サイトや新規登録(サインアップ)画面、またはお問い合わせ画面なんかもそうですよね。

ログインしていない場合のPOST処理は重要なものではないとして、チェックしないというサイトの方針の下ではありかと思いますが、できれば方法1のリファラチェックか次に述べる方法3のOriginチェックと組み合わせて使いたいです。

ちなみに…
このトークン認証ではワンタイムトークンを使わなくていいの?と思われる方もいるでしょう。
それに関しては、以下の記事が参考になりました。

egapool.hatenablog.com


方法3 CORSと組み合わせてOriginをチェック

方法1ではヘッダー情報のリファラをチェックしましたが、欠点があることは先に述べた通りです。
今回の方法は、それを補うわけではありませんが、同じヘッダー情報のOriginを使用して照合します。

OriginヘッダーのチェックではCORSと組み合わせる必要があるのですが、そもそもCORSとはなんでしょうか。

またまたそもそもの話になるのですが、SOPをご存知でしょうか?
詳細は以下のページがとても参考になります。

簡単にSOP周辺を理解するページ

CORSとはCross-Origin Resource Sharingの略であり、簡単に言えば他のサイトにAPIなどでアクセスする場合は同じドメイン同士でなければいけないというセキュリティーポリシーです。
ブラウザによって制御されているセキュリティーであり、他ドメインからのアクセスをブロックすることができます。

そもそもブラウザにそのようなセキュリティー対策が備わっているのであれば、もう対策をしなくて大丈夫じゃない?
と思ってしまいがちですが、APIサーバとHTMLを描画するWebサーバを別ドメインにしている場合、話が変わってきます。
SOPの設定により、別のドメインからのアクセスを防いでいるわけですから、正規のWebサーバからのアクセスでもブラウザが弾いてしまいます。


そこでCORSが必要になるわけです。
CORSでは、アクセスを許可するドメインをWhitelist(ホワイトリスト)に指定しておき、同一ドメイン以外からのアクセスであってもWhitelistに指定している場合であればアクセスを許可できるようにブラウザに教えてあげる仕組みです。

ここでWhitelistと照合する情報が、リクエストのヘッダー情報に含まれるOriginになります。
これでWebサーバからのアクセスは弾かれずに済みますね。

また、他ドメインからのアクセスを防げるということは、同時にCSRF対策にもなっているのです。
でもそのOrigin情報が書き換えられたらどうなるの?
と思われる方もいるでしょう。
でもご安心ください。

ドメインをまたいだ(Cross-Origin)リクエストの場合、リクエストのヘッダ情報は書き換えることはできないのです。

このOriginチェックでCSRF対策とはなりますが、自分でカスタムヘッダーを付与してそれで照合させたい場合も出てくるかもしれません。
そのような場合、まずはpreflightリクエストを送り、問題ないリクエストと受け入れられてからリクエストを送ることになります。
以下のあたりの記事がとても参考になりました。

サーバサイドのCORS対応 - Carpe Diem
https://dev.classmethod.jp/etc/about-cors/

イラスト図解式 この一冊で全部わかるセキュリティの基本

イラスト図解式 この一冊で全部わかるセキュリティの基本

基礎から学ぶ Vue.js

基礎から学ぶ Vue.js

Spring Boot + MySQLで絵文字を保存する

絵文字を保存したい

スマホが普及してから絵文字を文中に入れるのはもはや普通のことですよね。
掲示板などでも絵文字を入力できるものは増えていますよね。

Webアプリの開発者の中には絵文字を入力できるフォームを作りたいという人もいるのではないでしょうか?
実はこれ、意外と簡単なのです。

ここではSpring BootとMySQLを使っている場合の絵文字の保存方法を共有したいと思います。

MySQL文字コードを変更

普段データベースを設定する時、文字コードは何を使っていますか?
大体utf8を使うことが多いですよね。
普通の文字列であればそれでも構いませんが、絵文字を登録する場合はutf8を拡張したutf8mb4を使う必要があります。

文字コードを変更するためにはmy.cnfファイルに変更を加えます。
※my.cnfファイルの場所は環境によって異なってくるのでご自身で調べてもらう必要があります。
Macの場合は「/usr/local/etc/」直下、Linuxの場合は「/etc/my.cnf」あたりになるかと思います。

以下のように「character-set-server」と「default-character-set」の値を変更してください。

my.cnf

[mysqld]
character-set-server = utf8mb4

[client]
default-character-set = utf8mb4

設定変更後、MySQLを再起動させれば文字コードが反映されます。
反映されていることを確認するために、MySQLにログインした後に以下のコマンドを入力し、各文字コードが以下のようにutf8mb4になっていることを確認してください。

mysql> show variables like 'character%';
+--------------------------+----------------------------+
| Variable_name            | Value                      |
+--------------------------+----------------------------+
| character_set_client     | utf8mb4                    |
| character_set_connection | utf8mb4                    |
| character_set_database   | utf8mb4                    |
| character_set_filesystem | binary                     |
| character_set_results    | utf8mb4                    |
| character_set_server     | utf8mb4                    |
| character_set_system     | utf8                       |
| character_sets_dir       | /usr/share/mysql/charsets/ |
+--------------------------+----------------------------+
8 rows in set (0.01 sec)

この状態でデータベース、ひいてはテーブルを作成すればMySQLに絵文字を登録できます。
実験的に以下の絵文字を何らかのカラムに登録してみてください。

😀

※注意点として、データベースやテーブルを新しく作成する場合に、utf8mb4以外の文字コードを使用しないでください。
MySQLサーバの文字コードはutf8mb4だけどカラムの文字コードはutf8の場合などはもちろん絵文字は登録できません。

また、utf8で作成していた既存のカラムをutf8mb4に変更したい場合、MySQLサーバの文字コードを変えてもカラムの文字コードは変わっていないので注意してください。

Spring Bootでデータベース接続時の文字コードを変更

あとはSpring Bootでデータベースに接続する時の文字コードを変更する必要があります。
変更するファイルは環境変数を書き込んでいるapplication.ymlかapplication.propertiesです。

以下は変更前と変更後を並べますが、変更後ではデータベースの接続で文字コードを指定しています。
※データベース名は「test-database」であるとします。

application.ymlの場合 <変更前>

spring:
  datasource:
    url: jdbc:mysql://localhost/test-database

application.propertiesの場合 <変更前>

spring.datasource.url=jdbc:mysql://localhost/test-database


application.ymlの場合 <変更後>

spring:
  datasource:
    url: jdbc:mysql://localhost/test-database?character_set_server=utf8mb4

application.propertiesの場合 <変更後>

spring.datasource.url=jdbc:mysql://localhost/test-database?character_set_server=utf8mb4

これでSpringBootより絵文字が登録できるはずです。

まとめ

いかがでしたか?
設定自体は簡単ですよね。
ちなみに以下のサイトも同じことをして絵文字を登録しています。
会員登録なしで書き込みなどできるので、ぜひ試してみてください。

ikujip.jp

Spring Boot 2 プログラミング入門

Spring Boot 2 プログラミング入門

Spring Boot in Action

Spring Boot in Action

ユーザにSFTPの特定ディレクトリのみ操作できる権限を付与

一般ユーザは、SFTPで特定のディレクトリのみしか触れないようにしたい

サーバのファイル操作を複数人で行うようなことは、エンジニアの現場ではよく起きることです。
そのような場合、全員が管理者権限を持ってしまうと、みんなが自由にサーバの設定を変えることができるため、セキュリティ面で何かと問題があります。
管理者以外のユーザは、SFTPで特定のディレクトリのみしか触れないようにしたい場合、以下の手順で対応できます。
ちなみにこの手順では、SSHのログインもできないように設定します。

ディレクトリの作成

SSHでサーバにログイン後、「su -」で管理者ユーザに切り替わった状況からスタートします。

まずはディレクトリを用意します。
これは、今回操作権限を付与するユーザが、唯一操作できるディレクトリになります。
「/var/www/」直下にhomeDirというディレクトリを作るとします。
※すでに用意してある場合は不要です。

cd /var/www
mkdir homeDir

最後にも確認しますが、homeDirの所有権はrootユーザになるようにしてください。
そうしないとエラーが発生してしまいます。

ここで終わりではなく、さらにhomeDirの中にuploadDirというディレクトリを作成してください。

cd homeDir
mkdir uploadDir

最終的には、今回操作権限を与えるユーザがログインした場合、ホームディレクトリ(ログインした後にたどり着くディレクトリ)をhomeDirとしますが、実際にファイルの更新などができるのはuploadDirの中だけとしたいです。

ユーザとグループの作成

次にユーザを作成しましょう。
今回はtestUserというユーザを作成することにします。

useradd testUser

そして作成したユーザのパスワードを設定します。

passwd testUser

続いてグループを作成します。
今回はsftpgroupというグループを作成し、このグループに所属するユーザは、操作権限が与えられるものとします。
※gpasswdはユーザのグループ情報を管理するコマンドです。

groupadd sftpgroup
gpasswd -a testUser sftpgroup


※今後同じようなユーザを増やしたい場合、上記で作成したグループに入れましょう。

この時点でSSHやSFTPでログインできることを確認してください。
今のままではこのユーザのディレクトリ操作制限が設定されていないので、書き込み権限はないもののサーバの中身が丸見えです。

ディレクトリの権限変更

この対応では権限設定を間違えると上手くいきません。
ディレクトリに対して以下のような権限設定をしてください。

homeDirは今回唯一操作可能なディレクトリということになりますが、書き込み・読み込み権限を与えた上で、ユーザ所有権・グループ所有権共にrootにしてください。

cd /var/www
chmod 775 homeDir
chown root:root homeDir

次にファイルの更新を行うディレクトリに関してですが、こちらはユーザ所有権をtestUser、グループ所有権をsftpgroupとしてください。
このディレクトリの所有権をrootにしてしまうと、誰も中身を更新できなくなりますし、グループにsftpgroupを指定することで、後ほどユーザをグループに追加すれば誰でも更新できるようになります。

cd /var/www/homeDir
chown testUser:sftpgroup uploadDir

sshd_configを変更して操作権限を付与

あとは以下のようにsshd_configの一番下の部分を変更してください。

vim /etc/ssh/sshd_config

#Subsystem      sftp    /usr/libexec/openssh/sftp-server # コメントアウトする
Subsystem sftp internal-sftp # SSHログインを制御してSFTPでないとログイン不可に

Match Group sftpgroup
    ChrootDirectory /var/www/homeDir
    PasswordAuthentication yes

簡単に説明しますと、sftpgroupのグループに属するユーザがログインした場合、homeDirしか操作できなくするという意味です。

sshdの再起動を忘れずにしましょう。

service sshd restart

ログインがうまくいかない場合は以下のログで確認してください。

/var/log/secure

以下は権限の設定ミスにより発生したログですが、なぜログインできないのか原因を吐き出してくれています。

fatal: bad ownership or modes for chroot directory

まとめ

いかがでしたか?
長い手順に見えますが、意外とすぐにできてしまいます。
セキュリティのためにも、一般ユーザには極力触れる範囲を絞るようにしましょう。

定量的リスク管理の実務―流動性リスク管理、FTP、リスク統合、ストレステストの実践的手法

定量的リスク管理の実務―流動性リスク管理、FTP、リスク統合、ストレステストの実践的手法

  • 作者: ジミー・スコグランド,ウェイ・チェン,SAS Institute Japan Risk Management ソリューショングループ
  • 出版社/メーカー: きんざい
  • 発売日: 2018/03/06
  • メディア: 単行本
  • この商品を含むブログを見る
見てわかるTCP/IP

見てわかるTCP/IP

FTPとTELNET―パソコン・LAN活用法

FTPとTELNET―パソコン・LAN活用法

いきなりできます! 最新ホームページ作り&HTML超入門 第3版

いきなりできます! 最新ホームページ作り&HTML超入門 第3版

Vue.jsで配列の値をAPIでPOSTする方法

値を配列でPOST

フォームでPOSTする項目数が決まっていない時、配列にして値をPOSTしたい場面があると思います。
例えばチェックボックスなんかは1つのnameに対して値の数は決まっていないものです。
また、テキストフォームであっても、入力フォームを追加して不特定多数の値をPOSTする場面があるでしょう。

今回はそのような場合のPOST方法についてです。
やり方としては2つありますので、それぞれの方法を共有します。

※今回はaxiosを使い、APIによりvalueという項目名でPOSTするものとします。

方法1 dataで定義する変数を配列で初期化してv-modelに設定

大体の場合はこのパターンでうまくいきます。

チェックボックスの例)

※中身は要点だけおさえて色々要約して書いています。
testCheckboxValuesの中身はチェックボックスの候補が入った配列です。

<ul>
  <li v-for="testCheckbox in testCheckboxValues">
    <label>
      <!-- v-modelに設定するだけ -->
      <input v-model="checkboxList" type="checkbox" :value="testCheckbox.value"/>{{ testCheckbox.name }}
    </label>
  </li>
</ul>
export default {
  name: ’test'
  data () {
    return {
      checkboxList: [] // 配列で初期化
    }
  },
  methods: {
    postMethod () {
      let params = new FormData()
      // valueという項目名でPOST
      // 配列をそのまま入れるだけ
      params.append('value', this.checkboxList)
      this.axios.post('/api/test/post', params, {
        ・・・・
      })
      .then(res => {
        ・・・・
      })
    }
  }
}

上記のやり方が一番ベーシックですね。
POSTしたい変数を配列で定義し、v-modelに設定するだけで本当に配列としてPOSTできるのです。
画面を開いた時、すなわちGETのタイミングでも、testCheckboxValuesの各valueの中にcheckboxListの値と同じものがあれば、勝手に:checkedが付いてくれるのです。

複数のテキストフォームがある時の例)

※下記はなんらかの方法でtextListの要素数を増やしたり減らしたりできるものとします。

<ul>
  <li v-for="index in textList.length">
    <!-- v-modelに設定するだけ -->
    <input v-model="textList[index - 1]" type="text"/>
  </li>
</ul>
export default {
  name: ’test'
  data () {
    return {
      textList: [] // 配列で初期化
    }
  },
  methods: {
    postMethod () {
      let params = new FormData()
      // valueという項目名でPOST
      // 配列をそのまま入れるだけ
      params.append('value', this.textList)
      this.axios.post('/api/test/post', params, {
        ・・・・
      })
      .then(res => {
        ・・・・
      })
    }
  }
}

上記もただただdataで定義した配列をそのままPOSTしているだけですが、これで配列のPOSTになるのです。
注意点としてはv-modelの指定方法です。
例えば以下のようにしてしまうと、

<ul>
  <li v-for="text in textList">
    <input v-model="text" type="text"/>
  </li>
</ul>

こんなエラーが出てしまいます。

You are binding v-model directly to a v-for iteration alias. This will not be able to modify the v-for source array because writing to the alias is like modifying a function local variable. Consider using an array of objects and use v-model on an object property instead.

よってテキストの配列の場合は、要素を直接v-modelに入れないように注意しましょう。

方法2 POSTする前に配列にまとめてしまう方法

あまりないパターンですが、dataで定義した配列をそのままPOSTできない場面が来た場合は以下の方法もあります。

チェックボックスの例)

<ul>
  <li v-for="testCheckbox in testCheckboxValues">
    <label>
      <!-- v-modelに設定するだけ -->
      <input v-model="checkboxList" type="checkbox" :value="testCheckbox.value"/>{{ testCheckbox.name }}
    </label>
  </li>
</ul>
export default {
  name: ’test'
  data () {
    return {
      checkboxList: [] // 配列で初期化
    }
  },
  methods: {
    postMethod () {
      let params = new FormData()

      // ループで配列に格納
      if (this.checkboxList.length > 0) {
        this.checkboxList.forEach((check, index) => {
          param.append('value[' + index + ']', check)
        })
      } else {
        param.append('value', [])
      }

      // valueという項目名でPOST
      this.axios.post('/api/test/post', params, {
        ・・・・
      })
      .then(res => {
        ・・・・
      })
    }
  }
}

結局POSTする値は方法1の時と同様になりますが、上記のようにループを使って配列に格納することも可能です。
チェックがない場合(this.checkboxList.length === 0の場合)は空の配列を格納することを忘れないでください。

複数のテキストフォームがある時の例)

<ul>
  <li v-for="index in textList.length">
    <!-- v-modelに設定するだけ -->
    <input v-model="textList[index - 1]" type="text"/>
  </li>
</ul>
export default {
  name: ’test'
  data () {
    return {
      textList: [] // 配列で初期化
    }
  },
  methods: {
    postMethod () {
      let params = new FormData()

      // ループで配列に格納
      if (this.textList.length > 0) {
        this.textList.forEach((text, index) => {
          param.append('value[' + index + ']', text)
        })
      } else {
        param.append('value', [])
      }

      // valueという項目名でPOST
      this.axios.post('/api/test/post', params, {
        ・・・・
      })
      .then(res => {
        ・・・・
      })
    }
  }
}

チェックボックスの時とほとんど同じですね。

まとめ

いかがでしょうか。
少なくともjQueryなどを使うよりはVue.jsの方が配列の扱いは簡単ですよね。
ちなみに以下のページはこの技術を用いてチェックボックス周りを実装しています。
会員登録なしでPOSTできるので、よければ実験的に触ってみてください。

ikujip.jp

このページはChart.jsでグラフも表現していますが、このあたりはまた別な機会に記そうと思います。

基礎から学ぶ Vue.js

基礎から学ぶ Vue.js

速習Vue.js 速習シリーズ

速習Vue.js 速習シリーズ

Vue.js入門 基礎から実践アプリケーション開発まで

Vue.js入門 基礎から実践アプリケーション開発まで