web-dev-qa-db-ja.com

条件付きPAMエントリを作成する方法

RedHat 7.5でPAMシナリオをテストしています

PAMモジュールpam_succeed_if.soは、PAMが提供する必要がある条件テストの最も基本的なレベルのように見え、それは私のニーズを満たしていません。テストは、user、uid、gid、Shell、home、ruser、rhost、tty、およびserviceフィールドでのみ作成できます。

私の状況では、「rhost」フィールドに基づいてテストしたいのですが、モジュールをデバッグに入れた後、rhostフィールドが設定されていないことがわかりました。

私の目標は、ユーザーがローカルでマシンにログインしている場合にのみ、/ etc/pam.d/SudoでPAMモジュールを実行することです。ユーザーがSSH経由でログインしていることを検出できた場合は、モジュールをスキップします。私は実際にはうまくいくと思っていた3つの異なるアイデアを思いついたが、すべて失敗した。

失敗してしまったいくつかの解決策を共有します

Pam_exec.soを使用した条件付きエントリ

条件付きでスキップするpamモジュールの上に次のpamエントリを追加したいと思いました。

auth    [success=ok default=1]    pam_exec.so /etc/security/deny-ssh-user.sh

/etc/security/deny-ssh-user.shの内容

#!/bin/bash
# Returns 1 if the user is logged in through SSH
# Returns 0 if the user is not logged in through SSH
SSH_SESSION=false
if [ -n "${SSH_CLIENT}" ] || [ -n "${SSH_TTY}" ] || [ -n "${SSH_CONNECTION}" ]; then
    SSH_SESSION=true
else
    case $(ps -o comm= -p $PPID) in
        sshd|*/sshd) SSH_SESSION=true;;
    esac
fi

if "${SSH_SESSION}"; then
    exit 1
else
    exit 0
fi

https://github.com/linux-pam/linux-pam/blob/master/modules/pam_exec/pam_exec.c でpam_exec.soのソースコードを調べたところ、驚くほど、スクリプトの終了コードに関係なく、常にPAM_SUCCESSを返します。また、pam_execモジュールがPAM_SERVICE_ERR、PAM_SYSTEM_ERR、またはPAM_IGNOREを返すようにするスクリプトを取得できません。

Pam_access.soを使用した条件付きエントリ

繰り返しますが、条件付きでスキップするpamモジュールの上に次のpamエントリを追加します

auth    [success=ok perm_denied=1]    pam_access.so    accessfile=/etc/security/ssh-Sudo-access.conf noaudit

/etc/security/ssh-Sudo-access.confの内容

+:ALL:localhost 127.0.0.1 
-:ALL:ALL

うわー、すごくきれいだよね?ローカルにログインしている場合は成功を返し、それ以外はすべて拒否します。うーん、ダメ。 pam_access.soモジュールがデバッグに入ると判明します。リモートホストの知識はなく、使用されているターミナルのみです。したがって、pam_accessは実際にはリモートホストによるアクセスをブロックできません。

PAMモジュールをスキップできるように、どのような黒魔術をかけなければならないかを知るために、文字通りソースコードを読むのは大変な一日でした。

7
Luke Pafford

まあ、私は実際にはばかであることが判明しました。pam_exec.soモジュールはPAM条件を作成するのに完全に適しています。

Tim Smithは、私の/etc/security/deny-ssh-user.shスクリプトの両方のテストで変数SSH_SESSIONがtrueに設定されていないことを評価する際に正しかったです。スクリプトは通常のシェルで機能するため、これは考慮しませんでしたが、pam_exec.soによって実行されると、環境コンテキストが削除されます。

彼の例のようにlastユーティリティを使用するようにスクリプトを書き直してしまいましたが、lastのスイッチがArch LinuxからRedHatに異なるため、一部を変更する必要がありました。

/etc/security/deny-ssh-user.shにある修正されたスクリプトは次のとおりです。

#!/bin/bash
# Returns 1 if the user is logged in through SSH
# Returns 0 if the user is not logged in through SSH
SSH_SESSION=false

function isSshSession {
    local terminal="${1}"
    if $(/usr/bin/last -i | 
        /usr/bin/grep "${terminal}" |
        /usr/bin/grep 'still logged in' |
        /usr/bin/awk '{print $3}' |
        /usr/bin/grep -q --invert-match '0\.0\.0\.0'); then
        echo true
    else
        echo false
    fi
}

function stripTerminal {
    local terminal="${1}"

    # PAM_TTY is in the form /dev/pts/X
    # Last utility displays TTY in the form pts/x
    # Returns the first five characters stripped from TTY
    echo "${terminal:5}"
}

lastTerminal=$( stripTerminal "${PAM_TTY}")
SSH_SESSION=$(isSshSession "${lastTerminal}")

if "${SSH_SESSION}"; then
    exit 1
else
    exit 0
fi

/etc/pam.d/Sudoの内容

....
auth    [success=ok default=1]    pam_exec.so /etc/security/deny-ssh-user.sh
auth    sufficient    pam_module_to_skip.so
....
3
Luke Pafford

ここに私のために働く解決策があります。私の_/etc/pam.d/Sudo_:

_#%PAM-1.0
auth            [success=1]     pam_exec.so    /tmp/test-pam
auth            required        pam_deny.so
auth            include         system-auth
account         include         system-auth
session         include         system-auth
_

そして_/tmp/test-pam_:

_#! /bin/bash
/bin/last -i -p now ${PAM_TTY#/dev/} | \
    /bin/awk 'NR==1 { if ($3 != "0.0.0.0") exit 9; exit 0; }'
_

私はこの動作をします:

_$ Sudo date
[Sudo] password for jdoe:
Thu Jun 28 23:51:58 MDT 2018
$ ssh localhost
Last login: Thu Jun 28 23:40:23 2018 from ::1
valli$ Sudo date
/tmp/test-pam failed: exit code 9
[Sudo] password for jdoe:
Sudo: PAM authentication error: System error
valli$
_

デフォルトの_pam.d/Sudo_に追加された最初の行は_pam_exec_を呼び出し、成功した場合は次のエントリをスキップします。 2行目は、無条件にアクセスを拒否するだけです。

_/tmp/test-pam_では、lastを呼び出して、pamが呼び出されたTTYに関連付けられたIPアドレスを取得します。 _${PAM_TTY#/dev/}_は、lastが完全なデバイスパスを認識しないため、値の前から_/dev/_を削除します。 _-i_フラグを使用すると、IPアドレスがない場合はlastにIPアドレスまたはプレースホルダー_0.0.0.0_が表示されます。デフォルトでは、確認が非常に難しい情報文字列が表示されます。これが、lastまたはwhoではなくwを使用した理由でもあります。それらには同様のオプションはありません。 awkは出力の最初の行のみをチェックするため、_-p now_オプションは厳密には必要ありませんが、現在ログインしているユーザーのみを表示するようにlastを制限していますに。

awkコマンドは最初の行をチェックするだけで、3番目のフィールドが_0.0.0.0_でない場合、エラーで終了します。これは_/tmp/test-pam_の最後のコマンドなので、awkの終了コードがスクリプトの終了コードになります。

私のシステムでは、_deny-ssh-user.sh_で試行したテストはどれも機能しませんでした。スクリプトの先頭に_env > /tmp/test-pam.log_を配置すると、環境が取り除かれているため、SSH_FOO変数は設定されません。また、$ PPIDは任意の数のプロセスを指すことができます。たとえば、Perl -e 'system("Sudo cat /etc/passwd")'を実行して、$ PPIDがPerlプロセスを参照していることを確認します。

これは、Arch Linux、カーネル_4.16.11-1-Arch_です(重要な場合)。とはいえ私はそうは思わない。

5
Tim Smith