項目實戰:CMDB自動化資產掃描和自動化運維
1、項目介紹
本項目基於Linux系統搭建系統集羣,使用Ansible實現Linux集羣下的批量部署和自動化管理,實現Web形式的自動化運維繫統,集中批量控制服務器,最終實現能支撐1000臺實例的環境提供管理和自動化任務,提高運維工程師的工作效率和質量。項目基於HTTP實現自動化任務接受和響應接口設計,基於MySQL用作的關係型數據存取,基於Redis的任務鎖機制和消息隊列, 基於MongoDB的事件日誌記錄, 最終實現郵件通知功能、敏感數據加密功能、日誌事件記錄功能。
主要目標是實現自動化資產掃描, 掃描指定網段的服務器資產信息,後續功能會進一步完善。
2、項目技術分析
運維自動化難點和痛點
- 開發人員: 沒有系統管理、網絡管理等相關運維工作經驗,項目設計往往是大打折扣的。
- 運維人員: 不具備開發能力、沒有項目的開發經驗或能力
- 做好一個優秀的運維開發人員DevOPS = 運維能力 + 開發能力
項目技術難點
- 基本技能
- DevOPS構建之路
- Python基礎語法
- Django框架
- 自動化資產掃描發現
- 資產掃描的作用
- nmap的作用
- telnetlib端口掃描
- pexpect登錄探測
- paramiko登錄探測
- Docker容器掃描
- KVM虛擬機掃描
- snmp網絡設備掃描
- SDK調用掃描ESXI資產信息
- Ansible自動化任務
- Ansible的安裝與配置
- Python與Ansible的操作
- Ansible adhoc
- Ansible playbook
- 核心類調用
- API 接口的封裝
- 方法的改寫
- Redis消息存儲
- Mongo事件日誌
整體工程設計
- 資產的自動化掃描發現
用Python程序掃描發現企業內部的所有資產(服務器資產),當資產出現變動時能自動及時的發現並完成資產變更(eg: 服務器IP變更、機器集體報廢)。 - Ansible的自動化任務執行
用Ansible的ad-hoc和playbook實現批量主機的自動化任務。
3、項目環境搭建
項目環境要求
- Python解釋器:3.x
- Django框架:2.x
- IDE編輯器工具Pycharm:不限制
- 自動化運維工具Ansible:2.x
- 關係型數據庫MySQL/Mariadb:5.5.x
- Git工具與Git代碼倉庫:不限制
項目環境的搭建
項目目錄的配置
- 創建Django項目devops
如果有云服務器時執行下面的操作,沒有云服務器的操作在這個之後進行說明
-
連接並配置遠程服務器
[tools]>[Deployment]
- 配置本地目錄和遠程服務器目錄的映射(Mapping)
上述操作完成,修改本地文件,遠程服務器文件也同時被修改。
遠程服務器虛擬環境的配置
- 連接遠程服務器命令行bash
- 創建虛擬環境並激活虛擬環境
cd /data/www/devops
virtualenv
-p /usr/bin/python3 env
source env/bin/active
pip install Django==2.2
- 出現的報錯及處理方式
# sqlite版本問題
django.core.exceptions.ImproperlyConfigured: SQLite 3.8.3 or later is required (found 3.7.17).
解決方式:不使用sqlite存儲數據,使用mysql
# CMDB/settings.py
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'devopsProject',
'USER': 'devops',
'PASSWORD': '',
'HOST': '127.0.0.1',
'PORT': '3306',
}
}
- 遠程服務器解釋器和目錄映射的配置
- 在遠程服務器上測試Django項目是否運行成功
python manage.py runserver
如果沒有云服務器直接進行虛擬環境的創建
執行下面命令:
witch python3 # /usr/bin/python3
virtualenv -p /usr/bin/python3 env #創建虛擬環境
source env/bin/active #切換到虛擬環境
pip install Django==2.2
MySQL數據庫配置
(遠程)用戶登錄配置
- 管理數據庫服務
[root@foundation0 ~]# systemctl start mariadb
[root@foundation0 ~]# systemctl enable mariadb
- 用戶授權
[root@foundation0 ~]# mysql -uroot -pServer version: 5.5.52-MariaDB MariaDB Server
# 創建數據庫
MariaDB [(none)]> create database if not exists devopsProject default charset
utf8;
Query OK, 1 row affected (0.01 sec)
# 新建用戶
MariaDB [(none)]> create user devops@'%' identified by 'westos';
Query OK, 0 rows affected (0.03 sec)
# 用戶授權
MariaDB [(none)]> grant all on devopsProject.* to devops@'%';
Query OK, 0 rows affected (0.00 sec)
MariaDB [(none)]> Bye
- 測試用戶授權是否成功
[root@foundation0 ~]# mysql -udevops -pwestos -hIP
Server version: 5.5.52-MariaDB MariaDB Server
MariaDB [(none)]> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| devopsProject
|
+--------------------+
2 rows in set (0.01 sec)
Django數據庫配置
# CMDB/settings.py
ALLOWED_HOSTS = ['*']
#
配置數據庫: 使用mysql數據庫,而不是默認的sqlite數據庫。
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'devopsProject',
'USER': 'devops',
'PASSWORD': 'devops',
'HOST': '47.92.255.98',
'PORT': '3306',
}
}
# 語言和時區的設置
LANGUAGE_CODE = 'zh-hans'
TIME_ZONE = 'Asia/Shanghai'
- 生成數據庫表
python manage.py makemigrations # 生成遷移腳本
python manage.py migrate
# 寫入數據庫, 創建關於用戶和用戶組等數據表信息
python manage.py createsuperuser # 創建超級用戶
# 啓動項目, 訪問網址http://IP:8000/admin
python manage.py runserver 0.0.0.0:8000
- 測試數據表是否創建?數據信息是否寫入?
連接mariadb數據庫
配置數據庫信息
如果是本地數據庫,Host填寫127.0.0.1即可
訪問數據庫表和數據內容
數據庫報錯處理
運行項目時,出現報錯如下,是因爲缺少mysqlclient安裝包.
解決方法:
pip install mysqlclient
安裝失敗,報錯如下是缺少開發包的緣故。
解決方法
yum install mariab-devel -y
yum install python-devel -y
4、第一個DevOPS工程
項目功能
記錄HTTP訪問的IP及用戶UA信息
- 運維模塊: 瞭解運維的工作、Linux系統的基本操作、數據庫基本管理操作、網絡知識等。
- 開發模塊: 本項目的重點, 掌握Python基礎知識、常見數據類型、Django框架的技術模塊、DevOPS項目構建模塊等。
項目開發步驟
- 創建Django工程
- 創建Django APP應用
$ python manage.py startapp scanhosts
- 文件配置settings
# settings.py
# 1). 將新建的APP加入到項目中
INSTALLED_APPS = [
......省略部分
'django.contrib.staticfiles',
'scanhosts',
]
# 2). 配置數據庫: 使用mysql數據庫,而不是默認的sqlite數據庫。
# DATABASES = {
# 'default': {
# 'ENGINE': 'django.db.backends.sqlite3',
# 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
# }
# }
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'devopsProject',
# 數據庫名稱 'USER': 'devops', # 用戶名
'PASSWORD': 'westos', # 用戶密碼
'HOST': '127.0.0.1', # 數據庫服務器所在主機名
'PORT': '3306', # 數據庫端口
}
}
# 3). 語言和時區的設置(根據自己項目的需求, 選擇修改)
# LANGUAGE_CODE = 'en-us'
# TIME_ZONE = 'UTC'
LANGUAGE_CODE = 'zh-hans' # 語言選擇中文
TIME_ZONE = 'Asia/Shanghai' # 時區選擇亞洲/上海
- 數據庫模型建模models
安裝數據庫開發軟件
$ yum install mariadb-devel -y
安裝數據庫連接模塊(必須先安裝mariadb-devel, 否則會報錯)
$ pip install mysqlclient
編輯數據庫模型文件:
# models.py
"""
- 一個類對應一個數據庫表;
- 類的一個屬性對應數據庫表的一個表頭;
- max_length: 字符串最大長度, 對應數據庫的varchar類型
- default: 指定默認值
- verbose_name: 指定Django後臺顯示的列頭信息
- auto_now: 每次修改記錄時自動更新爲當前時間
- Meta類的設置
- verbose_name: 指定Django後臺顯示的表名稱單數
- verbose_name_plural: 指定Django後臺顯示的表名稱複數
- db_table: 指定數據庫表的名稱, 默認是APP名稱_類名稱.
"""
class UserIPInfo(models.Model):
ip = models.CharField(max_length=150, default='', verbose_name='IP地址')
time = models.DateTimeField(verbose_name='更新時間', auto_now=True)
class Meta:
verbose_name = '用戶訪問地址信息表'
verbose_name_plural = verbose_name db_table = 'user_IP_info'
class BrowseInfo(models.Model):
# null=True: 是針對數據庫而言,True表示數據庫的該字段可以爲空。
user_agent = models.CharField(max_length=100, default='',
verbose_name='用戶瀏覽器信息', null=True)
disk_id = models.CharField(max_length=256, default='', verbose_name='唯
一設備ID')
"""
ForeignKey是一種關聯字段,將兩張表進行關聯的方式
on_delete: 是否級聯刪除, Django1.x默認級聯刪除, Django2.x必須手動指定
on_delete有CASCADE、PROTECT、SET_NULL、SET_DEFAULT、SET()五個可選擇的值
CASCADE:此值設置,是級聯刪除。
PROTECT:此值設置,是會報完整性錯誤。
SET_NULL:此值設置,會把外鍵設置爲null,前提是允許爲null。
SET_DEFAULT:此值設置,會把設置爲外鍵的默認值。
SET():此值設置,會調用外面的值,可以是一個函數。
"""
user_ip = models.ForeignKey('UserIPInfo', on_delete=models.DO_NOTHING)
class Meta:
verbose_name = '用戶瀏覽器信息表'
verbose_name_plural = verbose_name
db_table = 'browse_info'
根據ORM(對象關係映射)將面向對象形式的模型進行遷移, 生成中間代碼
$ python manage.py
makemigrations
# 代碼執行效果, 生成遷移文件,所在位置: scanhosts/migrations
Migrations for 'scanhosts':
scanhosts/migrations/0001_initial.py
- Create model UserIPInfo
將生成的遷移文件轉成SQL語句並執行SQL語句, 創建對應的數據庫及數據庫表
$ python manage.py migrate
- Django後臺管理界面
創建後臺管理的超級用戶
$ python manage.py
createsuperuser
Username (leave blank to use 'kiosk'): admin
Email address: admin@qq.com
Password:
Bypass password validation and create user anyway? [y/N]: y
Superuser created successfully.
啓動Django項目,默認開啓的端口是8000
$ python manage.py runserver
Django version 2.2.5, using settings 'first_devops.settings'
Starting development server at http://127.0.0.1:8000/
訪問項目後臺管理界面, 輸入超級用戶名稱和密碼即可進入後臺界面.
- 項目工程設計
當用戶發起HTTP請求時, Django的採集接口將HTTP請求的頭部信息headers裏面的IP和UA信息採集, 並存儲到數據庫中。 當用戶想要訪問採集數據時, 從數據庫中讀取,以界面的方式展示給用戶。
- 項目開發
項目開發(一) 信息採集接口的實現
- url設計
配置URL, 當用戶訪問http://127.0.0.1:8000/sendinfos這個網址時, 將用戶請求交給user_info視圖函數處理。
# first_devops/urls.py
urlpatterns = [
path('admin/', admin.site.urls),
url(r'^sendinfos/$', user_infos),
]
- 視圖函數的實現
# scanhosts/views.py
def user_info(request):
# request.META 是一個Python字典,包含了所有本次HTTP請求的Header信息,比如用戶IP
地址和用戶Agent(通常是瀏覽器的名稱和版本號)
ip = request.META.get('REMOTE_ADDR')
user_agent = request.META.get('HTTP_USER_AGENT')
# 使用filter()方法對數據進行過濾, 返回的是列表, 列表元素是符合條件的對象。
user_obj = UserIPInfo.objects.filter(ip=ip)
# 如果沒有找到,則新建UserIPInfo對象,並獲取對象編號(爲了和BrowseInfo表關聯)
if not user_obj:
res = UserIPInfo.objects.create(ip=ip)
user_ip_id = res.id
else:
user_ip_id = user_obj[0].id
# 新建BrowseInfo對象
BrowseInfo.objects.create(user_agent=user_agent, user_ip_id=user_ip_id)
# 字典封裝返回的數據信息
result = {
'STATUS': 'success',
'INFO': 'User Info',
'IP': ip,
'User-Agent': user_agent
}
# 以json的方式封裝返回, 下面的兩種方式任選一種.
# return
HttpResponse(json.dumps(result),
content_type='application/json')
return JsonResponse(result)
- 瀏覽器訪問效果圖
瀏覽器訪問結束後, 訪問MySQL數據庫, 看是否將數據信息採集成功並通過ORM的方式寫入數據庫中。
[root@foundation0 ~]# mysql -udevops -p
Welcome to the MariaDB monitor.
Commands end with ; or \g.
MariaDB [(none)]> use devopsProject;
MariaDB [devopsProject]> select * from user_IP_info;
MariaDB [devopsProject]> select * from browse_info;
項目開發(二)信息獲取接口的實現
- url設計
配置URL, 當用戶訪問http://127.0.0.1:8000/getinfos這個網址時, 將用戶請求交給user_history視圖函數處理。
# first_devops/urls.py
urlpatterns = [
path('admin/', admin.site.urls),
url(r'^sendinfos/$', user_info),
url(r'^getinfos/$', user_history),
]
- 視圖函數的實現
# scanhosts/views.py
def user_history(request):
# 獲取UserIPInfo表的所有對象信息;
ip_lists = UserIPInfo.objects.all()
infos = {}
# 獲取每個IP訪問網站瀏覽器的信息, 格式如下:
"""
infos = {
'127.0.0.1' : ['UA-1', 'ua-2'],
'172.25.254.1' : ['UA-1', 'ua-2'],
}
"""
for item in ip_lists:
infos[item.ip] = [b_obj.user_agent for b_obj in
BrowseInfo.objects.filter(user_ip_id=item.id)]
result = {
'STATUS': 'success',
'INFO': infos
}
return JsonResponse(result)
Django項目日誌管理
在編寫程序過程中,很難免的會出現一些問題,程序並非按照我們預想的那樣運行,這個時候我們通常會對程序進行調試,來看看到底是哪邊出了問題。而程序日誌是來幫助我們記錄程序運行過程的幫手,善用日誌的程序員也就能很快找出自己程序的問題所在從而快速解決問題。
在服務器級別的組件中都有對應的日誌文件,例如MySQL、Redis、nginx、uWSGI都會在運行過程中將一些信息寫到日誌文件中。
Django使用python的內置模塊logging來管理自己的日誌, 包含四大組件: 日誌記錄器Loggers、日誌處理器Handlers、日誌過濾器Filters和日誌格式化工具Formatters。
Django項目日誌管理詳情查看官方文檔:https://docs.djangoproject.com/en/2.2/topics/logging/
- 配置日誌的相關信息
# first_devops/settings.py
# 日誌管理的配置
LOGGING = {
'version': 1,
# disable_existing_loggers是否禁用已經存在的logger實例。默認值是True.
'disable_existing_loggers': False,
# formatters: 定義輸出的日誌格式。
'formatters': {
'verbose': {
# 格式化屬性查看資料:
https://docs.python.org/3/library/logging.html#logrecord-attributes
'format': '{levelname} {asctime} {module} : {lineno} {message}',
# 變量的風格
'style': '{',
# 日期顯示格式
'datefmt': '%Y-%m-%d %H:%M:%S',
},
},
# handlers定義處理器。
'handlers': {
'file': {
# 日誌處理級別
'level': 'INFO',
# 日誌處理類, 詳細的請查看網站:
https://docs.python.org/3/library/logging.handlers.html
'class': 'logging.FileHandler',
# 日誌處理後輸出格式
'formatter': 'verbose',
# 記錄日誌的文件名, 存儲在當前項目目錄下的devops.log文件
'filename': os.path.join(BASE_DIR, 'devops.log')
},
}, # loggers定義logger實例。
'loggers': {
'django': {
# 對應的handles對象列表
'handlers': ['file'],
# logger實例輸出的日誌級別
'level': 'INFO',
# 日誌是否向上級傳遞。True 向上級傳,False 不向上級傳。
'propagate': True,
},
}
}
- 寫入日誌
修改視圖函數的邏輯內容, 在合適的位置添加日誌輸出, 便於程序的測試與排錯。
- 重新訪問網頁, 查看devops.log文件測試日誌是否成功寫入
Django項目郵件告警管理
在web應用中,服務器對客戶發送郵件來通知用戶一些信息,可以使用郵件來實現。Django中提供了郵件接口,使我們可以快捷的建設一個郵件發送系統。通常用於發送自定義消息或者通知告警等信息(當然
也可以通過短信接口或者微信接口, 便於維護人員快速響應)。
- 服務器端開啓smtp協議支持(此處以QQ郵件服務器爲例服務器)
- 客戶端配置settings文件
# first_devops/settings.py
# 郵件配置
EMAIL_HOST = 'smtp.qq.com'
EMAIL_HOST_USER = 'QQ郵箱'
EMAIL_HOST_PASSWORD = '登錄授權碼(注意: 不是登錄密碼)'
EMAIL_PORT = 465
EMAIL_SUBJECT_PREFIX = 'Python開發社區'
EMAIL_USE_SSL = True
- 在Django的交互式環境測試郵件是否發送成功
$ python manage.py
shell
>>> from django.core.mail import send_mail
>>> help(send_mail)
>>> send_mail(subject="Django郵件發送測試代碼", message='郵件發送測試成功',
from_email='發送人的郵箱地址', recipient_list=['接收人的郵箱地址1', '接收人的郵箱地址2'])
1
- 查看郵箱是否收到測試郵件
發送郵件在很多業務場景都會適用, 爲了方便操作, 將郵件發送的內容封裝成一個工具, 減少開發過程中的重複操作, 提高效率。 操作如下:
- 將項目常用的功能封裝到utils模塊中, 創建的項目結構,如下:
- 編寫封裝類SendMail的類
# first_devops/scanhosts/utils/tools.py
import logging
from django.core.mail import send_mail
from datetime import datetime
from first_devops import settings
class SendMail(object):
"""發送郵件的封裝類"""
def __init__(self, subject, message, recipient_list, ):
# 給每個郵件的標題加上當前時間, 時間格式爲年月日_小時分鐘秒_傳入的郵件標題
subject_time = datetime.now().strftime('%Y%m%d_%H%M%S_')
self.recipient_list = recipient_list
self.subject = subject_time + subject
self.message = message
def send(self):
try:
send_mail(
subject=self.subject,
message=self.message,
from_email=settings.EMAIL_HOST_USER,
recipient_list=self.recipient_list,
fail_silently=False
)
return True
except Exception as e:
logging.error(str(e))
return False
- 在Django自帶的交互式環境shell‘中進行測試
$ python manage.py
shell
>>> from scanhosts.utils.tools import SendMail
>>> mail = SendMail('Django 測試標題', '郵件正文內容', ['[email protected]'])
>>> mail.send()
True
5、第二個DevOPS工程
- 使用Pycharm編輯器工具創建devops項目。
Django工程多配置文件
- base.py文件: 基本的配置文件,將原先seetings.py文件的內容拷貝進來.(參考第一個devops項目)
- 配置數據庫
- 配置時區和語言
- development.py文件: 開發環境的配置文件
from .base import *
DEBUG = True
- production.py文件: 生產環境的配置文件
from .base import *
# 開發環境一定要關閉調試模式
DEBUG = False
# 允許所有主機訪問
ALLOWED_HOSTS = ['*']
- 修改manage.py文件, 默認尋找的設置文件是當前項目中的settings文件, 如果是開發環境, 修改如下:
def main():
os.environ.setdefault('DJANGO_SETTINGS_MODULE',
'devops.settings.development')
# ......此處省略代碼
if __name__ == '__main__':
main()
如果項目將來需要上線, 修改啓動項目訪問的配置文件爲生產環境的配置文件即可, 如下:
def main():
os.environ.setdefault('DJANGO_SETTINGS_MODULE',
'devops.settings.production')
# ......此處省略代碼
if __name__ == '__main__':
main()
- 啓動項目
$ python manage.py runserver
Django version 2.2.5, using settings 'devops.settings.development'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
Django工程應用與模塊加載
爲了方便在一個大的Django項目中,管理實現不同的業務功能, 我們會在項目中創建多個APP實現功能。爲了更加方便管理APP, 項目結構更加清晰。可以專門創建apps目錄存儲項目應用, 專門創建extra_apps存儲項目第三方APP, 項目結構如下所示:
但項目運行時, 不會自動尋找apps和extra_apps子目錄中創建的APP, 需要手動在配置文件中配置,修改devops/settings/base.py文件, 添加內容如下:
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# 將apps目錄和extra_apps添加到python的搜索模塊的路徑集中
sys.path.insert(0, os.path.join(BASE_DIR, 'apps'))
sys.path.insert(0, os.path.join(BASE_DIR, 'extra_apps'))
添加成功後, 進入Django工程的交互式環境, 查看sys.path變量的值, 判斷是否添加成功?
$ python manage.py shell
In [1]: import sys
In [2]: sys.path
Out[2]:
['/home/kiosk/PycharmProjects/devops/devops/extra_apps',
'/home/kiosk/PycharmProjects/devops/devops/apps',
'/home/kiosk/PycharmProjects/devops',
# .......此處爲了美觀, 省略部分路徑........
]
資產管理
爲什麼優先實現資產管理?
- 資產管理是運維的基本工作;
- 資產管理是DevOPS系統的基礎;
資產管理是自動化運維平臺構建的基礎。
資產管理探測流程
主機存活探測協議
- ICMP(Internet Control Message Protocol)Internet控制報文協議。它是TCP/IP協議簇的一個子協議,用於在IP主機、路由器之間傳遞控制消息。控制消息是指網絡通不通、主機是否可達、路由是否可用等網絡本身的消息。這些控制消息雖然並不傳輸用戶數據,但是對於用戶數據的傳遞起着重要的作用。
- 傳輸控制協議(TCP,Transmission Control Protocol)是一種面向連接的、可靠的、基於字節流的傳輸層通信協議。
主機存活探測模塊和工具
Nmap探測工具
Nmap,也就是Network Mapper,最早是Linux下的網絡掃描和嗅探工具包。是一款用於網絡發現和安全審計的網絡安全工具。
- 主機發現 - 識別網絡上的主機。例如,列出響應TCP和/或ICMP請求或打開特定端口的主機。
# 使用-sP開關(Arp ping)執行PING命令,與windows / linux ping命令類似。
$ ping -c1 -w1 172.25.254.197
# 探測主機IP是否存活,
&>/dev/null並將所有的輸入重定向到垃圾箱
# && 如果前面的指令執行成功, 做什麼操作(echo ok)
# || 如果前面的指令執行失敗, 做什麼操作(echo fail)
$ ping -c1 -w1 172.25.254.250 &>/dev/null && echo ok || echo fail
# 使用nmap命令, 如果報錯-bash: nmap: command not found, 則yum 安裝nmap安裝包
$ nmap -n -sP 172.25.254.197
$ nmap -n -sP 172.25.254.0/24
- 端口掃描 - 枚舉目標主機上的開放端口。
# Nmap默認端口的掃描範圍1-10000
$ nmap -n -p 172.25.254.197
# 具體指定要掃描的端口爲50-80
$ nmap -n -p50-80 172.25.254.197
# 具體指定要掃描的端口爲22和80
$ nmap -n -p22,80 172.25.254.197
- 版本檢測 - 詢問遠程設備上的網絡服務以確定應用程序名稱和版本號。
- OS檢測 - 確定網絡設備的操作系統和硬件特性。
# -O是檢測操作系統交換機
$ nmap -O 172.25.254.197
- 可與腳本進行腳本交互 - 使用Nmap腳本引擎(NSE)和Lua編程語言。
查看172.25.254.197這臺 主機是否開啓?
查看172.25.254.0/24局域網內存活的主機信息及存活主機個數。
Nmap的Python操作接口:python-nmap
python-nmap是一個使用nmap進行端口掃描的python庫,它可以很輕易的生成nmap掃描報告,並且可以幫助系統管理員進行自動化掃描任務和生成報告。同時,它也持nmap腳本輸出。
# 安裝nmap的第三方模塊
$ pip install python-nmap
具體的代碼調用如下:
import nmap
# 實例化對象, portScanner()類用於實現對指定主機進行端口掃描
nm = nmap.PortScanner()
# 以指定方式掃描指定主機或網段的指定端口
result = nm.scan(hosts='172.25.254.0/24', arguments='-n -sP')
print("掃描結果: ", result)
# 返回的掃描具體的nmap命令行
print("nmap命令行: ", nm.command_line())
# 返回nmap掃描的主機清單,格式爲列表類型
print("主機清單: ", nm.all_hosts())
# 查看指定主機信息
print('172.25.254.197的主機信息: ', nm['172.25.254.197'])
代碼執行效果如下圖所示:
掃描結果:
{'nmap': {'command_line': 'nmap -oX - -n -sP 172.25.254.0/24', 'scaninfo': {},
'scanstats': {'timestr': 'Wed Dec 25 16:14:47 2019', 'elapsed': '6.06',
'uphosts': '2', 'downhosts': '254', 'totalhosts': '256'}}, 'scan':
{'172.25.254.197': {'hostnames': [{'name': '', 'type': ''}], 'addresses':
{'ipv4': '172.25.254.197'}, 'vendor': {}, 'status': {'state': 'up', 'reason':
'syn-ack'}}, '172.25.254.250': {'hostnames': [{'name': '', 'type': ''}],
'addresses': {'ipv4': '172.25.254.250'}, 'vendor': {}, 'status': {'state': 'up',
'reason': 'syn-ack'}}}}
nmap命令行:
主機清單:
nmap -oX - -n -sP 172.25.254.0/24
['172.25.254.197', '172.25.254.250']
172.25.254.197的主機信息:
{'hostnames': [{'name': '', 'type': ''}], 'addresses': {'ipv4':
'172.25.254.197'}, 'vendor': {}, 'status': {'state': 'up', 'reason': 'syn-ack'}}
SSH端口存活掃描
- 使用telnet命令探測主機列表是否屬於Linux服務器。
- telnet命令探測
$ telnet 172.25.254.34 22
- telnetlib模塊探測
telnetlib模塊提供的Telnet類實現了Telnet協議。
# 實例化對象
tn = telnetlib.Telnet(host='172.25.254.34', port=22)
# read_until讀取直到遇到了換行符或超時秒數。默認返回bytes類型,通過decode方法解碼爲字符串。
result = tn.read_until(b'\n', timeout=5).decode('utf-8')
# 通過正則匹配且忽略大小寫, 尋找是否ssh服務開啓。
searchObj = re.search('ssh', result, re.I)
# 如果能匹配到內容, 說明ssh服務開啓, 是Linux服務器.
if searchObj:
print("ssh服務是開啓的,且是Linux操作系統")
else:
print('ssh服務未開啓或者不是Linux服務器')
掃描探測小結
主機登錄探測
什麼是主機登錄探測?
用一系列的驗證方式循環進行SSH登錄, 得到爭取的登錄方式。
主機SSH登錄驗證方式
SSH常用來遠程登錄到遠程機器,有兩種常用的方法
- 第一種便是賬號密碼登錄。
- 第二種就是公鑰私鑰無密碼登錄。(如何實現無密碼登錄?)
Python的SSH登錄模塊pexpect
Pexpect 用來實現與 ssh、ftp 、telnet 等程序的自動交互。是 Expect 語言的一個 Python 實現,是一個用來啓動子程序,並使用正則表達式對程序輸出做出特定響應,以此實現與其自動交互的 Python模塊。
pexpect的核心類和函數。
- 直接進程運行run()函數, 返回結果和狀態。
import pexpect
cmd_result, exitstatus = pexpect.run('hostname', withexitstatus=True)
print("命令執行結果: ", cmd_result.decode('utf-8'))
print("命令執行的狀態碼: ", exitstatus)
執行結果如下:
命令執行結果:foundation0.ilt.example.com
命令執行的狀態碼: 0
- pexpect指令執行的兩種方式—無交互和交互式
import pexpect
# 1.通過pexpect執行指令(無交互)
# 執行命令並返回命令執行結果和狀態碼(0代表成功,其他-執行失敗)
(command_output, exitstatus) = pexpect.run('hostname', withexitstatus=1)
command_output = command_output.decode('utf-8')
if exitstatus == 0:
print("命令執行成功:", command_output)
else:
print("命令執行失敗:", command_output)
def login_ssh_password(user, host, password, port=22):
# 2. 通過pexpect執行指令(有交互)
command = 'ssh -p22 [email protected]'
# command = 'ssh -p%s %s@%s' %(port, user, host)
# spawn開啓一個子進程處理交互式操作
ssh = pexpect.spawn(command=command, timeout=3)
# 匹配交互信息,返回的是匹配到的信息的索引
match_index = ssh.expect(['Are you sure you want to continue connecting (yes/no)? ', 'password:'])
print(match_index)
# 如果索引爲0代表第一次連接,如果索引爲1代表非第一次連接
if match_index == 0:
print("第一次連接")
ssh.sendline('yes')
ssh.expect(['password:'])
ssh.sendline(password)
elif match_index == 1:
print('非第一次連接')
ssh.sendline(password)
login_index = ssh.expect(['Last login: ', pexpect.EOF, pexpect.TIMEOUT])
print(login_index)
if login_index == 0:
print("用戶登錄成功")
# 進入元成服務器的命令行
ssh.interact()
elif login_index == 1:
print("登錄失敗:Logout")
elif login_index == 2:
print('登錄超時')
if __name__ == '__main__':
login_ssh_password(user='root',host='172.25.254.34', password='Asimov',port=22)
pexpect模塊的缺陷:
- 依賴終端命令的方式
- 不同的ssh登陸環境兼容性差
Python的SSH登錄模塊paramiko
- 什麼是paramiko?
**paramiko是一個用於做遠程控制的模塊,**使用該模塊可以對遠程服務器進行命令或文件操作,paramiko是用python語言寫的一個模塊,遵循SSH2協議,支持以加密和認證的方式,進行遠程服務器的連接。 - 安裝paramiko
# 使用豆瓣的鏡像源, 安裝paramiko模塊並指定安裝版本爲2.6.0.
$ pip install -i https://pypi.douban.com/simple paramiko==2.6.0
- paramiko核心組件
paramiko包含兩個核心組件:SSHClient和SFTPClient(sftp=ssh file transfer protocol)。
-
SSHClient的作用類似於Linux的ssh命令,是對SSH會話的封裝,該類封裝了傳輸(Transport),通道(Channel)及SFTPClient建立的方法(open_sftp),通常用於執行遠程命令。
-
SFTPClient的作用類似與Linux的sftp命令,是對SFTP客戶端的封裝,用以實現遠程文件操作,如文件上傳、下載、修改文件權限等操作。
-
項目代碼:基於paramiko實現ssh客戶端密鑰遠程登錄
測試之前生成公鑰和私鑰進行測試:
# 生成公鑰和私鑰, 默認存儲在 ~/.ssh/目錄下. id_rsa私鑰, id_rsa.pub公鑰
ssh-keygen
# 希望我的主機可以無密碼連接其他主機(需要將公鑰分發給其他主機)
ssh-copy-id -i ~/.ssh/id_rsa.pub user@ip
# 測試無密碼連接是否成功
ssh user@ip
ssh -i 私鑰位置 user@ip
import paramiko
def login_ssh_password(hostname, port, username, password, command):
# 實例化SSH客戶端對象
with paramiko.SSHClient() as client:
#自動添加當前主機到遠程服務器的known_hosts,遠程連接不再詢問yes/no
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
# 通過用戶名和密碼連接遠程服務器
client.connect(
hostname=hostname,
port=port,
username=username,
password=password,
)
# 連接成功後執行的命令
stdin,stdout,stderr = client.exec_command(command)
# 獲取命令執行的正確輸出
# return stdin, stdout,stderr
return stdout.read().decode('utf-8')
def login_ssh_key(hostname, port, username, keyfile, command):
# 實例化SSH客戶端對象
with paramiko.SSHClient() as client:
# 配置私人密鑰文件位置
private = paramiko.RSAKey.from_private_key_file(keyfile)
#自動添加當前主機到遠程服務器的known_hosts,遠程連接不再詢問yes/no
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
# 通過用戶名和密碼連接遠程服務器
client.connect(
hostname=hostname,
port=port,
username=username,
password=private,
)
# 連接成功後執行的命令
stdin,stdout,stderr = client.exec_command(command)
# 獲取命令執行的正確輸出
# return stdin, stdout,stderr
return stdout.read().decode('utf-8')
if __name__ == '__main__':
# stdout = login_ssh_password(hostname='172.25.254.34',port=22,username='root',password='Asimov',command='uname')
# print(stdout)
stdout = login_ssh_key(hostname='172.25.254.34',port=22,username='root',keyfile='/home/kiosk/.ssh/id_rsa',command='uname')
print(stdout)
- SFTPClient實戰代碼: 上傳和下載文件
SFTPCLient作爲一個sftp的客戶端對象,根據ssh傳輸協議的sftp會話,實現遠程文件操作,如上傳、下載、權限、狀態。
from_transport(cls,t)
客戶端通道
put(localpath, remotepath, callback=None, confirm=True) 將本地文件上傳到服務
器 參數confirm:
是否調用stat()方法檢查文件狀態,返
回ls -l的結果
get(remotepath, localpath, callback=None)
從服務器下載文件到本
地
mkdir() 在服務器上創建目錄
remove() 在服務器上刪除目錄
rename() 在服務器上重命名目錄
stat() 查看服務器文件狀態
listdir() 列出服務器目錄下的文件
具體的實例代碼如下:
import paramiko
# 獲取Transport實例
with
paramiko.Transport(('172.25.254.197', 22)) as tran:
# 連接SSH服務端,使用password
tran.connect(username="root", password='westos')
# # 或使用
# # 配置私人密鑰文件位置
# private = paramiko.RSAKey.from_private_key_file('./id_rsa')
# # 連接SSH服務端,使用pkey指定私鑰
# tran.connect(username="root", pkey=private)
# 獲取SFTP實例
sftp = paramiko.SFTPClient.from_transport(tran)
# 設置上傳的本地/遠程文件路徑
localpath = "/etc/passwd" remotepath = "/mnt/passwd"
# 執行上傳動作
sftp.put(localpath, remotepath)
# # 執行下載動作
# sftp.get(remotepath, localpath)
系統信息獲取
- 通過系統獲取哪些信息
命令 | 作用 | 舉例 |
---|---|---|
主機名 | 通過名稱識別資產的作用、位置等信息 | Nginx01、KVM、aliyun01 |
MAC地址 | 記錄網卡的信息,可以作爲主機的唯一標識 | 6e:40:08:f9:84:00 |
SN | 物理服務器、網絡設備有唯一的資產標識 | J156R12 |
系統版本 | 查看服務器系統和具體的版本 | Redhat7.0、Centos7.0、Ubuntu12.04 |
服務器機型 | 查看主機或者服務器類型 | Dell R610、HP、DL580 |
-
爲什麼要獲取這些信息
- 有利於識別資產設備
- 是資產的基本信息, 是自動化平臺的基礎(實現自動化任務執行、定時任務、自動化報表、監控等相關功能)
- 此處只能實現主機類型信息的探測, 而不是Docker容器的探測
-
獲取信息的Linux命令介紹
- 獲取主機名的命令(選擇通用方式): hostname、uname -a、cat /etc/sysconfig/network(主要針對Centos)
- 獲取系統版本: cat /etc/issue(可能爲空)、cat /etc/redhat-release、uname、lsb_release
- 獲取MAC地址: cat /sys/class/net/ [^vtlsb] */address、ifconfig ens33
- 獲取服務器硬件機型: dmidecode -s system-manufacturer、dmidecode -s system-product-name
Django數據庫模型設計
# apps/scanhost/models.py
class Server(models.Model):
"""服務器設備"""
sub_asset_type_choice = (
(0, 'PC服務器'),
(1, '刀片機'),
(2, '小型機'),
) created_by_choice = (
('auto', '自動添加'),
('manual', '手工錄入'),
)
sub_asset_type = models.SmallIntegerField(choices=sub_asset_type_choice,
default=0, verbose_name="服務器類型")
created_by = models.CharField(choices=created_by_choice, max_length=32,
default='auto', verbose_name="添加方式")
hosted_on = models.ForeignKey('self', related_name='hosted_on_server',
blank=True, null=True, verbose_name="宿主機",
on_delete=models.CASCADE) # 虛擬機專用字段
IP = models.CharField('IP地址', max_length=30, default='')
MAC
= models.CharField('Mac地址', max_length=200, default='')
model = models.CharField(max_length=128, null=True, blank=True,
verbose_name='服務器型號')
hostname = models.CharField(max_length=128, null=True, blank=True,
verbose_name="主機名")
os_type = models.CharField('操作系統類型', max_length=64, blank=True,
null=True)
os_distribution = models.CharField('發行商', max_length=64, blank=True,
null=True)
os_release = models.CharField('操作系統版本', max_length=64, blank=True,
null=True)
def __str__(self):
return '%s-%s' % (self.id, self.hostname)
class Meta:
verbose_name = '服務器'
verbose_name_plural = "服務器"
- models.py文件做了修改一定要生成遷移腳本並寫入數據庫中
python manage.py makemigrations
python manage.py migrate
# 創建超級用戶用於後臺登錄
python manage.py createsuperuser
配置文件配置
# devops\settings\base.py
scanhosts = [
# '127.0.0.1',
'172.25.254.0/24',]
# '47.92.255.98']
commands = {
'hostname': 'hostname',
'os_type': 'uname',
'os_distribution': 'dmidecode -s system-manufacturer',
'os_release': 'cat /etc/redhat-release',
'MAC': 'cat /sys/class/net/`[^vtlsb]`*/address',
}
視圖函數
import re
import telnetlib
import nmap
import paramiko
from django.http import HttpResponse
from django.shortcuts import render
# Create your views here.
from CMDB.settings import base
from apps.scanhost.models import Server
def get_active_hosts(hosts):
"""根據提供的網段或者IP返回存活的主機IP"""
# 實例化對象, portScanner()類用於實現對指定主機進行端口掃描
nm = nmap.PortScanner()
# 以指定方式掃描指定主機或網段的指定端口
result = nm.scan(hosts=hosts, arguments='-n ')
return nm.all_hosts()
def is_ssh_up(host, port=22, timeout=5):
# 實例化對象
tn = telnetlib.Telnet(host=host, port=port)
# read_until讀取直到遇到了換行符或超時秒數。默認返回bytes類型,通過decode方法解碼爲字符串。
result = tn.read_until(b'\n', timeout=timeout).decode('utf-8')
# print(result) #SSH-2.0-OpenSSH_7.4
# 通過正則匹配且忽略大小寫, 尋找是否ssh服務開啓。
searchObj = re.search('ssh', result, re.I)
# 如果能匹配到內容, 說明ssh服務開啓, 是Linux服務器.
if searchObj:
return True
else:
return False
def login_ssh_key(hostname, port, username, keyfile, command):
# 實例化SSH客戶端對象
with paramiko.SSHClient() as client:
# 配置私人密鑰文件位置
private = paramiko.RSAKey.from_private_key_file(keyfile)
# 自動添加當前主機到遠程服務器的known_hosts,遠程連接不再詢問yes/no
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
# 通過用戶名和密碼連接遠程服務器
client.connect(
hostname=hostname,
port=port,
username=username,
password=private,
)
# 連接成功後執行的命令
stdin, stdout, stderr = client.exec_command(command)
# 獲取命令執行的正確輸出
# return stdin, stdout,stderr
return stdout.read().decode('utf-8')
def scanhost(request):
# 訪問所有要掃描的網段/IP
for host in base.scanhosts:
print("正在掃描%s......" % (host))
# 獲取所有可以ping通的主機IP
active_hosts = get_active_hosts(hosts=host)
# 一次遍歷判斷ssh服務是否開啓
for active_host in active_hosts:
if is_ssh_up(active_host):
server = Server()
# 設置IP地址
server.IP = active_host
# 執行指令
for attr, command in base.commands.items():
# attr ='hostname' , command = 'hostname'
# 存儲主機名、操作系統.....指令執行的結果
result = login_ssh_key(active_host, 22, 'root', '/home/kiosk/.ssh/id_rsa', command)
setattr(server, attr, result)
server.save()
return HttpResponse('掃描成功')
路由配置
# devops/urls.py
urlpatterns = [
path('admin/', admin.site.urls),
path('scan/', scanhost)
]
後臺Admin管理
# apps/scanhost/admin.py
# 可以在admin後臺管理服務器信息
admin.site.register(Server)
測試
運行項目python manage.py runserver 0.0.0.0:8000