キアラの備忘録

雑多なメモを書いていきます

【SSH】SSHログイン時のチェック内容について

背景

  • 職場で .ssh関連ファイルの権限を変えてしまい、ログインできなくなる事象が発生
  • 対応過程で ssh 関連の権限をどうすればいいかがすぐ出て来なかったので、復習を兼ねて仕組みを調べてみました

前提

  • OpenSSH バージョン: V_7_6_P1
  • ログインユーザー名: Chiara
  • ssh_config にて 厳格モードが有効である

詳細

authorized_key の所有者・権限チェック

  • 所有者(以下、いずれかを満たす)
    • rootユーザー(ないし、root相当ユーザー)である
    • 所有者が、ログインするユーザーになっている
  • 権限
    • グループ, その他ユーザーに書き込み権限がない

https://github.com/openssh/openssh-portable/blob/V_7_6_P1/auth.c#L467

static FILE *
auth_openfile(const char *file, struct passwd *pw, int strict_modes,
    int log_missing, char *file_type)
{
    /// 一部省略 ///
    if (strict_modes &&
        safe_path_fd(fileno(f), file, pw, line, sizeof(line)) != 0) {
        fclose(f);
        logit("Authentication refused: %s", line);
        auth_debug_add("Ignored %s: %s", file_type, line);
        return NULL;
    }
    /// 一部省略 ///
}

https://github.com/openssh/openssh-portable/blob/V_7_6_P1/misc.c#L1673

int
safe_path_fd(int fd, const char *file, struct passwd *pw,
    char *err, size_t errlen)
{
    struct stat st;

    /* check the open file to avoid races */
    if (fstat(fd, &st) < 0) {
        snprintf(err, errlen, "cannot stat file %s: %s",
            file, strerror(errno));
        return -1;
    }
    return safe_path(file, &st, pw->pw_dir, pw->pw_uid, err, errlen);
}

https://github.com/openssh/openssh-portable/blob/V_7_6_P1/misc.c#L1608

int
safe_path(const char *name, struct stat *stp, const char *pw_dir,
    uid_t uid, char *err, size_t errlen)
{
    /// 一部省略 ///
    if ((!platform_sys_dir_uid(stp->st_uid) && stp->st_uid != uid) ||
        (stp->st_mode & 022) != 0) {
        snprintf(err, errlen, "bad ownership or modes for file %s",
            buf);
        return -1;
    }
    /// 一部省略 ///
}

.ssh、ホームディレクトリの所有者・権限チェック

  • 所有者(以下、いずれかを満たす)
    • rootユーザー(ないし、root相当ユーザー)である
    • 所有者が、ログインするユーザーになっている
  • 権限
    • グループ, その他ユーザーに書き込み権限がない
  • チェック範囲
    • /home/Chiara/.ssh
    • /home/Chiara

 ※ .sshから再帰的に、ホームディレクトリまでチェック

https://github.com/openssh/openssh-portable/blob/V_7_6_P1/auth.c#L467

static FILE *
auth_openfile(const char *file, struct passwd *pw, int strict_modes,
    int log_missing, char *file_type)
{
    /// 一部省略 ///
    if (strict_modes &&
        safe_path_fd(fileno(f), file, pw, line, sizeof(line)) != 0) {
        fclose(f);
        logit("Authentication refused: %s", line);
        auth_debug_add("Ignored %s: %s", file_type, line);
        return NULL;
    }
    /// 一部省略 ///
}

https://github.com/openssh/openssh-portable/blob/V_7_6_P1/misc.c#L1673

int
safe_path_fd(int fd, const char *file, struct passwd *pw,
    char *err, size_t errlen)
{
    struct stat st;

    /* check the open file to avoid races */
    if (fstat(fd, &st) < 0) {
        snprintf(err, errlen, "cannot stat file %s: %s",
            file, strerror(errno));
        return -1;
    }
    return safe_path(file, &st, pw->pw_dir, pw->pw_uid, err, errlen);
}

https://github.com/openssh/openssh-portable/blob/V_7_6_P1/misc.c#L1608

int
safe_path(const char *name, struct stat *stp, const char *pw_dir,
    uid_t uid, char *err, size_t errlen)
{
    /// 一部省略 ///
    /* for each component of the canonical path, walking upwards */
    for (;;) {
        if ((cp = dirname(buf)) == NULL) {
            snprintf(err, errlen, "dirname() failed");
            return -1;
        }
        strlcpy(buf, cp, sizeof(buf));

        if (stat(buf, &st) < 0 ||
            (!platform_sys_dir_uid(st.st_uid) && st.st_uid != uid) ||
            (st.st_mode & 022) != 0) {
            snprintf(err, errlen,
                "bad ownership or modes for directory %s", buf);
            return -1;
        }

        /* If are past the homedir then we can stop */
        if (comparehome && strcmp(homedir, buf) == 0)
            break;

        /*
         * dirname should always complete with a "/" path,
         * but we can be paranoid and check for "." too
         */
        if ((strcmp("/", buf) == 0) || (strcmp(".", buf) == 0))
            break;
    }
    /// 一部省略 ///
}

今後について

  • authorized_key が rootユーザーでもいいような結果になったが、おそらく間違っていると思われます
    • 今後調査をして、正しいものに修正します

ssh コマンドで host名を補完する

■前提

  • /.ssh/known_hosts がハッシュ化されていない

■内容

~/.bashrc に以下のように記載することで、補完が可能になります。

$ cat ~/.bashrc

function _complete_ssh() {
  if [ -f $HOME/.ssh/known_hosts ]; then
    local cur=${COMP_WORDS[COMP_CWORD]};
    COMPREPLY=( $(compgen -W "`cat $HOME/.ssh/known_hosts | tr ',' ' ' | cut -d' ' -f1`2"-- $cur))
  fi
}

complete -F _complete_ssh ssh

■使用例 ssh w を打ったところで、[tab]キーを押すと、wから始まるホストのみが候補として出て、該当が1件の場合、補完がされる。

■ ~/.ssh/known_hosts の表記例

以下のようにhash化されていない場合のみ使用可能

$ cat ~/.ssh/known_hosts
web01,10.0.0.2 ssh-rsa AA...

■雑記

~/.ssh/known_hosts をハッシュ化した場合の運用の実例は、誰か記事にしているでしょうか?

アクセス可能なIPが分かりずらいのは、サーバーに入られたときに、そこを踏み台に 悪さされるのを難しくはできるでしょうが、今思いつく限りでは運用が面倒になりそうな気がして...

■参考になった記事

AWS Certificate Manager で証明書を更新する(2018.11)

■背景

AWSで使用しているELB用の証明書を更新する機会があったため、 その際の操作手順をまとめました。 各種パラメータは、ダミーのものに置き換えています。

■前提

  • 証明書はAWS発行のものはなく、機関によって発行してもらった証明書
  • AWSコンソールから更新

■更新手順

1. AWS コンソールから「AWS Certificate Manager 」を開きます。

開くと、以下のようなページが表示されます。

f:id:chiarab:20181108001602p:plain

*.example.com を今回は更新していきます。

2. 「Reimport certificate」を押して、証明書を貼り付け、「Review and import」を押します。

  • Certificate body: PEM 形式のサーバー証明書
  • Certificate private key: PEM 形式の証明書のプライベートキー
  • Certificate chain: PEM 形式の中間証明書 ※「空白行」があった場合、消した上で入力が必要なようです。

f:id:chiarab:20181108001739p:plain

3. 取り込みした証明書が問題ないことを確認して、「Import」を押します。

f:id:chiarab:20181108001744p:plain

4. 取り込みした証明書が反映されたことを確認します。

「Certificate Manager」トップから、*.example.com 欄の行をクリックして詳細を出します。

Expires in が延長されていることを確認します。

f:id:chiarab:20181108001747p:plain

svn+ssh 接続しているリポジトリへの svn:externals 設定

■背景

svn+ssh で接続が可能なsvnリポジトリに、外部参照設定をする必要が出ました。

初めての設定で少し戸惑った部分があったので、設定からフェッチするところまでをまとめました。

■結論

以下の書式で設定ができます。 リポジトリのURL が、http:// と svn+ssh:// で特に違いはないようです。

svn propset svn:externals "[属性を付与するディレクトリ] [リポジトリのURL]" [ディレクトリの配置パス]

■使用例

以下の条件における、svn:externals設定の方法を記載します。

[chiara@web01]$ cd tmp
[chiara@web01~/tmp]$ ls
[chiara@web01~/tmp]$ svn propset svn:externals "test svn+ssh://chiara@svn/repos/hoge/trunk/test" .
property 'svn:externals' set on '.'
[chiara@web01~/tmp]$ svn proplist -v .                          
Properties on '.':
  svn:externals
    test svn+ssh://chiara@svn/repos/hoge/trunk/test

※この時点では、まだプロパティの設定だけでtestディレクトリは作成されていません。 testディレクトリをsvn up しても変化はありません。

[chiara@web01 ~/tmp]$ ls
[chiara@web01 ~/tmp]$ svn up test
At revision 10.
[chiara@web01 ~/tmp]$ ls

プロパティの設定がされた tmp ディレクトリを svn up することで、初めて testディレクトリがフェッチされます。

[chiara@web01 ~/tmp]$ svn up .
Fetching external item into 'test'
A hoge.png
Updated external to revision 10.

Updated to revision 10.

【トラブル対応】td-agent(fluentd) 再起動時の "Address already in use" エラー

■背景

  • 複数台のWebサーバーのログを、td-agent によって、1台のログ集約サーバーに送っている
  • Webサーバー の td-agentをstepし、ログ集約サーバーの td-agent をrestart した
  • Webサーバーのtd-agentをstart すると ログ集約サーバーのポートにアクセスできないエラー出て、ログが送れなくなった
  • ログ集約サーバーのtd-agent のログには "Address already in use" のエラーが出た

■環境

  • td-agent v1.1.12
  • fluentd v0.10.33

■原因

  • ログ集約サーバーの td-agent を restart した際に、プロセスが残っていて、ポートが占有されたままになっていた

■対応

  • Webサーバーの td-agent を stop
  • ログ集約サーバーの td-agent を stop した上で、同サーバーの td-agent のプロセスを殺した
  • ログ集約サーバーのtd-agent を start
  • Webサーバーのtd-agent を start

■今回学んだこと

  • td-agent restart では、プロセスが残ってしまう場合がある

■トラブル発覚までの操作

1. webサーバーの td-agent を停止

[chiara@web01 ~]$ sudo /etc/init.d/td-agent stop

2. ログ集約サーバーの td-agent を再起動

[chiara@log ~]$ sudo /etc/init.d/td-agent stop

3. webサーバーの td-agent を開始

[chiara@web01 ~]$ sudo /etc/init.d/td-agent stop

4. Webサーバーのtd-agentのログを確認

ログ集約サーバーに接続できないエラーが発生した

[chiara@web01 ~]$ sudo cat /var/log/td-agent/td-agent.log
2018-11-01 10:55:00 +0900 [info]: detached forwarding server 'log' host="log" port=24224 hard_timeout=true

5. ログ集約サーバーのtd-agent のログを確認

ポート: 24224 が既に使われているエラーが発生していた

[chiara@log ~]$ sudo cat /var/log/td-agent/td-agent.log
  2018-11-01 11:00:00 +0900 [info]: listening fluent socket on 0.0.0.0:24224
  2018-11-01 11:00:00 +0900 [error]: unexpected error error="Address already in use - bind(2)"
  2018-11-01 11:00:00 +0900 [error]: /usr/lib64/fluent/ruby/lib/ruby/gems/1.9.1/gems/cool.io-1.1.1/lib/cool.io/server.rb:57:in `initialize'
  2018-11-01 11:00:00 +0900 [error]: /usr/lib64/fluent/ruby/lib/ruby/gems/1.9.1/gems/cool.io-1.1.1/lib/cool.io/server.rb:57:in `new'
  2018-11-01 11:00:00 +0900 [error]: /usr/lib64/fluent/ruby/lib/ruby/gems/1.9.1/gems/cool.io-1.1.1/lib/cool.io/server.rb:57:in `initialize'

■調査内容

ログ集約サーバーのtd-agentのプロセスを確認

確認すると、4つのプロセスが確認できた td-agent は、データ通信(TCP), UDP(死活監視) で2プロセス使うので、それ以外の2プロセスが余計に見える。

[chiara@log ~]$ ps aux | grep td-agent | grep -v grep
root      3580 0.0  0.5 841112 89744 ?        Sl   Oct29   0:37 /usr/lib64/fluent/ruby/bin/ruby /usr/sbin/td-agent --group root --log /var/log/td-agent/td-agent.log --daemon /var/run/td-agent/td-agent.pid

root     14311 78.0  0.1 723748 31088 ?        Rl   11:15   0:00 /usr/lib64/fluent/ruby/bin/ruby /usr/sbin/td-agent --group root --log /var/log/td-agent/td-agent.log --daemon /var/run/td-agent/td-agent.pid

root     24111 0.0  0.5 841112 87232 ?        S    Oct29   0:00 /usr/lib64/fluent/ruby/bin/ruby /usr/sbin/td-agent --group root --log /var/log/td-agent/td-agent.log --daemon /var/run/td-agent/td-agent.pid

root     29982  0.0  0.0 168292 14516 ?        Sl   11:15   0:00 /usr/lib64/fluent/ruby/bin/ruby /usr/sbin/td-agent --group root --log /var/log/td-agent/td-agent.log --daemon /var/run/td-agent/td-agent.pid

td-agent を停止後に再度プロセスを確認

stop しても消えないプロセスが残っていた

[chiara@log ~]$ sudo /etc/init.d/td-agent stop                                           
Shutting down td-agent:                                    [  OK  ]
[chiara@log ~]$ ps aux | grep td-agent | grep -v grep
root      3580 0.0  0.5 841112 89744 ?        Sl   Oct29   0:37 /usr/lib64/fluent/ruby/bin/ruby /usr/sbin/td-agent --group root --log /var/log/td-agent/td-agent.log --daemon /var/run/td-agent/td-agent.pid

root     24111 0.0  0.5 841112 87232 ?        S    Oct29   0:00 /usr/lib64/fluent/ruby/bin/ruby /usr/sbin/td-agent --group root --log /var/log/td-agent/td-agent.log --daemon /var/run/td-agent/td-agent.pid

■対応内容

不要なプロセスをkillコマンドで殺した

[chiara@log ~]$ sudo kill -9 24111 3580