Администрирование большого количества “чужих” серверов, влечет за собой ответственность за защиту данных наших клиентов. Что бы надежно контролировать список лиц с ssh доступом к серверовам было решено продумать систему авторизации с ограниченого набора хостов.
Что имеем в условии задачи:
- Более 400 физических машин;
- Все клиентские службы хранятся в виртуальных контейнерах openvz ( в основном):
- root доступ на все сервера закрыт по ssh;
- доступ через sudo к привилегиям root есть только у админов на физических серверах;
- несколько серверов, принадлежащих нашей компании, половина из которых территориально распределены. Назовем их: “серверы доступа”.
Что сделано:
- Первым шагом мы через систему управления конфигурациями распространили на все физические сервера ssh ключи, для входа на них с серверов доступа без пароля. Для каждого админа свой ключ.
- Вторым шагом закрываем доступ по ssh на физические сервера, со всех хостов, кроме серверов доступа.
Конечно, в такой схеме будут исключения, но это все индивидуально.
Теперь возникает вопрос о безопасности такого решения: любой кто взламывает учетку на сервере доступа получает не ограниченный доступ ко всем серверам, причем по ключу без пароля.
Не очень хорошо, мягко выражаясь!
Поступило предложение ограничить ssh доступ на серверы доступа через firewall, разрешив доступ только с ip наших сотрудников. Хорошо. Но тут проблема: админов у нас не мало, у многих динамические ip. Да и что если срочно потребуется “поработать” находясь в поездке и т.д.?
Решили остановиться на репортах. То есть каждый раз, когда определенный логин, успешно авторизуется с ip, отсутствующего в списке допустимых, нам в redmine падает задача об этом событии с высоким статусом и далее мы выясняем что произошло, применяя допрос с пристрастием.
Реализация:
Были просмотрены текущие готовые решения для ssh аудита, но ничего подходящего найдено не было, или очень монструозно, или не о том.
И как всегда решили написать свой велосипед, то есть скрипт. Который:
- анализирует ssh лог на предмет успешной авторизации.
- берет данные за последние 10 минут.
- сравнивает пару username/ip из соответствующего списка
- в случае успешной проверки нич его не делает
- в случае не совпадения отправляет алерт, который впоследствии становится задачей в redmine.
Скрипт написан на python. Писал я на python второй раз в жизни, так что замечания по коду принимаются в объективном виде. К тому же, оно работает!
Листинг прилагается:
#!/usr/bin/env python
import sys, os, time
from datetime import datetime, timedelta, date, time as dt_time
import socket
hostname = socket.gethostname()
fileList = "./List.ip"
date = datetime.now() - timedelta(minutes=10)
date = date.strftime('%H:%M:%S')
from commands import *
import smtplib
def mail(message):
smtp_server = "localhost"
smtp_port = 25
smtp_user= "root@%s" % hostname
subject = 'Ahtung!! Security SSH audit alert'
to = "mail@example.ru"
mail_lib = smtplib.SMTP(smtp_server, smtp_port)
msg = 'From: %s\r\nTo: %s\r\nContent-Type: text/html; charset="utf-8"\r\nSubject: %s\r\n Return-Path: <root@%s>\r\n\r\n' % (smtp_user, to, subject, hostname)
msg += message
mail_lib.sendmail(smtp_user, to, msg)
listLog = getoutput('''cat /var/log/secure |grep "Accepted password for" | awk '{if($3>="'''+date+'''"){print $3 " " $9 " " $11 }}' ''')
if listLog:
for line in listLog.split('\n'):
matchIp = 0
matchName = 0
curIp = line.split()[2]
for lineLs in open(fileList):
if len(lineLs.strip()) == 0 or lineLs[0] == "#" or lineLs[0] == " ":
continue
if line.split()[1] == lineLs.split()[0]:
matchName = 1
listIp = lineLs.split()[1]
i = 0
for c in listIp.strip().split("."):
if c.strip() == "0":
break
i = i + 1
if listIp.strip().split(".")[0:i] == curIp.split(".")[0:i]:
matchIp = 1
if not matchIp:
if not matchName:
print("This user %s not found !!!" % line.split()[1])
message = "This user %s not found !!!" % line.split()[1]
mail(message)
else:
print("Ahtung! User {0} logged from unknown IP {1} in time {2}").format(line.split()[1], line.split()[2], line.split()[0])
message = "Ahtung! User "+line.split()[1]+" logged from unknown IP "+line.split()[2]+" in time "+line.split()[0]
mail(message)
Файл List.ip имеет вид:
#Vasya
vasya 1.2.3.4
vasya 12.13.14.0
Сопутствующие действия:
- поместить скрипт в cron на запуск каждые 10 минут;
- настроить ежедневную ротацию ssh логов на полночь;
- отключить авторизацию по ключам на серверах доступа;
- для большей пароноидальности можно хранить список username/ip в гите и раскидывать на сервера доступа через хуки.
Дополнительный (относительный) плюс этой схемы: если придется увольнять сотрудника ( а такое все же бывает :( ), то нужно будет просто убить сессии на серверах доступа.