项目实战: CMDB自动化资产扫描
1. 项目介绍
本项目通过KVM虚拟化搭建Linux系统集群,使用 Ansible实现Linux集群下的批量部署与自动化管理,
实现Web形式的自动化运维系统,集中批量控制服务器, 最终实现能支撑1000台实例的环境提供管理和自
动化任务, 提高运维工程师的工作质量和效率。 项目基于HTTP实现自动化任务接受和响应接口设计,
基于MySQL用作的关系型数据存取, 基于Redis的任务锁机制和消息队列, 基于MongoDB的事件日志
记录, 最终实现邮件通知功能、敏感数据加密功能、日志事件记录功能。
这个文档主要目标是实现自动化资产扫描, 扫描指定网段的服务器资产信息。
互联网快速发展
网站用户规模、使用快速上升
要求庞大系统支撑能力
更加快速的运维效率应对突发流量
更加自动化的方式减少人工投入成本
更加可靠的技术手段,保障系统的稳定
云时代
腾讯云、阿里云、亚马逊云、青云等云厂商的市场份额不断增加
大部分技术架构设计不再以网络设计、IDC和系统硬件等方面作为重点
运维基础的、繁琐的工作逐步减少
小公司也不再需要一个运维工程师或者系统工程师
结论: 在这样的时代背景下, 大型互联网公司为了应对市场的快速变化,就需要运维自动化。
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
高性能的key-value非关系型数据库 Redis 软件下载: 5.0.5
分布式文件存储数据库 MongoDB 软件下载: 4.2.4
Git工具与Github代码仓库: 不限制项目环境的搭建
项目目录的配置
创建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
#devops/settings.py
#将sqlite数据库存储配置注释
#DATABASES = {
#'default': {
#'ENGINE': 'django.db.backends.sqlite3',
#'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
#}
#}
远程服务器解释器和目录映射的配置
在远程服务器上测试Django项目是否可以成功运行
python manage.py runserver
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数据库配置
修改配置文件的信息
#devops/settings.py
ALLOWED_HOSTS = ['*']
配置数据库: 使用mysql数据库,而不是默认的sqlite数据库。
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'devopsProject',
'USER': 'devops',
'PASSWORD': 'devops',
'HOST': '172.25.135.13',
'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数据库
配置数据库信息
访问数据库表和数据内容
关于数据库的配置结束。
数据库报错处理
运行项目时,出现报错如下,是因为缺少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: [email protected]
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信息
采集, 并存储到数据库中。 当用户想要访问采集数据时, 从数据库中读取,以界面的方式展示给
用户。
接口名称 接口API 对应函数名
采集接口 http://127.0.0.1:8000/sendinfos user_info
信息接口 http://127.0.0.1:8000/getinfos user_history
项目开发
项目开发(一) 信息采集接口的实现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, ):给每个邮件的标题加上当前时间, 时间格式为年月日_小时分钟秒_传入的邮件标题
subjecttime = 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
测试效果:
知识点拓展: Ipython
Ipython介绍
一个基于Python的交互式解释器
自动补全、历史记录、获得对象信息
Ipython的安装
$ pip install ipython
Ipython使用
$ ipython
In [1]:
常用快捷操作
快捷操作 功能含义
Tab 自动补齐
Ctrl + r 历史搜索
? 对象名 查看对象的相关信息
?? 对象名 查看对象的详细信息
- 第二个DevOPS工程实现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系统的基础;
资产管理是自动化运维平台构建的基础。
资产管理的技术实现
资产信息的手工录入: 手工操作工作量大,尤其是首次录入。
客户端主机主动上报: 需要给不同的设备编写对应的客户端程序, 开发工作量大。
服务器端主动发现探测:
抽象与约定:
内网Linux服务器主机类型(Centos4-6、Ubuntu12、14+)
系统内部开通ssh服务端口为Linux服务器
Linux系统开放ssh的端口范围(22、20022、202)
安全规则(开放允许探测协议和登录的限制)
网络设备开通snmp服务,且community都已经统一
虚拟机不再运行容器等虚拟资产
网络设备(cisco系列等)
资产管理探测流程
存活探测: 获取局域网内存活的IP列表
主机探测: 获取系统版本(SN、版本、MAC地址)
主机关系探测: 识别宿主主机和虚拟机的关系
网络设备的探测: 探测网络设备信息(SN、设备名等),使用snmp协议
其他设备的探测:
###
主机存活探测协议
ICMP(Internet Control Message Protocol)Internet控制报文协议。它是TCP/IP协议簇的一个
子协议,用于在IP主机、路由器之间传递控制消息。控制消息是指网络通不通、主机是否可达、路
由是否可用等网络本身的消息。这些控制消息虽然并不传输用户数据,但是对于用户数据的传递起
着重要的作用。
传输控制协议(TCP,Transmission Control Protocol)是一种面向连接的、可靠的、基于字节流
的传输层通信协议。
探测协议 协议特性 用途
ICMP 无连接 网络探测、网络质量
TCP 有连接 应用服务
主机存活探测模块和工具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.197 22
telnetlib模块探测
telnetlib模块提供的Telnet类实现了Telnet协议。
import telnetlib
import re
#实例化对象
tn = telnetlib.Telnet(host='172.25.254.197', port=22, timeout=5)
#read_until读取直到遇到了换行符或超时秒数。默认返回bytes类型,通过decode方法解码为字符
串。
tn_result = tn.read_until(b"\n", timeout=5).decode('utf-8')
#通过正则匹配且忽略大小写, 寻找是否ssh服务开启。
ssh_result = re.search(pattern=r'ssh', string=tn_result, flags=re.I)
#如果能匹配到内容, 说明ssh服务开启, 是Linux服务器.
if ssh_result:
print("Linux服务器存活")
else:
print("其他服务器存活")
代码执行结果如下:
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
启动子进程运行spawn()函数, 读取缓冲区数据
如果正则匹配成功
发送指令(send、sendline、sendcontrol)
退出当前会话, 进入终端交互式命令行
如果正则匹配不成功, 等待超时(TIMEOUT)或者子进程退出(pexpect.EOF)。
正则不管是否匹配成功, 都可以打印输出匹配的缓冲区结果(before和after)
pexpect模块的缺陷:
依赖终端命令的方式
不同的ssh登陆环境兼容性差
pexpect项目实战: ssh仿真SSH登陆远程设备
下面的代码通过子进程运行spawn函数的方式实现了SSH客户端登陆的两种方式:
login_ssh_passwd函数是密码登录的封装。
login_ssh_key函数是密钥登陆的封装。
import pexpect
#logging:专门做日志记录或者处理的模块。
import logging
#日志的基本配置# 官方符网站基本配置及使用: https://docs.python.org/zh-
cn/3/howto/logging.html#logging-basic-tutorial
logging.basicConfig(
level=logging.DEBUG, # 控制台打印的日志级别
filename='message.log', # 日志文件位置
filemode='a', # 写入文件的模式,a是追加模式,默认是追加模式
#日志格式
format='%(asctime)s - %(pathname)s[line:%(lineno)d] - %(levelname)s: %
(message)s'
)
class SSH(object):
def login_ssh_passwd(self, hostname, username, password, port=22):
"""
用于pexpect实现ssh自动化用户密码登录
"""
print("ssh -p%s %s@%s" % (port, username, hostname))
if port and username and password:
ssh = pexpect.spawn("ssh -p%s %s@%s" % (port, username, hostname))
status_code = ssh.expect(['password', 'continue connecting
(yes/no)?'], timeout=5)
if status_code == 0:
ssh.sendline(password)
elif status_code == 1:
ssh.sendline('yes\n')
ssh.expect('password: ')
ssh.sendline(password)
index = ssh.expect(["#", pexpect.EOF, pexpect.TIMEOUT])
if index == 0:
print("logging in as root!")
logging.info("logging in as root!")
ssh.interact()
elif index == 1:
print("logging process exit!")
logging.error("logging process exit!")
elif index == 2:
print("logging timeout exit!")
logging.error("logging timeout exit!")
else:
print("Parameter error!")
logging.error("Paramiko error!")
def login_ssh_key(self, hostname, keyfile, username, port=22):
"""
用于实现pexpect实现ssh的自动化密钥管理
"""
print("ssh -i %s -p%s %s@%s" % (keyfile, port, username, hostname))
if hostname and keyfile and username and port:
ssh = pexpect.spawn("ssh -i %s -p%s %s@%s" % (keyfile, port,
username, hostname))
status_code = ssh.expect([pexpect.TIMEOUT, 'continue connecting
(yes/no)?'], timeout=5)
if status_code == 1:
ssh.sendline('yes\n')
index = ssh.expect(["#", pexpect.EOF, pexpect.TIMEOUT])
if index == 0:
print("logging in as root!") logging.info("logging in as root!")
#进入交互式环境
ssh.interact()
elif index == 1:
print("logging process exit!")
logging.error("logging process exit!")
elif index == 2:
print("logging timeout exit!")
logging.error("logging timeout exit!")
else:
print("Paramiko error!")
logging.error("Parameter error!")
def main():
sshClient = SSH()
sshClient.login_ssh_passwd('172.25.254.197', 'root', 'redhat')
#必须做过无密码连接
sshClient.login_ssh_key('172.25.254.197','/root/.ssh/id_rsa', 'root', )
if name == 'main':
main()
项目执行效果如下:
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客户端密码远程登录
import paramiko
def login_ssh_passwd(hostname='172.25.254.197', port=22, username='root', password='westos', command='df -h'):
"""
基于paramiko实现ssh自动化用户密码登录
:return:
"""
#实例化SSHClient
with paramiko.SSHClient() as client:
#自动添加策略,保存服务器的主机名和密钥信息,如果不添加,那么不再本地
know_hosts文件中记录的主机将无法连接
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
#连接SSH服务端,以用户名和密码进行认证
client.connect(hostname=hostname, port=port, username=username,
password=password)
打开一个Channel并执行命令。 stdout 为正确输出,stderr为错误输出,同时是有1
个变量有值
stdin, stdout, stderr = client.exec_command(command)
#打印执行结果
print(stdout.read().decode('utf-8'))
if name == 'main':
login_ssh_passwd(hostname='127.0.0.1', command='hostname')
运行效果如下:
foundation0.ilt.example.com
项目代码: 基于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
pyhon‘代码实现公钥和私钥的无密码连接
def login_ssh_key( hostname='172.25.254.197', keyfile='./id_rsa',
username='root', port=22, command='df -h'):
"""
基于paramiko实现ssh的自动化密钥管理
"""
#配置私人密钥文件位置
private = paramiko.RSAKey.from_private_key_file(keyfile)
#实例化SSHClient
with paramiko.SSHClient() as client:
自动添加策略,保存服务器的主机名和密钥信息,如果不添加,那么不再本地
know_hosts文件中记录的主机将无法连接
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
#连接SSH服务端,以用户名和密码进行认证 client.connect(hostname=hostname, port=port, username=username,
pkey=private)
打开一个Channel并执行命令。 stdout 为正确输出,stderr为错误输出,同时是有1
个变量有值
stdin, stdout, stderr = client.exec_command(command)
#打印执行结果
print(stdout.read().decode('utf-8'))
if name == 'main':
#login_ssh_passwd(hostname='127.0.0.1', command='hostname')
login_ssh_key(hostname='127.0.0.1', command='hostname')
运行效果如下:
foundation0.ilt.example.com
SFTPClient实战代码: 上传和下载文件
SFTPCLient作为一个sftp的客户端对象,根据ssh传输协议的sftp会话,实现远程文件操作,如上
传、下载、权限、状态。
创建一个已连通的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)
paramiko的应用和缺陷
系统信息获取
通过系统获取哪些信息
命令
主机名
MAC地址
通过名称识别资产的作用、位置等信
息
记录网卡的信息,可以作为主机的唯
一标识
物理服务器、网络设备有唯一的资产
SN
标识
系统版本
服务器机
型
作用
查看服务器系统和具体的版本
查看主机或者服务器类型
举例
Nginx01、KVM、aliyun01
6e:40:08:f9:84:00
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',
'172.25.135.13']
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',
}
视图函数# apps/scanhost/views.py
def get_active_hosts(hosts='47.92.255.98', ports='22,80'):
pass
def is_ssh_up(host='47.92.255.98', port=22, timeout=5):
pass
def login_ssh_key(host, port, user, keyfile, command):
pass
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',
'/mnt/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
访问网址http://ip:port/scan
访问网址http://ip:port/admin
完整项目代码
https://gitee.com/huojin181/cmdb