自分用Cloud9をSSL化して、oauth2_proxyで自分だけの認証を追加した

この記事では、SSH使うから認証は後回しで、って書いてたんだけど、それじゃあChromebookで気軽にお遊びができないことに気づいたので、結局SSL化と認証を追加した。

ドメイン取得&SSL

certbot(Let's encrypt)を使うには、TXTレコードやCNAMEレコードをイジる必要があるので、しぶしぶ c9work.net というドメインを購入。(胡散臭いキャンペーンとか嫌いなので、AWS経由で購入した)

証明書の取得は

letsencrypt.org

にあるように、certbotで行う。

  • Cloud9 IDEを表示するURL(hogehoge.ide.c9work.net)
  • 成果物を誰かに見せる用のURL(hogehoge.preview.c9work.net)
  • 認証用その他もろもろのページのURL(auth.c9work.net, page.c9work.net, ...)

の3つを想定していたので、雑にワイルドカード証明書を3つ。

sudo certbot certonly --manual --manual-public-ip-logging-ok -d *.preview.c9work.net -m ore_ore@yahoo.co.jp
sudo certbot certonly --manual --manual-public-ip-logging-ok -d *.ide.c9work.net -m ore_ore@yahoo.co.jp
sudo certbot certonly --manual --manual-public-ip-logging-ok -d *.c9work.net -m ore_ore@yahoo.co.jp

みたいな感じで。

都度、TXTレコードに確認用文字列を入れる、みたいなことはやる必要があったけど、そこはRoute53なりAzure DNSなりで管理してたらなんの問題もなくいける。

前段にGitHub認証を入れる

これが結構つまった。(ひとえに、nginxのAuth Requestを知らなかったからなんだけどw)

SSL化をした時点で、

f:id:YusukeIwaki:20190722171647p:plain

なんとなくこんな感じに、nginx二段構えにしていた。なので、認証はとりあえずSSLオフロードをやってる前段のnginxに何かしらやればいいんだろうなーと。Cloud9 SDK自身も、なんとなく認証っぽい仕組みは内部で持っていそう( core/auth.js at master · c9/core · GitHub )だったんだけど、SSLオフロードした先に認証機構があるのはちょっと筋が悪い気がした、なのでnginxかその前段に持たせようかなと。

んで、CW社の偉大なる同期が昔書いていた記事も参考に、

qiita.com

これはとりあえずoauth2_proxyをAuth Requestと組み合わせて使えばラクかなと。

認証エンドポイントと認証した先に戻るURLでドメインが違うのをなんとかする

手っ取り早くウェブアプリケーションにOAuth2認証を導入する - その手の平は尻もつかめるさ あたりを参考にするとわかりやすいんだけど、oauth2_proxyは /oauth2/start?rd=/return_back_to/this/address のようにパスを渡すと、認証完了後に /return_back_to/this/address に帰ってきてくれる。

今回の場合は、

と想像していた。が、現実はそうはいかず。 https://auth.c9work.net/ にリダイレクトされてしまった。

oauth2_proxyのソースをみたら、「あーー」ってなんたんだけど

https://github.com/pusher/oauth2_proxy/blob/863539154366f456d8ba57142b3a66b4544fda82/oauthproxy.go#L477-L483

redirect = req.Form.Get("rd")
if !p.IsValidRedirect(redirect) {
    redirect = req.URL.Path
    if strings.HasPrefix(redirect, p.ProxyPrefix) {
        redirect = "/"
    }
}

をチェックして、NGだったら強制的に / に書き換えられてしまっているではありませんかー!

OAUTH2_PROXY_EMAIL_DOMAINS に .ide.c9work.net を設定すると解決。

GitHubユーザをYusukeIwakiに限定する

これも結構ハマった。

本当は前段の認証側でどうにかしたかったんだけど、nginx.confの記述方法わからなすぎたので、

  • 前段の認証ではGitHubユーザだったらとりあえず通すようにする
    • バックのnginxにリクエストを渡すときに X-Remote-Username というカスタムヘッダにGitHubユーザ名を渡す
  • バックに居るコンテナ振り分けをやるnginxのほうで、GitHubユーザ名が YusukeIwaki じゃなかったら強制的に403を返すようにする

という雑な方法で実現。

        location / {
            auth_request /oauth2/auth;
            error_page 401 = /oauth2/start?rd=https:///$host$request_url;

            proxy_set_header X-Forwarded-Scheme $http_x_forwarded_proto;
            proxy_set_header Host $host;
            auth_request_set $user $upstream_http_x_auth_request_user;  # ←認証完了時に、oauth_proxy2 がX-Auth-Request-UserヘッダにGitHubユーザ名をセットしてくれるのを拾う。
            proxy_set_header X-Remote-Username $user;  # ←nginx_backにリクエストをプロキシするときに、 X-Remote-Usernameというカスタムヘッダを付ける
            proxy_pass http://nginx_back:8888;
        }
    server {
        listen             8888;
          (中略)

        if ($http_x_remote_username != 'YusukeIwaki') {
            return 403;
        }

たぶんもっといいやり方はあるんだろうなぁ...。

そんなわけで

Chromebookでも開発をできるようになった。

うぇいうぇい

Cloud9 PRO終了のお知らせにカッとなって、自分用のCloud9を作った

まえおき

f:id:YusukeIwaki:20190713151336p:plain

Cloud9AWS Cloud9という似て非なるサービスに変わった。

自分はCloud9 PROというサービスをとても熱心に(課金して)使っていたので、「これからはAWS Cloud9を使ってね♪」って気軽に言われてもな、そうはいかないんだ。

  • AWSGitHubじゃログインできないし
  • Cloud9 PROだと Runボタンをポチッとおしたらhttpsで即座に外から見れた のが、AWS Cloud9はできないし
  • そもそも開発環境なのに従量課金なんてありえないだろ!!!!!!

ということで、ものすごくフラストレーションが溜まっていた。

Cloud9 PROが良かった理由

他のクラウドIDEは使えなさすぎた

Cloud9終了のお知らせを受けて、 Alternatives to Cloud9 とかでGoogle検索して色々探したよ。有名ドコロだと

などなど。

ただ、実際に使ってみて使い物にならなかった。Paiza Cloudは謎のGUIがとても邪魔くさかったし、CodeAnywhereはエディタの品質が悪すぎ&コンテナごとに画面が分けられていないのがしっくりこなかった。

あと、両者とも致命的にダメだったのが、コンソールからエディタを起動できないこと。

Cloud9は c9 ってコマンドを使うとコンソールからエディタが立ち上がるんだけど、

f:id:YusukeIwaki:20190713013551g:plain

それに相当する機能がPaiza CloudにもCodeAnywhereにもなかった。

これエンジニアだったら必須機能だと思うんだけど、PaizaやCodeAnywhereはユーザの使い込み試験やってないの?

料金体系がエンジニアフレンドリーだったCloud9 PRO

成果物をhttpsでササッと公開する、というだけならCloud9じゃなくても他のクラウドIDEでもできるんだけど、無料でそこまで太っ腹なことをやっているところは無かった。完全に蛇足だけど、いろいろ話題を読んでいるプログラミングスクール とかでも、昔は↑を最大限に活用したカリキュラムを組んでいた(と、人づてに聞いた)。今はどうしてるんだろう。

個人的には課金はしていたので、無料であることは求めていなかったんだけど、それでも

  • 「あ、これやってみたい」と思ったものは基本的に新規に作業用コンテナ作って始めたい
    • 作成可能なコンテナ数に上限があるとか論外
  • せめて1つくらいは常時(外部から)接続可能なコンテナはあってほしい
  • 料金は10ドル/月 くらいであってほしい

という、エンジニアの平均的な要望を満たしてくれるIDEはCloud9 PRO以外には無かったのだ。

無いなら作るしかない

Cloud9 はエディタとしては100% ではないんだけど、ちょうどいい具合に整っていた。Cloud9 PROが終了してもAWS Cloud9は使いたくないし、他のクラウドIDEも使いものにならないので、もう自分で作る以外の選択肢が現状では無い。

Cloud9は親切にも、SDKを公開してくれている。

github.com

あとは、クラウドワ ークス って会社で働いていたときに

qiita.com

こんな素晴らしいことをやってるメンバーがいて、これにCloud9 SDKを組み込むと理想的な使い捨て開発環境ができることはお試し済みだった。

なので、サーバーの月額費用だけなんとかなれば、自分用のCloud9 PROはできるという確信はあった。

ちょうどタイミングよく、今の会社でMicrosoft Azureの優待利用(1ヶ月あたり○○円までは無料でずっと使える、的なやつ)があったのでそれも相まって、作ることにした。

自分用のCloud9 PROっぽい環境の作る前に・・・

Qiita記事にするか迷ったけど、面倒なのでここに書く。

前述の通り、ほぼほぼ Docker swarmで作る社内heroku: yadockeri - Qiita のパクリである。

用意するもの

制限事項

自分専用なので

  • 認証は特にやらない
  • SSL化はいったんあきらめる

ということにする。

SSHのポートだけ開放して、SSHポートフォワーディングで使う想定。

自分用のCloud9 PROっぽい環境を作る

さて作るぞ。

とりあえずEC2のインスタンスが3台あると仮定してすすめる。

同じVPC(Azureだったらリソースグループ)上に配置して、プライベートIPで相互に通信できるようにしておく。

dockerを入れる

個々のサーバーにSSHで入り、

curl https://raw.githubusercontent.com/YusukeIwaki/cloud-pine/190e8454b1dbae0521f035f7f37ceb62f8f126f4/install-docker-ce.sh | bash

する。

Docker Swarmクラスタを組む

c9-masterで docker swarm init する。

swarm initすると、「ワーカーを追加するには docker swarm join ….. —token=xxxxxx を打ってね」みたいなのが出てくるので、そのコマンドをコピって、c9-slave1, c9-slave2 で実行する。

ちなみに最近のDockerはSSH経由でリモートのDockerホストに接続できる(参考: Docker CE 18.09からssh経由でリモートのdockerデーモンに接続できるようになるってよ - Qiita )ので、c9-masterから

DOCKER_HOST=ssh://c9-slave1 docker swarm join …….. —token=xxxxxx

のように実行すれば、いちいちc9-slave1やc9-slave2にSSHしにいかなくても済む。

c9-masterで docker node ls を打って

$ docker node ls
ID                            HOSTNAME            STATUS              AVAILABILITY        MANAGER STATUS      ENGINE VERSION
eeshee1yai5Moh5saGu0Ueje *   c9-master   Ready               Active              Leader              18.09.7
ahm3iexaeth5co2eir0Hahno     c9-slave1   Ready               Active                                  18.09.7
eangie6ohDigh1Zu4epung2I     c9-slave2   Ready               Active                                  18.09.7

みたいな出力が得られたらOK。

ワークスペース用のコンテナをつくる

Cloud9 PROはUbubtu 14.04ベースで、 RubyPythonもNodeもgitもheroku-cliaws-cliも(バージョンは古いけど)入っている状態だった。

とりあえず今はそこまでは求めないので、Cloud9 SDKだけ入れたDebianにする。(本気で使い始めたら、RubyPythonくらいは入れようと思う) cloud-pine/Dockerfile at df0962b100241db3eb3fd40219d6cc22e4195ea8 · YusukeIwaki/cloud-pine · GitHub

docker buildして、DockerHubにプッシュする。 https://hub.docker.com/r/yusukeiwaki/cloud-pine-workspace

docker-composeを↓みたいに作り、 docker-compose pull で正常にワークスペースが取得できる状態になればOK。 cloud-pine/docker-compose.yml at 190e8454b1dbae0521f035f7f37ceb62f8f126f4 · YusukeIwaki/cloud-pine · GitHub

ワークスペースを試しにデプロイしてみる

c9-masterにSSHで入り

$ wget https://raw.githubusercontent.com/YusukeIwaki/cloud-pine/190e8454b1dbae0521f035f7f37ceb62f8f126f4/docker-compose.yml
$ docker stack deploy --compose-file docker-compose.yml playground1

をする。docker-compose.yml は1つ前の手順で作成したものを指定する。

Creating network playground1_default
Creating service playground1_cloud9

こんな感じで、オーバーレイネットワークとサービスが作られたらOK。

$ docker service ps playground1_cloud9
ID                  NAME                   IMAGE                                     NODE                DESIRED STATE       CURRENT STATE            ERROR               PORTS
prviwbnxxu3l        playground1_cloud9.1   yusukeiwaki/cloud-pine-workspace:latest   c9-slave1   Running             Running 43 seconds ago       

みたいな感じで、無事にc9-slave1 に配置された。(空いているDockerノードにいい感じに配置されるので、c9-masterやc9-slave2に配置されることもある)

ただ、これ単体では外からアクセスはできない。

外からアクセスできるようにする

グローバルIPを持っているc9-masterにnginx(リバースプロキシ)サーバーを立てるわけだが、ネイティブにリバースプロキシサーバを立てると、

$ ping playground1_cloud9
ping: playground1_cloud9: 名前またはサービスが不明です

のように、playground1_cloud9コンテナへのルーティングがうまくできない。docker inspectを使ってIPアドレスを特定して、・・・とかをやるのは茨の道だ。

ということで、nginxはネイティブで立てるのではなくdockerサービスとして立てる。そんで、playground1_cloud9とかと同じオーバーレイネットワークに入れれば、

$ docker exec -it 4936af085963 ping playground1_cloud9
PING playground1_cloud9 (10.0.6.88): 56 data bytes
64 bytes from 10.0.6.88: seq=0 ttl=64 time=0.058 ms
64 bytes from 10.0.6.88: seq=1 ttl=64 time=0.125 ms
64 bytes from 10.0.6.88: seq=2 ttl=64 time=0.076 ms
64 bytes from 10.0.6.88: seq=3 ttl=64 time=0.067 ms

のようにnginxコンテナからplayground1_cloud9が見えるようになる。

この辺の仕組みは Docker swarmで作る社内heroku: yadockeri - Qiita あたりで言及されているやつ丸パクリです。

nginxコンテナをつくる

nginx.confを

user  nginx;
worker_processes  1;

error_log  /var/log/nginx/error.log warn;
pid        /var/run/nginx.pid;

events {
    worker_connections  1024;
}

http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    keepalive_timeout  65;

    #gzip  on;

    server {
      listen             80;

      set_real_ip_from   '10.0.0.0/8';
      real_ip_header     X-Forwarded-For;
      proxy_set_header   X-Forwarded-Scheme $http_x_forwarded_proto;
      proxy_set_header   Host $host;
      resolver           127.0.0.11 valid=5s; # nginx:alpineコンテナの/etc/resolve.conf にべた書きされているnameserverの値をそのまま転記

      if ($host ~* ([a-z0-9][a-z0-9_-]*)\.ide\.mydomain\.com$) {
        set $subdomain $1;
        set $dst_port 8888;
      }

      if ($host ~* ([a-z0-9][a-z0-9_-]*)\.preview\.mydomain\.com$) {
        set $subdomain $1;
        set $dst_port 8080;
      }

      location / {
        proxy_pass http://${subdomain}_cloud9:${dst_port};
      }
    }
}

こんな感じ。 mydomain.net のところは自分のドメインにする。

あとは、c9-masterにSSHで入って

$ docker network create --driver overlay --internal c9-overlay-network
$ docker service create --constraint 'node.role==manager' --mount type=bind,source=$(pwd)/nginx.conf,destination=/etc/nginx/nginx.conf,readonly --name nginx --network c9-overlay-network -p 80:80 nginx:stable-alpine

のようにDockerサービスを立ち上げれば、リバースプロキシが立つ。

ドメインの向き先をc9-masterにする

AWS Route53側の設定。

*.preview.mydomain.com*.ide.mydomain.com のAレコードをc9-masterのグローバルIPにする。(ちなみにAWS EC2じゃなくてAzure VMだったら、AレコードでIP指定するのではなく、CNAMEレコードで hogehoge.japanease.cloudapp.azure.com を指定する)

nginxからCloud9を見えるようにする

先の手順までで、 http://playground1.preview.mydomain.com/ とか http://playground1.ide.mydomain.com/ とかを指定すると、nginxが502エラーを返す状態にまでなるはず。

f:id:YusukeIwaki:20190713145639p:plain

これはnginxまでは到達できているけど、その先(Cloud9コンテナ)にリクエストを転送できていない状態。

なんでエラーになるかと言うと、この時点では先に立ち上げたplayground1サービスのCloud9コンテナが c9-overlay-network ネットワークに参加していないので、nginxからplayground1_cloud9の名前解決ができないのだ。じゃあどうすればできるかというと、とてもシンプルで

$ docker service update --network-add c9-overlay-network playground1_cloud9
playground1_cloud9
overall progress: 1 out of 1 tasks 
1/1: running   [==================================================>] 
verify: Service converged 

のように、ネットワークに参加させたらよい。リロードしたら・・・

f:id:YusukeIwaki:20190713145951p:plain

キタ━━━━(゚∀゚)━━━━!!

f:id:YusukeIwaki:20190713150017p:plain

キタ━━━━━━━━━━(゚∀゚)━━━━━━━━━━!!

となるだろう。

Cloud9 PROもどきの運用方法

構築手順はいろいろあったが、一旦作ってしまえばあとは簡単で、

新規のワークスペース立ち上げたいなーって思ったら

c9-masterに入って

docker stack deploy --compose-file docker-compose.yml nanika_new_idea
docker service update --network-add c9-overlay-network nanika_new_idea_cloud9

nanika_new_idea のところは都度変える)

と2つコマンドを打てば、1分もたたないうちに

  • http://nanika_new_idea.ide.mydomain.com/ でCloud9 IDEにアクセスできる
  • Cloud9上で8080番ポートでHTTPサーバーを立ち上げれば http://nanika_new_idea.preview.mydomain.com/ で見れる

ようになる。

ワークスペースを捨てたいなーって思ったら

c9-masterに入って

$ docker service ps playground1_cloud9 | grep Running | awk '{print $4}'
c9-slave1

のように、Cloud9を実行しているノードを特定してから、

$ docker stack rm playground1
Removing service playground1_cloud9
Removing network playground1_default
$ DOCKER_HOST=ssh://c9-slave1 docker volume rm  playground1_workspace-data
playground1_workspace-data

って感じで、2つコマンドを打てば瞬時に消せる。

まとめ

ほぼほぼ Docker swarmで作る社内heroku: yadockeri - Qiita なので、何も新しいことはやっていない。

とりあえず自分用Cloud9ができたので、しばらくは楽しくエンジニアやっていけそう。

「してもしなくてもいい転職」をするに踏み切った話

yusukeiwaki.hatenablog.com

から1年とちょっとが経ち、あまりに福岡市が住みやすすぎたので(誇張w)7/1から福岡の会社に転職しました。

しないといけなかった転職(前々職の話)

前々職である富士通は、割と明確な理由で退職していました。

2014年頃に発売されたらくらくスマートフォンのある機能を当時かなり本気でつくっていて、多分その成果は一部の(直属ではない)偉い人には評価されてたと思います。なんですが、「遅刻が多い」とかつまらん理由で人事評価下げられちゃったんですよね(苦笑)

みんな成果だしてて遅刻なくやってるなら納得いくんですが、実情は

  • 窓際に置かれている置物みたいなオジサンに月額80万以上課金されている
  • 夜は生活残業して、朝は悠々とフレックス出社してまわりに迷惑かけている社員も周りにけっこういた

みたいな環境(雑な説明ですみません…)だったので、そりゃ納得いかないし仕事に身も入らなくなる。超絶尊敬する人も4人くらいは居た(いまだに尊敬している)んですが、それ以上に富士通のために働く不快感が強かったので転職しました。

(完全に過去の話です。今の組織が同じではないと思うので、その点はご注意ください)

 

しなくてもいい転職(今回の話)

それと比べると、今回の転職はあまり動機がはっきりしていませんでした。

古株なエンジニアが改めてUXの回復に取り組んでいる話 - クラウドワークス エンジニアブログ を書いてた頃には実は今の職場の面接すすめてたんですが、毎度「どうしても転職しないといけないとは全く思っていない。今の会社と比べて「これだ!」と思える何かがあれば転職したいと思っている」ということを面接で言っていました。

CW社は

  • 社長のビジョンとプレゼン能力が本当に神がかっている
    • ウマが合わないでやめていく人も時々いるけど、個人的にはかなり尊敬している
  • エンジニア文化が昔から尊重されている
    • 一時的にそうじゃない時期もあってやめていく人も結構いたけど、個人的にはそれは誤差の範囲と思っている
  • 部門やチーム間の垣根は有るようで無いので、結構いろんなことをやれる

という環境で、少なくともあと3年くらいはいても大丈夫だろうという感触はありました。

 

ネガティブなことはないの?と言われると無いわけではないのでそれも一応かいておくと、

  • 個人的にはクラウドソーシングを「大企業ではたらく人が、気軽にスキルアップできる&感謝されて幸せになる手段」として布教したかったんだけど、営業利益のほとんどはおそらくフリーランスとプロいライターによって支えられていて、施策もそっちに寄りがちだった。
  • 1つ隣の部署やチームくらいまでは仲良くお互い理解して仕事してるんだけど、2つ隣くらいの部署/チームがなにやってるか(なにを求めて仕事を遂行しているのか)全くわからん。
    • なので、目標で「○○の数字を△△%あげよう!」みたいな話になっても、誰がどう働きかけてそこを目指すのか、みたいな話にはならない。(なったとしても実感が伴わない)
      • ようするに目標設定も評価もわりと形骸化してた
        • そういえば年収は4年勤めてほとんど変わらなかった

みたいなところはあった。それでも富士通に比べれば、辞める理由なんてなかった。

 

じゃあなんでわざわざ転職??

今後のことを考えると、組織の課題解決屋として活躍する経験積んでおかないと、いつまでたっても歯車のままで人生終わっちゃうなー

みたいなことを考え始めたのが一番のきっかけです。

確かに今すぐ転職しないといけない理由はないんだけど、何もしないとただただ歯車街道・・・。とはいえ経験のない人がいきなりリモートで(4年勤めて良くも悪くも慣れてしまった)組織の課題解決なんてできないので、やっぱり福岡でなにか仕事探して経験積みに行こう、という感じで考えたのでした。

ただ、福岡もやっぱり地方都市あるあるなところがあって、

  • SIer天国。あとはイケイケの(安い給料の)ベンチャーが少し。
  • 意思決定はみんな東京でやってしまうから、地方支社だと不快な思いをすることがある
  • リモートワークできますって宣言してる会社も蓋を開けてみると数人だけが時々リモートしてるだけで、そういう設備投資が全くされていない

みたいな会社ばっかりで、半分諦めてました。

ただ、Wantedlyで2社くらい、↑には全くあてはまらない気になる会社をみつけちゃったわけです。隠さずに言うと、

  • ぬーらぼ
  • アイキューブドシステムズ

の2社。

面談を通じていろいろ話をきいて、「これはマネジメント超絶苦手な自分でも、組織の課題解決屋として会社の成長に貢献できるチャンスがありそうかもー」と薄々思って、一か八かではあったものの転職に踏み切りました。

 

転職をした結果

少しオチンギン下げての転職になってしまったので、まじでお金がなくなってきたぞ。

副業でRails 4→5のお手伝いとか、Androidアプリつくってくれとか、負債解消してくれとかやるんで、お仕事ください(違w

 

さいごに

CW社の人からすると「なんでいきなり転職するん?」みたいな感じだったと思うので、とりあえず適当に書いてみました。

うーん、おれ文章下手だな・・・・・・