RareJob Tech Blog

レアジョブテクノロジーズのエンジニア・デザイナーによる技術ブログです

パスワードや API key を history コマンドに出さないようにする

つい先ほど今日のブログ担当だったことを思い出した塚田です。 なんか書きますと言って、思いついたのがこのシンプルなネタです。

最近では ssh することは少なくなりましたが、自分以外の人も使う踏み台サーバ(bastion server)のような 環境で、パスワードやAPI key のような クレデンシャルな情報を入力しなければならない時があります。

普通は紳士的にやらないですが、システム上は root アカウントから su してしまえば ユーザーが実行した history ログが見えてしまいます。

入力したクレデンシャルな情報はせめて過去の実行コマンドの history コマンドに出てきて欲しくない場合があります。

今回は history に出さなくなる方法を紹介します。 といっても超絶シンプルです。

HISTCONTROL=ignorespace

先頭にスペースを入力してから コマンドを入力すると、history 履歴に残らなくなります。

 export password=xxxxxxxx

それでは。

Kaggle参加してみた話

レアジョブEdTech Lab.の齋藤です。

この1ヶ月程でAPEXにハマりました。人生初FPSなので上位3チームに入れると嬉しいくらいのテンション感で戦っています。
現実ではKaggleで上位10%を目指して戦っていたので、今回はそのことを書いていきたいと思います。

Kaggleとは

ご存知の方も多いと思いますが少し説明させていただきます。
Kaggleとはデータサイエンスコンペティションを提供しているプラットフォームです。1コンペティションあたり約2〜3ヶ月程度の期間で、スコアを競い合っていきます。

www.kaggle.com

目標と参加体制

目標

冒頭に上位10%を目指したと書きました。
では、なぜ今回の目標が上位10%に設定したかというと、単純に銅メダルボーダーであるということです。

Kaggleにはメダル制度が存在しています。コンペの参加チーム総数でメダル獲得基準は若干変わりますが、順位に応じて金銀銅のメダルが与えられます。
詳細はKaggle Progression Systemを参照してください。

ということで、今回参加したコンペでは銅メダルの獲得ラインが全体の10%に入るというものだったので10%を目指しました。
1143チームが出場しているので、114位がボーダーです。

メンバー


チームは3人、全員EdTech Lab.のメンバーで出場しました。それぞれのKaggle経験は以下のような状態で開始しました。

  • 齋藤(私): 2年ぶりくらいのKaggle。
  • Kさん: 初Kaggle。
  • Cさん: しっかりKaggler。

2/3がほぼほぼKaggle初心者の状態での開始となりました。ちなみに私はチーム参加初めてです。

進行


チーム全体としては週に3時間ほど時間を作りコンペに参加しました。
ナレッジや議論、Kaggle上の気になるDiscussion・Notebookなどの情報共有をgoogle meet上で行い、その後個人作業という形で進行しました。
また、それらの情報はGithubでメンバーのみのプライベートリポジトリを作成し管理を行いました。

コンペ概要

参加したコンペ

で、どのコンペに参加したのかですが、 Rainforest Connection Species Audio Detection | Kaggleに参加しました。鳥蛙コンペと呼ばれているコンペです。

www.kaggle.com

課題

熱帯雨林に生息する鳥やカエルの種類を与えられた音声データから分類するもので、いわゆる多クラス分類のタスクになっていました。

データ

60秒の音声データが与えられました。
また、学習データは現行のアルゴリズムが判定した結果に対して

  • 専門家が正しいと判断したもの(train_tp)
  • 専門家が間違いと判断したもの (train_fp)

の2種類が与えられました。

しかし、テストデータは専門家によるアノテーション結果でした。

これにより、ラベルの分布が学習時とテスト時で大きく変わり、データシフトが起きやすい状態が発生しました。

難しい点

  • データをtrain_tpだけにするとデータ数が極端に少なくなってしまう。

  • missing labelなものやノイズが多く含まれている

  • submitすると精度が下がりがち(Validationが機能不全を起こしている)

手法

Submitしたモデル

3人の作成したsubmission用csvを混ぜたものを提出しました。

アプローチ

基本的にはmixupというaugmentation手法を使用してデータをかさ増ししていきました。
簡単な説明としては、2つのデータを混ぜて新しいデータを作成する手法になります。
嬉しい点は、ドメイン知識がなくてもデータが増やせるというところにあります。
そのため、

  • ある程度正しくアノテーションされているデータであるtrain_tp数が少ない

  • 生物の鳴き声音に対するドメイン知識を持っていない

という状態なので非常に助かりました。

詳細な数式や理論は以下の論文を参照してもらえるとわかるかと思います。

arxiv.org

予測はResNetで行いました。画像認識において有用な手法として挙げられている物です。
今回は音声データをメルスペクトログラムに変換したものを使用したので、画像系の分類タスクと捉えられるのでこの手法を取りました。

本音では、このコンペに参加したとき

ResNet34 More Augmentations+Mixup+TTA (Inference) | Kaggle というNotebookが公開されていたためとっつきやすいと思い選択しました。

余談ですが、上位解法として自力でアノテーションをしたというものがあり、”力”を感じました。 www.kaggle.com

結果

肝心の結果ですが... f:id:mag_sn:20210310200509p:plain メダル獲得ならずでした。
取得まで0.08程足りなかったというあまりにも惜しすぎる結果でした。 172位ほどshake upしましたが、全体の13.2%の位置までで止まってしまい、もう一息〜という感じでした。

振り返ってみて

今回Kaggleに参加してみての反省ですが、

  • チームのタスク管理が難しかった
  • データへの理解が足りていなかった

この2点が大きな課題だと感じました。

特にタスク管理は、チーム内の議論が手法共有などKaggleのDiscussionに寄ってしまい、お互い着手しているタスクを認識し切れていない状況を発生させてしまいました。
作業を個人に任せすぎた面もあり、作業報告が「今こういう手法でやってます」という報告になってしまい手法被りも事前に検知できなかったという点もあります。 結果的に近い手法を使用した実験を行っており、アンサンブル時にモデルのバリエーションが少ないという状態となり、スコアを上げ切れなかった原因にも繋がってしまったかなと反省です。

次回は行う作業や使用手法を事前に共有・管理できるようにしくみを作っていきたいと思います。

チーム参加でよかったことは、

  • 情報が追いやすい
  • 日本語で議論可能な場を持てるという安心感
  • モチベーションが維持される

といったものが挙げられると思います。
気になるDiscussionや論文といった情報が逐次的に共有されるので、一人での参加よりも多くの情報を追っていけるのは非常に助かる側面だと思いました。
また、それらの情報に対して日本語で議論できるので思考や理解がスムーズなのも利点だなと感じました。

最後に

今回はかなり悔しい結果に終わってしまいました。
というわけで、新メンバーを1名追加し Indoor Location & Navigation | Kaggle に参加してメダルを目指します。

www.kaggle.com

私たちの戦いはまだまだこれからです。次回の結果にご期待ください。

参考

https://connpass.com/event/204547/presentation/ https://kutohonn.hatenablog.com/entry/2021/02/18/224002 https://nonbiri-tereka.hatenablog.com/entry/2020/01/06/082921 https://qiita.com/iwashi-kun/items/fec710294e01a48c9e5d http://wazalabo.com/mixup_1.html https://openreview.net/forum?id=r1Ddp1-Rb https://www.inference.vc/mixup-data-dependent-data-augmentation/ https://www.kaggle.com/c/rfcx-species-audio-detection/discussion/220522 https://ichi.pro/merusupekutoroguramu-o-rikaisuru-277775661583955 https://www.bigdata-navi.com/aidrops/2611/

毎年恒例の iOS メジャーアップデートで iOS 14 対応した話しを振り返ります

APP/UX チームの玉置(@tamappe)です。
今回は今年に入って iOS 版レアジョブ アプリの iOS 14 対応が完了しましたので、どんな風に変更したのかについてまとめてみました。 簡単に言えば、みんな大嫌いな iOS アプリのメジャーアップデート対応が完了した話です。

毎年恒例の iOS メジャーアップデート対応

iOS アプリエンジニアは毎年この iOS のメジャーアップデートに苦しめられています。 毎年というので去年の今頃もメジャーアップデートをしていました。 当時は墓穴を掘ってしまい、ブログ記事すら書けないぐらいにメンタルをやられました。 またレアジョブに入社して最初に行ったタスクも Swift 3 系からのアップデート作業でした。エモいですね。

rarejob-tech-dept.hatenablog.com

今までSwiftのバージョンアップデートで破壊的な変更が入るたびに iOS アプリエンジニアは悲鳴を上げてきました。 なぜかというと Swift のバージョンを上げるとだいたいビルドが通らないからです。 さらに使用しているライブラリが対応できていなかったなどの要因で、端末の OS によっては UI が変わるといった謎現象も多くエンジニアはその対応に終われます。 今回もご多分に漏れず苦しめられるかとヒヤヒヤしていましたが、なんとわずか1週間で完了できてしまいました。

主な変更点

主な変更点はこちらになります。

差分内容 変更前 変更後
Swift version Swift 5 Swift 5.3
iOS のサポートバージョン iOS 11 ~ 13 iOS 12 ~ 14
Xcode Xcode 11.5 Xcode 12.3
CocoaPods 1.6.0 1.10.0

(まだSwiftPM対応はできていません。 CocoaPods を使っているのか遅れてるなぁと思う方は、是非とも弊社に入社してレアジョブアプリに SwiftPM を導入していただけると助かります。)

それでは、実際に対応した作業と苦しめられたことについてお話していきます。

移転先の Xcode のダウンロードバージョンを確定させる

iOS のメジャーアップデートをする時にいつも腰が重くなりがちなのが、アップデートする Xcode のバージョンを何にするかということです。

メジャーアップデートするたびに頭を悩ませるのはアップデート後の Xcode のバージョンです。 僕はいつも Appleアーカイブから「 Safari 上で」 Xcode をダウンロードしますが、このタスクの度に検証のためにダウンロードする Xcode のアプリケーションの数が増えてしまい Mac PC のストレージにダメージを蓄積していきます。

https://developer.apple.com/download/more/

複数の XcodeMac のローカルに保存して共存させるには Xcode アプリのリネームが必要になりますね。 なので、「Xcode_xxx.app」のアプリケーションがダウンロードするバージョン数だけ増えることになります。「xxx」はバージョンの数字を入れています。

そして、 Google Chrome から上記のアーカイブページでダウンロードするといつも解凍に失敗します。 そのため、いつも Safari からダウンロードしています。

Xcodeは10GB近くあるめちゃでかアプリケーションなので、2つほどダウンロードが完了する頃には半日が終わっていることになります。 今回はこの当時の最新版の Xcode 12.3 と 12.1 に狙いを定めてダウンロードしてみました。

結果的には Xcode 12.3 で大きなエラーが起きずにビルドが成功しましたので Xcode 12.3 で確定しました。 あまり頻繁に Xcode のバージョン上げをすることは宜しくないと思っておりますので、できるだけ最新にして長持ちできる方針を貫いています。

iOS Deployment Target の minimumを iOS 12 以降に変更する(iOS 11 サポートを切る)

無事 Xcode のダウンロードが完了したら、だいたい次に行うのが iOS のサポートバージョンを変更する作業です。 業界の定石は特にありませんが、サポートする iOS のメジャーバージョンは (最新メジャーバージョン - 2)で計算しています。 iOS 14 が最新版なら iOS 12 ~ 14 という感じです。 もっと比較的余裕のあるアプリでしたら (最新メジャーバージョン - 1)でいいかもしれません。 iOS 11 は今ではユーザーが2%ほどです。 サポートバージョンを狭くすればするほど、当然ですが運用が楽になります。古いAPIを切ることができるためです。

レアジョブアプリは iOS 14 対応をリリースしてからは iOS 11 のサポートを切ることができました。

このバージョンを変更する方法はとても簡単で XcodeDeployment Target の項目を変更すれば可能です。

f:id:qed805:20210201154908p:plain
iOS suport version

これで iOS 12 以上からになります。

Swift 5.0 から 5.3 へアップデート

次に頑張りたかったことが、 Swift バージョンの変更でした。 Xcode 11.5の時は Swift 5.0 でしたが Xcode12.3 では Swift 5.3 が使えますのでこちらに変更しました。 また、レアジョブアプリはライブラリ管理ツールに CocoaPods を使っています。その関係で、 Pods の Swift バージョンも Swift 5.3 に変更する必要がありました。

そのため、 Podfile ファイルで次のように変更しました。

post_install do |installer|
  installer.pods_project.targets.each do |target|
    target.build_configurations.each do |config|
      config.build_settings['SWIFT_VERSION'] = "5.3"
      config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '12.0'
    end
  end
end

これでターミナルから pod update でライブラリの Swift のバージョンが変わります。

CocoaPods のアップデート (1.6.0 から 1.10.0)

ですが、ここではそう上手いこといきませんでした。 使用しているライブラリによっては Pods のバージョンが足りないと言われてしまいました。

そのため、 CocoaPods のバージョンを上げる必要がありました。 レアジョブアプリは Pods のバージョン管理は Gem で行っていましたので、 Gemfile を修正してから

bundle install --path vendor/bundle

を実行して Pods のバージョンをあげました。 これで再度、 Pods の更新を行うと無事ライブラリのバージョンアップが完了しました。

一部 UI のレイアウト崩れの改修

これでビルドが成功してめでたしめでたしであれば非常に楽なのですが、 iOS のメジャーアップデートの作業の本番は大抵ここからスタートになります。 できるだけ UI のレイアウトが崩れていないことを願いながらシミュレーターなり実機で画面を確認していきますが、中には画面が開こうとするとアプリがクラッシュしたり UI がガッツリ崩れていたりします。

ここで予め用意しておいたテスト前に実施するテスト項目書を使ってマニュアルで全ての画面を表示させながらアプリを操作して挙動を確認しました。 今回は一画面だけ UI が崩れていましたのでその部分が治るように修正しました。

まさかの iOS 13 端末では UI が崩れず iOS 14 でのみ崩れる不具合で、改修後も複数のシミュレーターを起動してみたりと確認作業にかなり時間を取られました。

総括

そんな感じでレアジョブアプリは無事に iOS 14 対応が完了しました。 レッスンルームも綺麗になりましたし、前回の投稿からユーザーが劇的に増えましたので是非レアジョブアプリを使っていただければと思います。

CloudFrontの署名付きURLを利用したコンテンツ配信

はじめまして、7月に入社しましたサービス開発チームの越です。

今年は初めて東京で年を越したので、せっかくなら東京の郷土料理を食べようと思い、人生で初めてどじょう鍋を食べました。

どじょう鍋(柳川鍋)では、どじょう特有の泥臭さをごぼうが取り除いているとのことでした。

開発者から泥臭い仕事を取り除いてくれる、どじょう鍋で言うごぼうのようなツールやサービスに私も日々お世話になっています。

今回は業務で使用したCloudFrontの署名付きURLによるコンテンツ配信について書こうと思います。

AWS CloudFrontのドキュメントを読んで自分が理解するときにつまずいたところをまとめてみました。

AWS CloudFrontの署名付きURLをこれからはじめて利用する方の助けになれば幸いです。

AWS CloudFrontとは

以下AWSのドキュメントより引用

Amazon CloudFront は、ユーザーへの静的および動的なウェブコンテンツ (.html、.css、.js、イメージファイルなど) の配信を高速化するウェブサービスです。CloudFront では、エッジロケーションというデータセンターの世界的ネットワークを経由してコンテンツを配信します。...

CloudFront自体の説明はAWSのドキュメントを読むのが確実なので、ここでは割愛しますが、軽く全体の概要だけ知りたい場合は以下のAWS Summit Tokyo 2014のセミナーがまとまっていてわかりやすかったです。

AWS Summit Tokyo 2014

セミナーでは以下の3点について話されていて、署名付きURL(Signed URL)を用いたコンテンツ配信についてCloudFrontの利用をするときの概要やTipsについて説明があり、CloudFrontについて最初に理解するときに参照しました。

  • Amazon CloudFrontとは
  • CloudFrontによるサイト高速化
  • CloudFrontによるセキュア配信

署名付きURL(Signed URL)

署名付きURL(Signed URL)とは

有効期限を指定したワンタイムのデジタル署名付きURLでコンテンツにアクセスできるようにする機能で、主にコンテンツを保護する必要がある場面で利用するものになります。

たとえば、特定の会員やコンテンツを購入された方に対して、ダウンロードを許可するような場面になります。

デジタル署名とは

自分の言葉で簡単に説明できないので、以下より説明を引用させていただきます。 https://wa3.i-3-i.info/word14396.html

デジタル署名とは

コンピュータの世界のハンコ

であり

主に他の人に送るファイルにくっつけるデータで「そのファイルは○○さんが作ったやつですよ~」と「そのファイルは悪い人に改ざんされていませんよ~」を証明するものです。

補足として、AWS CloudFrontの署名付きURLで使用するデジタル署名はRSA署名方式が利用されているので、有効期限の設定には気をつけて利用します。

RSA署名方式の場合、その安全性の基礎となっているのは素因数分解問題の困難性であり、計算量的に困難であった素因数分解ができる確率が高まるという意味で、署名が生成されてから時間が経つほど、安全性が低下します。

署名付きURLの作成

実際に署名付きURLをを利用するときには、まず既定ポリシーとカスタムポリシーのどちらを利用するか選択し、あとは選択したポリシーのフォーマットに合わせて署名を作成し、URLを組み立てるだけになります。

署名付きURLの既定ポリシーとカスタムポリシーの選択

まずはAWSのドキュメントにある比較表を引用して、違いを確認していきます。

説明 既定ポリシー カスタムポリシー
ポリシーステートメントを複数のファイル用に再利用できる。ポリシーステートメントを再利用するには、Resource オブジェクトでワイルドカード文字を使用する必要があります。詳細については、「カスタムポリシーを使用する署名付き URL のポリシーステートメントで指定する値」を参照してください。 いいえ                           はい                          
ユーザーがコンテンツへのアクセスを開始できる日時を指定できる。 いいえ はい
(オプション)
ユーザーがコンテンツにアクセスできなくなる日時を指定できる。 はい はい
コンテンツにアクセスできるユーザーの IP アドレスまたは IP アドレス範囲を指定できる。 いいえ はい
(オプション)
署名付きURLにポリシーのbase64エンコードされたバージョンが含まれているため、URL が長くなる。 いいえ はい

動画の方で説明があったのですが、カスタムポリシー(Custom Policy)は既定ポリシー(Canned Policy)を拡張したものとのことです。

既定ポリシー(Canned Policy)

既定ポリシーでは以下2点の設定を行い、署名を作成します。

  • Resource: コンテンツのURL(フルパス)
  • DateLessThan: URLの有効期限切れ日時

これらの項目は既定ポリシーのポリシーステートメントで設定可能な値がこの2つだからになります。

既定ポリシーのポリシーステートメントの例

{
    "Statement": [
        {
            "Resource": "http://xxxxxxxxxxx.cloudfront.net/resources/horizon.jpg?size=large&license=yes",
            "Condition": {
                "DateLessThan": {
                    "AWS:EpochTime": 1357034400
                }
            }
        }
    ]
}

カスタムポリシー(Custom Policy)

カスタムポリシーでは、既定ポリシーの2点に加えて、オプションで以下の2点が設定可能になります。

  • DateGreaterThan: URLの有効期限の開始日時
  • IpAddress: GETリクエストを実行するクライアントの IP アドレス

つまり、カスタムポリシーのポリシーステートメントで設定可能な値は既定ポリシーで指定可能なものと合わせて4つになります。

さらに、既定ポリシーで指定可能な項目に加えて、カスタムポリシーではURLのPATHに対してワイルドカードが指定できるという特徴があります。

そのため、既定ポリシーではコンテンツ1つに対して1つ署名を必ず付けなければいけないというルールがありますが、カスタムポリシーの方を使用すると、特定のパターンのURLに対してすべて同じ署名を利用できるという特徴があります。

既定ポリシーと比較したときのカスタムポリシーの特徴として、比較表に記載があるようにURLが長くなるというのがあります。

既定ポリシーのポリシーステートメントの例

{
    "Statement": [
        {
            "Resource": "Resource": "http://xxxxxxxxxxx.cloudfront.net/resources/*",
            "Condition": {
                "IpAddress": {
                    "AWS:SourceIp": "192.0.2.10/32"
                },
                "DateGreaterThan": {
                    "AWS:EpochTime": 1357034400
                },
                "DateLessThan": {
                    "AWS:EpochTime": 1357120800
                }
            }
        }
    ]
}

既定ポリシーとカスタムポリシーの選択

選択の基準としては、以下の3点のいずれかの用途がある場合にはカスタムポリシーを利用し、どれも利用しないのであれば既定ポリシーを選択することになると思います。

  • URLの有効期限の開始日時を指定したい
  • GETリクエストを実行するクライアントの IP アドレスを制限したい
  • URLのPATHにワイルドカードを使用して、複数のリソースに対して1つの署名でアクセスしたい

署名付き URL の作成

キーペアの作成

事前準備として、署名を生成する際に必要になる秘密鍵と、CloudFrontに登録する公開鍵を作成します。

AWSのドキュメントを参考に作成していきます。

以下のコマンドで、秘密鍵を生成し、private_key.pem という名前のファイルに保存します。

openssl genrsa -out private_key.pem 2048

以下のコマンドで、上記のコマンドで生成したprivate_key.pemという名前のファイルから公開鍵を抽出します。

openssl rsa -pubout -in private_key.pem -out public_key.pem

パブリックキーをCloudFront にアップロード

パブリックキーを CloudFront にアップロードするには」を参考に、公開鍵をCloudFrontにアップロードします。

このとき生成されるパブリックキーIDをメモしておきます。 このIDは、このあと署名付きURLを作成するときに、Key-Pair-Idフィールドの値として使用します。

CloudFrontの設定

プライベートコンテンツをCloudFrontの署名付きURLで供給するように設定するには、以下のタスクを実行します。

プライベートコンテンツ提供のためのタスクリスト

署名付き URLの作成(既定ポリシー)

awscliのreferenceを参考に署名付きURLを生成します。

https://awscli.amazonaws.com/v2/documentation/api/latest/reference/cloudfront/sign.html

aws cloudfront sign \
    --url https://xxxxxxx.cloudfront.net/private-content/private-file.html \
    --key-pair-id XXXXXXXXXXX \
    --private-key file:///.../private_key.pem \
    --date-less-than 2020-11-18T19:30:00

生成されたURLのクエリパラメーターに以下の3つが設定されているので、既定ポリシーで作成されているのがわかります。 - Expires - Signature - Key-Pair-Id

https://xxxxxxx.cloudfront.net/private-content/private-file.html?Expires=1605727800&Signature=XXXX...&Key-Pair-Id=XXXXXXXXXXX

署名付きURLの作成(カスタムポリシー)

コマンドのオプションで有効期限の開始日を指定すると、URLにポリシーのbase64エンコードされたバージョンが含まれているので、カスタムポリシーで作成されているのがわかります。

aws cloudfront sign \
    --url https://xxxxxxx.cloudfront.net/private-content/* \
    --key-pair-id XXXXXXXXXXX \
    --private-key file:///Users/.../private_key.pem \
    --date-greater-than 2020-11-1T19:30:00 \
    --date-less-than 2020-11-18T19:30:00

URLのパスにワイルドカードを指定したので、生成された署名を使用して、https://xxxxxxx.cloudfront.net/private-content/*のパターンにマッチする複数のリソースに対してアクセスできます。

https://xxxxxxx.cloudfront.net/private-content/*?Policy=xxxx...&Expires=1605727800&Signature=XXXX...&Key-Pair-Id=XXXXXXXXXXX

確認と理解のために署名の有効性を検証してみる

生成されたURLでコンテンツにアクセスできるかどうか確認したら終わりですが、URLのクエリパラメーターにExpiresとKey-Pair-Idがあり、私は最初なぜこれが必要なのかわからなかったです。

CloudFront はパブリックキーを使用して署名を検証し、URLが改ざんされていないことを確認します。署名が無効である場合、リクエストは拒否されます。

ExpiresとKey-Pair-Idが署名の検証に使われていると分かると疑問が解決したので、署名付きURLを検証を実装してみます。

既定ポリシーで作成した署名付きURLを検証

https://xxxxxxx.cloudfront.net/private-content/private-file.html?Expires=1605727800&Signature=MOEBCVLfRO5CVOglBi9t-dUqI4RZpc5tqOZRwZceD5AHMLTTNFPP2WpXqw~UtpmkB1FF-t8wgiCzIQ~Qvd8hbOIffyb9Dh1I1saxN7HVpoLh99GwwV~LWZdGM3GdO3H6f~IqxLukTqnQf2F5xKrkD-g62Lyc3ShHRnIBOeug7seJpL3~hmFak2mUAa1NaFX2cw-XoZcXYqbi6RbK7hgxliXiLGcTa-D6FO3~r3qxohy5KxLR4A-fW-1gnRblaXUwFVp8eLG9Pvly5D5cH1NQMqgR9CAclJSYNM1ov-wUXQiwbkHMqW1m90KGxdQWluZLfdEHnrFwAUk40EOYfoUHMg__&Key-Pair-Id=XXXXXXXXXXX

既定ポリシーで作成した上記の署名付きURLを検証してみます。

署名のデコードや検証部分を実装するときに、署名生成のサンプルコードを参考にしながら実装しました。

<?php

// 公開鍵のファイルのPATH
$publicKeyFilename = './public_key.pem';
// プレミアムコンテンツのURL
$resource = "https://xxxxxxx.cloudfront.net/private-content/private-file.html";
// 有効期限(EpochTime)
$expiration = 1605727800;
// 署名(URLセーフなbase64でエンコードされてる)
$encoded_signature = "MOEBCVLfRO5CVOglBi9t-dUqI4RZpc5tqOZRwZceD5AHMLTTNFPP2WpXqw~UtpmkB1FF-t8wgiCzIQ~Qvd8hbOIffyb9Dh1I1saxN7HVpoLh99GwwV~LWZdGM3GdO3H6f~IqxLukTqnQf2F5xKrkD-g62Lyc3ShHRnIBOeug7seJpL3~hmFak2mUAa1NaFX2cw-XoZcXYqbi6RbK7hgxliXiLGcTa-D6FO3~r3qxohy5KxLR4A-fW-1gnRblaXUwFVp8eLG9Pvly5D5cH1NQMqgR9CAclJSYNM1ov-wUXQiwbkHMqW1m90KGxdQWluZLfdEHnrFwAUk40EOYfoUHMg__";

function verify($policy, $signature, $publicKeyFilename) {
    $fp = fopen($publicKeyFilename, "r");
    $publicKey = fread($fp, 8192);
    fclose($fp);
    $publicKeyId = openssl_get_publickey($publicKey);

    return openssl_verify($policy, $signature, $publicKeyId);
}

function decode($policy) {
    return base64_decode(strtr($policy, '-_~', '+=/'));
}

function createCannedPolicy($resource, $expiration)
{
    return json_encode([
        'Statement' => [
            [
                'Resource' => $resource,
                'Condition' => [
                    'DateLessThan' => ['AWS:EpochTime' => $expiration],
                ],
            ],
        ],
    ], JSON_UNESCAPED_SLASHES);
}

$signature = decode($encoded_signature);
$policy = createCannedPolicy($resource, $expiration);

$result = verify($policy, $signature, $publicKeyFilename);

if ($result == 1) {
    echo "正しいです";
} elseif ($ok == 0) {
    echo "正しくありません";
} else {
    echo "署名を確認する際にエラーが発生しました";
}
echo PHP_EOL;

公開鍵

手元で確認するので、コードでは秘密鍵から抽出した公開鍵のファイルのpathを使用しています。

AWS上では公開鍵はKey-Pair-Idと紐付いています。

<?php

$publicKeyFilename = './public_key.pem';

URLのクエリパラメータから取得した署名のデコード

URLのクエリパラメータから取得した署名はbase64エンコードされ、一部URLで無効な文字が置き換えられているので、以下の処理でデコードします。

<?php

function decode($policy) {
    return base64_decode(strtr($policy, '-_~', '+=/'));
}

署名を作成するときに使用したポリシーステートメントJSONを生成

既定ポリシーのポリシーステートメントjsonの生成に必要なものは、コンテンツのURLと有効期限の2つでしたので、以下の処理でjsonの文字列を生成します。

<?php

function createCannedPolicy($resource, $expiration)
{
    return json_encode([
        'Statement' => [
            [
                'Resource' => $resource,
                'Condition' => [
                    'DateLessThan' => ['AWS:EpochTime' => $expiration],
                ],
            ],
        ],
    ], JSON_UNESCAPED_SLASHES);
}

既定ポリシーを使用する署名付きURLの作成

署名の検証

PHP を使用したURL署名のサンプルコードでは、openssl_signで署名が作成されていたので、 phpopenssl_verifyで署名の検証を行います。

openssl_verifyでは以下のようなロジックで検証が行われていると思います。

  1. デコードした署名($signature)を、公開鍵を使って復号し、ハッシュ値を取得
  2. 既定ポリシーのポリシーステートメントjsonの文字列($policy)をハッシュ化して、ハッシュ値を生成
  3. 公開鍵で復号したハッシュ値jsonから生成したハッシュ値を比較し、完全に一致することを確認
<?php

function verify($policy, $signature, $publicKeyFilename) {
    $fp = fopen($publicKeyFilename, "r");
    $publicKey = fread($fp, 8192);
    fclose($fp);
    $publicKeyId = openssl_get_publickey($publicKey);

    return openssl_verify($policy, $signature, $publicKeyId);
}

デジタル署名の検証

署名の検証結果では以下のようなことが確認できます。

デジタル署名の検証が成功すると、以下のことが確認できます。
(1) メッセージが改ざんされていないこと
(2) メッセージは、検証に使用した公開鍵と対になる秘密鍵によって署名されたこと

ダイジェストが一致せずに検証が失敗すると、以下のいずれかの事象が発生したことが確認できます。
(1) メッセージが改ざんされたこと
(2) デジタル署名が改ざんされたこと

なお、メッセージとデジタル署名のどちらが改ざんされたかまでは分かりません。

https://www.ipa.go.jp/security/pki/024.html

1点注意点としては、有効期限が改ざんされていないことは検証できますが、有効期限切れかどうかは署名の検証では行っていないので、有効期限切れかどうかの判定については、署名の検証とは別で行います。

有効期限切れかどうかの判定を行うために、クエリパラメータにはExpiresとして有効期限を渡す必要があるのだと理解しました。

まとめ

CloudFrontの署名付きURLの手軽さと便利さがすごい!

参考

年末なのでみんなの質問に答えてみた

特に学びのある記事ではないので暇な人とレアジョブに興味ある方向けです(笑)。

お世話になっております。レアジョブの山田です。CTOをやっています。
今年最後のブログ担当で、連続投稿です。

今年を振り返る

f:id:ymdrock:20201220164823j:plain

今年は、コロナ禍によってニューノーマルと呼ばれる程、社会が一変しました。 企業としても個人としても様々な変化が求められています。

この状況下でレアジョブは、2020/11/20に東証一部へ市場変更を行なうことが出来ました。 会社として、また個人として社会的責任を果たせるように、グループビジョン「Chances for everyone, everywhere」の実現に向けて邁進して行きたいと思います。

東証一部へ市場変更以外ではProgosをリリースしました。ProgosはCEFRで英語スピーキング力を図るサービスで、AIによる自動採点の導入で瞬時の判定とスキルに応じた細かなフィードバックで効率のよい学習に繋げることが出来ます。

また、中長期戦略実現に向けて、様々な市場ニーズ、事業拡張に応えていくために、設立当初から稼働するレアジョブ英会話のシステムを抜本的にリプレイスする意思決定をしました。いくつかのPhaseに分けて進めており、最初のPhaseの着地が見えてきました。引き続き慎重かつ迅速に進めて行きたいと思います。

質問を受け付け、すべてに答える

まだまだ、エンジニアの募集をしていますので、このブログを通じて社内の雰囲気がもっと伝わればなと思いと、また最近全員とコミュニケーションを取る機会が少なかったので、聞いてみたいこと、疑問など質問を募集しました。
思ったより質問が少なったのですが(笑)、来た質問にすべて回答したいと思います。
来年はもっと興味を持って頂けるように努めます。。。

最近 注目している技術について

ノーコード/ローコード開発ツールですね。GoogleのAppSheet買収、AWSAmazon Honeycodeリリースなどの動きでこの領域はさらに加速していくでしょう。 用途に応じた開発ツールが数多くリリースされており、レアジョブでもどう活用するかを慎重に検討をしております。
個人的には、bubbleを触り始めました。

一緒に働きたい(or社内にマッチしそうな)エンジニアはどんな人と考えているか?

スキル的なのは別として、グループビジョン、サービスミッションに共感出来る、Rarejob Wayを体現してくれる人に尽きますね。

www.rarejob.co.jp

最近、読んだ本について(技術書・実用書・小説etc ジャンルは問わず)

実業務でたまにGolangを書いたりしていますので技術書も必要に応じて読みますが、最近は組織論や企業カルチャーに関わる本を読むことが多いです。
最近では、NO RULES(ノー・ルールズ) 世界一「自由」な会社、NETFLIXが非常に興味深かったです。

コロナ禍で変わった身の回りのことなど(はじめたことや止めたことなど)

コロナ以前は、週末は毎週のように家族で遠出していましたが、Go To トラベルも使わなかったですし全く遠出していないです。
落ち着いたら温泉行きたいです。

座右の銘など、大切にしている言葉について(意味も教えて欲しいです)

「常に謙虚でいること」です。
自分自身を常に客観視して、みなから多くのことを学び、意思決定を最善なものにしていきたいと考えています。
今CTOという重責を任せられているからこそより謙虚でいたいです。

きのこの山たけのこの里どっち派か?(理由もあると嬉しいですw)

どっちでもいいです(笑)
敢えていうなら、購入の際に在庫が多い方を買います。

普段、どういうメディアで技術動向などを追っているのか?

全体の流れであれば日経、IT Media、Tech Crunch、CNETなどで、あとはSNSはてブあたりを見ながら気になった情報を調べる感じですね。

好きな曲(アーティスト名などでもOKです)

季節性も加味すると、Awich - Happy X-mas (War Is Over)がよかったです。
MVもかっこいい。

一番好きな言語を教えてください

日本語。。。開発言語と解釈しますね。レアジョブでも導入しましたがGolangです。
煩わしいことも多々ありますが、書いてて楽しいです。

最近、買った高価なもの

スケボーかな。コンプリートでなく自分で組み立てたので23,000円くらいしました。
しかし、うまくならない...

尊敬する人物について

自分では出来ないことをやれる人、自分にない発想を持った人になりますかね。
仕事面では、現レアジョブを含めてこれまでのキャリアでそういった人達と働けたことは運がいいなと思っています。

レアジョブに入って他の会社よりも最も優れていると思ったところはなんですか?

レアジョブ入社まで3社に在籍していましたが、いずれも規模も環境も違うので比較が難しいですし、 他社と比べる必要はないと思うので、技術本部に絞っていいなと思っていることは挙げます。

  • 横の繋がりが強い
    • 私が至らないことがあっても横連携して組織で様々な課題を解決してくれる(感謝)
  • 組織学習の施策が継続出来ている
    • 勉強会
    • 社内情報共有ツールの活用
    • レビュー会
    • ブログ
  • 言語化・ドキュメント化を徹底している
  • 入社時に英語が出来ないくても英語で仕事する機会がある
    • 自社サービスでそれが実現出来ることも証明してくれている
  • みんな勉強家で技術が好き
    • 自分だけでなく周りを巻き込んで学ぼうというも姿勢もいい

他にも色々あるので、興味ある人は遊びに来て下さい(笑)

お子さんへのクリスマスプレゼントを教えてください

こんなの知りたいですか(笑)?

  • 長女(10歳) - ミシン ※最新型を所望されてました
  • 次女(8歳) - Swicthのすみっコぐらしのゲーム

最後に

とりとめない記事を読んで頂きありがとうございました。
メリークリスマス、よいお年を、また来年会いましょう。

Rarejob Tech Blogを見て、興味持って頂いた/頂いている方へ
エンジニア絶賛募集中です。オンラインでのカジュアル面談も実施しているので、お気軽にお問い合わせ下さい。 appeal.rarejob.co.jp

Swaggerで定義したRestAPIをOWASP ZAPでScanする

お世話になっております。
レアジョブの山田です。CTOをしています。

ブログの登場は年1で年度初めにという決まりだったはずですが、 圧力により今年最後のブログを担当させて頂くことになりました。

現在、レアジョブではレアジョブ英会話のシステムリプレイスを行っております。 このリプレイスでマイクロサービスアーキテクチャへ変更していきます。 私自身も移行関連タスクを担っており、RestAPIの脆弱性診断のタスクを割当てられ、Scanning APIs with ZAPに沿ってRest API脆弱性診断を実施したので共有したいと思います。

ZAPとは

ご存知の方も多いかと思いますが、 Zed Attack Proxy(ZAP)は、The Open Web Application Security Project(OWASP)が提供するWeb脆弱性診断のOSSです。

また、OWASPから数年に一度、OWASP Top10として認識すべき重大セキュリティリスクも発表しています。

owasp.org

OpenAPI Support

The API scanning script is an easy way for you to automate security scanning of APIs defined using OpenAPI/Swagger or SOAP.

現在開発中のAPIはSwaggerで定義しており、Scanの自動化のためSwaggerがサポートされているのでシナリオ作成する必要がないのでテストがぐっと楽になります。

実施環境

OWASPからZAPのDockerイメージが提供されているので、クライアントマシンのDockerでZAPを立ち上げ、API ServerへScanを行ないます。 ZAPの起動は、docker-compose.ymlに定義しておきましょう。

version: '3'

services:

  zap:
    image: owasp/zap2docker-weekly
    command: zap.sh -daemon -host 0.0.0.0 -port 8080 -config api.disablekey=true -config api.addrs.addr.name=.\* -config api.addrs.addr.regex=true
    ports:
      - "8080:8080"

準備

Scanの準備として、以下の2つを準備します。 なお、これらのファイルは、/zap/wrkというディレクトリに格納します(scanスクリプトで定義されている)。

リクエスト変更ルールは、例えばAPIサーバにはAPI Keyが必要な場合があります。このような場合、正しいAPI Keyが設定されたリクエストでないとテストが成立しません。 そこで以下のようにScan時に送られるリクエストの特定の値を書き換えます。この例では、HTTP HeaderとPostの一部のフィールドを置換する内容になります。

 replacer.full_list(0).description=Authorization
 replacer.full_list(0).enabled=true
 replacer.full_list(0).matchtype=REQ_HEADER
 replacer.full_list(0).matchstr=Authorization
 replacer.full_list(0).regex=false
 replacer.full_list(0).replacement=Bearer abcdefg
 replacer.full_list(1).description=api key
 replacer.full_list(1).enabled=true
 replacer.full_list(1).matchtype=REQ_HEADER
 replacer.full_list(1).matchstr=X-Api-Key
 replacer.full_list(1).regex=false
 replacer.full_list(1).replacement=1234567890
 replacer.full_list(2).description=Transaction ID
 replacer.full_list(2).enabled=true
 replacer.full_list(2).matchtype=REQ_HEADER
 replacer.full_list(2).matchstr=X-Transaction-ID
 replacer.full_list(2).regex=false
 replacer.full_list(2).replacement=sample
 formhandler.fields.field(0).fieldId=message
 formhandler.fields.field(0).value=this message was changed forcibly
 formhandler.fields.field(0).enabled=true

実施

Scanは、zap-api-scan.pyを実行します。オプションとしてAPIフォーマットとSwaggerファイル名を指定します。 レポートは、定義ファイル同様/zap/wrkに出力されます。 デバッグメッセージ表示、レポートフォーマットなどオプションは他にもあるので、ヘルプをご確認下さい。

docker exec <container id> ./zap-api-scan.py -t <Swaggerファイル名> -f openapi -r <レポートファイル名> -z "-configfile /zap/wrk/<定義ファイル名>"

まとめ

Swaggerで定義されたAPIに対しては、zap-api-scan.pyで簡易にAPI脆弱性診断が出来ることはわかりました。また、定期的に実施することで品質の担保に繋がります。 ただ、ZAPだけでは完全とは言えないので、システム特性に合わせて専門家による第三者診断も必要だと考えています。

では。

Chromebookってどうなの?

お久しぶりです、ディズニーをこよなく愛する岩堀です。みなさん再開後ディズニーには行けていますか? 私は毎月のようにチケット争奪戦をくぐり抜け、つい先日に新アトラクションをすべて体験できました!

そんな体験をレポートしようかと思いましたが、そちらは別の機会で別の体験をレポートしたいと思います。 今回は弊社情シス部門にて導入検討しているChromebookについて、話をしてみたいと思います。

きっかけ

弊社では通常、WindowsMacマシンを基本的に社員に割り当てておりましたが、 これらの端末を社員に配布するまでの準備にかなりの工数を必要としています。 この部分を長年課題として考えておりましたが、具体的な解決ができずにいました。

そのような中で弊社は今年度の初めに全社的にGoogleWorkspace(旧GSuite)の導入を進めていくことになり、徐々にGoogleツールを多用できる状況となってきました。ある程度Googleツールが利用できる環境下の中で、どの程度Chromebookにて業務を行えるのか検証を行いたいと思っていました。 そんなタイミングで私が使用しているMac端末のバッテリー交換が必要となったので、交換完了までの間、Chromebookを使って通常行っている業務がどこまで行えるか検証してみました。

私自身、自宅ではWindowsMacChromebookの3種を用途によって使い分けていたので、Chromebookへ端末を変えることには大きな抵抗もなく臨めましたが、どこまで同じように業務を行えるのかは若干の不安を持ちながら臨んでみました。

参考に私が普段端末を利用して行っている作業は主に以下になります。

  • 各種Webアプリケーションでの対応
  • ZoomやMeetを利用しての会議参加
  • MSOfficeドキュメントの閲覧/編集
  • gitによるソース管理
  • IDEを使ったソースコード閲覧・編集
  • CLIによるサーバ業務
  • Windowsサーバへのリモートアクセス

移行までの時間は?

まず代替え機となるChromebookに移るまでに要した時間ですが、こちらは正直に申しまして0分でした。 メールに関してはGmailを利用しているのと、ローカルファイルに関しては既にGoogleファイルストリームにて GoogleDriveをローカルドライブのように扱っていったことで、端末移行を行う必要がなかったです。

各種Webアプリケーション利用は?

次に業務に欠かせない各種Web アプリケーションですが、弊社ではコミュニケーションツールとしてChatworkとSlack、社内共有ドキュメントツールとしてconfluence等、多くのWebアプリケーションを使っています。基本的にChromeが使えるWebアプリケーションであれば、問題なく利用ができるので、こちらで問題なることはなかったです。また普段アプリベースで使っているもので、通知を表示させていても、WebPush通知が使えるものも多いので、特に違和感なく利用でき、業務で問題となることはなかったです。

Web会議ツールに関しても、MeetやZoomはブラウザのみで利用が可能なので、問題なく会議を行うことができました。

Officeは使えないのでは?

次にMSOfficeドキュメントですが、こちらはMacのときからOffice365を利用しており、引き続きこちらを使いました。ChromebookAndroidベースで作られているため、基本的にPlayストアからアプリを落とすことができます。そのためOffice365をPlay ストアからインストールを行うことで、Chromebook上でもOfficeドキュメントを問題なく扱うことができます。もちろんマクロで作りこんであるExcelも問題なく扱うことができました。

IDEとかはないでしょう?

gitやIDE周りは、Chromebook上でLinuxを起動させて、IDEを起動させることが可能なので、そちらで対応いたしました。 Chromebookの設定の中でLinuxを有効にすることで、Chromebook上でLinuxマシンがVMとして立ち上げることができます。 support.google.com

これによりLinuxを立ち上げることができ、その上でgitやIDEをインストールし、使うことができるようになります。今回は詳細なインストール方法は省略しますが、gitやIDEを使用することができるようになることで、コード管理、修正、レビュー等も問題なく対応が行えました。

CLI操作がしたいときは?

ターミナル操作が必要となるサーバ業務は先程のLinux機能を利用すると合わせてターミナルを使うことができます。こちらの方法でも問題はないのですが、ローカルリソースを上へのファイルダウンロードやアップロードに関してはVM上での作業になり、その後の業務に移る際に若干面倒であるため、私はChromeのウェブストアからインストールできるブラウザアプリのSecure Shell Appを使いました。

chrome.google.com

こちらを使うとブラウザ上でSSH操作が行え、ローカルリソースを扱えるので、ファイルのダウンロードやアップロードにてリソースを直接操作することができるので、とても便利です。

リモートデスクトップは無理でしょう?

最後にWindowsへのリモートデスクトップ操作ですが、こちらもPlay ストアよりMS製のリモートデスクトップアプリをインストールすることができ、使用することが可能となりますので、こちらですべてが対応できるようになります。リモートデスクトップアプリとしてはVNCもあるので、AWSのEC2インスタンスとして立ち上げることができるようになったMacOSにもリモートアクセスが可能になります。

感想

結果として一週間ほどChromebookを業務で使っておりましたが、大きく困ったことは発生しませんでした。逆に起動が早いということもありますし、固まることも殆どないので、業務効率は上がったと思われます。WindowsMac端末が必要な場合にはAWSのWorkspacesやEC2インスタンスで立ち上げたマシンにリモートアクセスすれば対応もできるので、大きく問題になることはないと思われました。

Office製品に関してはほとんどGoogleDocやSpreadSheet等に移すことも可能ですので、ドキュメント周りはそちらに寄せていくことも可能になると考えられます。Excelマクロの互換性の問題に関しても、先日Googleから対応したアドオンの発表もあり、より一層移行が進められると考えられます。 forest.watch.impress.co.jp

弊社では今後Chromebookを徐々に取り入れて行こうと考えておりましたが、今回の検証でより一層、早めに進めていきたいと感じました。 通常業務の影響も少ないことがわかり、逆に業務効率が上がる部分もあることがわかりました。キッティングの面から見ても、ログインするGoogleアカウントの設定の内容が端末に反映されていくので、端末準備にそれほど時間を必要としないことにもメリットを感じました。

みなさんも、一度はChromebookを使ってみて便利さを感じてみてください。ちなみに弊社サービスでありますレアジョブ英会話のレッスンルームも、まだChromebookを推奨環境としていないですけど快適に使うことはできるのを確認しています。私自身レッスンを受けるときは検証も兼ねてChromebookを利用しておりますので、推奨環境に含まれましたらぜひご利用を試してみてください。