ssh - 特定ユーザに対して時間限定でリモートサーバへアクセスを許可する方法（Linux版）

# **はじめに**
　サーバを運用保守する際に、特定のサーバに対して複数のユーザが自由にアクセス可能な状態にあると、不用意なコンソール接続が可能になり、サーバのセキュリティレベルが低下する。その為、サーバの位置づけによっては、ユーザごとに一定時間のみアクセスを許可し、それ以外の時間はアクセスができないような仕組みを構築したいことがある。
　本記事はそのような要求に対して実現方法を検討した際のメモである。

# **想定環境**

## **ローカル環境**
- OS : Windows / macOS
- 条件 : sshコンソール接続が可能なツールが存在し、任意のネットワークに接続可能なこと。

## **リモート環境**
- OS : Linux
- 条件 : sshdが可能し、sshによる接続が可能なこと。
- 備考 : 今回は、ローカル環境内に Vagrant で CentOS 6.9 環境を構築して動作検証を実施した。

# **構築手順**
## **概要**
　今回の記事では、「sshd」及び「PAM(Pluggable Authentication Modules)」を使用した制御方法について記載する。
設定手順は以下の通りである。

1. 環境の確認
2. 環境の構築
3. 手動による設定変更
4. Scriptによる設定変更
5. Script と Cronによる設定変更
6. 履歴取得

## **概念図**

![図1.png](https://qiita-image-store.s3.amazonaws.com/0/79222/bbea7cc2-ee84-7f02-b26f-08ed091ad207.png)


## **対象ファイル**
本操作によって修正を実施する *ssh* 及び *PAM* の設定ファイルは以下の通り。

|対象ファイル|解説|
|:--|:--|
|/etc/security/sshd_access.conf|sshd用のアクセスの許可・禁止の設定を記載する設定ファイル。<br>デフォルトは「/etc/security/access.conf」だが、今回はsshd用にファイルを作成し、そちらを修正する。|
|/etc/pam.d/sshd|PAMの設定ファイル。設定対象のサービスごとに設定を記載する。|
|/etc/ssh/sshd_config|sshdの設定ファイル。|

**※設定順序やファイルを間違えた場合、sshアクセスが不可能になるため注意すること**

## **手順１. 環境の確認**

```console:Command
# ssh -V
OpenSSH_5.3p1, OpenSSL 1.0.1e-fips 11 Feb 2013
```

```console:Command
# cat /etc/ssh/sshd_config | grep UsePAM
#UsePAM no
UsePAM yes
```

```bash:/etc/pam.d/sshd
#%PAM-1.0
auth       required     pam_sepermit.so
auth       include      password-auth

account    required     pam_nologin.so
account    include      password-auth

password   include      password-auth

# pam_selinux.so close should be the first session rule
session    required     pam_selinux.so close
session    required     pam_loginuid.so

# pam_selinux.so open should only be followed by sessions to be executed in the user context
session    required     pam_selinux.so open env_params
session    required     pam_namespace.so
session    optional     pam_keyinit.so force revoke
session    include      password-auth
```

## **手順２. 環境の構築**

### **2-1. 事前準備：動作検証用ユーザの作成**

```console:Command
// 動作確認用ユーザの作成
# useradd sample-user

// 動作確認用ユーザのパスワード設定
# passwd sample-user
Changing password for user sample-user.
New password:
Retype new password:
passwd: all authentication tokens updated successfully.
```

### **2-2. アクセス制御ファイルの作成**

```console:Command
# cat << EOT > /etc/security/sshd_access.conf
+ : vagrant : ALL
- : sample-user : ALL
EOT 
```

### **2-3. PAMの設定ファイルにアクセス制御ファイルを追記**
以下の記述を設定ファイルの適切な箇所に追記する。
`account    required     pam_access.so accessfile=/etc/security/sshd_access.conf`

**※ `/etc/ssh/sshd_config` に `UsePAM yes` が設定されている場合、ファイル保存と同時に設定が反映される為、注意すること！**

```bash:/etc/pam.d/sshd
#%PAM-1.0
auth       required     pam_sepermit.so
auth       include      password-auth

account    required     pam_nologin.so
account    required     pam_access.so accessfile=/etc/security/sshd_access.conf
account    include      password-auth

password   include      password-auth

# pam_selinux.so close should be the first session rule
session    required     pam_selinux.so close
session    required     pam_loginuid.so

# pam_selinux.so open should only be followed by sessions to be executed in the user context
session    required     pam_selinux.so open env_params
session    required     pam_namespace.so
session    optional     pam_keyinit.so force revoke
session    include      password-auth
```

### **2-4. 動作確認**

1. リモートサーバに対し、「**vagrant**」ユーザでアクセスし、**ログインが出来る**ことを確認
2. リモートサーバに対し、「**sample-user**」ユーザでアクセスし、**ログインが出来ない**ことを確認

## **手順３. 手動による設定変更**

「sample-user」をアクセス可能にする場合は以下のようにする。

```bash:/etc/security/sshd_access.conf
+ : vagrant : ALL
+ : sample-user : ALL
```

設定ファイルの書式については、以下のサイトを参照のこと。

- [sshを、ユーザ、IPでアクセス制限 - kazmax](http://kazmax.zpp.jp/linux/lin_pam.html)
- [access.conf (5) - Linux Man Pages](https://www.systutorials.com/docs/linux/man/5-access.conf/)

## **手順４. Scriptによる設定変更**

- Scriptの実行によってアクセス制御ファイルの該当行を修正

### **即席shellscript**

```bash:changeAccessAuth.sh
#!/bin/bash

echo "[INFO] 特定ユーザに対するアクセス設定処理を開始します"

# Constant
#======================================================
FILE_ACCESS_CONF=/etc/security/sshd_access.conf

echo "[INFO] Target Configuration File : "${FILE_ACCESS_CONF}

# Arguments
#======================================================
ARGS=$#
ARG_USER_NAME=$1
ARG_ACCESS_PATTERN=$2

if [ ${ARGS} -ne 2 ]; then
  echo "[ERROR] 引数の数が異なります"
  exit 1
fi

echo "[INFO] User Name :" ${ARG_USER_NAME}
echo "[INFO] Access Pattern : " ${ARG_ACCESS_PATTERN}

# Procedure
#======================================================

# 設定ファイルに設定が存在するか確認
#------------------------------------------------------
grep -e ": ${ARG_USER_NAME} :" -n ${FILE_ACCESS_CONF} > /dev/null
RESULT_CHECK_USER_SETTING=$?

if [ ${RESULT_CHECK_USER_SETTING} -eq 0 ]; then
  echo "[INFO] 設定が存在する為、既存の設定を削除して新しい設定を追加します"

  sed -i".org" -e "/: ${ARG_USER_NAME} :/d" ${FILE_ACCESS_CONF}

else
  echo "[INFO] 設定が存在しません"
fi

# 設定ファイルに新しい設定を追加
#------------------------------------------------------
ACCESS_PATTERN="-"
if [ ${ARG_ACCESS_PATTERN} = "allow" ]; then
  echo "[INFO] 許可設定を追加します"
  ACCESS_PATTERN="+"

elif [ ${ARG_ACCESS_PATTERN} = "deny" ]; then
  echo "[INFO] 拒否設定を追加します"

else
  echo "[ERROR] 権限の指定方法に誤りがあります"
  exit 2
fi

echo "${ACCESS_PATTERN="-"}" ":" "${ARG_USER_NAME}" ":" "ALL" >> ${FILE_ACCESS_CONF}
echo "[INFO] cat" ${FILE_ACCESS_CONF}
cat ${FILE_ACCESS_CONF}


echo "[INFO] 特定ユーザに対するアクセス設定処理を終了します"
```

**※アクセス元を `ALL` としていますが、適切な設定に変更しましょう。**

### **実行方法**

#### **許可する場合**

```console:
# sh changeAccessAuth.sh sample-user allow
[INFO] 特定ユーザに対するアクセス設定処理を開始します
[INFO] Target Configuration File : /etc/security/sshd_access.conf
[INFO] User Name : sample-user
[INFO] Access Pattern :  allow
[INFO] 設定が存在する為、既存の設定を削除して新しい設定を追加します
[INFO] 許可設定を追加します
[INFO] cat /etc/security/sshd_access.conf
+ : vagrant : ALL
+ : sample-user : ALL
[INFO] 特定ユーザに対するアクセス設定処理を終了します
```

#### **拒否する場合**
```console:
# sh changeAccessAuth.sh sample-user deny
[INFO] 特定ユーザに対するアクセス設定処理を開始します
[INFO] Target Configuration File : /etc/security/sshd_access.conf
[INFO] User Name : sample-user
[INFO] Access Pattern :  deny
[INFO] 設定が存在する為、既存の設定を削除して新しい設定を追加します
[INFO] 拒否設定を追加します
[INFO] cat /etc/security/sshd_access.conf
+ : vagrant : ALL
- : sample-user : ALL
[INFO] 特定ユーザに対するアクセス設定処理を終了します
```

## **手順５. Script と Cronによる設定変更**

- Script実行によってCronにアクセス終了時刻を設定し、アクセス不可の制御を自動設定するよう処理を実施

**※以下のScriptは特定条件下では正常動作するが、もう少し精査と改修が必要な状態**

```bash:setAccessAuth.sh
#!/bin/bash


echo "[INFO] 特定ユーザに対するアクセス設定処理を開始します"

# Constant
#======================================================
# crontabファイル
cron_file="/var/spool/cron/root"


# Arguments
#======================================================
ARGS=$#
ARG_USER_NAME=$1
ARG_START_DATE=$2
ARG_START_TIME=$3
ARG_END_DATE=$4
ARG_END_TIME=$5

if [ ${ARGS} -ne 5 ]; then
  echo "[ERROR] 引数の数が異なります"
  exit 1
fi


# Variables
#======================================================

# 型 : "YYYYmmdd HHMM"
START_DATE="${ARG_START_DATE} ${ARG_START_TIME}"
END_DATE="${ARG_END_DATE} ${ARG_END_TIME}"

date +%Y%m%d-%H%M%S --date "${START_DATE}"
if [ $? -ne 0 ]; then
  echo "[ERROR] 日時の指定に誤りがあります"
  exit 2
fi

date +%Y%m%d-%H%M%S --date "${END_DATE}"
if [ $? -ne 0 ]; then
  echo "[ERROR] 日時の指定に誤りがあります"
  exit 2
fi

START_MONTH=`date +%m --date "${START_DATE}"`
START_DAY=`date +%d --date "${START_DATE}"`
START_HOUR=`date +%H --date "${START_DATE}"`
START_MINUTE=`date +%M --date "${START_DATE}"`

END_MONTH=`date +%m --date "${END_DATE}"`
END_DAY=`date +%d --date "${END_DATE}"`
END_HOUR=`date +%H --date "${END_DATE}"`
END_MINUTE=`date +%M --date "${END_DATE}"`

CRON_JOB_LINE_ALLOW_START="${START_MINUTE} ${START_HOUR} ${START_DAY} ${START_MONTH} * sh /root/sh/changeAccessAuth.sh ${ARG_USER_NAME} allow"
CRON_JOB_LINE_ALLOW_END="${END_MINUTE} ${END_HOUR} ${END_DAY} ${END_MONTH} * sh /root/sh/changeAccessAuth.sh ${ARG_USER_NAME} deny"


# Procedure
#======================================================

## Crontab ファイルが存在しない場合、作成する.
#------------------------------------------------------
[ -f ${cron_file} ] && touch ${cron_file}


## 既に登録されているかどうかを判定し、なければ登録する
#------------------------------------------------------

### アクセス許可のcron設定
cron_job_line_for_grep="${CRON_JOB_LINE_ALLOW_START//\\/\\\\}"
if [ `grep "${cron_job_line_for_grep}" "${cron_file}" | wc -l` -eq 0 ] ; then
  echo "[INFO] Not registered yet. begin registering..."

  # 追記
  echo "${CRON_JOB_LINE_ALLOW_START}" >> "${cron_file}"
else
  echo "[INFO] already registered."
fi

### アクセス拒否のcron設定
cron_job_line_for_grep="${CRON_JOB_LINE_ALLOW_END//\\/\\\\}"
if [ `grep "${cron_job_line_for_grep}" "${cron_file}" | wc -l` -eq 0 ] ; then
  echo "[INFO] Not registered yet. begin registering..."

  # 追記
  echo "${CRON_JOB_LINE_ALLOW_END}" >> "${cron_file}"
else
  echo "[INFO] already registered."
fi


# cronを再起動することで設定を反映する
/etc/init.d/crond restart


echo "[INFO] 特定ユーザに対するアクセス設定処理を終了します"
```

### **実行方法とCronの設定状況の確認**

```console:Command
// アクセスの許可設定
# sh setAccessAuth.sh sample 20180714 0910 20180714 0911
[INFO] 特定ユーザに対するアクセス設定処理を開始します
20180714-091000
20180714-091100
[INFO] Not registered yet. begin registering...
[INFO] Not registered yet. begin registering...
Stopping crond:                                            [  OK  ]
Starting crond:                                            [  OK  ]
[INFO] 特定ユーザに対するアクセス設定処理を終了します

// Cronの設定状況
# cat /var/spool/cron/root
10 09 14 07 * sh /root/sh/changeAccessAuth.sh sample allow
11 09 14 07 * sh /root/sh/changeAccessAuth.sh sample deny
```

### **Cron処理実行後の設定の変更結果**

```console:Command
// 許可処理の実行後
# cat /etc/security/sshd_access.conf
+ : vagrant : ALL
+ : sample : ALL

// 拒否処理の実行後
# cat /etc/security/sshd_access.conf
+ : vagrant : ALL
- : sample : ALL

```

## **手順６. 履歴取得**
　こういう仕組みを構築する場合は、過去のアクセスログを取得したいという要望も発生しがちなので、Script内に以下のようなコマンドを仕込んでログする処理を組み込んでおくと、内部監査などの際に便利である。

- `last` : 直近のログイン履歴を確認する
- `lastlog` : すべてのユーザの最終ログインを確認する

```console:Command
# last
vagrant  pts/0        10.0.2.2         Sat Jul 14 05:01   still logged in
vagrant  pts/0        10.0.2.2         Sat Jul 14 05:00 - 05:01  (00:00)
vagrant  pts/1        192.168.33.1     Sat Jul 14 04:47 - 04:47  (00:00)
sample-u pts/1        192.168.33.1     Sat Jul 14 04:47 - 04:47  (00:00)
vagrant  pts/1        192.168.33.1     Sat Jul 14 04:47 - 04:47  (00:00)
sample-u pts/1        192.168.33.1     Sat Jul 14 04:43 - 04:45  (00:01)
vagrant  pts/1        192.168.33.1     Sat Jul 14 04:43 - 04:43  (00:00)
sample-u pts/1        192.168.33.1     Sat Jul 14 04:40 - 04:40  (00:00)
sample-u pts/1        192.168.33.1     Sat Jul 14 04:38 - 04:39  (00:01)
vagrant  pts/1        192.168.33.1     Sat Jul 14 04:37 - 04:37  (00:00)
vagrant  pts/0        10.0.2.2         Sat Jul 14 04:08 - 05:00  (00:51)
reboot   system boot  2.6.32-696.30.1. Sat Jul 14 04:08 - 05:30  (01:22)
reboot   system boot  2.6.32-696.30.1. Fri Jun  8 16:29 - 23:33  (07:03)
reboot   system boot  2.6.32-696.el6.x Fri Jun  8 16:25 - 16:28  (00:03)
```

```console:Command
# lastlog
Username         Port     From             Latest
root                                       **Never logged in**
bin                                        **Never logged in**
daemon                                     **Never logged in**
adm                                        **Never logged in**
lp                                         **Never logged in**
sync                                       **Never logged in**
shutdown                                   **Never logged in**
halt                                       **Never logged in**
mail                                       **Never logged in**
uucp                                       **Never logged in**
operator                                   **Never logged in**
games                                      **Never logged in**
gopher                                     **Never logged in**
ftp                                        **Never logged in**
nobody                                     **Never logged in**
rpc                                        **Never logged in**
vcsa                                       **Never logged in**
saslauth                                   **Never logged in**
postfix                                    **Never logged in**
rpcuser                                    **Never logged in**
nfsnobody                                  **Never logged in**
sshd                                       **Never logged in**
vagrant          pts/0    10.0.2.2         Sat Jul 14 05:01:59 +0000 2018
vboxadd                                    **Never logged in**
sample-user      pts/1    192.168.33.1     Sat Jul 14 04:47:16 +0000 2018
```

# **まとめ**

- *sshd* と *PAM* を使用することで、ユーザのアクセス制御を簡単に設定することが出来る。
- Script と組み合わせることで、特定時間のアクセス制御などを設定することが出来る。

# **所感・メモ**
　この設定はクラウドで自動拡張・自動縮退するようなサーバでは使い勝手が悪い印象だ。
　一方で、オンプレサーバやレンタルの専有サーバなど、特定用途のサーバでこういった制御により不正アクセスのリスクを軽減できるはずである。特に、Webホスティング系のサービスでは、コンテンツの更新時などに使用すると、運用担当者の「こっそりコンテンツ更新」みたいなことも制御することが出来る。
　また、アプリケーション系のサーバシステムにおいては、プロキシサーバにこの設定をしておくことで、システム運用メンテのタイミングで担当者ごとのアクセス制御も可能になる。ただし、設定の変更にあたっては管理者アカウントによる設定ファイルが変更になるし、システムメンテを実施する担当者は相当なアクセス権限もあるはずなので、果たしてこのレイヤでの制御が妥当なセキュリティ制御かと考えると疑問が残る。（PCの紛失や情報漏えいの際、管理者権限さえ盗まれていなければアクセス出来ない状態を担保出来るというケースにおいては、有効かもしれない。）

　この設定はあくまで、「iptablesなどのFirewallによって、接続可能な IP や Port を絞る・変更する」であったり、「rootでの直アクセス禁止する」だったりなど、基本的なアクセス制御をした上で、更にセキュリティレベルを上げたい時に使用する際に行う設定であるということは、最後に記しておこう。

# **参考**
- [いますぐ実践! Linux システム管理 / Vol.057](http://www.usupi.org/sysad/057.html)
- [Linuxでのログインユーザの確認や制御の方法 - mzgkworks.com](http://mzgkworks.com/post/linux-login-user-management/)
- [SSHでログインできるユーザを制限](http://www.ksknet.net/cat13/ssh_1.html)
- [sshを、ユーザ、IPでアクセス制限 - kazmax](http://kazmax.zpp.jp/linux/lin_pam.html)
- [IP制限かつユーザの制限を同時に行う - Qiita](https://qiita.com/tjinjin/items/ca6f5518e881bdf5488c)
- [bashでcronジョブを自動登録する　（Linuxバッチでcrontabを編集） - 主に言語とシステム開発に関して](http://language-and-engineering.hatenablog.jp/entry/20101210/p1)
- [シェルスクリプト日付判定 - etuts+](http://server.etutsplus.com/sh-check-date/)