2019年4月14日日曜日

Samba4.8 における idmap からのユーザ毎ホームディレクトリパスに苦しむ(回避策のみで解決せず)

何年ぶりかで Samba(Samba 4.8 on CentOS 7) のファイルサーバ構築をやってるのでメモ。AD と LDAP が混在したような環境の中で,あるユーザが UNIX からも Windows からも自身の同じホームディレクトリにアクセスしたいっていう大学とかにはよくある構成(今回のこの Samba はドメインコントローラの役割は担わない)。

設定はほぼ終えた。が,一点困っているのは,上記のような構成において[homes] セクション内の %H 変数で得られるホームディレクトリのパスが template homedir ディレクティブの設定に固定されてしまうということ。今回はユーザ毎にホームディレクトリのパス形式が違うのでこれだと困る。なので NSS から取得できる値にしたい(getpwnam()で取れるやつ)。LDAP の homeDirectory 属性に設定されているやつでも良い。

しかし,顧客要件から設定は security = ads が前提。この場合 Samba 4.8 からは winbind 必須とのこと(Samba 4.8 のリリースノート参照)。idmap_nss と idmap_rfc2307(LDAPをバックエンドにする) を試したが、どちらも sid-uid のマッピングは成功するものの、ホームディレクトリは template homedir の値になってしまう。どうも winbind はマッピングに関連する属性(uid, uidNumber, cn , gidNumber等)しかバックエンドから取得せず,それ以外は自前で処理(template xxxxから)してしまう仕様ようだ。Web には idmap_rfc2307 なら出来るという情報もちらほらあるが,少なくとも CentOS 7.6 上の Samba4.8.3 では無理なようだ。

証拠をつかもうと samba-4.8.3-4.el7 のソースを見た。すると以下のようなので,根本解決は Samba のソース改変しか無いと思われる。Workaround としては template homedir で表現可能なように,ホームディレクトリのシンボリックリンクを作成するくらいしか無いのではないか。

winbindd の getpwnam() の定義↓
source3/winbindd/winbindd.c:
           :
    578         { WINBINDD_GETPWNAM, "GETPWNAM",
    579           winbindd_getpwnam_send, winbindd_getpwnam_recv },
           :

winbindd_getpwnam_send() の中で wb_lookupname_send() のコールバックとして winbindd_getpwnam_lookupname_done() を定義↓
source3/winbindd/winbindd_getpwnam.c:

           :
     37 struct tevent_req *winbindd_getpwnam_send(TALLOC_CTX *mem_ctx,
     38                                           struct tevent_context *ev,
     39                                           struct winbindd_cli_state *cli,
           :
     80         subreq = wb_lookupname_send(state, ev,
     81                                     state->namespace,
     82                                     state->domname,
           :
     88         tevent_req_set_callback(subreq, winbindd_getpwnam_lookupname_done,
     89                                 req);
     90         return req;
     91 }
           :

winbindd_getpwnam_lookupname_done() で wb_getpwsid_send() をコール↓
source3/winbindd/winbindd_getpwnam.c:

           :
     93 static void winbindd_getpwnam_lookupname_done(struct tevent_req *subreq)
     94 {
           :
    107         subreq = wb_getpwsid_send(state, state->ev, &state->sid, &state->pw);
           :


wb_getpwsid_send() で wb_queryuser_send() をコール↓
source3/winbindd/wb_getpwsid.c:

           :
     34 struct tevent_req *wb_getpwsid_send(TALLOC_CTX *mem_ctx,
     35                                     struct tevent_context *ev,
           :
     56         subreq = wb_queryuser_send(state, ev, &state->sid);
           :


wb_queryuser_send() で wb_sids2xids_send() のコールバックとして wb_queryuser_got_uid() を定義↓
source3/winbindd/wb_queryuser.c:
           :
     39 struct tevent_req *wb_queryuser_send(TALLOC_CTX *mem_ctx,
     40                                      struct tevent_context *ev,
     41                                      const struct dom_sid *user_sid)
     42 {
          :
     63         subreq = wb_sids2xids_send(
     64                 state, state->ev, &state->info->user_sid, 1);
     65         if (tevent_req_nomem(subreq, req)) {
     66                 return tevent_req_post(req, ev);
     67         }
     68         tevent_req_set_callback(subreq, wb_queryuser_got_uid, req);
     69         return req;
     70 }
           :

wb_queryuser_got_uid() の中では wbint_userinfo の homedir 要素に lp_template_homedir() を設定↓
source3/winbindd/wb_queryuser.c:
           :
     72 static void wb_queryuser_got_uid(struct tevent_req *subreq)
     73 {
           :
    106         info->homedir = talloc_strdup(info, lp_template_homedir());
           :
winbind においてホームディレクトリを設定しているのは上記箇所のみと思われる(見落としあったらどなたかご一報を)。 この lp_template_homedir() はおそらく source3/param/loadparm.c の以下から得られる値なので,idmap からの NSS getpwnam() や LDAP (RFC2307) homeDirectory の入る余地は無いと思われる。
source3/param/loadparm.c :
           :
    797         lpcfg_string_set(Globals.ctx, &Globals.template_homedir,
    798                          "/home/%D/%U");
           :

2018年12月5日水曜日

ldap.cidict.cidict

何年前の話してるんだよと言われそうだが、python-ldap で search_s とかで返ってくるディクショナリが case-insensitive じゃないてことで、わざわざ key(属性名)を lower() して reduce() で別のディクショナリ作って in とかで存在確認したりしてたとかもう誰にも言えないけど自分への戒めでここにメモっておきたい。

ldap.cidict.cidict(result[0][1]) で case-insensitive なディクショナリで返してくれるっていや、ホント知らなかった。ググっても意外と少ない。例えば以下のように objectClass の C が大文字でも小文字でも cidict した後なら in で存在チェックできる。

>>> 'objectclass' in ret[0][1]
False
>>> 'objectClass' in ldap.cidict.cidict(ret[0][1])
True

2018年9月28日金曜日

NGINX の proxy_pass のちょっとだけメモ

NGINX の proxy_pass で GET リクエストがどうなっちゃうのかいつもわからなくなるのでメモ。

http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_pass

まぁ全ては上記の NGINX に書いてあるんだけど、ここでは前方一致のやつだけ。

  • proxy_pass に URI が含まれる場合
  • location /name/ {
        proxy_pass http://127.0.0.1/remote/;
    }
    上記の NGINX ドキュメントページにおける「URI」という用語はどうも NGINX における $uri と同じで、ホスト名より後ろの文字列のようだ。上記例だと /remote/ という部分と思われる。

    この場合は、normalized URI (URI のデコード後: // を / にしたり %xx をデコードしたり)の location に定義した部分に一致する箇所を、proxy_pass て定義した URI 部分に置き換えて後ろのサーバに渡す模様。上記設定例の場合は例えば以下のようになる。
    http://www.example.com/name/foo?bar=baz

    http://127.0.0.1/remote/foo?bar=baz
    (/name/ を /remote/ に置き換えて GET リクエストはそのまま)

  • proxy_pass に URI が含まれない場合
  • location /some/path/ {
        proxy_pass http://127.0.0.1;
    }
    上記 NGINX ドキュメントには、proxy_pass に定義された値がホスト名部分で終わっちゃう場合は request uri ($request_uri)をそのまんま後ろのサーバに渡すって書いてある(または完全な normalized URI = $uri)。

    つまり上記設定例の場合は以下のような感じ。
    http://www.example.com/some/path/foo?bar=baz

    http://127.0.0.1/some/path/foo?bar=baz
    (ホスト名の後ろからはそのまま)

  • proxy_pass で変数を利用している場合
  • location /name/ {
        set myhost myhost.example.com;
        proxy_pass http://$myhost/;
    }
    この例のように proxy_pass 内で変数を利用してしまうと proxy_pass に指定した値のまま後ろのサーバに渡してしまう。つまり:
    http://www.example.com/name/foo?bar=baz

    http://myhost.example.com/
    (とにかく proxy_pass で指定したものしか渡さない)
    もしリクエストを渡したい場合、proxy_pass は以下のようにすれば良いだろう。
    proxy_pass http://$myhost$request_uri;

2018年9月20日木曜日

Dockerfile の EXPOSE もしくは docker run --expose の意味

どこのサイトとは言わないが、docker の EXPOSE について誤った情報が蔓延しているような気がしてならない。

そのサイトは docker0 で繋がっているコンテナ同士の通信のために EXPOSE が必要と書いている。つまり EXPOSE しないと他のコンテナからそのポートにアクセスできないということを言っている。

しかし Dockerfile に EXPOSE なんて書かなくても(docker run で --expose を与えなくても)、あるポートを listen しているコンテナに対して他のコンテナから接続することは可能だ。

嘘だと思うなら以下のような Dockerfile から作ったイメージを --expose せずに docker run して、他のコンテナから「nc -vz <相手> 8080」でもしてみるといい。普通に connected になるはず。
FROM fedora:28
RUN dnf install -y nc
ENTRYPOINT ["/usr/bin/nc", "-kl", "8080"]

私の EXPOSE の理解は以下。間違ってたらコメント等で指摘ください。

EXPOSE で指定されたポートは docker run に -P (--publish-all) を付与した際の対象ポートとなる。つまりコンテナ内のポートを -P でホストの任意ポートにマッピングしたい場合にEXPOSE でポートを明示する必要がある。

2018年8月30日木曜日

curl で Proxy Protocol

NGINX で proxy_protocol を設定しちゃうと、curl なんかのクライアントから直接 NGINX のサーバにアクセスできなくなっちゃって辛い。
検証やテストなんかのときに応答を確認したいのに、上段のロードバランサ等を介さなくちゃならないのは非常に辛い。
そこで curl に Proxy Protocol オプションが無いかなと探してたらありました。7.60.0 からの実装だそうです。


--haproxy-protocol
(HTTP) Send a HAProxy PROXY protocol v1 header at the beginning of the connection. This is used by some load balancers and reverse proxies to indicate the client's true IP address and port.
This option is primarily useful when sending test requests to a service that expects this header.
Added in 7.60.0.

Fedora はまだ追いついてないや(7.59.0)。バックポートもされてない。


2018年8月25日土曜日

fcitx-mozc for Fedora 28

Fedora Copr で fcitx-mozc を build してみました(fedora-28-x86_64 のみ)。

https://copr.fedorainfracloud.org/coprs/ryohayakawa/fcitx-mozc/

よろしければどなたか使っていただけるとうれしかったり。不安な方は src.rpm 見てみて下さい。怪しいことはしてません。

まぁそのうち epel とか Fedora 29 の chroot もやろうかと。