Commit 45df5811 authored by ibuler's avatar ibuler

Merge branch 'connect'

parents 6856fad0 a04d7725
......@@ -16,3 +16,4 @@ db.sqlite3
config.py
migrations/
*.log
host_rsa_key
......@@ -30,11 +30,17 @@ def get_object_or_none(model, **kwargs):
def encrypt(*args, **kwargs):
return signing.dumps(*args, **kwargs)
try:
return signing.dumps(*args, **kwargs)
except signing.BadSignature:
return ''
def decrypt(*args, **kwargs):
return signing.loads(*args, **kwargs)
try:
return signing.loads(*args, **kwargs)
except signing.BadSignature:
return ''
def date_expired_default():
......
......@@ -54,7 +54,7 @@ INSTALLED_APPS = [
'users.apps.UsersConfig',
'assets.apps.AssetsConfig',
'perms.apps.PermsConfig',
'webterminal.apps.WebterminalConfig',
# 'terminal.apps.TerminalConfig',
'ops.apps.OpsConfig',
'audits.apps.AuditsConfig',
'common.apps.CommonConfig',
......@@ -274,36 +274,36 @@ REST_FRAMEWORK = {
}
# This setting is required to override the Django's main loop, when running in
# development mode, such as ./manage runserver
WSGI_APPLICATION = 'ws4redis.django_runserver.application'
# WSGI_APPLICATION = 'ws4redis.django_runserver.application'
# URL that distinguishes websocket connections from normal requests
WEBSOCKET_URL = '/ws/'
# WEBSOCKET_URL = '/ws/'
# WebSocket Redis
WS4REDIS_CONNECTION = {
'host': CONFIG.REDIS_HOST or '127.0.0.1',
'port': CONFIG.REDIS_PORT or 6379,
'db': 2,
}
# WS4REDIS_CONNECTION = {
# 'host': CONFIG.REDIS_HOST or '127.0.0.1',
# 'port': CONFIG.REDIS_PORT or 6379,
# 'db': 2,
# }
# Set the number of seconds each message shall persisted
WS4REDIS_EXPIRE = 3600
# WS4REDIS_EXPIRE = 3600
WS4REDIS_HEARTBEAT = 'love you'
# WS4REDIS_HEARTBEAT = 'love you'
WS4REDIS_PREFIX = 'demo'
# WS4REDIS_PREFIX = 'demo'
SESSION_ENGINE = 'redis_sessions.session'
# SESSION_ENGINE = 'redis_sessions.session'
SESSION_REDIS_PREFIX = 'session'
# SESSION_REDIS_PREFIX = 'session'
SESSION_REDIS_HOST = CONFIG.REDIS_HOST
# SESSION_REDIS_HOST = CONFIG.REDIS_HOST
SESSION_REDIS_PORT = CONFIG.REDIS_PORT
# SESSION_REDIS_PORT = CONFIG.REDIS_PORT
SESSION_REDIS_PASSWORD = CONFIG.REDIS_PASSWORD
# SESSION_REDIS_PASSWORD = CONFIG.REDIS_PASSWORD
SESSION_REDIS_DB = CONFIG.REDIS_DB
# SESSION_REDIS_DB = CONFIG.REDIS_DB
# Custom User Auth model
......
......@@ -25,7 +25,6 @@ urlpatterns = [
url(r'^(api/)?users/', include('users.urls')),
url(r'^assets/', include('assets.urls')),
url(r'^perms/', include('perms.urls')),
url(r'^terminal/', include('webterminal.urls')),
]
......
......@@ -112,6 +112,12 @@ class User(AbstractUser):
else:
return True
@property
def is_valid(self):
if self.is_active and not self.is_expired:
return True
return False
@property
def private_key(self):
return decrypt(self._private_key)
......
......@@ -12,7 +12,8 @@ from django.utils.translation import ugettext as _
from paramiko.rsakey import RSAKey
from common.tasks import send_mail_async
from common.utils import reverse
from common.utils import reverse, get_object_or_none
from .models import User
try:
......@@ -147,3 +148,75 @@ def send_reset_ssh_key_mail(user):
logger.debug(message)
send_mail_async.delay(subject, message, recipient_list, html_message=message)
def validate_ssh_pk(text):
"""
Expects a SSH private key as string.
Returns a boolean and a error message.
If the text is parsed as private key successfully,
(True,'') is returned. Otherwise,
(False, <message describing the error>) is returned.
from https://github.com/githubnemo/SSH-private-key-validator/blob/master/validate.py
"""
if not text:
return False, 'No text given'
startPattern = re.compile("^-----BEGIN [A-Z]+ PRIVATE KEY-----")
optionPattern = re.compile("^.+: .+")
contentPattern = re.compile("^([a-zA-Z0-9+/]{64}|[a-zA-Z0-9+/]{1,64}[=]{0,2})$")
endPattern = re.compile("^-----END [A-Z]+ PRIVATE KEY-----")
def contentState(text):
for i in range(0, len(text)):
line = text[i]
if endPattern.match(line):
if i == len(text) - 1 or len(text[i + 1]) == 0:
return True, ''
else:
return False, 'At end but content coming'
elif not contentPattern.match(line):
return False, 'Wrong string in content section'
return False, 'No content or missing end line'
def optionState(text):
for i in range(0, len(text)):
line = text[i]
if line[-1:] == '\\':
return optionState(text[i + 2:])
if not optionPattern.match(line):
return contentState(text[i + 1:])
return False, 'Expected option, found nothing'
def startState(text):
if len(text) == 0 or not startPattern.match(text[0]):
return False, 'Header is wrong'
return optionState(text[1:])
return startState([n.strip() for n in text.splitlines()])
def check_user_is_valid(**kwargs):
password = kwargs.pop('password', None)
public_key = kwargs.pop('public_key', None)
user = get_object_or_none(User, **kwargs)
if password and not user.check_password(password):
user = None
if public_key and not user.public_key == public_key:
user = None
if user and user.is_valid:
return user
return None
from django.contrib import admin
# Register your models here.
from __future__ import unicode_literals
from django.apps import AppConfig
class WebterminalConfig(AppConfig):
name = 'webterminal'
from __future__ import unicode_literals
from django.db import models
# Create your models here.
{% extends 'base.html' %}
{% block content %}
<div class="container">
<div id="term">
</div>
</div>
<div class="termChangBar">
<input type="number" min="100" value="100" placeholder="col" id="term-col"/>
<input type="number" min="35" value="35" placeholder="row" id="term-row"/>
<button id="col-row">修改窗口大小</button>
</div>
{% endblock %}
{% block custom_foot_js %}
<script type="application/javascript" src="/static/js/jquery-2.1.1.js"></script>
<script type="application/javascript" src="/static/js/term.js"></script>
<script>/**
* Created by liuzheng on 3/3/16.
*/
var rowHeight = 1;
var colWidth = 1;
function WSSHClient() {
}
WSSHClient.prototype._generateEndpoint = function (options) {
console.log(options);
if (window.location.protocol == 'https:') {
var protocol = 'wss://';
} else {
var protocol = 'ws://';
}
var endpoint = protocol + document.URL.match(RegExp('//(.*?)/'))[1] + '/ws/foobar?subscribe-broadcast&publish-broadcast&echo';
return endpoint;
};
WSSHClient.prototype.connect = function (options) {
var endpoint = this._generateEndpoint(options);
if (window.WebSocket) {
this._connection = new WebSocket(endpoint);
}
else if (window.MozWebSocket) {
this._connection = MozWebSocket(endpoint);
}
else {
options.onError('WebSocket Not Supported');
return;
}
this._connection.onopen = function () {
options.onConnect();
};
this._connection.onmessage = function (evt) {
try {
options.onData(evt.data);
} catch (e) {
var data = JSON.parse(evt.data.toString());
options.onError(data.error);
}
};
this._connection.onclose = function (evt) {
options.onClose();
};
};
WSSHClient.prototype.send = function (data) {
this._connection.send(JSON.stringify({'data': data}));
};
function openTerminal(options) {
var client = new WSSHClient();
var rowHeight, colWidth;
try {
rowHeight = localStorage.getItem('term-row');
colWidth = localStorage.getItem('term-col');
} catch (err) {
rowHeight = 35;
colWidth = 100
}
if (rowHeight) {
} else {
rowHeight = 35
}
if (colWidth) {
} else {
colWidth = 100
}
var term = new Terminal({
rows: rowHeight,
cols: colWidth,
useStyle: true,
screenKeys: true
});
term.open();
term.on('data', function (data) {
client.send(data)
});
$('.terminal').detach().appendTo('#term');
//term.resize(colWidth, rowHeight);
term.write('Connecting...');
client.connect($.extend(options, {
onError: function (error) {
term.write('Error: ' + error + '\r\n');
},
onConnect: function () {
// Erase our connecting message
client.send({'resize': {'rows': rowHeight, 'cols': colWidth}});
term.write('\r');
},
onClose: function () {
term.write('Connection Reset By Peer');
},
onData: function (data) {
if (data == "love you")
console.log(data);
else
term.write(data);
}
}));
//rowHeight = 0.0 + 1.00 * $('.terminal').height() / 24;
//colWidth = 0.0 + 1.00 * $('.terminal').width() / 80;
return {'term': term, 'client': client};
}
//function resize() {
// $('.terminal').css('width', window.innerWidth - 25);
// console.log(window.innerWidth);
// console.log(window.innerWidth - 10);
// var rows = Math.floor(window.innerHeight / rowHeight) - 2;
// var cols = Math.floor(window.innerWidth / colWidth) - 1;
//
// return {rows: rows, cols: cols};
//}
$(document).ready(function () {
var options = {};
$('#ssh').show();
var term_client = openTerminal(options);
console.log(rowHeight);
// by liuzheng712 because it will bring record bug
//window.onresize = function () {
// var geom = resize();
// console.log(geom);
// term_client.term.resize(geom.cols, geom.rows);
// term_client.client.send({'resize': {'rows': geom.rows, 'cols': geom.cols}});
// $('#ssh').show();
//}
try {
$('#term-row')[0].value = localStorage.getItem('term-row');
$('#term-col')[0].value = localStorage.getItem('term-col');
} catch (err) {
$('#term-row')[0].value = 35;
$('#term-col')[0].value = 100;
}
$('#col-row').click(function () {
var col = $('#term-col').val();
var row = $('#term-row').val();
localStorage.setItem('term-col', col);
localStorage.setItem('term-row', row);
term_client.term.resize(col, row);
term_client.client.send({'resize': {'rows': row, 'cols': col}});
$('#ssh').show();
});
$(".terminal").mouseleave(function () {
$(".termChangBar").slideDown();
});
$(".terminal").mouseenter(function () {
$(".termChangBar").slideUp();
})
});</script>
{% endblock %}
from django.test import TestCase
# Create your tests here.
# coding:utf-8
from django.conf.urls import url
from .views import *
from django.contrib import admin
admin.autodiscover()
app_name = 'webterminal'
urlpatterns = [
url(r'^$', TerminalView.as_view(), name='webterminal'),
]
\ No newline at end of file
from django.shortcuts import render
from django.urls import reverse_lazy
from django.db.models import Q
from django.views.generic.list import ListView
from django.views.generic.edit import CreateView, DeleteView, UpdateView
from django.views.generic.detail import DetailView
from django.views.generic.base import TemplateView
from django.views import View
from django.http import HttpResponse
from ws4redis.redis_store import RedisMessage
from ws4redis.publisher import RedisPublisher
from django.conf import settings
# Create your views here.
class TerminalView(TemplateView):
template_name = 'main.html'
def get(self, request, *args, **kwargs):
welcome = RedisMessage('Hello everybody') # create a welcome message to be sent to everybody
RedisPublisher(facility='foobar', broadcast=True).publish_message(welcome)
return super(TerminalView, self).get(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
redis_publisher = RedisPublisher(facility='foobar', groups=[request.POST.get('group')])
message = RedisMessage(request.POST.get('message'))
redis_publisher.publish_message(message)
return HttpResponse('OK')
......@@ -70,6 +70,10 @@ class Config:
# EMAIL_USE_TLS = False # If port is 587, set True
# EMAIL_SUBJECT_PREFIX = '[Jumpserver] '
# SSH use password or public key for auth
SSH_PASSWORD_AUTH = False
SSH_PUBLIC_KEY_AUTH = True
def __init__(self):
pass
......
......@@ -21,3 +21,6 @@ django-simple-captcha==0.5.2
django-formtools==1.0
sshpubkeys==2.2.0
djangorestframework-bulk==0.2.1
python-gssapi==0.6.4
tornado==4.4.2
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
if __name__ == '__main__':
pass
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
import logging
import os
BASE_DIR = os.path.dirname(os.path.abspath(__name__))
class Config:
SSH_HOST = ''
SSH_PORT = 2200
LOG_LEVEL = 'INFO'
LOG_DIR = os.path.join(BASE_DIR, 'logs')
LOG_FILENAME = 'ssh_server.log'
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'verbose': {
'format': '%(levelname)s %(asctime)s %(module)s %(process)d %(thread)d %(message)s'
},
'main': {
'datefmt': '%Y-%m-%d %H:%M:%S',
'format': '%(asctime)s [%(module)s %(levelname)s] %(message)s',
},
'simple': {
'format': '%(levelname)s %(message)s'
},
},
'handlers': {
'null': {
'level': 'DEBUG',
'class': 'logging.NullHandler',
},
'console': {
'level': 'DEBUG',
'class': 'logging.StreamHandler',
'formatter': 'main',
'stream': 'ext://sys.stdout',
},
'file': {
'level': 'DEBUG',
'class': 'logging.handlers.TimedRotatingFileHandler',
'formatter': 'main',
'filename': os.path.join(LOG_DIR, LOG_FILENAME),
'when': 'D',
'backupCount': 10,
},
},
'loggers': {
'jumpserver': {
'handlers': ['console', 'file'],
# 'level': LOG_LEVEL_CHOICES.get(LOG_LEVEL, None) or LOG_LEVEL_CHOICES.get('info')
'level': LOG_LEVEL,
'propagate': True,
},
'jumpserver.web_ssh_server': {
'handlers': ['console', 'file'],
# 'level': LOG_LEVEL_CHOICES.get(LOG_LEVEL, None) or LOG_LEVEL_CHOICES.get('info')
'level': LOG_LEVEL,
'propagate': True,
},
'jumpserver.ssh_server': {
'handlers': ['console', 'file'],
# 'level': LOG_LEVEL_CHOICES.get(LOG_LEVEL, None) or LOG_LEVEL_CHOICES.get('info')
'level': LOG_LEVEL,
'propagate': True,
}
}
}
def __init__(self):
pass
def __getattr__(self, item):
return None
class DevelopmentConfig(Config):
pass
class ProductionConfig(Config):
pass
class TestingConfig(Config):
pass
config = {
'development': DevelopmentConfig,
'production': ProductionConfig,
'testing': TestingConfig,
'default': DevelopmentConfig,
}
env = 'default'
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
import logging
import os
BASE_DIR = os.path.dirname(os.path.abspath(__name__))
class Config:
LOG_LEVEL = 'INFO'
LOG_DIR = os.path.join(BASE_DIR, 'logs')
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'verbose': {
'format': '%(levelname)s %(asctime)s %(module)s %(process)d %(thread)d %(message)s'
},
'main': {
'datefmt': '%Y-%m-%d %H:%M:%S',
'format': '%(asctime)s [%(module)s %(levelname)s] %(message)s',
},
'simple': {
'format': '%(levelname)s %(message)s'
},
},
'handlers': {
'null': {
'level': 'DEBUG',
'class': 'logging.NullHandler',
},
'console': {
'level': 'DEBUG',
'class': 'logging.StreamHandler',
'formatter': 'main'
},
'file': {
'level': 'DEBUG',
'class': 'logging.FileHandler',
'formatter': 'main',
'filename': LOG_DIR,
},
},
'loggers': {
'jumpserver': {
'handlers': ['console', 'file'],
# 'level': LOG_LEVEL_CHOICES.get(LOG_LEVEL, None) or LOG_LEVEL_CHOICES.get('info')
'level': LOG_LEVEL,
},
'jumpserver.web_ssh_server': {
'handlers': ['console', 'file'],
# 'level': LOG_LEVEL_CHOICES.get(LOG_LEVEL, None) or LOG_LEVEL_CHOICES.get('info')
'level': LOG_LEVEL,
},
'jumpserver.ssh_server': {
'handlers': ['console', 'file'],
# 'level': LOG_LEVEL_CHOICES.get(LOG_LEVEL, None) or LOG_LEVEL_CHOICES.get('info')
'level': LOG_LEVEL,
}
}
}
def __init__(self):
pass
def __getattr__(self, item):
return None
class DevelopmentConfig(Config):
pass
class ProductionConfig(Config):
pass
class TestingConfig(Config):
pass
config = {
'development': DevelopmentConfig,
'production': ProductionConfig,
'testing': TestingConfig,
'default': DevelopmentConfig,
}
env = 'default'
if __name__ == '__main__':
pass
This diff is collapsed.
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
import logging
from logging.config import dictConfig
from ssh_config import config, env
CONFIG_SSH_SERVER = config.get(env)
def get_logger(name):
dictConfig(CONFIG_SSH_SERVER.LOGGING)
return logging.getLogger('jumpserver.%s' % name)
class ControlChar:
CHARS = {
'clear': '\x1b[H\x1b[2J',
}
def __init__(self):
pass
def __getattr__(self, item):
return self.__class__.CHARS.get(item, '')
class SSHServerException(Exception):
pass
control_char = ControlChar()
# -*- coding: utf-8 -*-
#
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment