未分類

概要

apache2.2で.htaccessしか使えない状況での設定で手間取りましたが、VALUE-SERVERでgitリポジトリを公開することが出来たので、その方法を記事にしました。本来こういう用向きはGitHubでいいのですが、やっぱりやってみたいでしょ?

なお、今回はgitコマンドで扱えるhttpsのリポジトリを作成するところまでで、WebUIの設定は次回予定です。

設定の検討

今回はGitのマニュアルをベースに設定を検討していきます。

Smart HTTPで公開

Git – Smart HTTP

Gitのリポジトリ公開方法は、2020年1月31日現在、gitプロトコル、ssh、httpの3種類で、httpはさらにdum/smartの2種類に分かれます。gitプロトコルはVALUE-SERVERでは使えないし、sshだと公開してもVALUE-SERVER上ではアカウント作れないので自分しか使えません。 今回はhttpのより洗練されたsmartで公開する、ということです。

Apache自体のインストール

マニュアルではここからやっているのですが、これはVALUE-SERVERでは出来ません。

$ sudo apt-get install apache2 apache2-utils
$ a2enmod cgi alias env rewrite

https://git-scm.com/book/ja/v2/Git%E3%82%B5%E3%83%BC%E3%83%90%E3%83%BC-Smart-HTTP

最初に行っているのはdebian系Linuxでのapache2とapache2-utilのインストールです。apache2相当は絶対にインストールされてます。apache2-utilは、恐らくhtpasswdコマンドをインストールするためだと思うので、これはVALUE-SERVERにも入っており、問題ありません。

次に行っているのは、mod_cgi、mod_alias、mod_env、mod_rewriteなどのモジュールの有効化です。これらもVALUE-SERVERで有効になってます。apachectl -Mで確認できるので、気になる人はやってみてください。

つまりApache自体のインストールについては、すでにやってあるので問題ないということです。

gitリポジトリの所有グループ変更

次にやってるのはリポジトリ内のファイル・ディレクトリの所有グループ変更です。

$ chgrp -R www-data /opt/git

https://git-scm.com/book/ja/v2/Git%E3%82%B5%E3%83%BC%E3%83%90%E3%83%BC-Smart-HTTP

これはapacheからリポジトリを読み書きするために必要なことなのですが、VALUE-SERVERの設定では、ユーザーが扱える$HOME/public_html/*のファイルを実行するときにユーザーのアカウントで実行されるため設定の必要がありません。逆に言うとWebから自分のアカウントで出来ることは何でも出来るということなのですが…そういうセキュリティのプランなのです。

Apache の設定

いよいよ本題に近付いてきました。マニュアルで次に記載があるのは、httpd.confとかapache2.confやその分散された設定ファイルの内容です。これはディストリビューションごとにファイル名やディレクトリ構成が違いますが、そもそも管理者権限のないレンタルサーバでは変更できない部分です。

SetEnv GIT_PROJECT_ROOT /opt/git
SetEnv GIT_HTTP_EXPORT_ALL
ScriptAlias /git/ /usr/lib/git-core/git-http-backend/

https://git-scm.com/book/ja/v2/Git%E3%82%B5%E3%83%BC%E3%83%90%E3%83%BC-Smart-HTTP

これらは、ユーザーが唯一編集可能な設定ファイルである、.htaccessで記述する必要があります。内容とともに解説すると…

SetEnvは環境変数を設定するもの(ディレクティブ)です。ここではGIT_PROJECT_ROOT(リポジトリの場所)とGIT_HTTP_EXPORT_ALL( GIT_PROJECT_ROOT以下のリポジトリを全て公開する意思表示)を設定しています。これらは.htaccessに記載が可能であり、またVALUE-SERVERではOverrideが許可されている(つまり普通に使える)ので問題ありません。

ScriptAliasは、スクリプトの位置とURLの紐付けを行うもの(ディレクティブ)です。これは.htaccessには記載できないので、代替手段が必要になります。

ScriptAliasの代替

今回の設定は、URLパスとして/git/以下でアクセスされた場合に、/usr/lib/git-core/git-http-backendが処理をし、環境変数PATH_INFOに/git/をルートとしたパスが入るということを意味します。git-http-backendはgitパッケージに付属するコマンドで、VALUE-SERVERにも /usr/libexec/git-core/git-http-backend に入っています。ユーザーがWebから直接 git-http-backend を処理させる手段はありませんが、CGIスクリプトから呼び出すことは可能です。ということはつまり、$HOME/public_html/ドメイン/git ディレクトリの.htaccessに以下の記述を入れて、

AddHandler cgi-script .cgi

git-http-backend.cgiという名前で

#!/bin/sh
/usr/libexec/git-core/git-http-backend

とすれば、 URLパスとして/git/git-http-backend.cgi/以下でアクセスされた場合に、/usr/libexec/git-core/git-http-backendが処理をし、環境変数PATH_INFOに/git/git-http-backend.cgi/ をルートとしたパスが入る、を実現できます。

BASIC認証の設定

マニュアルで次に行っているのはBASIC認証の設定です。

RewriteEngine On
RewriteCond %{QUERY_STRING} service=git-receive-pack [OR]
RewriteCond %{REQUEST_URI} /git-receive-pack$
RewriteRule ^/git/ - [E=AUTHREQUIRED]

<Files "git-http-backend">
    AuthType Basic
    AuthName "Git Access"
    AuthUserFile /opt/git/.htpasswd
    Require valid-user
    Order deny,allow
    Deny from env=AUTHREQUIRED
    Satisfy any
</Files>

解説

  1. Rewriteを有効にします
  2. URLのクエリ文字列(?以降の部分)が"service=git-receive-pack"である場合、もしくは
  3. URLのパス文字列が"/git-receive-pack"で終わっている場合
  4. URLのパス文字列が"/git/"で始まっているなら環境変数AUTHREQUIREDを設定する
  5. ファイルシステム内のファイル名がgit-http-backendである場合
    1. 認証方式をBASIC認証とする
    2. realmを"Git Access"とする
    3. BASIC認証のパスワードを/opt/git/.htpasswdから読むものとする
    4. ユーザーの認証を「必要」とする
    5. 全てを許可した上で、Deny処理→Allow処理の順で処理する
    6. 環境変数 AUTHREQUIRED が設定されていたらDenyする
    7. Allowされているか、「必要」を満たせばアクセスを許可する

つまり git-http-backendで処理するケースでは、「URLのクエリ文字列(?以降の部分)が"service=git-receive-pack"である」か「URLのパス文字列が"/git-receive-pack"で終わっている」場合はBASIC認証を要求し、それ以外の場合は無条件にアクセスを許可するという設定。ようはgit cloneやpullなど読み取りは無条件アクセスで、git pushなど書き込みはBASIC認証を付けたいということ。

VALUE-SERVERでの対応検討

この部分は全て.htaccessに記述可能です。しかし、VALUE-SERVERのapache2.2では期待したとおりに機能しません。理由は以下の記事のとおりです。

ようは、適用順番のせいで「環境変数 AUTHREQUIRED が設定されていたらDenyする」が機能しないということなので、常に「無条件にアクセスを許可するという設定」になってしまいます。さすがに認証なしで無制限に書き込みが出来てしまったら何が起きるか分かりません(デフォルトでは無条件にアクセスしても書き込みはエラー(403)になりますが)。

RewriteでダメならSetEnvIfではどうか?

Rewriteは主にリダイレクトや、読み替え、つまりURLの書き換えに似た機能です。しかし、今回はURLの書き換えは行っておらず、HTTPリクエストのパラメータを解析してBASIC認証の必要判定を環境変数に入れたいだけです。であれば、SetEnvIfでも出来そうな気がしますが…しかしSetEnvIfではQUERY_STRINGパラメータを扱えません。今回の用件では使用できないということです。

じゃあQUERY_STRINGを使わない方法はないの?

ありました。

If you do not have mod_rewrite available to match against the query string, it is sufficient to just protect git-receive-pack itself, like:

https://git-scm.com/docs/git-http-backend
<LocationMatch "^/git/.*/git-receive-pack$">
 AuthType Basic
 AuthName "Git Access"
 Require group committers
 ...
</LocationMatch>

BASIC認証をするタイミングは遅れますが、これでもpushなど書き込みに制限付けられます。ただし、

In this mode, the server will not request authentication until the client actually starts the object negotiation phase of the push, rather than during the initial contact. For this reason, you must also enable the http.receivepack config option in any repositories that should accept a push. The default behavior, if http.receivepack is not set, is to reject any pushes by unauthenticated users; the initial request will therefore report 403 Forbidden to the client, without even giving an opportunity for authentication.

https://git-scm.com/docs/git-http-backend

遅れた分のアクセスはデフォルトだとエラーになるので、 git-http-backendには「認証してなくてもエラーにしない設定」(http.receivepack)にする必要があります。apacheが許可したらOKということです。

ただURLとのマッチングを行うLocationMatchセクションは.htaccessでは使用できません

解: LocationMatchをSetEnvIfで置き換える

こうしてしまえばいいのです。

SetEnvIf Request_URI "/git-receive-pack$" AUTHREQUIRED=yes
AuthType Basic
AuthName "Git Access"
Require group committers
...
Order deny,allow
Deny from env=AUTHREQUIRED
Satisfy any

これが今回使用した方法の骨子になります。

パスワードの設定

マニュアルの続きです。htpasswd -c パスワードファイル ユーザー名 して、パスワードファイルを作成して認証可能なユーザーを1人作ってるだけです。 htpasswdの使い方はそこら中にあるので各自で調べてください。

実際の設置方法

URIのベースとなるディレクトリを決めて作成

マニュアルだと/git/になっていた部分ですね。そのまま使うと悪戯されそうなので、ちょっと変えました。

$ cd $HOME/public_html/(ドメイン名)/
$ mkdir gitrepos

.htaccessの設置

$HOME/public_html/(ドメイン名)/gitrepos/に以下の.htaccessを置きました。

SetEnv GIT_PROJECT_ROOT (ホーム)/(リポジトリの設置場所)
SetEnv GIT_HTTP_EXPORT_ALL
AddHandler cgi-script .cgi

SetEnvIf Request_URI "/git-receive-pack$" AUTHREQUIRED=yes

Order Deny,Allow
Deny from env=AUTHREQUIRED
AuthType Basic
AuthName "Git Access"
AuthUserFile (ホーム)/(リポジトリの設置場所)/.htpasswd
AuthGroupFile /dev/null
Require valid-user
Satisfy Any

CGIスクリプトの設置

$HOME/public_html/(ドメイン名)/gitrepos/に以下のgit-http-backend.cgiを置きました。

#!/bin/sh
/usr/libexec/git-core/git-http-backend

実行可能属性を付けておきます。

$ cd $HOME/public_html/(ドメイン名)/gitrepos/
$ chmod u+x git-http-backend.cgi

gitリポジトリの作成

このディレクトリはセキュリティ上の理由から、$HOME/public_html/(ドメイン名)/ 配下には置きません。ここでは $HOME/(リポジトリの設置場所)/に複数のリポジトリを置くものとし、今回はsample1という名前のリポジトリを作成します。

$ mkdir -p $HOME/(リポジトリの設置場所)
$ cd $HOME/(リポジトリの設置場所)
$ mkdir sample1
$ cd sample1
$ git init --bare
$ git config http.receivepack true

最後のコマンドが、「認証してなくてもエラーにしない設定」をしています。今回の認証方式を使う場合は、公開リポジトリを作成する度に必要な操作になります。

パスワード認証ファイルの作成

$ cd $HOME/(リポジトリの設置場所)
$ htpasswd -c .htpasswd ユーザー名
$ chmod og-rwx .htpasswd

ユーザーは必要な数だけ作成してください。VALUE-SERVERでは、パスワード認証ファイルは自分以外読めない設定で十分です。

最後に

BASIC認証はパスワードが暗号化されません。必ずhttpsでアクセスするようにしてください。

公開リポジトリへの接続確認

早速リポジトリにアクセスしてみます。

$ git clone https://(ドメイン名)/gitrepos/git-http-backend.cgi/sample1
Cloning into 'sample1'...
warning: You appear to have cloned an empty repository.
$ 

取得成功したようです。それでは適当に何かcommitしてpushしてみます。

$ cd sample1
$ touch hoge
$ git add .
$ git commit -m "initial commit."
[master (root-commit) 23b1fde] initial commit.
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 hoge
$ git push origin master
Enumerating objects: 3, done.
Counting objects: 100% (3/3), done.
Writing objects: 100% (3/3), 205 bytes | 205.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0)
Username for 'https://(ドメイン名)': (htpasswdで設定したユーザー名)
Password for 'https://(ユーザー名)@(ドメイン名)': (htpasswdで設定したパスワード)
To https://(ドメイン名)/gitrepos/git-http-backend.cgi/sample1
 * [new branch]      master -> master
$

pushの途中でちゃんとBASIC認証が入り、pushできました。

→成功

未分類

前回の Apache設定ファイルのSetEnv/SetEnvIf適用順番 と似たような記事です。

確認したいこと

VALUE-SERVERの現行Apache 2.2の.htaccessで、RewriteRuleとDenyの適用順番を確認したい

実験

まずはやってみる

.htaccess

RewriteCond REMOTE_ADDR .*
RewriteRule .* - [E=HOGE:deny]
Order Deny,Allow
Deny from env=HOGE

解説

  1. クライアントのIPアドレスが正規表現で.*であるかどうか?=全てに一致するので、常に条件を満たす
  2. .htaccessの置いてあるディレクトリ部分を除いたURLのパス指定部分が正規表現で.*である部分(つまり全て)を無変更とし、環境変数HOGEに値denyを設定する
  3. Orderはまず全てをAllowとした上で、Deny処理を適用後に、Allow処理を入れるという宣言
  4. 環境変数HOGEが設定されていたら全てDenyとする

つまり、記述された順番どおりに適用されるなら、全て403 Forbiddenにするという設定

結果

こちらに上記.htaccessを置いたが、200 OKで見えてしまっている。RewriteRuleの適用順序がDenyよりも遅い可能性が高まった。

RewriteRuleで環境変数を設定できていることの確認

.htaccess

RewriteCond REMOTE_ADDR .*
RewriteRule .* - [E=HOGE:deny]
Header set X-Frame-Options %{HOGE}e env=HOGE

解説

前回記事とあわせて全て説明されているので詳細は省略。環境変数経由でHTTPレスポンスヘッダX-Frame-Optionsにdenyを設定している。

結果

こちらに上記.htaccessを置いたが、Chromeの開発者ツールのネットワークで、以下のレスポンスが確認できた。

HTTP/1.1 200 OK
...
X-Frame-Options: deny
...

つまり、RewriteRuleの記述に問題はない→最初の実験の設定では、RewriteRuleより先にDenyが評価されている。

DenyをFilesMatchセクションに置いたら、評価が遅くならないか確認

.htaccessで置けるセクションとしてはFileかFilesMatchだと思うので、その条件で最初の実験を再確認する。

.htaccess

RewriteCond REMOTE_ADDR .*
RewriteRule .* - [E=HOGE:deny]
<FilesMatch ".*">
Order Deny,Allow
Deny from env=HOGE
</FilesMatch>

解説

FilesMatchは、ファイルシステムベースでのファイル名でマッチングをかけるセクション。このケースではどんなファイル名でも該当する。なので最初の実験と内容は同じ。

結果

こちらに上記.htaccessを置いたが、最初の実験同様見えてしまっている。こう書いてもRewriteRuleの適用順序がDenyよりも遅い 。

結論

VALUE-SERVERの現行Apache 2.2の.htaccessでは、Deny→RewriteRuleの順で適用される。

※Apacheが2.4だったり、server configに記載された場合などは確認していない

未分類

2020年1月30日現在、ココVALUE-SERVERで使用されているapache2.2(古い)で.htaccessを使っていろいろな設定が出来る。出来るけど、その設定には適用順番があるって話(2.4でも同様)。

事の発端

SetEnv HOGE "deny"
Order Deny,Allow
Deny from env=HOGE

この.htaccessが置かれたディレクトリが見えてしまう。論より証拠がこちら

参考リンクに置いたapacheのドキュメントに従って解説を入れると、

  1. SetEnvは環境変数HOGEに値denyを設定している
  2. Orderはまず全てをAllowとした上で、Deny処理を適用後に、Allow処理を入れるという宣言
  3. Denyは環境変数HOGEが設定されていたら、全部Denyとするという意味

つまり、書かれた順番どおりに処理されたなら、この.htaccessが置かれたディレクトリでは全て403 Forbiddenになるはず」ということ。しかし200 OKで見えてしまっているわけです。

いろいろ疑い調べましたが、日本語の2.2のマニュアルには記載がありませんが、英語の2.2以降のマニュアルと、日本語の2.4以降のマニュアルに、

SetEnv はリクエスト処理の 段階の中でも遅くに実行されます。つまり SetEnvIf や RewriteCond などからは、変数がそこで設定されていることがわかりません。

https://httpd.apache.org/docs/2.4/ja/env.html#setting

という、よ~く読み漁らないと見つからない記述を見つけました。2.2の日本語版ではどれだけ読み漁っても見つかりませんけど…。

で、「それ本当なの?」「本当ならどう書けばいいの?」というのを読み解くのが今回のミッションです。

実験

そもそもSetEnv出来てるの?

SetEnvの成功例が必要です。確認できたのが、こちらの.htaccessになります。

SetEnv HOGE "deny"
Header set X-Frame-Options %{HOGE}e env=HOGE

解説

  1. 環境変数HOGEに値denyを設定する
  2. 環境変数HOGEが設定されていたら、HTTPレスポンスヘッダのX-Frame-Optionsに環境変数HOGEの内容を設定する

結果

こちらのリンク先のディレクトリに当該.htaccessを置いています。Chromeなどの開発者ツールでNetworkモニタリングしつつ、リンク先を読み込めば、HTTPプロトコルのレスポンスヘッダを見ることで分かります。

HTTP/1.1 200 OK
...
X-Frame-Options: deny
...

SetEnv自体は出来てますね。成功です。つまり処理順序の問題であるという可能性が高まりました。

じゃあDeny fromは出来てるの?

次はDeny fromの成功例が必要です。環境変数パターンは試せてませんが、.htaccessでAllow/Deny設定を上書き可能かどうかはApacheの設定次第なので、確認が必要ということです。確認用の.htaccessが以下です。

Order Deny,Allow
Deny from All

解説

  1. Orderはまず全てをAllowとした上で、Deny処理を適用後に、Allow処理を入れるという宣言
  2. 全てをDenyする

つまり、「この.htaccessを含むディレクトリ以下は全て403 Forbiddenになる」という設定です。

結果

こちらのリンク先のディレクトリに当該.htaccessを置いています。 辿れば分かりますが、403になります。置かれてるファイルはいつものsample.htmlです。

SetEnvIfとSetEnvの処理順序確認

記述内容の確認になります。SetEnvIfからSetEnvの結果を参照して本当にうまく動作しないかどうかを見ます。確認用.htaccessが以下になります。

SetEnv HOGE "deny"
SetEnvIf HOGE "deny" HOGECHILD=deny
Header set X-Frame-Options %{HOGECHILD}e env=HOGECHILD

解説

  1. 環境変数HOGEに値denyを設定する
  2. 環境変数HOGEの値がdenyだったら、環境変数HOGECHILDに値denyを設定する
  3. 環境変数HOGECHILDが設定されていたら、HTTPレスポンスヘッダX-Frame-Optionsに環境変数HOGECHILDの値を設定する

結果

こちらのリンク先のディレクトリに当該.htaccessを置いています。Chromeなどの開発者ツールでNetworkモニタリングしつつ、リンク先を読み込めば、HTTPプロトコルのレスポンスヘッダを見ることで分かります。

HTTP/1.1 200 OK
Date: Wed, 29 Jan 2020 23:38:37 GMT
Server: Apache
Last-Modified: Wed, 29 Jan 2020 21:28:41 GMT
ETag: "1581901-31-59d4e063ccc5b"
Accept-Ranges: bytes
Vary: Accept-Encoding,User-Agent
Content-Encoding: gzip
Content-Length: 61
Connection: close
Content-Type: text/html

X-Frame-Optionsヘッダの出力がありません。確かにうまく動作しておらず、SetEnvIfがSetEnvより処理が早い可能性が高まります。

SetEnvIfは単体なら動くの?

単体で機能するなら、処理順序で確定します。確認用.htaccessが以下になります。

SetEnvIf Remote_Addr ".*" HOGE=deny
Header set X-Frame-Options %{HOGE}e env=HOGE

解説

  1. クライアントIPアドレスRemote_Addrの値が正規表現で".*"にマッチしたら、環境変数HOGEに値denyを設定する(何でもマッチするので、必ず設定される)
  2. 環境変数HOGEが設定されていたら、HTTPレスポンスヘッダX-Frame-Optionsに環境変数HOGEの値を設定する

結果

こちらのリンク先のディレクトリに当該.htaccessを置いています。Chromeなどの開発者ツールでNetworkモニタリングしつつ、リンク先を読み込めば、HTTPプロトコルのレスポンスヘッダを見ることで分かります。

HTTP/1.1 200 OK
...
X-Frame-Options: deny
...

X-Frame-Optionsヘッダの出力されています。単体なら確かにうまく動作しており、SetEnvIfがSetEnvより処理が早いことが確定しました。

SetEnvの代わりにSetEnvIfにしてみる

最後にどうすればいいの?の回答です。.htaccessが以下になります。

SetEnvIf Remote_Addr .* HOGE=deny
Order Deny,Allow
Deny from env=HOGE

新しい要素がないので解説は省略

結果

こちらのリンク先のディレクトリに当該.htaccessを置いています。 辿れば分かりますが、403になります。置かれてるファイルはいつものsample.htmlです。

参考リンク