ぽよメモ

レガシーシステム考古学専攻

Octopassで簡単ユーザ管理

きっかけ

ちょっとした技術コミュニティを作りたくなって友人と学内で一つ非公式サークルを立ち上げました.
当然のようにVPSを借り,Webサーバを立ち上げ,Wikiを建て…なんていうのを考えているとき,ふと「ユーザ管理面倒だな.LDAPほど大げさじゃない何か良いものはないだろうか」と考えてググったら出てきました.

github.com

Githubをバックエンドとして使用してユーザ認証できるなんてステキ!と飛びついてみたら簡単にできて最高だったという話です.

環境

メンバーもまだ少なく,ほとんど創立メンバーの友人と二人で折半するので費用は抑えたい…ということでさくらのVPSのメモリ1GBプランを契約.年額一括1万円程度で固定IPのあるサーバが使えるのはありがたいです.

OSには使い慣れているUbuntuを選択しました.

導入

基本は下記ページ及びgithubのREADMEに従うだけです.

linyo.ws

下記操作は全てさくらのVPSのコントロールパネルからアクセスできるシリアルコンソールから行っています.

Octopassのインストール

UbuntuCentOSならソースからコンパイルしなくてもpackagecloudに既に用意されています.ありがたい.

$ sudo apt-get update
$ sudo apt-get upgrade
$ curl -s https://packagecloud.io/install/repositories/linyows/octopass/script.deb.sh | sudo bash
$ sudo apt-get install octopass

Personal Access Tokenの取得

https://github.com/settings/tokens/newからログインして新しいトークンを作成します.

このとき,必要な権限はadmin:orgの中のread:orgのみで良いようです.

f:id:pudding_info:20171211000509p:plain

設定ファイルの作成

設定ファイルのテンプレートが用意されているのでmvします.

$ sudo mv /etc/octopass.conf.example /etc/octopass.conf
$ sudo vim /etc/octopass.conf

設定ファイルの必要な項目を埋めます.僕は下記のコメントを外しているところのみを埋めています.

# O C T O P A S S

# Required
Token           = "{Your token}"

## Use team
Organization    = "{Your org}"
Team            = "{Your team}"

## Use collaborators
#Owner           = "yourname"
#Repository      = "yourrepository"

# Default
#Endpoint        = "https://api.github.com/"
#Group           = ""
Home            = "/home/%s"
Shell           = "/bin/bash"
#UidStarts       = 2000
#Gid             = 2000
#Cache           = 300
Syslog          = true

# Advanced
#SharedUsers     = [ "admin", "deploy" ]

情報が取れるか確認してみます.

$ sudo octopass passwd
hoge:x:120000:2000:managed by octopass:/home/hoge:/bin/bash
fuga:x:130000:2000:managed by octopass:/home/fuga:/bin/bash

こんな感じでチームに所属しているメンバーが一覧で表示できるはずです.

sshdやpamの設定

これらをsshdやpamから叩けるようにします.とはいってもgithubとかにあるコードをそのままコピペすれば動きます.ステキ.

/etc/ssh/sshd_config

デフォルトではパスワードによるsshなどが可能なので制限します.

# rootログイン禁止
PermitRootLogin no 

# 鍵認証にoctopassをrootから叩いて使用
AuthorizedKeysCommand /usr/bin/octopass
AuthorizedKeysCommandUser root

# 公開鍵認証を有効に
RSAAuthentication yes
PubkeyAuthentication yes

# パスワード認証を無効
PasswordAuthentication no

# PAMを使用
UsePAM yes

/etc/pam.d/sshd

@include common-authは元から記述されているのでコメントアウトします.

#@include common-auth
auth requisite pam_exec.so quiet expose_authtok /usr/bin/octopass pam
auth optional pam_unix.so not_set_pass use_first_pass nodelay
session required pam_mkhomedir.so skel=/etc/skel/ umask=0022

/etc/nsswitch.conf

これを触ったのは初めてだったのですが,名前解決の順序を決定するための設定のようです*1

passwd, shadow, groupの3項目を下記のように変更.

passwd:     files octopass sss
shadow:     files octopass sss
group:      files octopass sss

Githubへの鍵の登録

既にやってるひとは飛ばして構わないです.

ここの操作はMacBook Pro上で行っていますが,やることとしてはWindowsLinuxでも同じで

  1. 公開鍵と秘密鍵のペアを作る
  2. Githubに公開鍵を登録

の2つだけです.

鍵ペアの作成

ssh-keygenコマンドを使用します.-Cはコメントで,末尾に付加されるユーザ名@ホスト名を上書きします(オプション).

$ ssh-keygen -t rsa -b 4096 -f ~/.ssh/{key name} -C "youremail@hoge.com"

公開鍵の中身をクリップボードにコピーします.

$ pbcopy < ~/.ssh/{key name}.pub

登録

  1. https://github.com/settings/keys からログイン
  2. 右上のNew SSH keyをクリック
  3. わかりやすいTitleを入力してKeyセクションに先ほどコピーした公開鍵をペースト
  4. Add SSH keyをクリック

SSHしてみる

クライアントPCからアクセスしてみます.今後のためにまず~/.ssh/configに設定を追記しておきます.sshするポートを変更している人などは適宜読み替えてください.

ServerAliveInterval 60

Host hoge
    HostName {address of your server}
    User {your github name}
    Port 22
    IdentityFile ~/.ssh/{key name}

いざログイン(初回はAre you sure you want to continue connecting (yes/no)?みたいなメッセージが出ます)

$ ssh hoge
Welcome to Ubuntu 16.04.3 LTS (GNU/Linux 4.4.0-62-generic x86_64)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/advantage

SAKURA Internet [Virtual Private Server SERVICE]

Last login: Sun Dec 10 00:00:00 2017 from 000.00.00.0
{your github name}@{your host}: 

わーいやったー!

sudoしたい

ここまででsshはできましたが,実はこのままではsudoできません./etc/octopass.confGroupsがユーザの所属するグループを決定するのですが,これをsudoとかにしてもsudoはできません.どうやるんだーーーーとなってissueで質問させていただきました.

github.com

まさかその日中にレスポンスを頂けるとは…ありがたい.

というわけで,/etc/sudoers.d/以下に設定を追記します.

sudoerへのグループの追加

/etc/octopass.confGroupsを設定しなかった場合,初期値はGithub Teamのチーム名が使用されます(ここではhogeとします).

このhogeグループに対してパスワードを要求せずsudoする権限を与える必要があるようです.(ここからはサーバ上での作業です.さくらのVPSのシリアルコンソールから作業しました*2

$ sudo visudo -f /etc/sudoers.d/hoge

立ち上がるのがvimじゃなくていつも戸惑うのですが,下記のように記述します.

%hoge ALL=(ALL) NOPASSWD: ALL

これで保存するとhogeグループのユーザはパスワード無しでsudoできるようになります*3

まとめ

Team単位で認証できるため,例えば新しく鯖管を育成したいとなったときもユーザ作って〜鍵登録して〜とかせず,Teamにメンバーを追加するだけで済みます.また,複数サーバを建てた際にも最初にこれを導入さえしてしまえば他のメンバがログインして作業できるため効率的です.

サーバ数台程度の規模ならばGithubAPI制限にかかる可能性は低いですし,速度面も特に気になりませんでした.Githubが落ちてるときはどうせまともに作業できないのでゆっくりコーヒーでも飲んで待ちます.

あとは複数ロールへの対応が欲しい…sudo不可のTeamを追加して運用できれば小規模チームにとってはかなり最高なソリューションなのでは.
以上,導入してみた所感でした.

*1:riceplanting.hatenablog.jp

*2:Githubアカウントをつかってsshしてもsudo権限なくて作業できないため

*3:ちょっと怖いですが正直sshの公開鍵認証が破られた時点で終わりみたいなものという気がしているので現状あまり気にしていません.sshコネクション張ったままPCを放置していたら知らん…そんな奴うちには居ないはず…

ghq管理下のリポジトリを色々するAlfredWorkflowを作った

色々するとは言っても基本的に何かで開くだけです.

きっかけ

最近ghq*1リポジトリを管理,pecoで開くというスキーム*2が流行りだという情報を耳にしたので導入してみたところかなり快適になりました.
Windowsでも同様のことが出来るというのが嬉しくて,ついつい散らばりがちなリポジトリたちをちゃんと管理していこうという気持ちが芽生えました.

開発でメインで使用している端末はMacBook Proなのですが,せっかくAlfredがあるのだからterminal以外からでも叩けると嬉しいなという気持ちで作り始めました.

環境

今回もGoで書いています.Workflowを使うだけであればGoの環境もGlideも必要ありません.ghqは必要です.

ghq-alfred

リポジトリは以下になります.

github.com

準備

現在最新のworkflowは以下からダウンロードできます.

Release v0.2.0: Actual 'First Release' · pddg/go-ghq-alfred · GitHub

インストールしたら設定画面を開き,Workflow Environment Variableghqの値を環境に合わせて設定してください*3
また,こちらでは使用するエディタとしてVS Code,ターミナルアプリにiTermを指定していますが,環境に合わせて指定してください.

できること

ghq list -pの結果と入力したクエリを元にリポジトリを絞り込みます.ghq {query}で検索を掛けます.

Finderで開く

選択してEnterまたはCommand+EnterでFinderで開きます.

f:id:pudding_info:20170924014501g:plain

ブラウザで開く

選択してShift+Enterでデフォルトブラウザでそのリポジトリのページを開きます.

f:id:pudding_info:20170924014623g:plain

ターミナルで開く

選択してFn+Enterでターミナルで開きます.僕はiTermを指定していますが,標準のterminalでも問題なく表示できるかと思います.

f:id:pudding_info:20170924014803g:plain

エディタで開く

選択してControl+Enterでエディタでそのリポジトリディレクトリを開きます.僕はVS Codeを指定していますが,大抵のエディタで可能かと思います.

f:id:pudding_info:20170924015028g:plain

Googleで検索

{user名}/{repository名}で検索をかけます.

f:id:pudding_info:20170924015133g:plain

できないこと

  • 上に書いたこと以外の全て.

既知の不具合

ghqではgit config --global --add ghq.root path/to/dirでデフォルトのroot以外にもリポジトリの入ったディレクトリを指定し参照できます.
が,このときghqの形式,つまり{root}/{site}/{user}/{repo}の形を取っていないディレクトリも含めることが出来てしまいます.

このWorkflowではフルパスから推察してそのリポジトリをbitbucketやgithubで開く設計になっているため,そういった形式のリポジトリ

  • ブラウザで開くことが出来ません.
  • Google検索も上手く機能しません.
  • Alfredでの表示が{user}/{repo}になりません.
  • Finderで開く,エディタで開く,などの動作は愚直にパスを渡すだけの設定になっているため問題なく動作いたします.

今のところghqディレクトリ形式を取らないリポジトリに対する動作を保証する予定はありません.

改善したいこと

アイコンをflaticonからお借りしているのですが,黒の部分以外が透過処理になっているため見づらい…
探してはいるのですが,あまりよい代替品を見つけられていません.何か良いアイコンをお知りでしたら,教えて頂ければ幸いです.

あと1日で書いたコードなのでバグなどがあるかと思います.見つけたらできればgithubの方まで…

まとめ

pythonrubyでScript Filterを頑張って書くよりも,Goで書いたバイナリを叩く方が気兼ねなく色々できて良いんじゃ無いかと思います.
ghqは便利なツールなのでid:motemen氏に感謝しつつ使わせて頂いています.

Alfredはいいぞ.

*1:motemen.hatenablog.com

*2:blog.craftz.dog

*3:AlfredのScript Filterではbashなどで設定したPATHが読み込まれません.環境の違いを吸収する手段も色々考えましたが,一度設定するだけなので諦めてこの形をとりました.

Entrykitのrenderで遊ぶ -後編-

頑張りすぎじゃ無いか感が溢れてきます.

発展的な記法

環境変数からの値の取得しか存在しないわけでは無く,色々できます.

include

これは比較的便利に扱えるのでは無いでしょうか,templateのモジュール化ができます.また,第二引数以降に"VAR=VALUE"の形で渡すと,includeするtemplateの中からvar "VAR"などとして値を取り出すことが出来ます.

ここではecho '{{ var "INCLUDED" }}' > include_test.tmplとしてincludeするtemplateを作成しておきます.

{{ include "include_test.tmpl" "INCLUDED=True" }} # -> True

file, text

ファイルの中身を読みとって文字列として展開します.例えばecho "hello world" > hello.txtとしてテンプレートと同じ階層に置いたとします.

# file
{{ file "hello.txt" }} # -> hello world

# text
{{ text "hello.txt" }} # -> hello world

ソースを読む限りこの二つの内部的な実装は全く同じに見えます*1

dir, dirs, files

引数として任意のディレクトリへのパスを渡します.文字列を返すわけでは無いことに注意してください.

# 指定されたディレクトリ中のディレクトリ・ファイルの名称の配列を返す
{{ dir "./" }}

# 指定されたディレクトリ中のディレクトリのみの名称の配列を返す
{{ dirs "./" }}

# 指定されたディレクトリ中のファイルのみの名称の配列を返す
{{ files "./" }}

存在しないディレクトリを参照しようとするとnot foundなエラーを発して中断します.

httpget, urlquery

APIリクエストしたりすることができます*2ca-certificatesをインストールしていないとエラーを吐いて死にます.

{{ httpget "http://search.twitter.com/search.json?lang=ja&rpp=20&q=%23poyo" }}

注意すべきなのは,返すオブジェクトが文字列では無いことでしょう.Goのオブジェクトが返却されるため,htmlがそのまま出力されるわけではありません.

json, tojson

ではAPIリクエストしたところでどうするんだという話ですが,これでjsonオブジェクトに変換できます.

{{ httpget "http://search.twitter.com/search.json?lang=ja&rpp=20&q=%23poyo" | json }}

これもまた返り値はGoのオブジェクトなのでノイズが混ざっています.純粋にレスポンスを記述するだけなら以下の様にします.

{{ httpget "http://search.twitter.com/search.json?lang=ja&rpp=20&q=%23poyo" | json | tojson }}

tojsonはgoのオブジェクトを受取り,jsonをstringにして返します.

yaml, toyaml

jsontojsonyaml版です.

split, join

stringから配列を生成することができます.

# 配列の生成
{{ split "a,b,c,d" }} # -> [a, b, c, d]

# 任意のセパレータで区切った文字列に変換
{{ split "a,b,c,d" | join ":" }} # -> a:b:c:d

splitkv, joinkv

少し難解で使いどころがよく分からないですが,splitjoinが配列を扱うのに対してmapを扱います.

# mapの生成
{{ split "\n" "a=A,b=B,c=C" | splitkv "=" }} # -> [a=A b=B c=C] 

# mapの値へアクセス
## mapを変数へ代入
{{ $map_var := (split "\n" "a=A,b=B,c=C" | splitkv "=") }}
 ## アクセス
{{ $map_var.a }} # -> A
{{ $map_var.d }} # -> <no value>
## 代入はできないようでエラーを吐く
{{ $map_var.d := "D" }} # -> unexpected ":=" in operand

# 任意のセパレータで文字列にする
{{ joinkv ":" $map_var }} # -> [a:A b:B c:C]

joinkvは直接文字列を返すわけではなく,key{{任意のセパレータ}}valueの形で文字列化し,その配列を返します.全体を文字列にするにはさらにjoinをはさむ必要があります,

seq

数値の配列を作成します.引数に数字(intまたはstring)をとり,0からその値までの配列を作って返します.

{{ seq 10 }} # -> [0 1 2 3 4 5 6 7 8 9 10]

append, drop

配列のへの値の追加,削除ができます.

# 数値でも
{{ seq 10 | append 11 | drop 1 }} #-> [0 2 3 4 5 6 7 8 9 10 11]

# 文字列でも
{{ split "," "a,b,c,d" | append "e" | drop "a" }} # -> [b c d e]

index

配列への添字アクセスができます.

{{ index (seq 5) 1 }} # -> 1

第一引数に配列を取ることに注意が必要です.また,index {{任意の多次元配列}} 1 2 3などは{{任意の多次元配列}}[1][2][3]と同義になります.

sh

もはやなんでもありですが任意のコマンド実行が出来ます.出力された値が文字列として取得できます.

{{ sh "ping -c 1 www.google.com" }}

sigilにはあるが使えなかったもの

  • jmespath

jsonからクエリを使って値を取り出すことができるようなのですが,renderではnot definedと言われてしまいました.

組み合わせる

ファイル一覧でループ

配列の生成ができることから,色々なループ処理が可能になります.例えば任意のディレクトリ内のファイル一覧に対して,*.logの場合には監視対象に加える,などといった処理が可能になります.

{{ range $index, $f := (files "/path/to/log") }}
{{ if match $f "*.log" }}
    something $f to do
{{ end }}

環境変数を判定

()を用いるとだいたいなんでもできるので,例えば環境変数も(どっかのQiitaではできないとか書いてましたが)ifで用いることが出来ます

{{ if eq "test" (var "TEST") }}
  This is test.
{{ else }}
  This is not test.
{{ end }}

まとめ

もっと色々やろうかと思ったけど力尽きた.

*1:textの方は何かエラー処理のコードが入っていますが,エラーがnilかどうかを判定して受け流しているだけのように思います.

*2:例ではTwitter API 1.0を使用しているので普通にエラーメッセージが返ってきます