新着記事

Viewing posts from September, 2021

実務経験で出会った便利なあれこれ

新卒エンジニアとして半年間働いてきて、現場でさまざまなことを勉強させていただきました。その中でも、もっと早く知っておきたかった便利なツール、Python の書き方など、幅広いあれこれを記事にしたいなと思います。

あれこれ、というざっくりとした括りになってしまっているのでまずは紹介したいものを示しておきたいと思います。

  • git submodule
  • Fabric3
  • Flake8
  • Python
    • 型ヒント
    • 整形文字列 f-string

git submodule

git submodule とは、git のレポジトリをサブモジュール化して複数のプロジェクトで共有することができる機能のことです。これを使うことで一度開発したコードを別のプロジェクトで簡単に再利用できます。例えばTORICOでは、 TORICO-ID のソーシャルログイン機能や決済機能だけでなく、ユーティリティ関数などを Django, Vue, Flutter それぞれのフレームワークでまとめたレポジトリが用意されてあります。

導入も至ってシンプルです。プロジェクト内で

git submodule add <リポジトリURL> <インストールパス>

たったこれだけで、今まで開発してきたコードを再利用することができます。わざわざプロジェクト間でコピペをしてくる必要はありません。さらに、git submodule はバージョン管理もしてくれます。サブモジュールを更新しても影響があるのはそのプロジェクト内だなので安心してサブモジュールの改良をすることができます。

1点注意として、submodule はメインのプロジェクト内で git clone や git pull を行っても自動で更新されることはありません。そのため、

git submodule update (初回のみ -i オプションで初期化)

コマンドを忘れずに実行する必要があります。

Fabric3

こちらは Python のモジュールの一つで、所定のアクションをコマンド一つで呼び出すことができます。例えば、デプロイするときの一連の流れを Python コードにしておくことで、ターミナルに fab deploy と入力するだけでその流れを自動で行ってくれるようになります。流れは以下のように記述します。

env.hosts = ['app1.sample.com', 'app2.sample.com']
def deploy():
    #  通知などを行う
    slack_announce('deploy')
    with cd ('/var/src'):
        run('git checkout master')
        run('git pull origin master')
        run('git submodule update')

このように一連の流れをコードとして残しておくことで、それをコマンド一つで呼び出すことができるようになります。さらに、新規に加入したメンバーでも簡単にデプロイができるというメリットがあります。その環境に慣れていない人が、例えば git submodule などのコマンドを忘れる心配もありません。また、私のような実務経験の無い人でもコードを確認することで流れを理解することができます。

TORICO では上記の git submodule を使ってほぼ全てのプロジェククトに deploy, ssh, flake8(後述), dsh (docker 環境にSSH)のコマンドが用意されています。

Flake8

こちらも Python のモジュールで、自動でコードレビューをしてくれます。導入は pip でインストールするだけです。実行は flake8 コマンドとオプションをターミナルにします。

flake8 --exclude="*migrations/*,venv/*,.venv/*,~* .

flake8 result

すると、このようにコードレビューをして規約に反する部分を教えてくれます。規約コードも教えてくれるため、わからない部分は検索できるようになっています。また、どうしても諸事情で変更できない場合は

from django.contrib import admin  # NOQA: F401

とコメントをつけることで、指定した規約コードの違反を行単位で無視させることもできます。

TORICO では毎回オプションを入力するのは面倒なため、上の Fabric3 を使って実行を簡単にしています。

Python 型ヒント

Python3.5 から導入された機能で型ヒントというものがあります。これは引数や帰り値の型をコードに書くことで可読性を向上させることができる機能です。

def hello(name: str) -> str:
    return f'hello, {name}.'

型ヒントはあくまでも補助的な役割のため、宣言とは異なる型を渡したところでエラーになることはありません。ただ、PyCharmでは型ヒントとは違う型を渡すと警告をしてくれます。可読性が飛躍的にあがるので、ぜひとも書くことを癖にしたいなと思っています。

Python F文字列

上の例でもしれっと使っていましたが、F文字列を使用することで .format 部分を短縮することができます。さらに、f文字列では変数だけでなく式なども使用できます。

print({a} + {b} = {a + b})  # format では a + b はエラーになる

format で記述するとコードが長くなる傾向があるので、スッキリと書けるf文字列は積極的に使用しています。

最後に

今回は現場で知った便利なあれこれを記事にしました。個人で勉強をしているとフレームワークや言語の知識は増やせますが、運用に関わる部分はなかなか知ることができないと思います。便利だなと思ったら、ぜひ積極的に使用してみてください。

新卒エンジニアが今になって就職前にやっておけば良かったと思うこと3選



就社してからは初めてのブログ投稿となります。
お久しぶりです。開発部の鈴木海人です。

株式会社TORICOにエンジニアとして入社して、半年が経ちました。
今回のブログでは、過去の内定をいただいてから入社までの間に対して何もしなかった自分に対して
入社までにやっておいたほうがいいことについてまとめました。
過去の自分のような過ちを他の人が犯さないようにまとめましたのでエンジニア内定をもらって何をすればいいかわからない人はぜひ参考にしてみてください。



私の簡単な経歴はこちら↓
都内私立文系大学卒業
大学3年時の夏に某大手プログラミングスクールに通い、プログラミングの基礎について学ぶ
スクール卒業後、都内のスタートアップの会社で2ヶ月ほどインターン(作業内容は主にLPの作成を行っていました)
大学4年時は就活を行い、株式会社TORICOに内定をいただき、今に至ります。



注意事項
簡単なコーディング知識があることを前提にお話しします。もしプログラミングが全くわからないという人はprogateなどのプログラミングを簡単に学べるサイトでまずは学びましょう。



それでは本題に戻ります。
まず結論からお話しします。以下の3つになります。
  1. タイピング強化

  2. 会社で使用する言語の参考書を1冊読んでおく

  3. ドキュメントで調べる癖をつける





それでは1つずつ説明していきます。

1.タイピング


これはどんな人でも絶対にやっておきましょう。
目安としましてはe-typingで安定してA以上や寿司打で1万円コースクリアでしょうか。
上記のサイトですと日本語入力ですので英単語を打つようなサイトを探してみてもいいかもしれません。
僕は入社してから、過去タイピング練習をしてこなかったことを最も後悔しています。

タイピングを強化しておくメリットには下記が挙げられます。
  1. 仕事スピードが上がる
  2. 成長スピードが上がる
  3. 教えていただいている時の時間を少なくできる


考えてみれば当たり前なのですが、タイピングスピードが2倍になればかけるコードも2倍になり、そのため成長スピードも2倍になります。
逆にタイピングスピードが1/2倍になればかけるコードも1/2倍になり、そのため成長スピードも1/2倍になります。
もはやエンジニアにとって一番重要なのではと思っています。
もちろん最初はコードを書くことよりも調べたり読んだりする時間の方が長いので、タイピングスピードの恩恵をあまり受けられないかもしれません
しかし後々大きく影響してくるので鍛えておきましょう。
後、単純にタイピングで遅くてミスりまくると恥ずかしいです。
1日10分とかでもいいので毎日タイピングの練習をするのがおすすめです。私も練習中です
タイピングに慣れてきたら数字や記号などもしっかりと打ち込めつように練習しましょう。

また少し話はタイピングから話がずれてしまうのですが、
よく使用するショートカットキーの暗記やカーソルの移動スピードmaxなどの使いやすいPC設定も行っておきましょう
こちらもPCを使う上での基礎スキルとなり、使っているか使っていいないかで作業効率が大幅に変わるので意識してみてください。






2.会社で使用する言語の参考書を1冊読んでおく


参考書を読むというのに抵抗感がある人は多いのではないでしょうか?
実際僕もそうでした。ネットなどで調べてみると「ネットに全部載っているのに本を買う必要はない」、「わからないことはその都度ググって調べればいい」など
本に対しては比較的、良い情報が流れていないようなイメージが僕にはあります。
これは私の上司から教えていただいて、確かにとなったのですが、本は体系的(一つ一つのものがある系統に従ってまとまっているさまのことという意味みたい)になっているため正確な情報をしっかりとインプットできるのです。
今までとりあえずわからなくなったらググってを繰り返していたのですが1通り本を読むことにより、もちろん完全暗記はできませんがコードを書いているとき、あれが使えるかなとか、それが出てこなくても
調べて出てきたメソッドなど、そういえばこんなのあったなと思い出せます。
また、おすすめの参考書なのですが
私の上司のおすすめの参考書はとりあえず分厚い本みたいです。。。
残念ながら優しくて短い本では情報量が少なすぎたりであまりお勧めをしていないようです。
参考書を買うときは分厚くて情報量の多い参考書を選びましょう。
こちらもタイピングと同様少ない時間でもいいので移動時間などを活用して少しずつ読み進めましょう。






3.ドキュメントで調べる癖をつける


皆さんはドキュメントで調べ物をしていますか?
僕は基本的にQiitaだったり個人ブログなどを参考にすることが多いです。。。
わかりやすいですよね。。。
なるべく意識はしていますが今でもあまりできていないのが現状です。
ドキュメントで調べる癖をつけた方がいい理由は、正確な情報が手に入れられるからです。
調べ物をしているとき正確な情報じゃないことや、記事が古く参考にならなかったり、バージョン違いで動作しなかったりと
結構クソみたいな記事が上に表示されることはあるあるではないでしょうか
ドキュメントで調べる癖をつけておくと正確な情報を手に入れることができるのはもちろんなのですが
英語で文を読む癖がついたり、英語で調べたりする癖がつくのでためになると思います。
日本語検索とは比べ物にならないほど英語の情報は出てくるので、英語めっちゃできるぜ!って感じを目指さなくてもいいですが
グーグル翻訳を使いながらでも少しずつ調べ物ができるようになると良いです。
プログラミングをしている人にとって英語は切っても切り離せない関係なので
早い段階で慣れておきましょう。





以上3つが私が入社前にやっておけば良かったことになります。
最後にもう1度
  1. タイピング強化

  2. 会社で使用する言語の参考書を1冊読んでおく

  3. ドキュメントで調べる癖をつける



重要順は上から1.2.3となります。
ぜひエンジニアに、これからなる人なりたい人は参考にしてみてください。




ビジネスサイドに知ってほしいエンジニア用語

今回の記事は新型コロナウイルスの影響で普段リモート開発をしていて浮き彫りになった問題点の1つとして「開発側と運用側でコミュニケーションが難しい時がある」といったものがありましたので、その原因として考えられる「IT専門用語が難しくしている」に焦点を当てて、業務中に発生した知ってもらえたら嬉しい用語を(個人的頻出度で)紹介していきます。(かなり噛み砕いていますので厳密で完璧にあっているものではないものもあります)




  1. デプロイ
    頻出度第一位は「デプロイ」です。ほぼ毎日slack上で打ち込んでいる気さえしてくる単語です。開発側が作った制作物を他人にも見てもらえるように反映することです。英和辞書などで調べると「配置する」といった意味がでてきますが、このままだと日常会話で出てくることもないでしょうし、直感的に理解することが難しいですね。もし自分がエンジニアをしていなく、デプロイと聞いたらどこかの国がミサイルでも配置しているのかと思ってしまうでしょう。


  2. 本番環境 & 検証環境
    1位のデプロイと関連しまして、よく使う定番フレーズが「本番環境にデプロイしました。」とか「検証環境にデプロイしました。」です。環境の単語がイメージしにくいのではないでしょうか。
    環境は目に見えるものではないので、得体の知れないところにデプロイしましたと言われたところで「どこか知らないけど、反映したことはわかったで。」のような感覚かと思います。この文脈でいう「環境」はサービスが動いているサーバーのことです。本番環境は実際にユーザーが見るサーバーで、検証環境はつくったサービスをいきなり公開にするのは怖いので、社内向けへの確認や動作の確認をするためのサーバーです。
    別名で本番環境はプロダクション( Production )環境、検証環境はデベロップメント( Development )環境やステージング( Staging )環境と呼ばれることもあります。

    ここまでで、もし「プロダクション環境にデプロイしました」というフレーズを聞いたらそれは「ユーザーが使えるように反映した」のような意味です。


  3. サーバー & サーバーサイド
    こちらは「サーバーが落ちた。」などで聞くと思われます。「サービスが動いている場所」のような感覚でなんとなくわかるけど、具体的にはよくわからない言葉ではないでしょうか。

    サービスが動いている場所というのは間違っていないですが、サーバーは英語の[ Server ]から来ており、給仕する人という意味で何かを給仕しているのですが、特定のものに限定されません。アプリケーションを動かす役割をサーブ( serve )していればアプリケーションサーバーで、データベースの役割をしていればそれはDB( データベースサーバー )です。そしてこの例のようにアプリケーションとデーターベースのサーバーが分けられていることもあれば1つのサーバーが両方を兼ねていることもあります。

    「サーバーが落ちた。」というのは何かしらの役割を給仕できなくなった状態です。

    そしてよくサーバーサイドとクライアントサイドのようなフレーズも見かけるかもしれません。ネットサーフィンしている私たちはクライアント(客)としてサービスを給仕されているのでサービス使用者はサーバーサイドに触れることができません。

    サーバーサイドエンジニアはユーザーが触れることができない箇所を開発しているエンジニアです。代表的なサーバーサイドを開発するプログラミング言語ではPHP、Ruby、Python( 当社で使用しているメイン言語 )があります。


  4. クライアント
    クライアントはつまりユーザーのことです。ですがユーザーは人間なので上記のサーバーから来たものを受け取ることもできません。なのでここでいうクライアントはブラウザやスマートフォンのことです。ブラウザとはSafari、Chrome、Firefox、Microsoft Edgeなどで、スマートフォンでは主に知られているのがiOSで動くiPhoneとAndroid OSを載せたものです。それぞれ動作は同じではなく、違いがあります。

    ブラウザではサーバーから受け取ったものを解釈してそれを表示するようにしますが開発に主に使用されるプログラミング言語はJavaScriptです。またスマートフォンではiOSならSwift、AndroidならKotlinなどがあります。当社ではブラウザ側ではJavaScriptのフレームワークNuxt.jsとスマートフォンではiOSとAndroid両方同時に開発できるFlutterを採用しています。


  5. Cookie (クッキー)
    業務でなくとも、プライベートでネットサーフィンをしていれば、途中で見かけた人も多いのではないでしょうか。もちろん、甘くて美味しいお菓子のクッキーではありません。そもそもこのCookieは何のためにあるかといえばユーザーの情報を覚えておくためにあります。そしてその保管場所です。

    本来はネットサーフィンをしていてもあなたのことをサービスは判断できません。ユーザーAさんがまた同じサイトに訪れてもAさんということはわかりません。データベースに情報を保存してあっても誰かわからないので何を返してよいかはわかりません。つまりCookieがないとあなたが大好きなAmazonでショッピングカートに商品を入れても、カートの商品はそのまま入ったままではいてくれません。それでは困るので、初回のサイトへのアクセス時に使っているブラウザ( SafariやChromeなど )にCookieをサイトは仕込み、今後はCookieが送信されサービスはそれを頼りにあなたを認識してくれます。

    他にもあなたが気になる商品の広告が出てくるのもこのCookieが原因です。


  6. セッション
    上のCookieでユーザーを認識してくれると書きましたが、Cookieはサーバーにアクセスした時に持っていれば自動的に送られるデータです。
    先ほどの例につづき、ショッピングだったら、何の商品をカートに入れたかを認識するためにあるのがセッションIDです。これがあなたのブラウザのCookieの中に一部として入っており、サービスを利用しているとセッションIDがCookieごとサービスに密かに送られ、サービスはセッションIDを元にしてカートに入れる、外す、購入するなどの利用状態( セッションデータ )を判断しています。

    そして例えばログイン済みのユーザーがログアウトすると、以後、利用状態を追いかける必要はないのでセッションIDは捨てられます。この一連の流れをセッションという単位で表します。個人を認識するための値なのでこれはセキュリティの観点からもれては他人に知られてはいけません。

    開発中に当社のサービスの1つでイベント申し込みをしたのに申し込み済みになっていない。というケースがあったのですが、こちらのセッションが切れているので申し込みをしたユーザーだと認識できていないことが原因でした。


  7. Cache (キャッシュ)
    こちらも普段、ネットサーフィンをしていれば、見かけたことがある言葉かもしれません。
    業務ではよくある問い合わせで開発側が「デプロイしました。」と反映したが運用側では「確認したのですが、変わっていません」です。これはキャッシュが悪さをしているかもしれません。( キャッシュは悪いやつではありません )。

    インターネットを利用する上で、同じサイトにまた行くというのはかなりよくあることだと思います。例えば写真が多いサイトは読み込む写真が多いので当然、完全にページを表示するのは文字だけのページと比べれば遅いです。そこで自動的にページの一部のデータを保存しておき、また来た時に、それを利用し早く表示するということをやっています。それのデータの保存場所がキャッシュです。

    なのでページの表示が変わっていないというのはこのキャッシュにためられた古い情報を使うとしていることが原因になっていることがあります。なのでブラウザの設定各種からこちらを消せば解決することがあります。

    本質的には一度した処理を保存しておき、効率的に再度利用するためのものなので、今回のテーマはビジネスサイドなのでブラウザのキャッシュを取り上げましたが、他にもコンテンツキャッシュサーバーやクエリキャッシュがあります( 興味があれば調べてみてください )


  8. AWS
    AWSはAmazon Web Serviceの略称です。Amazonと聞くとどうしても買い物のAmazonや電子書籍のKindle、映画のAmazon Premiumを想像すると思いますが、実はWebサービスのインフラ( 基盤 )をネット上( クラウド )で提供しています。

    AWSはサーバーをネット上使えるEC2というサービスや、データベースのRDS、世界各地に置かれたコンテンツキャッシュサーバーの集合のCDN( Content Delivery Network )であるCloud Frontというサービスなどを提供しています。それを利用することで場所にとらわれずにサービスのインフラを構築することができます。

    逆にオフィスにコンピューターを冷却するスペースを用意し、実際に現物のサーバーを購入したりして、設置する方式はオンプレミスと呼ばれたりします。




番外編

  1. hoge
    たまにエンジニアがデプロイしました。と言い確認してみると次の言葉を見かけることがあるかもしれません。こちらはエンジニア達の中では有名なダミーテキストです。実際に使用できる形を想定したデータを用意しお見せするのがいいのですが、もし見かけてもこれはふざけてやっているわけではない可能性が高いです。

  2.  lorem ipsum
    こちらもよく使われるダミーテキストです。ダミーテキストなので意味は特になく、仮置きに使われます。少し長めのダミーテキストであり、一般的には

    Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

    として使用されることが多いです。


今回はビジネスサイドに知って欲しい用語を技術的な部分には触れない範囲で紹介してみました。これでみなさんのプロジェクトでのコミュニケーションがより円滑になる助けになれば嬉しいです。また需要があれば更新もしくは続編を出すかもしれません。それでは良い開発ライフを。

10年に一度巡る雑誌コードの話

ISBNコード(書籍JANコード)とともに本に付番されている雑誌コードについて解説します。
雑誌コードからJANコードを作成しなければならない時に役に立つ知識です。

雑誌コードとは

雑誌扱いの本に付番されている5桁+4桁、または5桁+2桁のコードです。
この5桁に意味がずっしりと詰まっています。
また、バーコードはISBNコード(書籍JANコード)と一般商品JANコードのものがあります。
雑誌コードの始まる1桁目で雑誌コードの種類がきまります。

1. 1桁目が01は月刊誌(隔月刊や季刊の雑誌も含む)

基本、末尾は奇数。別冊や増刊号は末尾の数に+1して偶数になる。
本に印刷されるバーコードは一般商品JANコード

例)雑誌コード5桁+「-」+月2桁+年の下2桁

月刊TORICO 8月号
01234-0821、または省略して01234-08
月刊TORICO 8月増刊号
01235-0821、または省略して01235-08

2. 1桁目が23は週刊誌(隔週刊や月2回発売の雑誌も含む)

末尾は発売した週で1〜5。
別冊や増刊号は末尾に6以上の数が振られる。
本に印刷されるバーコードは一般商品JANコード

例)雑誌コード5桁+「-」+月2桁+年の下2桁

週刊TORICO 8/6号(8月1週目)
21231-0821、または21231-8/6
週刊TORICO 8/13号(8月2週目)
21232-0821、または21232-8/13
週刊TORICO 8/20号(8月3週目)
21233-0821、または21233-8/20
週刊TORICO 8/20増刊号
21236-0821、または21236-8/20

3. 1桁目が45は漫画のコミックス

雑誌コード5桁は固定。
だいたいレーベルごとになっている(〇〇コミック)
月年はなく雑誌コードに対して発売された巻数の通巻数2桁。
本に印刷されるバーコードは書籍JANコード

例)雑誌コード5桁+「-」+通巻数2桁

TORICOコミックス 1巻
41234-01
TORICOコミックス 2巻
41234-02

4. 1桁目が6はムック

雑誌コード5桁は固定。
だいたいレーベルごとになっている(〇〇ムック)
月年はなく雑誌コードに対して発売された巻数の通巻数2桁。
本に印刷されるバーコードは書籍JANコード

例)雑誌コード5桁+「-」+通巻数2桁

TORICOムック よくわかるTORICO
61234-01
TORICOムック TORICO大全
61234-02

5. 珍しいコード

1桁目が7で始まる、オーディオ・ビジュアル商品
1桁目が8で始まる、直販誌
1桁目が9で始まる、PB商品

雑誌コードから一般商品JANコードの作成方法

月刊誌、週刊誌にはISBNコード(書籍JANコード)がありません。
雑誌コードのみ付番されています。
ではバーコードは何でできているか?というと雑誌コードから一般商品JANコードを作成することができます。

例)01234-0821の場合
一般商品JANコードは4910012340819となります。
内訳としては
491=>定期刊行物コードのプレフィックス。固定
0=>予備。雑誌コードが満数になった場合使うかもしれない
01234=>雑誌コード5桁
08=>月号2桁
1=>年の下1桁
9=>JANのチェックデジット。形式はモジュラス10、ウエイト1、3
これをphpで書くと下記の様になります

/**
 * 雑誌コードをJANコードに変換する
 * @param string $code 雑誌コード、5桁-4桁
 * @return string JANコード
 */
function magazineCode2Jan($code)
{
    // 雑誌コードと月号で分ける
    $exp_code = explode('-', $code);
    $magazine_code = $exp_code[0];
    // 年の下1桁
    $year = substr($exp_code[1], 3, 1);
    $month = substr($exp_code[1], 0, 2);
    $prefix = 491;
    $spare = 0;
    $jan12 = $prefix . $spare . $magazine_code . $month . $year;
    // チェックデジット計算
    $chkdgt = getCheckDigit13($jan12);
    $jan13 = $jan12 . $chkdgt;
    return $jan13;
}

※チェックデジットの計算はエンジニアの知らないISBNの話のコード例にある「ISBN13桁のチェックディジット」を使用しています。
ここでタイトル回収になるのですが、年の下1桁を使用するため雑誌コードは10年に一度同じコードになります。
データベースにカラムを作る際はユニーク制約をしない、あるいは絶版になった雑誌を定期的に削除するような仕組みが必要になります。

新卒エンジニアが最初の半年に任された業務

今年の春に入社しました、情報システム部の清瀬です。

私がTORICOに入社からそろそろ半年が経過しようとしています。このタイミングで、この半年どういった業務を担当してきたのかを記事にしようと思います。TORICOに興味をもっている方にTORICOのエンジニア業務がどんなものなのか、知ってもらえればと思います。

社内アプリへの権限付与の自動化

torico-id のホームページ

TOIRCOには TORICO-ID というログインを共通化できるアプリがあります。マンガ全巻ドットコム や マンガ展 と連携させることでログインやアカウント作成の簡略化ができます。このアプリは10近くある社内アプリにも使われています。社員全員がボタン一つで簡単にログインできるのですが、問題点が一つありました。それは、Admin サイトへの権限の問題です。

TORICO の社員と一般のユーザーを分けるフラグはあります。そのため、社員にのみ権限を与えることは今までもできていました。しかし、全社員に全ての社内アプリのAdmin権限を付与するわけにはいきません。そのため今までは情報システム部が手動で権限の付与をしていました。

そこで部署ごとに権限を付与するサイトを事前に決めておき、権限の付与の自動化を行えるようにしました。作成したモデルは具体的には

  • Website
    • 権限を与えるアプリのモデル
    • 部署とM2Mの関係
    • django-allauth の Application と 1:N の関係
  • ClientGroup
    • ログイン成功時にユーザーに与える、クライアント側の Django の Group のモデ
    • django-allauth の Application と 1:N の関係
  • Department
    • 部署・部門のモデル
    • Website と M2M の関係
    • ClientGroup と M2M の関係
    • User と M2M の関係

の3つです。TORICO では Mezzanin という Django の CMS を使用しているため、Mezzanine の権限も付与できるように Website を Application と 1:N の関係にしています。

あとは Department に Application と ClientGroup を結びつけておくことで、どの部署にどの権限を付与するのかを自動で判別できるようになりました。TORICO は現在拡大期であり毎月のように新しく入社される方がいるため、この機能は非常に役立っていると個人的には思います。また、エンジニアとしても予想外のメリットがありました。それは新しいプロジェクトのローカル環境を作成した際に、いちいち SQLクライアントツールで自分のアカウントにスタッフ権限を付与しなくて済むことです。特に情報システム部は担当するアプリが多いため、その一手間を省けるのは非常に便利です。

古いシステムの Django 化

長年TORICOを支えてきてくれたシステムですが、その中の幾つかを Django に移行するお手伝いもしました。元のコードを読みながら、その機能を Django で再現するという業務でした。元のコードは PHP で書かれているため、PHP と Python を比較するような作業で、個人的にはとても楽しかったです。なにより、古いシステムを新しいシステムに置き換えることで、一つ一つの処理が非常に効率が良くなり、格段にレスポンスを早くすることができました。あらためてフレームワークの凄さを目の当たりできてよかったです。TORICO にはまだ古いシステムが残っているので、来期も移行の業務を積極的にやっていきたいと思っています。

MySQL 5.6 を 5.7 にバージョンアップする

RDS の MySQL5.6 のサポート期限が8月末(延長されて22/3/1まで)までであるということで、MySQL のパージョンアップもさせていただきました。手順としては、

  1. dev 環境のバージョンアップを行い、全ての機能が使えるかテストする
  2. 1 で問題あった機能を修正する
  3. スナップショットでバックアップをとる
  4. 修正したコードをデプロイして、本番のバージョンアップを行う

今回のバージョンアップでは1の部分で問題がありました。対象のアプリでは、ログイン時に MySQL の OLD_PASSWORD というハッシュ関数を使っていたのですが、それがサポートされなくなりました。そのため、代替となる関数をアプリ側で作成しなければなりませんでした。幸い、既に再現をしてくれているコードがあったため、それを拝借するだけで問題はありませんでした。ただ、ログインというアプリの基幹機能の修正だったため、本番へのデプロイは非常に緊張しました。

また、本番のバージョンアップ時にオプションの設定を忘れるというミスをしてしまいました。古いものが引き継がれると勘違いしていたことが原因です。その結果タイムゾーンの設定が狂ってしまい、表示されるべきコンテンツが表示されないという状況になってしまいました。普段からお世話になっている先輩に手助けしていただいたため無事に解決できましたが、もう少しで大惨事となるところでした。今後はもっと慎重に、情報集めからテストまでを行わなければいけないなと反省しています。

最後に

今回は、私がこの半年で任せていただいた主要な業務をいくつか紹介させていただきました。以上に取り上げたもの以外にも、

  • 新機能の追加
    • 5件
  • Django 化
    • 2件
  • DB・ネットワーク
    • 2件
  • セキュリティ(別ブログに記載)
    • 10件
  • コードの軽微な修正
    • 16件

と、様々な業務を体験できました。今回はシステムに関係する業務だけを取り上げましたが、他にも壁にドリルで穴を開けたり社内サーバーを移動させたりと、普段はできない業務も経験させていただきました。TORICO ではフルスタックであることが求められるため、業務も多岐にわたります。そこに魅力を感じた方は、ぜひ TORICO に応募してみてください。

実務経験0 入社一年目のエンジニアが任されたセキュリティの話

2021年春に入社しました、情報システム部の清瀬です。

TORICOの情報システム部では、セキュリティの向上を上半期の目標に掲げておりました。上半期も終わりに近づいてきたということで、新卒で入社した私がどんなセキュリティ向上のために携わってきた業務を記事にしようとおもいます。多くのシステムでは Django を使用しているためコード部分は Django 前提の話になります。

1. インフラレベルでの対応

AWS WAF の導入

  • SQLインジェクション対策
  • クロスサイトスクリプティング
  • その他、包括的な対策

TORICOでは一部の社内用アプリを除き、全てのサービスをAWSでデプロイしています。それら全てのサーバを悪意のあるリクエストから守るために、AWS WAF を導入しました。

AWS WAF は2019年に大幅アップデートされ、今までのものは WAF Classic と名称を変更しました。新しいWAFの最大のメリットは、るAWSによって作られたルールを簡単に導入できる点です。例えば AWSManagedRlesCommonRuleSet では、OWASPに記載された主要な脆弱性・リスク10個をカバーしてくれています。クロスサイトスクリプティングや悪意のあるボットを排除してくれるルールをボタン一つで導入できるため、包括的なセキュリティ対策をすることができます。もちろん、今までのようにオリジナルのルールを作ることができます。そのため、社内からのアクセスには一部のルールを緩和させたりなどもできるようになっています。

余談ですが、WAFの導入の検証を担当したのは、まだ私がインターン1週間くらいの時でした。検証のために簡易的な Django アプリを Ec2 にデプロイしてWAFの導入でクロスサイトスクリプティングやSQLインジェクションがきちんとブロックされるのかを検証しました。インターンなのにこんな重要そうな仕事をさせていただけるのかと驚いたのを今でも覚えています。

過去に使用していた IP アドレスのルーティングを解除

これはセキュリティとは少し違う話かもしれませんが、ドメインの整理などもしました。一部の未使用のドメインが、既に手放してある IP アドレスと結びついたまま放置されていました。もし IP アドレスが悪意のあるサイトに使われていた場合、最悪ユーザーを悪意のあるサイトに誘導してしまうということも起こり得ます。そうならないためにも、不要なドメインを整理しました。

2. アプリレベルでの対応

サービスのAdmin ・社内アプリ を社内からのみ許可する

  • 不正なアクセス対策
  • 個人情報の保護

こちらは Django の Middleware を使って対応しました。サブネットマスクの表現方法などが複数あるため IP を取り扱うのは面倒くさそうだなと思っていましたが、python には ipaddress という便利なモジュールが標準で搭載されています。このモジュールを使うことで簡単に実装することができました。このタスクは上記の WAF でも実装できますが、1つのALBで複数の社内アプリを制御していたためアプリレベルで対応することにしました。アプリレベルのため、各アプリに適した制限を導入することも簡単にできます。

ログインセッションキーを適切な設定にする

  • セッションハイジャック対策
  • CSRF対策

大事なセッションを管理してくれるクッキーにはセキュリティ対策は欠かせません。TORICOの全てのサイトで、その大切なCookieの設定が適切になるように対策をしました。具体的には HttpOnly Secure SameSite=Lax  の3つの設定です。これらの設定を簡単に説明すると、

HttpOnly: JavaScript からセッションにアクセスできないようにする。設定されていなかった場合、JS 経由で セッション情報が漏洩する危険がある。

Secure: https 通信でのみ セッションを送るようにする。設定されていなかった場合、暗号化されない Http 通信でセッションが送られてしまう危険がある。

SameSite: Strict, Lax, None の3つの設定がある。Lax の場合、別オリジンからの POST の時はセッションを送らない。None の場合、例えばECサイトの届け先を変更するフォームのある偽サイトからユーザーのセッションを使ってデータが送信されてしまう。

Django では 2系以上であれば settings で設定することができます。1系の場合は SameSite を設定することができません。クッキーセッションの注意点として、あまりにも厳しい設定にした場合、サービスがうまく動かなくなる可能性があります。特に SameSite は決済部分でエラーがでることがあるそうです。

ファイル出力時に追跡IDとログを残す

  • 情報漏洩元の究明

TORICOではできるだけ業務を自動化するために、いろいろな情報をCSVなどに出力します。万が一そういったデータが社外に流出した時、どのデータが、どこから流出したのかを特定しやすいように追跡IDをそれぞれのファイルに追記するようにしました。ログにはリクエストの情報を全てを保存しているため、いつ・誰が・どの検索ワードで CSV を出力したのかを追うことができます。 Django には DB へのログ出力機能はないため、DBに出力する場合には自作する必要があります。

3. データベースの対応

重要なフィールドの暗号化

  • 個人情報の保護
  • データ漏洩時の影響を最小限にする

重要なフィールドを可逆暗号化することで、万が一情報漏洩した時でもその影響を最小限に留められるようにしました。可逆暗号は Django のオリジナルのフィールドを作ることで簡単に対応することができました。具体的な実装方法についてはこちらの記事で書きました。また、一部の古い社内アプリではアカウントのパスワードがハッシュ化されていなかったため、 bcrypt という方法で不可逆暗号化もしました。

ソルトとペッパーの役割の違いなどが曖昧だったため、とても勉強になったタスクだったなと思っています。

最後に

上半期に行った主要なセキュリティ対策を紹介していきました。セキュリティは深刻な障害の原因になるため、普段のコーディングから気をつけていきたいと思います。

PMS&ISMS後編 Pマーク認定、ISO27001規格認証でのコンサルを選ぶポイントは、これだ・・・。


株式会社TORICO情報システム部の四斗邊です。

PMS、ISMS関連の後編です。
今回はP
マーク認証、ISO27001認証を取得する際のコンサルを選ぶポイントについて話したいと思います。

まずTORICOのブログをご覧になられている人ならご存知だと思いますが、TORICOは漫画のECサイトをメインに、デジタルコンテンツ配信、イベント事業行っている会社です。

従業員は150(20216月現在) を超え、事業規模の拡大に伴いPマーク認証、ISO27001認証を取得する流れになりました。

そんな私は入社早々にして取得するためのPJに参加したわけですが、今まで経験した会社に存在した規定などは、実はPマーク認証、ISO27001認証に基づいて行われていたんだなということがわかり、今まで関わった仕事を振り返り立ち返り興味深く関わることができたと実感しています。

ちなみにPMS
Pマーク認証とISMSISO27001認証の違いや説明については、前編をご覧ください。

作業内容は、今まで無法地帯だった箇所にメスを入れ、規律やルールを作成することが多かったと思います。具体的には、管理ルールの定まっていなかったPCの扱いについて、退勤時に施錠を徹底することなどです。

作成時は、これで良いのかと自問する不安の中での取得となっていました。

最終的には、「あ、これで良かったんだ」とある程度の経験と知識は蓄えることができたかなと思います。

さて前置きが長くなりました。

実はPマーク認証、ISO27001認証取得を経験した人が会社内に数名いると独力で取れるぐらいの取得しやすい認証なのですが、これを未経験者のみだけで挑戦すると、「何から手をつけていいのか」、「どうやったらいいのかわからない」ということが多すぎます。そんなときは、コンサルに頼んだほうが近道です。

弊社はもちろんコンサルに依頼することになりました。

ただコンサルを選ぶ際には以下の注意点を考慮されることを強くおすすめします。



1 Google Driveをつかってくれ!


弊社が担当していただいたコンサルは、情報保護の観点からかコンサル独自のアプリケーション、クラウドサービスを提供する会社でした。

その結果、保存する形式としてはofficeフォーマットでやり取りすることがほとんどでした。弊社はどちらかというとofficeなどを使わず共同で同時に編集作業できる点からGoogleのグループウェアを業務に多く取り入れている会社でしたので心底やりづらいです。

一旦編集したファイルをofficeファイルに変換しなければいけないことや、最新版であることが分かりづらいこと、共同編集できないなどGoogleのグループウェアの恩恵を受けられず効率が悪いと何度思ったか。

またクラウドストレージもコンサル独自のものがあったりと、Google Driveで共有しても対応してくれなかったというのがあるので、できれば事前にGoogle Driveなどグループウェアを共同で作業できるなどの事前確認は必要です。



2 リスクマネジメントは、一回マクロなしでやりませんか?


ISMS構築で一番大事なポイントといってもいいリスクマネジメント。

この作業を抜いではないといっても過言ではありません。

この作業を簡単に噛み砕くと「情報資産を特定して、リスクを把握して、驚異を数値化する」業務である。

この「作業の流れ」については、コンサルから説明を受けたので言葉では理解できるのですが、コンサルを交えて実践となった際に、事前に弊社の情報資産を予めピックアップした情報を渡していたので、書式に基づいて処理の流れを見せてくれるのかと思いきや、その部分は割愛されマクロでの処理の流れを説明されるという。

その場では、特に不明な点はないと思われたのですが、後々実際リスクマネジメントを回す際に見返すと「何やってんだ、この作業」となってしまいました。

そのため、できる限りマクロを交えず、実際の業務として落とし込めるまでコンサル立会のもと行うことを強くおすすめします。

特に書式やルールについては、コンサル提供の書式でもいいですが、コンサルと相談して独自の物を作って作業や管理のしやすいように改良することを強くおすすめします。

その場で頑なに拒んでくるコンサルは、実際のISMS構築やPMS構築の実務よりも審査を通ることのみを考えているコンサルではないかなと思われますので、注意が必要です。

 


3 審査期間の情報を豊富に持っているか?


弊社のコンサルでよかった点として、審査期間の情報を豊富に持っていた点があげられます。

実はPマーク認証、ISO27001認証の審査機関は1つではありません。

その審査機関の特徴といった情報を持っていることもコンサルの選ぶポイントと言えます。

大まかに以下の点があげられる。
① 審査の難易度
② 審査において指摘される傾向のあるポイントや過去や例
③ 審査にかかる費用

これらのデータや経験を持っているコンサルはなかなか頼りになるのではないかなと思います。

以上3つのポイントをご説明しました。

これらを踏まえてPマーク認証、ISO27001認証関連のコンサルを選ぶ際の参考にしてください。

前編と後編とかなり文字数多めで書きました。なかなかのボリュームとなっておりますのでお時間あるときにみていただけると大変嬉しく思います。
また、ネタがあればブログを書きたいと思います!

PMS&ISMS前編 PMSとISMSの違いについて


はじめまして株式会社TORICO情報システム部の四斗邊です。

いきなりですが、PMSとISMSについてお話したいと思います。

なぜこの話をしようと思ったかといいますと、株式会社TORICOでは、2021年1月にPマーク認証を取得、2021年2月にISMS認証(別名ISO/IEC 27001)を取得したからです!

この機会に、TORICOを知っていただこうと思い技術ブログとして若干逸れる内容ですが情報系のブログ記事としてPMS及びPマークとISMSに関する内容を前編と後編2回に分けてお話ししたいと思います。

まず、前編としてPMSおよびPマークとISMSの違いについて、後編はこれら認証取得時のコンサルの選ぶポイントをお伝えできればと思います。

それでは、前編スタート


1 PMS
Pマークについて


さっそくですがPMSとは「Personal information protection Management Systems」の略で個人情報保護マネジメントとも呼ばれています。

PMSは、個人情報保護を目的とした継続的な組織の設立や運営管理方法の制定と言った仕組みのことを指し「システム」と書かれていますが機械やプログラミングで構築したソフトウェアなのではありません。

具体的な仕組みとして継続的な個人情報管理台帳の作成と更新、漏洩リスクへの対策、漏洩時の対応など、実際に運用したPMSを評価する会議体を実施するなどがあげられます。

PMSによる個人情報の適切な取り扱いができるか審査を経て、JIS Q 15001規格に定義された個人情報保護マネジメントを実施する適合事業者と認められるとPマーク認証を取得できます。

ただ、Pマーク認証を取得していないからと言って企業には個人情報保護マネジメントが無いということでなく、審査を得ていない場合や、JIS Q 15001規格に沿っていない独自の個人情報保護マネジメントの構築している場合が企業には考えられます。


2 ISMSISO27001について


ISMSとは「information security management system」の略で情報セキュリティマネジメントシステムとも呼ばれています。

ISMSは企業が保有する情報資産を保護する目的のため継続的な組織の設立や運営、管理方法の制定など仕組みを意味します。PMS同様、「システム」と書かれていますが機械やプログラミングで構築したソフトウェアなのではありません。

具体的な仕組みとして、継続的な情報資産の作成と更新、情報を「機密性」「完全性」「可用性」ごとにリスクを特定(リスクアセスメント)し、リスクに合わせて適切に管理改善していくことです。

ちなみにISO27001は、ISMSの国際規格を指します。

別名ISO/IEC 27001、JISQ27001とも呼ばれています。

Pマーク同様、ISMS構築後、審査を得て適合事業者と認められるとISO27001認証を取得できます。

ちなみに、PMSとISMSは個人情報の部分において重複します。

ISMSは全ての企業情報を保護対象としているため上位互換のように表現されますが、あくまで企業の情報資産の一部として個人情報を扱うISMSに対して、個人情報保護を重要視している点で差があります。具体的としては、PMS(JIS Q 15001)では個人情報の目的や取得方法の特定や個人情報の開示、個人情報を第三者へ提供する場合のルールなど規格内に盛り込まれています(問い合わせページとかでやたら同意を求めてくるこれです。)

また、Pマーク認証は法人単位、ISO27001認証は部門やセクション単位で取得できるようです。


3 メリットについて


TORICOではPマーク認証とISO27001認証を取得する過程で既にメリットはありました。

取得に伴い実施したリスクアセスメントや個人情報の洗い出しによって日常の業務において到底意識しない部分に目を向ける機会になったからです。また、それまで関わりのなかった部署に対しても協力していただく機会もあったので全社的に危機意識を培うチャンスとなったと思います。

またリスクを未然に防ぐためにPMSとISMS構築が一役買いました。

これまで個人情報や企業の情報など保管期間を明確に決めずに保存していたり、バックアップを取らなかった部分については改めるきっかけになったと思います。

本来のメリットとしては、公的機関への入札時などPマーク認証とISO27001認証を取得が条件になっているようで、入札時のメリットがあるようです。

またPマークやISO27001認証を取得している企業から取引の際、信用の目安になり、比較的に取引しやすいなどもメリットに挙げられます。

デメリットは、業務フローが増えたことと、書式などのフォーマットが増える点でしょうか。

以上、PMSとISMSの違いについてお話ししました。

後編に続きます。

参考文献


システム管理者の会 

第4回 プライバシーマーク(Pマーク)と他の認証制度の統合


帝国データバンクネットコミュニケーション

ismsとiso27001は何が違うのですか?

WORKーPJ

ISMSとプライバシーマーク(PMS)とは?概要と相違点、どちらを取得するべきか

エンジニアの知らないISBNの話

本には商品判別のためのバーコードがついています。
いわゆるISBNコード(JANコード)です。
ECショップでは本を取り扱うために本の書誌を記録するテーブルを作成しますが、その際にint型でいいだろう? ユニーク制約でいいか?とおもってはいけないません。

先に結論を書いてしまうと

  • 文字が含まれる可能性がある
  • 先頭に0が来る可能性がある
  • ユニークではない

char型にして、ユニーク制約をつけないほうがよい理由

1. 文字「X」が含まれる可能性がある

ISBNコードには10桁と13桁があります。
10桁ISBNコードは9桁+チェックデジット1桁で構成されます。
古い体系ですが、一部で使うことがあり、チェックデジットに文字「X」がはいる可能性があります。
これはチェックデジットがモジュラス11、ウェイト10-2という方法で算出されるためです。
計算方法をphpで書くと

/**
 * ISBN10桁のチェックディジットを返す
 */
function getCheckDigit10($jan9)
{
    $sum = 0;
    $cnt = 0;
    for ($i = 10; ; $i--) {
        if ($cnt > 10) {
            break;
        }
        $sum += substr($jan9, $cnt, 1) * $i;
        $cnt++;
    }
    $remainder = $sum % 11;
    if ($remainder == 0) {
        return 0;
    }
    $check_digit = 11 - $remainder;
    if ($check_digit == 10) {
        $check_digit = 'X';
    }
    return $check_digit;
}

2. 先頭に0がつく可能性がある

ISBNコードと書きましたが、実際にバーコードになっているのはJANコードです。
そしてJANコードは先頭が「0」になる可能性があります。
本を売るショップとして取り扱いそうなJANコードは3種類。

  1. 一般商品JANコード
    45,または49で始まる12桁+チェックデジット1桁の13桁JANコード。
    45と49は日本に割り振られた国コードなので固定。
    雑誌やグッズなどに使われています。
  2. 書籍JANコード
    9784で始まる12桁+チェックデジット1桁の13桁JANコード。
    978は世界共通の書籍コード
    4は日本に割り振られた国コードなので固定。
    いわゆる13桁ISBNコード。
    コミックや書籍などに使われています。
    バーコードは2段になっており、上段は書籍JANコード、分類・価格のコード。
  3. インストアコード
    02、04、20〜29で始まる12桁+チェックデジット1桁の13桁JANコード。
    ポイントカード、会員証やレジ袋などの管理・販売用に使われる、小売店が独自につくって使用してよいJANコード。
    20〜29始まりのJANコードを使用すればいい話だが、02、04も準備しておくに越したことはない。

JANコードのチェックデジットはモジュラス10、ウエイト3-1という方法で算出されるため0〜9の数字になります。

/**
 * ISBN13桁のチェックディジットを返す
 */
function getCheckDigit13($jan12)
{
    $sum_odd = 0;
    $sum_even = 0;
    for ($i = 11; $i >= 0; $i--) {
        if ($i % 2) {
            $sum_odd += substr($jan12, $i, 1);
        } else {
            $sum_even += substr($jan12, $i, 1);
        }
    }
    $sum = $sum_odd * 3 + $sum_even;
    $division = $sum % 10;
    return (10 - $division) % 10;
}

3. 同じJANコードが使われる

JANコードは同じコードが存在するのでユニークではありません。

  1. 書籍JANコードの場合
    「今現在販売中の本」と限定すれば書籍JANコードはユニークになります。
    ただし、本が絶版になったあと、そのJANコードは別の本に付番されます。
    そのため、今までに発売したすべての本のJANコードだと同じJANコードをもつ本が複数存在します。
  2. 雑誌の一般商品JANコードの場合
    雑誌に付番されている雑誌コードから一般商品JANコードが作成されます。
    その際に西暦の下1桁を使用しているため、10年ごとに同じコードになります。

以上がISBNコード(JANコード)をint型、ユニーク制約にしては行けない理由でした。
10桁と13桁のどちらも記録できるようにしてあると便利かもしれません。

海外回線からのブラウザリクエストを日本のオフィスから SOCK5プロキシを使って行う

海外回線からリクエストされた時に応答を変えるようなサービスで、実際の海外からアクセスした時のレスポンスを日本国内のPCから試したい場合は、SOCKS プロキシを使うと便利です。

実際に起動した、自分のコントロールしているサーバを使います。怪しげな VPN サービスを使わなくていいため安全です。

 SSHログインできる海外のサーバを用意する

海外リージョンで起動している AWS の EC2 などを使います。SSHでログインできるようになっており、HTTP関係のアウトバウンド通信がブロックされていなければOKです。

使っているPCで、 -D オプションをつけて SSHログインする

$ ssh -D 10080 user@example.com

これを行うと、普通にSSHのセッションが開始されますが、そのPCの 10080 ポートで SOCKS v5 プロキシが使えるようになってます。

SOCKS プロキシの接続先を、localhost:10080 にする

システム環境設定 から、プロキシの設定を行います。

Firefox はブラウザ内に固有のプロキシ設定を持っており、そこで特別な設定を行うこともできますが、デフォルトでは OS のプロキシ設定を使うようになっています。

接続を確認する

確認くんhttps://httpbin.org/ip を見て、接続元IPアドレスが変わっていることを確認できます。

Selenium でプロキシ設定済みのブラウザを起動する

システム環境設定を開いてプロキシの設定を変更するのは面倒ですが、Selenium を使えばプロキシ設定済みのブラウザインスタンスを起動できます。
( 127.0.0.1 となっている箇所は localhost でも同じです。)

Pythonスクリプトの例

Chrome

from selenium import webdriver
options = webdriver.ChromeOptions()
options.add_argument("--proxy-server=socks5://127.0.0.1:10080")
options.add_argument('--proxy-bypass-list=""')
driver = webdriver.Chrome(options=options)
driver.get('about:blank')

Firefox

from selenium import webdriver
profile = webdriver.FirefoxProfile()
profile.set_preference('network.proxy.type', 1)
profile.set_preference('network.proxy.socks', '127.0.0.1')
profile.set_preference('network.proxy.socks_port', 10080)
profile.set_preference('network.proxy.no_proxies_on', '')
driver = webdriver.Firefox(firefox_profile=profile)
driver.get('about:blank')

ターミナル(シェル)でプロキシ設定済みの Chrome を起動する

open -a "Google Chrome" --args --proxy-server="socks5://127.0.0.1:10080" --proxy-bypass-list=""

Google Chrome や Chromium の場合、コマンドラインオプションでプロキシの設定を行うこともできます。ただし、一度起動してしまうと、プロセスを完全終了しないと設定がクリアされないので戻す時が若干面倒です。

Amazon Pay Checkout v2 API の署名 (RSA-SHA256 (RS256) + RSA PSS Padding) を Python で行う

Amazon Pay の API クライアントを書く際、Amazon のAPIサーバに送信するリクエストに、RSA-SHA256, RSA PSS パディングを使って署名を作り、リクエストに含めて送信する必要があります。

Java や Node はクライアントライブラリがあったので、それを使って簡単に署名できたのですが、弊社 TORICO ではサーバサイドは主に Python を使っており、既存のクライアントライブラリは無かったため、Node のライブラリを参考に署名コードを書きました。

Amazon Pay のAPI

今回は、Amazon Pay の Checkout v2 API を使う必要がありました。

https://developer.amazon.com/ja/docs/amazon-pay/intro.html

リクエストヘッダに含める署名については、
この CV2 (Checkout V2) の 署名リクエストのページに解説があります。

このページを見ていくと、「署名を計算します」の箇所に

署名を計算するには、ステップ2で作成した署名する文字列に秘密鍵を使用して署名します。 SHA256ハッシュとソルト長20のRSASSA-PSSアルゴリズムを使用します。結果をBase64エンコードして、この手順を完了します。 RSASSA-PSSを使用して計算されたすべての署名は、入力が同じであっても一意であることに注意してください。

とありますので、この「 SHA256ハッシュとソルト長20のRSASSA-PSSアルゴリズム」を Pythonでコーディングする必要があります。

ところで、この最後の「 入力が同じであっても一意であることに注意してください」は、「〜一意でない」の間違いじゃないですかね。英語版ページは調べてませんが。

Amazon Pay Scratchpad

https://pay-api.amazon.jp/tools/scratchpad/index.html

検証リクエストを発行できるサイトが用意されていますので、署名ロジックの確認に使うと良さそうです。
私は、今回の開発中は存在を知らなかったので、使っていません。

Node.js の場合

node.js の場合は、クライアントライブラリは
@amazonpay/amazon-pay-api-sdk-nodejs があり、これを使うと署名を含めたAPIリクエストが一発で行えます。

ヘッダの署名を行っているコードは

https://github.com/amzn/amazon-pay-api-sdk-nodejs/blob/master/src/clientHelper.js#L142 このあたりで、
「 SHA256ハッシュとソルト長20のRSASSA-PSSアルゴリズム」のコードは
https://github.com/amzn/amazon-pay-api-sdk-nodejs/blob/master/src/clientHelper.js#L84 ここです。

Python の場合

上記 node.js 相当のコードを書くわけですが、hmac ライブラリには相当のコードはありません。

RSA や パディングの基礎的なロジックは cryptography に入っており、実際の使い方は PyJWT がいい感じになっているので、PyJWT を参考にします。

https://github.com/jpadilla/pyjwt/blob/master/jwt/algorithms.py#L232

PSS パディングはここです。

https://github.com/pyca/cryptography/blob/main/src/cryptography/hazmat/primitives/asymmetric/padding.py#L19

この PSS の第一引数の _mgf ってなんだ、と思いましたが、同モジュール中にある
MGF1(RSAAlgorithm.SHA256())
を入れたら動きました。

署名部分のコード

署名部分のコードはこのようになります。

import base64
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.primitives.serialization import load_pem_private_key

private_key = b'''-----BEGIN PRIVATE KEY-----
MIIEvQIB....
....N/Qn4=
-----END PRIVATE KEY-----'''

string_to_sign = 'AMZN-PAY-RSASSA-PSS\nxxxxxxxxxxxxxxxxxxxxxxxx'

key = load_pem_private_key(private_key, password=None)

signature = key.sign(
string_to_sign.encode('utf-8'),
padding.PSS(padding.MGF1(hashes.SHA256()), 20),
hashes.SHA256())

signature = base64.b64encode(signature).decode('utf-8')

後になって気づきましたが、PyJWT に RSAPSSAlgorithm という、今回の用途にぴったりなクラスがあったので、これを使うともうちょっとシンプルなコードになるかもしれません。

署名元の文字列の生成も含めたコード

checkoutSessions を行うコードはこのような感じです。

node.js のコードを参考にした箇所がいくつかあり、それらは実際には使われない、不要なコードになってます。

import base64
import datetime
from hashlib import sha256

import requests
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.primitives.serialization import load_pem_private_key

private_key = b'''-----BEGIN PRIVATE KEY-----
MIIEv....
....N/Qn4=
-----END PRIVATE KEY-----'''

config = {
'publicKeyId': 'SANDBOX-AEXXXXXXXXXXXX',
'privateKey': private_key,
'region': 'jp',
'sandbox': True,
}

constants = {
'SDK_VERSION': '2.1.4', 'API_VERSION': 'v2', 'RETRIES': 3,
'API_ENDPOINTS': {'na': 'pay-api.amazon.com', 'eu': 'pay-api.amazon.eu', 'jp': 'pay-api.amazon.jp'},
'REGION_MAP': {'na': 'na', 'us': 'na', 'de': 'eu', 'uk': 'eu', 'eu': 'eu', 'jp': 'jp'},
'AMAZON_SIGNATURE_ALGORITHM': 'AMZN-PAY-RSASSA-PSS',
}

checkoutSessionId = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"

options = {
'method': "GET",
'urlFragment': f"/v2/checkoutSessions/{checkoutSessionId}",
'headers': {},
'payload': ''
}

pay_date = datetime.datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%SZ')

headers = {
'x-amz-pay-region': config['region'],
'x-amz-pay-host': 'pay-api.amazon.jp',
'x-amz-pay-date': pay_date,
'content-type': 'application/json',
'accept': 'application/json',
'user-agent': 'amazon-pay-api-sdk-nodejs/2.1.4 (JS/14.15.1; darwin)',
}

lowercase_sorted_header_keys = list(sorted(headers.keys(), key=lambda x: x.lower()))
signed_headers = ';'.join(lowercase_sorted_header_keys)

canonical_request = [
options['method'],
options['urlFragment'],
'', # GETパラメータだが一旦無し
] + [
f'{h}:{headers[h]}' for h in lowercase_sorted_header_keys
] + [
'', # 空行入れる
signed_headers,
sha256(options['payload'].encode('utf-8')).hexdigest()
]

canonical_request_bytes = ('\n'.join(canonical_request)).encode('utf-8')

string_to_sign = constants['AMAZON_SIGNATURE_ALGORITHM'] + '\n' + sha256(canonical_request_bytes).hexdigest()

key = load_pem_private_key(config['privateKey'], password=None)

signature = key.sign(
string_to_sign.encode('utf-8'),
padding.PSS(padding.MGF1(hashes.SHA256()), 20),
hashes.SHA256())

signature = base64.b64encode(signature).decode('utf-8')

headers['authorization'] = \
f"{constants['AMAZON_SIGNATURE_ALGORITHM']} " \
f"PublicKeyId={config['publicKeyId']}, " \
f"SignedHeaders={signed_headers}, " \
f"Signature={signature}"

response = requests.get(
f"https://pay-api.amazon.jp{options['urlFragment']}",
headers=headers
)
print(response)
print(response.json())
Search