項目背景
需求分析
# 解決需求:
1、記錄使用者操作
實作方式1:改ssh用戶端的源代碼,10w+
實作方式2:修改已有python ssh 庫,加入指令記錄的功能(推薦)
2、實作權限管理:
A 支付系統的機器(500)
10.0.1.11 root
10.0.1.12 mysql
B 10.0.1.12 root
3、日志記錄:記入到資料庫
4、密碼&秘鑰:2種方式實作伺服器登陸
資料庫設計
from django.db import models
from django.contrib.auth.models import User
# Create your models here.
class IDC(models.Model):
""" 機房資訊 """
name = models.CharField(max_length=64, unique=True)
def __str__(self):
return self.name
class Host(models.Model):
"""存儲所有主機資訊"""
hostname = models.CharField(max_length=64, unique=True)
ip_addr = models.GenericIPAddressField(unique=True)
port = models.IntegerField(default=22)
idc = models.ForeignKey('IDC', on_delete='')
enabled = models.BooleanField(default=True)
def __str__(self):
return '%s-%s' % (self.hostname, self.ip_addr)
class HostGroup(models.Model):
"""主機組"""
name = models.CharField(max_length=64, unique=True)
host_user_binds = models.ManyToManyField('HostUserBind')
def __str__(self):
return self.name
class HostUser(models.Model):
"""存儲遠端主機的使用者資訊"""
auth_type_choices = ((0, 'ssh-password'), (1, 'ssh-key'))
auth_type = models.SmallIntegerField(choices=auth_type_choices)
username = models.CharField(max_length=32)
password = models.CharField(blank=True, null=True, max_length=128)
def __str__(self):
return '%s-%s-%s' % (self.get_auth_type_display(), self.username, self.password)
class Meta:
unique_together = ('username', 'password')
class HostUserBind(models.Model):
"""綁定主機和使用者"""
host = models.ForeignKey('Host', on_delete='')
host_user = models.ForeignKey('HostUser', on_delete='')
def __str__(self):
return '%s-%s' % (self.host, self.host_user)
class Account(models.Model):
"""堡壘機賬戶"""
user = models.OneToOneField(User, on_delete='') # 使用者Django架構提供的使用者驗證子產品
name = models.CharField(max_length=64)
host_user_bind = models.ManyToManyField('HostUserBind', blank=True)
host_groups = models.ManyToManyField('HostGroup', blank=True)
class SessionLog(models.Model):
"""記錄堡壘機使用者登陸堡壘機資訊的日志,同時用于生成記錄使用者指令檔案的名稱"""
account = models.ForeignKey('Account', on_delete='')
host_user_bind = models.ForeignKey('HostUserBind', on_delete='')
start_date = models.DateTimeField(auto_now_add=True)
end_date = models.DateTimeField(blank=True, null=True)
def __str__(self):
return '%s-%s' % (self.account, self.host_user_bind)
class AuditLog(models.Model):
"""記錄堡壘機賬戶操作遠端伺服器指令的日志"""
session = models.ForeignKey('SessionLog', on_delete='')
cmd = models.TextField() # 記錄操作指令
data = models.DateTimeField(auto_now_add=True) # 自動添加目前時間
def __str__(self):
return '%s-%s'
linux中用strace指令來實作監測使用者指令
指令說明:
strace指令是一個集診斷、調試、統計與一體的工具,我們可以使用strace對應用的系統調用和信号傳遞的跟蹤結果來對應用進行分析,以達到解決問題或者是了解應用工作過程的目的。
-c 統計每一系統調用的所執行的時間,次數和出錯的次數等.
-d 輸出strace關于标準錯誤的調試資訊.
-f 跟蹤由fork調用所産生的子程序.
-ff 如果提供-o filename,則所有程序的跟蹤結果輸出到相應的filename.pid中,pid是各程序的程序号.
-F-h 輸出簡要的幫助資訊.
-i 輸出系統調用的入口指針.
-q 禁止輸出關于脫離的消息.
-r 列印出相對時間關于,,每一個系統調用.
-t 在輸出中的每一行前加上時間資訊.
-tt 在輸出中的每一行前加上時間資訊,微秒級.
-ttt 微秒級輸出,以秒了表示時間.
-T-v 輸出所有的系統調用.一些調用關于環境變量,狀态,輸入輸出等調用由于使用頻繁,預設不輸出.
-V-x 以十六進制形式輸出非标準字元串
-xx 所有字元串以十六進制形式輸出.
-a column 設定傳回值的輸出位置.預設 為40.
-e expr 指定一個表達式,用來控制如何跟蹤.格式:[qualifier=][!]value1[,value2]...
qualifier隻能是 trace,abbrev,verbose,raw,signal,read,write其中之一.value是用來限定的符号或數字.預設的 qualifier是 trace.感歎号是否定符号.例如:-eopen等價于 -e trace=open,表示隻跟蹤open調用.而-etrace!=open 表示跟蹤除了open以外的其他調用.有兩個特殊的符号 all 和 none. 注意有些shell使用!來執行曆史記錄裡的指令,是以要使用\\.
-e trace=set 隻跟蹤指定的系統 調用.例如:-e-e trace=file 隻跟蹤有關檔案操作的系統調用.
-e trace=process 隻跟蹤有關程序控制的系統調用.
-e trace=network 跟蹤與網絡有關的所有系統調用.
-e strace=signal 跟蹤所有與系統信号有關的 系統調用
-e trace=ipc 跟蹤所有與程序通訊有關的系統調用
-e abbrev=set 設定strace輸出的系統調用的結果集.-v 等與 abbrev=none.預設為abbrev=all.
-e raw=set 将指定的系統調用的參數以十六進制顯示.
-e signal=set 指定跟蹤的系統信号.預設為all.如 signal=!SIGIO(或者signal=!io),表示不跟蹤SIGIO信号.
-e read=set 輸出從指定檔案中讀出 的資料.例如: -e read=3,5-e write=set 輸出寫入到指定檔案中的資料.
-o filename 将strace的輸出寫入檔案filename
-p pid 跟蹤指定的程序pid.
-s strsize 指定輸出的字元串的最大長度.預設為32.檔案名一直全部輸出.
-u username 以username的UID和GID執行被跟蹤的指令
strace監控堡壘機用ssh連接配接其它伺服器時輸入的指令(在xsheel終端連接配接堡壘機後,監控堡壘機輸入的指令):
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiI0gTMx81dsQWZ4lmZf1GLlpXazVmcvwFciV2dsQXYtJ3bm9CX9s2RkBnVHFmb1clWvB3MaVnRtp1XlBXe0xCMy81dvRWYoNHLwEzX5xCMx8FesU2cfdGLwMzX0xiRGZkRGZ0Xy9GbvNGLpZTY1EmMZVDUSFTU4VFRR9Fd4VGdsYTMfVmepNHLrJXYtJXZ0F2dvwVZnFWbp1zczV2YvJHctM3cv1Ce-cmbw5yNwMDM3MmNzIzN1kTY0ImZyYzX3EzM0UTM2EzLchDMyIDMy8CXn9Gbi9CXzV2Zh1WavwVbvNmLvR3YxUjLyM3Lc9CX6MHc0RHaiojIsJye.png)
将監控的指令結果輸出到檔案ssh_log:包含時間
通過Python subprocess連接配接遠端主機(實時互動+免輸入密碼登陸)
openssh源碼的linux版本下載下傳
下載下傳位址:http://www.openssh.com/portable.html
将源碼與python代碼一同上傳至堡壘機所處的伺服器中後,用堡壘機來連接配接其它伺服器。
通過FTP軟體将項目上傳至堡壘機伺服器上:
通過Python subprocess連接配接遠端主機(實時互動+免輸入密碼登陸)
openssh源碼的linux版本下載下傳
下載下傳位址:http://www.openssh.com/portable.html
将源碼與python代碼一同上傳至堡壘機所處的伺服器中後,用堡壘機來連接配接其它伺服器。
通過FTP軟體将項目上傳至堡壘機伺服器上。
上傳sshpass-1.06.tar檔案用于免密登陸的軟體
- 先編譯成二進制,再安裝:
- 也可以通過apt一步安裝到位:
- 檢視apt-get install所安裝的軟體所在位置:dpkg -L 軟體名
- 堡壘機無需再輸入密碼連接配接遠端伺服器:
代碼實作堡壘機免密登陸遠端伺服器:
- audit_shell.py檔案:
#_*_coding:utf-8_*_
import os
import sys
if __name__ == '__main__':
# 将django添加到環境變量中
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "audit.settings")
import django
django.setup() # 手動注冊django中所有的APP
from audit_init.backend import user_interactive
obj = user_interactive.UserShell(sys.argv) # 終端運作該腳本時傳入的參數
- user_interactive.py檔案:
from django.contrib.auth import authenticate
from audit_init import models
import subprocess
class UserShell():
"""使用者登陸堡壘機後的shell"""
def __init__(self, sys_argv):
self.sys_argv = sys_argv
self.user = None # 用來儲存驗證過的使用者對象
def auth(self):
"""用Django自帶認證系統進行使用者驗證"""
count = 0
while count < 3: # 允許使用者輸入3次,
username = input('請輸入堡壘機賬戶名:').strip()
password = input('請輸入堡壘機密碼:').strip()
# 進行使用者認證,通過則獲得user對象,
user = authenticate(username=username, password=password)
if not user:
count += 1
print('使用者不存在!請重新輸入')
else:
self.user = user
return True
else:
print('輸入次數超過3次,請稍候再試')
def start(self):
"""啟動互動程式"""
if self.auth(): # 如果登陸成功
while True:
host_groups = self.user.account.host_groups.all() # 通過user表反向查詢出所有主機組
print(host_groups)
for index, group in enumerate(host_groups):
# group.host_user_binds.count() 計算一個組下有多少台主機
print('%s, \t%s[%s]'%(index, group, group.host_user_binds.count()))
print("%s.\t未分組機器[%s]" % (len(host_groups), self.user.account.host_user_binds.count()))
try:
choice = input('請選擇組:').strip()
if choice.isdigit(): # 如果輸入的是數字
choice = int(choice)
host_bind_list = None
if choice >= 0 and choice < len(host_groups):#
selected_group = host_groups[choice]
host_bind_list = selected_group.host_user_binds.all()
elif choice == len(host_groups):#選擇的未分組機器
host_bind_list = self.user.account.host_user_binds.all()
if host_bind_list:
while True:
for index, host in enumerate(host_bind_list):
print('%s, \t%s'%(index, host))
choice2= input('請選擇主機:').strip()
if choice2.isdigit():
choice2 = int(choice2) # 轉換成int類型
if choice2 >= 0 and choice2 < len(host_bind_list):
selected_host = host_bind_list[choice2]
# StrictHostKeyChecking=no表示第一次連接配接伺服器時無需輸入yes作為簽名驗證
cmd = 'sshpass -p %s ssh %s@%s -p %s -o StrictHostKeyChecking=no ' % (
selected_host.host_user.password, selected_host.host_user.username,
selected_host.host.ip_addr, selected_host.host.port)
subprocess.run(cmd, shell=True) # 啟動shell程式視窗
elif choice2 == 'b':
break
except KeyboardInterrupt as e:
pass
- 該部分操作時引出的問題:
1、無法通過浏覽器通路linux伺服器上的djangoWeb項目:
解決方式:python manage.py runserver 0.0.0.0:8000 # 手動設定通路位址,同時清除浏覽器中的修改化設定内容
通過修改SSH源碼作唯一辨別,完成主機識别
問題背景:
1台堡壘機同時遠端連接配接多台伺服器時,無法将兩者的程序資訊進行區分,導緻擷取不到對應的端口号。
解決方式:通過ssh連接配接時傳入随機數作為唯一辨別,進行程序号的擷取,進而通過strace進行監控
- 修改ssh源碼(檔案ssh.c中修改):
- linux編譯并安裝openssh:
此處執行編譯時可能報error: * zlib.h missing - please install first or check config.log *“這是由于缺少zlib-devel所緻,隻需安裝zlib-devel即可,執行指令:yum install zlib-devel;
還有可能會包”OpenSSL headers missing - please install first or check config.log *“的錯誤,這是缺少openssl-devel所緻,隻需安裝openssl-devel即可,執行指令:yum install openssl-devel
備注:當編譯出錯時,修改編譯後,需要通過make clean清除原有的編譯檔案,保持環境幹淨.
- 該階段可能出現的報錯:
-
解決方式:校正時區
timedatectl set-timezone “Asia/Shanghai”
timedatectl set-timezone UTC #推薦使用和設定協調世界時,即UTC。
yum -y install ntp
systemctl status ntpd
- 測試修改後的ssh源碼是否能夠通過sshpass連接配接伺服器,并攜帶自定義的參數:
- 可看到目前程序攜帶了自定義的參數用來進行過濾篩選:
修改python代碼,篩選過濾出程序号,用來監控該程序号:
# 略……
if choice2 >= 0 and choice2 < len(host_bind_list):
selected_host = host_bind_list[choice2]
# 生成随機字元串作為連接配接每台伺服器後,監控程序的唯一辨別
import string
import random
s = string.ascii_lowercase + string.digits # 獲得所有小寫字母+數字
random_tag = ''.join(random.sample(s, 10)) # 取樣
# StrictHostKeyChecking=no表示第一次連接配接伺服器時無需輸入yes作為簽名驗證
cmd = 'sshpass -p %s /usr/local/openssh7/bin/ssh %s@%s -p %s -o StrictHostKeyChecking=no -Z %s' % (
selected_host.host_user.password, selected_host.host_user.username,
selected_host.host.ip_addr, selected_host.host.port, random_tag)
subprocess.run(cmd, shell=True) # 啟動shell程式視窗
# 略……
- 運作腳本後,可獲得堡壘機連接配接到不同伺服器時對應的程序号:
編輯linux系統中的腳本檔案.sh
- 前提準備:對目前使用者的sudo權限進行修改,免除運作.sh腳本時需要輸入sudo密碼進行安全驗證的動作
注意:一定要在root使用者下進行修改,否則會造成sudo無法使用,使得系統崩潰。
編寫執行腳本:
#!/bin/bash
for i in $(seq 1 30);do
echo $i $1 # $1表示傳入腳本檔案中的參數(随機字元串)
# ``表示可以執行的指令,echo時,如果不添加``,則直接以字元串形式輸出内容
process_id = `ps -ef | grep $1 | grep -v sshpass | grep -v grep | grep -v 'session_tracker.sh' | awk '{print $2}'`
# 輸出得到的$process_id變量值
echo "process: $process_id"
# 如果$process_id不為空時
if [ ! -z "$process_id" ];then
echo 'start run strace……'
# 監控使用者輸入的指令内容,将内容輸出到指定位置($0表示輸入的第一個參數)
strace -fp $process_id -t -o $(cd `dirname $0`; pwd)/nnnnnnnnn.log;
break;
fi
sleep 1
done;
運作腳本:
注:需先通過堡壘機的sshpass指令遠端登陸一台伺服器。
執行腳本指令:
會話監測日志
記錄 堡壘機賬戶,登入主機的賬戶,登入的主機ip , 建立SessionLog 表,每次會話前建立一個sessionlog 記錄, 把這個記錄的ID傳給session_tracker.sh ,是以 實作Session_tracker.sh 建立的會話日志 會以 sessionlog.id 命名 。
略……
s = string.ascii_lowercase +string.digits # 生成所有小寫字元加數字
random_tag = ''.join(random.sample(s,10)) # 随機選擇生成随機字元串
# 為session會話日志添加資料,獲得session的Queryset對象
session_obj = models.SessionLog.objects.create(account=self.user.account,host_user_bind=selected_host)
# 格式化cmd指令,實作免密遠端登陸
cmd = "sshpass -p %s /usr/local/openssh/bin/ssh %s@%s -p %s -o StrictHostKeyChecking=no -Z %s" %(selected_host.host_user.password,selected_host.host_user.username,selected_host.host.ip_addr,selected_host.host.port ,random_tag)
#start strace ,and sleep 1 random_tag, session_obj.id
session_tracker_script = "/bin/sh %s %s %s " %(settings.SESSION_TRACKER_SCRIPT,random_tag,session_obj.id)
# 執行腳本,循環檢測是否連接配接到遠端服務,記錄使用者發送給遠端伺服器的指令
session_tracker_obj =subprocess.Popen(session_tracker_script, shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
# 啟動新的shell視窗,連接配接到遠端伺服器
ssh_channel = subprocess.run(cmd,shell=True)
print(session_tracker_obj.stdout.read(), session_tracker_obj.stderr.read())
略……
注意: python3 manage.py migrate –fake # 僞裝執行完成資料庫記錄更改
解析指令日志檔案:
略……
通路堡壘機伺服器時自動啟動的腳本檔案:
通過修改腳本檔案來設定啟動内容項。
将項目移動至local目錄下作為生産環境的位置:
可能的報錯問題:
解決方式:
無需添加sudo指令來執行其它指令:
退出程式時應該一并退出堡壘機伺服器:
代碼級别的控制:
總結:
此方式不适用對使用者操作的方式無法有效記錄!!!
paramiko子產品監控使用者指令輸入
paramiko子產品指令監控示範:
1、從github上下載下傳該子產品
2、改寫interactive.py 中的代碼,添加一行print(‘->’, x)代碼:
3、執行該demo.py子產品檔案:
拼接指令:
修改paramiko源碼:
- ssh_interactive.py
import base64
from binascii import hexlify
import getpass
import os
import select
import socket
import sys
import time
import traceback
from paramiko.py3compat import input
import paramiko
try:
import interactive
except ImportError:
from . import interactive
def manual_auth(t, username,password):
t.auth_password(username, password)
def ssh_session(bind_host_user, user_obj):
# now connect
hostname = bind_host_user.host.ip_addr
port = bind_host_user.host.port
username = bind_host_user.host_user.username
password = bind_host_user.host_user.password
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((hostname, port))
except Exception as e:
print("*** Connect failed: " + str(e))
traceback.print_exc()
sys.exit(1)
try:
t = paramiko.Transport(sock)
try:
t.start_client()
except paramiko.SSHException:
print("*** SSH negotiation failed.")
sys.exit(1)
try:
keys = paramiko.util.load_host_keys(
os.path.expanduser("~/.ssh/known_hosts")
)
except IOError:
try:
keys = paramiko.util.load_host_keys(
os.path.expanduser("~/ssh/known_hosts")
)
except IOError:
print("*** Unable to open host keys file")
keys = {}
# check server's host key -- this is important.
key = t.get_remote_server_key()
if hostname not in keys:
print("*** WARNING: Unknown host key!")
elif key.get_name() not in keys[hostname]:
print("*** WARNING: Unknown host key!")
elif keys[hostname][key.get_name()] != key:
print("*** WARNING: Host key has changed!!!")
sys.exit(1)
else:
print("*** Host key OK.")
if not t.is_authenticated():
manual_auth(t, username, password)
if not t.is_authenticated():
print("*** Authentication failed. :(")
t.close()
sys.exit(1)
chan = t.open_session()
chan.get_pty()
chan.invoke_shell()
print("*** Here we go!\n")
from audit_init import models
# 建立會話日志
session_obj = models.SessionLog.objects.create(account=user_obj.account,
host_user_bind=bind_host_user,
)
interactive.interactive_shell(chan, session_obj) # 啟動shell終端
chan.close()
t.close()
except Exception as e:
print("*** Caught exception: " + str(e.__class__) + ": " + str(e))
traceback.print_exc()
try:
t.close()
except:
pass
sys.exit(1)
- interactive.py
import socket
import sys
from paramiko.py3compat import u
from audit_init import models
# windows does not have termios...
try:
import termios
import tty
has_termios = True
except ImportError:
has_termios = False
def interactive_shell(chan, session_obj):
if has_termios:
posix_shell(chan,session_obj)
else:
windows_shell(chan)
def posix_shell(chan, session_obj):
import select
oldtty = termios.tcgetattr(sys.stdin)
try:
tty.setraw(sys.stdin.fileno())
tty.setcbreak(sys.stdin.fileno())
chan.settimeout(0.0)
flag = False
cmd = ''
while True:
r, w, e = select.select([chan, sys.stdin], [], [])
if chan in r:
try:
x = u(chan.recv(1024))
if len(x) == 0: # 未輸入指令狀态/ctrl+c退出shell終端時
sys.stdout.write("\r\n*** EOF\r\n")
break
if flag:
cmd+=x
flag=False
sys.stdout.write(x)
sys.stdout.flush()
except socket.timeout:
pass
if sys.stdin in r:
x = sys.stdin.read(1)
if len(x) == 0:
break
if x == '\r':
models.AuditLog.objects.create(session=session_obj, cmd=cmd) # 将使用者輸入的指令寫入資料表
cmd='' # 清空
elif x == '\t':
flag = True
else:
cmd += x
chan.send(x)
finally:
termios.tcsetattr(sys.stdin, termios.TCSADRAIN, oldtty)
# thanks to Mike Looijmans for this code
def windows_shell(chan):
import threading
sys.stdout.write(
"Line-buffered terminal emulation. Press F6 or ^Z to send EOF.\r\n\r\n"
)
def writeall(sock):
while True:
data = sock.recv(256)
if not data:
sys.stdout.write("\r\n*** EOF ***\r\n\r\n")
sys.stdout.flush()
break
sys.stdout.write(data)
sys.stdout.flush()
writer = threading.Thread(target=writeall, args=(chan,))
writer.start()
try:
while True:
d = sys.stdin.read(1)
if not d:
break
chan.send(d)
except EOFError:
# user hit ^Z or F6
pass
- user_interactive.py
略……
if choice2 >= 0 and choice2 < len(host_bind_list):
selected_host = host_bind_list[choice2]
# 生成随機字元串作為連接配接每台伺服器後,監控程序的唯一辨別
from audit_init.backend.ssh_interactive import
- 在admin元件中展示特定字段:
from django.contrib import
from audit_init import
# Register your models here.
class AuditLogAdmin(admin.ModelAdmin):
# 在admin背景展示的字段
list_display = ['session', 'cmd', 'date']
# 在admin背景搜尋的條件字段
list_filter = ['date', 'session']
class SessionLogAdmin(admin.ModelAdmin):
list_display = ['id','account','host_user_bind','start_date','end_date']
list_filter = ['start_date','account']
admin.site.register(models.IDC)
admin.site.register(models.HostGroup)
admin.site.register(models.Host)
admin.site.register(models.HostUser)
admin.site.register(models.HostUserBind)
admin.site.register(models.Account)
admin.site.register(models.SessionLog, SessionLogAdmin)
admin.site.register(models.AuditLog, AuditLogAdmin)
将前端模版嵌入堡壘機
static路徑問題:
settings配置檔案:
# 靜态資源位置加載,可相容多個路徑
STATIC_URL = '/static/' # 靜态資源入口
STATICFILES_DIRS = (
os.path.join(BASE_DIR,'statics'), # 允許多個靜态資源路徑
母版改造:
- base.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title> audit | AI堡壘機</title>
<link href="http://fonts.googleapis.com/css?family=Open+Sans:300,400,600,700&subset=latin" rel="stylesheet">
<link href="/static/css/bootstrap.min.css" rel="stylesheet">
<link href="/static/css/nifty.min.css" rel="stylesheet">
<link href="/static/premium/icon-sets/icons/line-icons/premium-line-icons.min.css" rel="stylesheet">
<link href="/static/premium/icon-sets/icons/solid-icons/premium-solid-icons.min.css" rel="stylesheet">
<link href="/static/css/pace.min.css" rel="stylesheet">
<script src="/static/js/pace.min.js"></script>
<script src="/static/js/jquery.min.js"></script>
<script src="/static/js/bootstrap.min.js"></script>
<script src="/static/js/nifty.min.js"></script>
</head>
<body>
{% block body %}
{% endblock %}
</body>
</html>
- index.html
{% extends
{% block
略……
{% endblock%}
登陸與退出
urls:
r'^$', views.index),
url(r'^login.html/$', views.acc_login),
url(r'^login_out.html/$', views.acc_login_out),
views:
def index(request):
return render(request, 'index.html',)
def acc_login(request):
"""
登入
:param request:
:return:
"""
if request.method == 'POST':
# 登陸成功
print('11')
username = request.POST.get('username')
password = request.POST.get('password')
# 使用django自帶的使用者認證
user = authenticate(username=username, password=password)
if user:
# 将使用者封裝到request中的session中響應給浏覽器
login(request, user)
return redirect('/')
# 登陸失敗
return render(request, 'login.html',)
def acc_login_out(request):
"""
退出登陸
:param request:
:return:
"""
logout(request)
return redirect('/login.html/')
login.html
{% extends
{% block<div id="container" class="cls-container">
<!-- LOGIN FORM -->
<!--===================================================-->
<div class="cls-content">
<div class="cls-content-sm panel">
<div class="panel-body">
<div class="mar-ver pad-btm">
<h1 class="h3">使用者登陸</h1>
</div>
<form action="/login.html/" method="POST"
<div class="form-group">
<input type="text" class="form-control" placeholder="Username" autofocus name="username">
</div>
<div class="form-group">
<input type="password" class="form-control" placeholder="Password" name="password">
</div>
<div class="checkbox pad-btm text-left">
<input id="demo-form-checkbox" class="magic-checkbox" type="checkbox">
<label for="demo-form-checkbox">Remember me</label>
</div>
<button class="btn btn-primary btn-lg btn-block" type="submit">登陸</button>
</form>
</div>
<div class="pad-all">
<a href="#" class="btn-link mar-rgt">忘記密碼 ?</a>
</div>
</div>
</div>
</div>{% endblock
index.html:
從request中獲得session中的使用者名#}
<div class="username hidden-xs">{{ request.user</div>
<div class="pad-all text-right">
<a href="/login_out.html/" class="btn btn-primary">
<i class="pli-unlock icon-fw"></i>退出
</a>
控制導航欄高亮顯示
url:
r'^host_list/$', views.host_list, name='host_list'),
views:
def host_list(request):
return render(request, 'host_list.html',)
index.html:
省略頁面改造步驟
略……
<script>function (){#第一種顯示高亮的函數#}/*$("#mainnav-menu a").click(function () {
$(this).parent().addClass('active-link')
});*/{##第一種顯示高亮的函數:通過屬性選擇器獲得前當URL對應的a标簽,進而控制a标簽的高亮#}"#mainnav-menu a[href='{{ request.path }}']").parent().addClass('active-link');
});
</script>
Django自帶使用者認證
使用認證裝飾器@login_required
# views:
from django.contrib.auth.decorators import login_required
# 登陸認證過濾裝飾器
@login_required
def index(request):
return render(request, 'index.html',)
def acc_login(request):
"""
登入
:param request:
:return:
"""
if request.method == 'POST':
# 登陸成功
print('11')
username = request.POST.get('username')
password = request.POST.get('password')
# 使用django自帶的使用者認證
user = authenticate(username=username, password=password)
if user:
# 将使用者封裝到request中的session中響應給浏覽器
login(request, user)
# 從url中擷取next中的位址,可以重新載入登陸前的url位址
return redirect(request.GET.get('next') or '/')
# 登陸失敗
return render(request, 'login.html',)
settings配置檔案:
LOGIN_URL = '/login/' # 使用者認證裝飾器預設跳轉的頁面url為 /login/
主機清單開發
html改造:
{% extends
{% block
主機清單
{% endblock
{% block
主機清單
{% endblock
{% block<div class="panel col-lg-3">
<div class="panel-heading">
<h3 class="panel-title">
<trans oldtip="Panel with header" newtip="帶标頭的面闆" style="">主機組</trans>
</h3>
</div>
<div class="panel-body">
<p>
<div class="panel-body">
<!--List Group with Badges-->
<!--===================================================-->
<ul class="list-group">{% for group in<li class="list-group-item" onclick="GetHostlist({{ group.id }}, this)"><span
class="badge badge-primary">{{ group.host_user_binds.count }}</span>{{ group.name }}</li>{% endfor<li class="list-group-item" onclick="GetHostlist(-1, this)"><span
class="badge badge-primary">{{ request.user.account.host_user_binds.count }}</span>未分組主機
</li>
</ul>
</div>
</p>
</div>
</div>
<div class="panel col-lg-9">
<div class="panel-heading">
<h3 class="panel-title">
<trans oldtip="Panel with header" newtip="帶标頭的面闆" style="">主機清單</trans>
</h3>
</div>
<!-- Striped Table -->
<!--===================================================-->
<div class="panel-body">
<div class="table-responsive">
<table class="table table-striped">
<thead>
<tr>
<th>Hostname</th>
<th>IP</th>
<th>IDC</th>
<th>Port</th>
<th>Username</th>
<th>Login</th>
<th>Token</th>
</tr>
</thead>
<tbody id="hostlist">
</tbody>
</table>
</div>
</div>
<!--===================================================-->
<!-- End Striped Table -->
</div>{% endblock
js部分:
<script>function GetHostlist(gid, self)
$.get("{% url 'get_host_list' %}", {'gid': gid}, function (callback)
var data = JSON.parse(callback);
console.log(data)
var trs = ''
$.each(data, function (index, i)
var tr = "<tr><td>" + i.host__hostname + "</td><td>" + i.host__ip_addr + "</td><td>" + i.host__idc__name
+ "</td><td>" + i.host__port + "</td><td>" + i.host_user__username + "</td><td>"
trs += tr
$("#hostlist").html(trs);
})
});
$(self).addClass("active").siblings().removeClass('active');
}
</script>
urls:
r'^api/hostlist/$', views.get_host_list, name='get_host_list'),
views:
@login_required
def get_host_list(request):
gid = request.GET.get('gid')
if gid:
if gid == '-1': # 未分組
host_list = request.user.account.host_user_binds.all()
else:
group_obj = request.user.account.host_groups.get(id = gid)
host_list = group_obj.host_user_binds.all()
import json
data = json.dumps(list(host_list.values('id', 'host__hostname', 'host__ip_addr', 'host__port',
'host_user__username')))
print(data)
return
shellinabox使用
Shellinabox 是一個利用 Ajax 技術建構的基于 Web 的遠端Terminal 模拟器,也就是說安裝了該軟體之後,不需要開啟 ssh服務,通過 Web 網頁就可以對遠端主機進行維護操作了,出于安全考慮, Shellinabox 預設強制使用了https協定,這是個挺有趣的技術,因而就在rhel6上面折騰了下,下面記錄了主要的操作步驟:
安裝
下載下傳位址:https://github.com/shellinabox/shellinabox
安裝準備:(提前裝好C環境)yum -y install gcc
netstate 工具安裝: yum -y install net-tools
一:編譯安裝Shellinabox
[root@rhel6 ~]# cd /usr/local/src/tarbag/
[root@rhel6 tarbag]# wgethttp://shellinabox.googlecode.com/files/shellinabox-2.10.tar.gz
[root@rhel6 tarbag]# tar -zxvf shellinabox-2.10.tar.gz -C ../software/
[root@rhel6 tarbag]# cd ../software/shellinabox-2.10/
[root@rhel6 shellinabox-2.10]# ./configure --prefix=/usr/local/shellinabox
[root@rhel6 shellinabox-2.10]# make && make install
二:試啟動Shellinabox,可以加上--help參數檢視啟動選項,這裡可以看到預設Shellinabox采用https方式啟動
[root@rhel6 ~]# /usr/local/shellinabox/bin/shellinaboxd
Cannot read valid certificate from "certificate.pem". Check file permissions and file format.
[root@rhel6 ~]# /usr/local/shellinabox/bin/shellinaboxd -b -t //-b選項代表在背景啟動,-t選項表示不使用https方式啟動,預設以nobody使用者身份,監聽TCP4200端口
[root@rhel6 ~]# netstat -ntpl |grep shell # 檢視端口号
tcp 0 0 0.0.0.0:4200 0.0.0.0:* LISTEN 16823/shellinaboxd
使用:
1、關閉防火牆:
systemctl stop firewalled.service
systemctl disable firewalled.service
2、浏覽器通路:ip+端口号
背景生成token在前端頁面
html
<script>function GetToken(self, bind_host_id) # 生成token的函數
$.post("{% url 'get_token' %}", {'bind_host_id':bind_host_id, 'csrfmiddlewaretoken':'{{csrf_token}}'}, # 通過ajax使用反向url生成的形式發送post請求
function (callback)
console.log(callback)
var data = JSON.parse(callback)
$(self).parent().next().text(data.token)
})
}
function GetHostlist(gid, self)
$.get("{% url 'get_host_list' %}", {'gid': gid}, function (callback) # 通過ajax使用反向url生成的形式發送get請求
var data = JSON.parse(callback);
console.log(data)
var trs = ''
var tr= ''
$.each(data, function (index, i)
tr = "<tr><td>" + i.host__hostname + "</td><td>" + i.host__ip_addr + "</td><td>" + i.host__idc__name
+ "</td><td>" + i.host__port + "</td><td>" + i.host_user__username +"</td><td><a class='btn btn-info' onclick=GetToken(this,'"+i.id+"')>Token</a>Login</td><td><td></tr>" # 調用GetToken函數來生成token
trs += tr
})
$("#hostlist").html(trs);
});
$(self).addClass("active").siblings().removeClass('active');
}
</script>
url:
r'^api/token/$', views.get_token, name='get_token'),
views:
@login_required
def get_token(request):
"""生成token并傳回"""
from django.utils import timezone
bind_host_id = request.POST.get('bind_host_id')
# 這裡需要使用timezone.now來擷取本地目前時間,而非datatime().datatime(),因為關系到需要自适應時區的問題,否則報錯
time_obj = timezone.now() - datetime.timedelta(seconds=300) # 獲得5分鐘前的時間
exist_token_objs = models.Token.objects.filter(account_id=request.user.account.id,
host_user_bind_id=bind_host_id,
date__gt=time_obj )
if exist_token_objs: # has token already
token_data ={'token':exist_token_objs[0].val}
else:
token_val = ''.join(random.sample(string.ascii_lowercase+string.digits,8))
token_obj = models.Token.objects.create(
host_user_bind_id = bind_host_id,
account = request.user.account,
val = token_val
)
token_data = {'token':token_val}
return
class Token(models.Model):
host_user_bind = models.ForeignKey("HostUserBind", on_delete='')
val = models.CharField(max_length=128,unique=True)
account = models.ForeignKey("Account", on_delete='')
expire = models.IntegerField("逾時時間(s)",default=300)
date = models.DateTimeField(auto_now_add=True)
def __str__(self):
return "%s-%s"
批量指令頁面開發(全選功能+數量統計)
{% extends
{% block
主機清單
{% endblock
{% block
主機清單
{% endblock
{% block
{% csrf_token<div id="page-content">
<div class="panel col-lg-3">
<div class="panel-heading">
<h3 class="panel-title">
<trans oldtip="Panel with header" newtip="帶标頭的面闆" style="">
主機組 <label id="selected_hosts"></label>
</trans>
</h3>
</div>
<div class="panel-body">
<p>
<div class="panel-body">
<ul class="list-group " id="host_groups">{% for group in<li class="list-group-item ">
<span class="badge badge-primary">{{ group.host_user_binds.count }}</span>
<input type="checkbox" onclick="checkAll(this)">
# 循環主機名
<a onclick="DisplayHostList(this)">{{ group.name }}</a>
<ul class="hide">{% for bind_host in<input type="checkbox" value="{{ bind_host.id }}"
onclick="showCheckHostCount()">
<li>{{ bind_host.host.ip_addr }}</li>{% endfor</ul>
</li>{% endfor<li class="list-group-item" onclick="DisplayHostList(this)">
<input type="checkbox" onclick="checkAll(this)">
<span class="badge badge-primary">{{ request.user.account.host_user_binds.count }}</span>未分組主機
<ul class="hide">{% for bind_host in<li>{{ bind_host.host.ip_addr }}</li>{% endfor</ul>
</li>
</ul>
</div>
</p>
</div>
</div>
<div class="panel col-lg-9">
<div class="panel-heading">
<h3 class="panel-title">
<trans oldtip="Panel with header" newtip="帶标頭的面闆" style="">指令</trans>
</h3>
</div>
<div class="panel-body">
ddd
</div>
</div>
<div class="panel col-lg-9">
<div class="panel-heading">
<h3 class="panel-title">
<trans oldtip="Panel with header" newtip="帶标頭的面闆" style="">指令</trans>
</h3>
</div>
<div class="panel-body">
ddd
</div>
</div>
</div>
<script>function DisplayHostList(self)
$(self).next().toggleClass('hide')
}
function checkAll(self){#全選功能 複選框#}'ul :checkbox').prop('checked', $(self).prop('checked'))
showCheckHostCount()
}
{#統計選中的數量#}function showCheckHostCount()
var selected_host_count = $('#host_groups ul').find(':checked').length;
$('#selected_hosts').text(selected_host_count);
return</script>{% endblock