Unverified Commit 69f640da authored by 老广's avatar 老广 Committed by GitHub

Api (#2439)

* [Update] 迁移settings到独立app

* [Update] 修改settings migrations

* [Update] 修改docs说明
parent a43314f5
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
import os import os
import json
import jms_storage
import uuid import uuid
from rest_framework.views import Response, APIView from rest_framework.views import Response
from rest_framework import generics from rest_framework import generics, serializers
from ldap3 import Server, Connection
from django.core.mail import send_mail
from django.core.cache import cache from django.core.cache import cache
from django.utils.translation import ugettext_lazy as _
from django.conf import settings
from .permissions import IsOrgAdmin, IsSuperUser
from .serializers import (
MailTestSerializer, LDAPTestSerializer, OutputSerializer
)
from .models import Setting
class MailTestingAPI(APIView):
permission_classes = (IsOrgAdmin,)
serializer_class = MailTestSerializer
success_message = _("Test mail sent to {}, please check")
def post(self, request):
serializer = self.serializer_class(data=request.data)
if serializer.is_valid():
email_host_user = serializer.validated_data["EMAIL_HOST_USER"]
for k, v in serializer.validated_data.items():
if k.startswith('EMAIL'):
setattr(settings, k, v)
try:
subject = "Test"
message = "Test smtp setting"
send_mail(subject, message, email_host_user, [email_host_user])
except Exception as e:
return Response({"error": str(e)}, status=401)
return Response({"msg": self.success_message.format(email_host_user)})
else:
return Response({"error": str(serializer.errors)}, status=401)
class LDAPTestingAPI(APIView):
permission_classes = (IsOrgAdmin,)
serializer_class = LDAPTestSerializer
success_message = _("Test ldap success")
def post(self, request):
serializer = self.serializer_class(data=request.data)
if serializer.is_valid():
host = serializer.validated_data["AUTH_LDAP_SERVER_URI"]
bind_dn = serializer.validated_data["AUTH_LDAP_BIND_DN"]
password = serializer.validated_data["AUTH_LDAP_BIND_PASSWORD"]
use_ssl = serializer.validated_data.get("AUTH_LDAP_START_TLS", False)
search_ougroup = serializer.validated_data["AUTH_LDAP_SEARCH_OU"]
search_filter = serializer.validated_data["AUTH_LDAP_SEARCH_FILTER"]
attr_map = serializer.validated_data["AUTH_LDAP_USER_ATTR_MAP"]
try:
attr_map = json.loads(attr_map)
except json.JSONDecodeError:
return Response({"error": "AUTH_LDAP_USER_ATTR_MAP not valid"}, status=401)
server = Server(host, use_ssl=use_ssl)
conn = Connection(server, bind_dn, password)
try:
conn.bind()
except Exception as e:
return Response({"error": str(e)}, status=401)
users = []
for search_ou in str(search_ougroup).split("|"):
ok = conn.search(search_ou, search_filter % ({"user": "*"}),
attributes=list(attr_map.values()))
if not ok:
return Response({"error": _("Search no entry matched in ou {}").format(search_ou)}, status=401)
for entry in conn.entries:
user = {}
for attr, mapping in attr_map.items():
if hasattr(entry, mapping):
user[attr] = getattr(entry, mapping)
users.append(user)
if len(users) > 0:
return Response({"msg": _("Match {} s users").format(len(users))})
else:
return Response({"error": "Have user but attr mapping error"}, status=401)
else:
return Response({"error": str(serializer.errors)}, status=401)
class ReplayStorageCreateAPI(APIView):
permission_classes = (IsSuperUser,)
def post(self, request):
storage_data = request.data
if storage_data.get('TYPE') == 'ceph':
port = storage_data.get('PORT')
if port.isdigit():
storage_data['PORT'] = int(storage_data.get('PORT'))
storage_name = storage_data.pop('NAME')
data = {storage_name: storage_data}
if not self.is_valid(storage_data):
return Response({
"error": _("Error: Account invalid (Please make sure the "
"information such as Access key or Secret key is correct)")},
status=401
)
Setting.save_storage('TERMINAL_REPLAY_STORAGE', data)
return Response({"msg": _('Create succeed')}, status=200)
@staticmethod class OutputSerializer(serializers.Serializer):
def is_valid(storage_data): output = serializers.CharField()
if storage_data.get('TYPE') == 'server': is_end = serializers.BooleanField()
return True mark = serializers.CharField()
storage = jms_storage.get_object_storage(storage_data)
target = 'tests.py'
src = os.path.join(settings.BASE_DIR, 'common', target)
return storage.is_valid(src, target)
class ReplayStorageDeleteAPI(APIView):
permission_classes = (IsSuperUser,)
def post(self, request):
storage_name = str(request.data.get('name'))
Setting.delete_storage('TERMINAL_REPLAY_STORAGE', storage_name)
return Response({"msg": _('Delete succeed')}, status=200)
class CommandStorageCreateAPI(APIView):
permission_classes = (IsSuperUser,)
def post(self, request):
storage_data = request.data
storage_name = storage_data.pop('NAME')
data = {storage_name: storage_data}
if not self.is_valid(storage_data):
return Response(
{"error": _("Error: Account invalid (Please make sure the "
"information such as Access key or Secret key is correct)")},
status=401
)
Setting.save_storage('TERMINAL_COMMAND_STORAGE', data)
return Response({"msg": _('Create succeed')}, status=200)
@staticmethod
def is_valid(storage_data):
if storage_data.get('TYPE') == 'server':
return True
try:
storage = jms_storage.get_log_storage(storage_data)
except Exception:
return False
return storage.ping()
class CommandStorageDeleteAPI(APIView):
permission_classes = (IsSuperUser,)
def post(self, request):
storage_name = str(request.data.get('name'))
Setting.delete_storage('TERMINAL_COMMAND_STORAGE', storage_name)
return Response({"msg": _('Delete succeed')}, status=200)
class DjangoSettingsAPI(APIView):
def get(self, request):
if not settings.DEBUG:
return Response("Not in debug mode")
data = {}
for i in [settings, getattr(settings, '_wrapped')]:
if not i:
continue
for k, v in i.__dict__.items():
if k and k.isupper():
try:
json.dumps(v)
data[k] = v
except (json.JSONDecodeError, TypeError):
data[k] = str(v)
return Response(data)
class LogTailApi(generics.RetrieveAPIView): class LogTailApi(generics.RetrieveAPIView):
......
from __future__ import unicode_literals from __future__ import unicode_literals
import sys
from django.apps import AppConfig from django.apps import AppConfig
from django.dispatch import receiver
from django.db.backends.signals import connection_created
@receiver(connection_created, dispatch_uid="my_unique_identifier")
def on_db_connection_ready(sender, **kwargs):
from .signals import django_ready
if 'migrate' not in sys.argv:
django_ready.send(CommonConfig)
class CommonConfig(AppConfig): class CommonConfig(AppConfig):
name = 'common' name = 'common'
def ready(self):
from . import signals_handler
from .signals import django_ready
django_ready.send(self.__class__)
return super().ready()
# -*- coding: utf-8 -*-
# Generated by Django 1.11 on 2018-01-11 06:07
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('common', '0001_initial'),
]
operations = [
migrations.RenameModel(
old_name='Settings',
new_name='Setting',
),
migrations.AlterModelManagers(
name='setting',
managers=[
],
),
migrations.AlterModelTable(
name='setting',
table='settings',
),
]
# -*- coding: utf-8 -*-
# Generated by Django 1.11 on 2018-01-22 03:54
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('common', '0002_auto_20180111_1407'),
]
operations = [
migrations.AddField(
model_name='setting',
name='category',
field=models.CharField(default='default', max_length=128),
),
]
# Generated by Django 2.1 on 2018-09-03 03:32
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('common', '0003_setting_category'),
]
operations = [
migrations.AddField(
model_name='setting',
name='encrypted',
field=models.BooleanField(default=False),
),
]
# Generated by Django 2.1.7 on 2019-02-21 11:02
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('common', '0004_setting_encrypted'),
]
operations = [
migrations.AlterModelOptions(
name='setting',
options={'verbose_name': 'Setting'},
),
]
...@@ -2,7 +2,6 @@ from django.core.mail import send_mail ...@@ -2,7 +2,6 @@ from django.core.mail import send_mail
from django.conf import settings from django.conf import settings
from celery import shared_task from celery import shared_task
from .utils import get_logger from .utils import get_logger
from .models import Setting
logger = get_logger(__file__) logger = get_logger(__file__)
......
...@@ -59,6 +59,7 @@ INSTALLED_APPS = [ ...@@ -59,6 +59,7 @@ INSTALLED_APPS = [
'assets.apps.AssetsConfig', 'assets.apps.AssetsConfig',
'perms.apps.PermsConfig', 'perms.apps.PermsConfig',
'ops.apps.OpsConfig', 'ops.apps.OpsConfig',
'settings.apps.SettingsConfig',
'common.apps.CommonConfig', 'common.apps.CommonConfig',
'terminal.apps.TerminalConfig', 'terminal.apps.TerminalConfig',
'audits.apps.AuditsConfig', 'audits.apps.AuditsConfig',
......
...@@ -20,7 +20,7 @@ api_v1_patterns = [ ...@@ -20,7 +20,7 @@ api_v1_patterns = [
path('ops/v1/', include('ops.urls.api_urls', namespace='api-ops')), path('ops/v1/', include('ops.urls.api_urls', namespace='api-ops')),
path('audits/v1/', include('audits.urls.api_urls', namespace='api-audits')), path('audits/v1/', include('audits.urls.api_urls', namespace='api-audits')),
path('orgs/v1/', include('orgs.urls.api_urls', namespace='api-orgs')), path('orgs/v1/', include('orgs.urls.api_urls', namespace='api-orgs')),
path('common/v1/', include('common.urls.api_urls', namespace='api-common')), path('settings/v1/', include('settings.urls.api_urls', namespace='api-settings')),
])) ]))
] ]
...@@ -56,8 +56,7 @@ urlpatterns = [ ...@@ -56,8 +56,7 @@ urlpatterns = [
path('', include(api_v1_patterns)), path('', include(api_v1_patterns)),
path('luna/', LunaView.as_view(), name='luna-error'), path('luna/', LunaView.as_view(), name='luna-error'),
path('i18n/<str:lang>/', I18NView.as_view(), name='i18n-switch'), path('i18n/<str:lang>/', I18NView.as_view(), name='i18n-switch'),
path('settings/', include('common.urls.view_urls', namespace='settings')), path('settings/', include('settings.urls.view_urls', namespace='settings')),
path('common/', include('common.urls.view_urls', namespace='common')),
# path('api/v2/', include(api_v2_patterns)), # path('api/v2/', include(api_v2_patterns)),
# External apps url # External apps url
......
from django.contrib import admin
# Register your models here.
# -*- coding: utf-8 -*-
#
import os
import json
import jms_storage
from rest_framework.views import Response, APIView
from ldap3 import Server, Connection
from django.core.mail import send_mail
from django.utils.translation import ugettext_lazy as _
from django.conf import settings
from common.permissions import IsOrgAdmin, IsSuperUser
from .serializers import (
MailTestSerializer, LDAPTestSerializer
)
from .models import Setting
class MailTestingAPI(APIView):
permission_classes = (IsOrgAdmin,)
serializer_class = MailTestSerializer
success_message = _("Test mail sent to {}, please check")
def post(self, request):
serializer = self.serializer_class(data=request.data)
if serializer.is_valid():
email_host_user = serializer.validated_data["EMAIL_HOST_USER"]
for k, v in serializer.validated_data.items():
if k.startswith('EMAIL'):
setattr(settings, k, v)
try:
subject = "Test"
message = "Test smtp setting"
send_mail(subject, message, email_host_user, [email_host_user])
except Exception as e:
return Response({"error": str(e)}, status=401)
return Response({"msg": self.success_message.format(email_host_user)})
else:
return Response({"error": str(serializer.errors)}, status=401)
class LDAPTestingAPI(APIView):
permission_classes = (IsOrgAdmin,)
serializer_class = LDAPTestSerializer
success_message = _("Test ldap success")
def post(self, request):
serializer = self.serializer_class(data=request.data)
if serializer.is_valid():
host = serializer.validated_data["AUTH_LDAP_SERVER_URI"]
bind_dn = serializer.validated_data["AUTH_LDAP_BIND_DN"]
password = serializer.validated_data["AUTH_LDAP_BIND_PASSWORD"]
use_ssl = serializer.validated_data.get("AUTH_LDAP_START_TLS", False)
search_ougroup = serializer.validated_data["AUTH_LDAP_SEARCH_OU"]
search_filter = serializer.validated_data["AUTH_LDAP_SEARCH_FILTER"]
attr_map = serializer.validated_data["AUTH_LDAP_USER_ATTR_MAP"]
try:
attr_map = json.loads(attr_map)
except json.JSONDecodeError:
return Response({"error": "AUTH_LDAP_USER_ATTR_MAP not valid"}, status=401)
server = Server(host, use_ssl=use_ssl)
conn = Connection(server, bind_dn, password)
try:
conn.bind()
except Exception as e:
return Response({"error": str(e)}, status=401)
users = []
for search_ou in str(search_ougroup).split("|"):
ok = conn.search(search_ou, search_filter % ({"user": "*"}),
attributes=list(attr_map.values()))
if not ok:
return Response({"error": _("Search no entry matched in ou {}").format(search_ou)}, status=401)
for entry in conn.entries:
user = {}
for attr, mapping in attr_map.items():
if hasattr(entry, mapping):
user[attr] = getattr(entry, mapping)
users.append(user)
if len(users) > 0:
return Response({"msg": _("Match {} s users").format(len(users))})
else:
return Response({"error": "Have user but attr mapping error"}, status=401)
else:
return Response({"error": str(serializer.errors)}, status=401)
class ReplayStorageCreateAPI(APIView):
permission_classes = (IsSuperUser,)
def post(self, request):
storage_data = request.data
if storage_data.get('TYPE') == 'ceph':
port = storage_data.get('PORT')
if port.isdigit():
storage_data['PORT'] = int(storage_data.get('PORT'))
storage_name = storage_data.pop('NAME')
data = {storage_name: storage_data}
if not self.is_valid(storage_data):
return Response({
"error": _("Error: Account invalid (Please make sure the "
"information such as Access key or Secret key is correct)")},
status=401
)
Setting.save_storage('TERMINAL_REPLAY_STORAGE', data)
return Response({"msg": _('Create succeed')}, status=200)
@staticmethod
def is_valid(storage_data):
if storage_data.get('TYPE') == 'server':
return True
storage = jms_storage.get_object_storage(storage_data)
target = 'tests.py'
src = os.path.join(settings.BASE_DIR, 'common', target)
return storage.is_valid(src, target)
class ReplayStorageDeleteAPI(APIView):
permission_classes = (IsSuperUser,)
def post(self, request):
storage_name = str(request.data.get('name'))
Setting.delete_storage('TERMINAL_REPLAY_STORAGE', storage_name)
return Response({"msg": _('Delete succeed')}, status=200)
class CommandStorageCreateAPI(APIView):
permission_classes = (IsSuperUser,)
def post(self, request):
storage_data = request.data
storage_name = storage_data.pop('NAME')
data = {storage_name: storage_data}
if not self.is_valid(storage_data):
return Response(
{"error": _("Error: Account invalid (Please make sure the "
"information such as Access key or Secret key is correct)")},
status=401
)
Setting.save_storage('TERMINAL_COMMAND_STORAGE', data)
return Response({"msg": _('Create succeed')}, status=200)
@staticmethod
def is_valid(storage_data):
if storage_data.get('TYPE') == 'server':
return True
try:
storage = jms_storage.get_log_storage(storage_data)
except Exception:
return False
return storage.ping()
class CommandStorageDeleteAPI(APIView):
permission_classes = (IsSuperUser,)
def post(self, request):
storage_name = str(request.data.get('name'))
Setting.delete_storage('TERMINAL_COMMAND_STORAGE', storage_name)
return Response({"msg": _('Delete succeed')}, status=200)
class DjangoSettingsAPI(APIView):
def get(self, request):
if not settings.DEBUG:
return Response("Not in debug mode")
data = {}
for i in [settings, getattr(settings, '_wrapped')]:
if not i:
continue
for k, v in i.__dict__.items():
if k and k.isupper():
try:
json.dumps(v)
data[k] = v
except (json.JSONDecodeError, TypeError):
data[k] = str(v)
return Response(data)
from django.apps import AppConfig
class SettingsConfig(AppConfig):
name = 'settings'
def ready(self):
from . import signals_handler
...@@ -6,8 +6,9 @@ from django.utils.translation import ugettext_lazy as _ ...@@ -6,8 +6,9 @@ from django.utils.translation import ugettext_lazy as _
from django.db import transaction from django.db import transaction
from .models import Setting, settings from .models import Setting, settings
from .fields import FormDictField, FormEncryptCharField, \ from common.fields import (
FormEncryptMixin FormDictField, FormEncryptCharField, FormEncryptMixin
)
class BaseForm(forms.Form): class BaseForm(forms.Form):
......
# -*- coding: utf-8 -*- # Generated by Django 2.1.7 on 2019-02-26 03:11
# Generated by Django 1.11 on 2018-01-11 05:35
from __future__ import unicode_literals
from django.db import migrations, models from django.db import migrations, models
import django.db.models.manager
class Migration(migrations.Migration): class Migration(migrations.Migration):
...@@ -15,16 +12,19 @@ class Migration(migrations.Migration): ...@@ -15,16 +12,19 @@ class Migration(migrations.Migration):
operations = [ operations = [
migrations.CreateModel( migrations.CreateModel(
name='Settings', name='Setting',
fields=[ fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=128, unique=True, verbose_name='Name')), ('name', models.CharField(max_length=128, unique=True, verbose_name='Name')),
('value', models.TextField(verbose_name='Value')), ('value', models.TextField(verbose_name='Value')),
('category', models.CharField(default='default', max_length=128)),
('encrypted', models.BooleanField(default=False)),
('enabled', models.BooleanField(default=True, verbose_name='Enabled')), ('enabled', models.BooleanField(default=True, verbose_name='Enabled')),
('comment', models.TextField(verbose_name='Comment')), ('comment', models.TextField(verbose_name='Comment')),
], ],
managers=[ options={
('configs', django.db.models.manager.Manager()), 'verbose_name': 'Setting',
], 'db_table': 'setting',
},
), ),
] ]
# -*- coding: utf-8 -*-
#
from django.db import migrations, connection
class Migration(migrations.Migration):
dependencies = [
("settings", "0001_initial"),
]
sql = "INSERT INTO setting(name, value, category, encrypted, enabled, comment) " \
"SELECT name, value, category, encrypted, enabled, comment from settings"
settings_table_exist = 'settings' in connection.introspection.table_names()
operations = []
if settings_table_exist:
operations.append(migrations.RunSQL(sql))
...@@ -6,7 +6,7 @@ from django.db.utils import ProgrammingError, OperationalError ...@@ -6,7 +6,7 @@ from django.db.utils import ProgrammingError, OperationalError
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.conf import settings from django.conf import settings
from .utils import get_signer from common.utils import get_signer
signer = get_signer() signer = get_signer()
...@@ -122,5 +122,5 @@ class Setting(models.Model): ...@@ -122,5 +122,5 @@ class Setting(models.Model):
settings.AUTHENTICATION_BACKENDS = old_setting settings.AUTHENTICATION_BACKENDS = old_setting
class Meta: class Meta:
db_table = "settings" db_table = "setting"
verbose_name = _("Setting") verbose_name = _("Setting")
...@@ -20,7 +20,4 @@ class LDAPTestSerializer(serializers.Serializer): ...@@ -20,7 +20,4 @@ class LDAPTestSerializer(serializers.Serializer):
AUTH_LDAP_START_TLS = serializers.BooleanField(required=False) AUTH_LDAP_START_TLS = serializers.BooleanField(required=False)
class OutputSerializer(serializers.Serializer):
output = serializers.CharField()
is_end = serializers.BooleanField()
mark = serializers.CharField()
...@@ -3,15 +3,15 @@ ...@@ -3,15 +3,15 @@
import json import json
from django.dispatch import receiver from django.dispatch import receiver
from django.db.models.signals import post_save, pre_save from django.db.models.signals import post_save, pre_save, pre_migrate
from django.conf import LazySettings, empty from django.conf import LazySettings, empty
from django.db.utils import ProgrammingError, OperationalError from django.db.utils import ProgrammingError, OperationalError
from django.core.cache import cache from django.core.cache import cache
from jumpserver.utils import current_request from jumpserver.utils import current_request
from common.utils import get_logger, ssh_key_gen
from common.signals import django_ready
from .models import Setting from .models import Setting
from .utils import get_logger, ssh_key_gen
from .signals import django_ready
logger = get_logger(__file__) logger = get_logger(__file__)
......
...@@ -159,10 +159,10 @@ $(document).ready(function() { ...@@ -159,10 +159,10 @@ $(document).ready(function() {
data[name] = value data[name] = value
} }
}); });
var url = "{% url 'api-common:command-storage-create' %}"; var url = "{% url 'api-settings:command-storage-create' %}";
var success = function(data, textStatus) { var success = function(data, textStatus) {
console.log(data, textStatus); console.log(data, textStatus);
location = "{% url 'common:terminal-setting' %}"; location = "{% url 'settings:terminal-setting' %}";
}; };
var error = function(data, textStatus) { var error = function(data, textStatus) {
var error_msg = data.responseJSON.error; var error_msg = data.responseJSON.error;
......
...@@ -84,7 +84,7 @@ $(document).ready(function () { ...@@ -84,7 +84,7 @@ $(document).ready(function () {
data[field.name] = field.value; data[field.name] = field.value;
}); });
var the_url = "{% url 'api-common:mail-testing' %}"; var the_url = "{% url 'api-settings:mail-testing' %}";
function error(message) { function error(message) {
toastr.error(message) toastr.error(message)
......
...@@ -84,7 +84,7 @@ $(document).ready(function () { ...@@ -84,7 +84,7 @@ $(document).ready(function () {
data[field.name] = field.value; data[field.name] = field.value;
}); });
var the_url = "{% url 'api-common:ldap-testing' %}"; var the_url = "{% url 'api-settings:ldap-testing' %}";
function error(message) { function error(message) {
toastr.error(message) toastr.error(message)
......
...@@ -251,9 +251,9 @@ $(document).ready(function() { ...@@ -251,9 +251,9 @@ $(document).ready(function() {
var name = $(id_field).attr('name'); var name = $(id_field).attr('name');
data[name] = $(id_field).val(); data[name] = $(id_field).val();
}); });
var url = "{% url 'api-common:replay-storage-create' %}"; var url = "{% url 'api-settings:replay-storage-create' %}";
var success = function(data, textStatus) { var success = function(data, textStatus) {
location = "{% url 'common:terminal-setting' %}"; location = "{% url 'settings:terminal-setting' %}";
submitBtn.removeClass('disabled'); submitBtn.removeClass('disabled');
submitBtn.html(origin_text); submitBtn.html(origin_text);
}; };
......
...@@ -92,7 +92,7 @@ ...@@ -92,7 +92,7 @@
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
<a href="{% url 'common:command-storage-create' %}" class="btn btn-primary btn-xs">{% trans 'Add' %}</a> <a href="{% url 'settings:command-storage-create' %}" class="btn btn-primary btn-xs">{% trans 'Add' %}</a>
<div class="hr-line-dashed"></div> <div class="hr-line-dashed"></div>
<h3>{% trans "Replay storage" %}</h3> <h3>{% trans "Replay storage" %}</h3>
...@@ -114,7 +114,7 @@ ...@@ -114,7 +114,7 @@
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
<a href="{% url 'common:replay-storage-create' %}" class="btn btn-primary btn-xs">{% trans 'Add' %}</a> <a href="{% url 'settings:replay-storage-create' %}" class="btn btn-primary btn-xs">{% trans 'Add' %}</a>
<div class="hr-line-dashed"></div> <div class="hr-line-dashed"></div>
</form> </form>
...@@ -174,12 +174,12 @@ $(document).ready(function () { ...@@ -174,12 +174,12 @@ $(document).ready(function () {
}) })
.on('click', '.btn-del-replay', function(){ .on('click', '.btn-del-replay', function(){
var $this = $(this); var $this = $(this);
var the_url = "{% url 'api-common:replay-storage-delete' %}"; var the_url = "{% url 'api-settings:replay-storage-delete' %}";
deleteStorage($this, the_url); deleteStorage($this, the_url);
}) })
.on('click', '.btn-del-command', function() { .on('click', '.btn-del-command', function() {
var $this = $(this); var $this = $(this);
var the_url = "{% url 'api-common:command-storage-delete' %}"; var the_url = "{% url 'api-settings:command-storage-delete' %}";
deleteStorage($this, the_url) deleteStorage($this, the_url)
}); });
......
from django.test import TestCase
# Create your tests here.
...@@ -3,15 +3,15 @@ from django.shortcuts import render, redirect ...@@ -3,15 +3,15 @@ from django.shortcuts import render, redirect
from django.contrib import messages from django.contrib import messages
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from common.permissions import SuperUserRequiredMixin
from common import utils
from .forms import EmailSettingForm, LDAPSettingForm, BasicSettingForm, \ from .forms import EmailSettingForm, LDAPSettingForm, BasicSettingForm, \
TerminalSettingForm, SecuritySettingForm TerminalSettingForm, SecuritySettingForm
from common.permissions import SuperUserRequiredMixin
from . import utils
class BasicSettingView(SuperUserRequiredMixin, TemplateView): class BasicSettingView(SuperUserRequiredMixin, TemplateView):
form_class = BasicSettingForm form_class = BasicSettingForm
template_name = "common/basic_setting.html" template_name = "settings/basic_setting.html"
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = { context = {
...@@ -37,7 +37,7 @@ class BasicSettingView(SuperUserRequiredMixin, TemplateView): ...@@ -37,7 +37,7 @@ class BasicSettingView(SuperUserRequiredMixin, TemplateView):
class EmailSettingView(SuperUserRequiredMixin, TemplateView): class EmailSettingView(SuperUserRequiredMixin, TemplateView):
form_class = EmailSettingForm form_class = EmailSettingForm
template_name = "common/email_setting.html" template_name = "settings/email_setting.html"
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = { context = {
...@@ -63,7 +63,7 @@ class EmailSettingView(SuperUserRequiredMixin, TemplateView): ...@@ -63,7 +63,7 @@ class EmailSettingView(SuperUserRequiredMixin, TemplateView):
class LDAPSettingView(SuperUserRequiredMixin, TemplateView): class LDAPSettingView(SuperUserRequiredMixin, TemplateView):
form_class = LDAPSettingForm form_class = LDAPSettingForm
template_name = "common/ldap_setting.html" template_name = "settings/ldap_setting.html"
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = { context = {
...@@ -89,7 +89,7 @@ class LDAPSettingView(SuperUserRequiredMixin, TemplateView): ...@@ -89,7 +89,7 @@ class LDAPSettingView(SuperUserRequiredMixin, TemplateView):
class TerminalSettingView(SuperUserRequiredMixin, TemplateView): class TerminalSettingView(SuperUserRequiredMixin, TemplateView):
form_class = TerminalSettingForm form_class = TerminalSettingForm
template_name = "common/terminal_setting.html" template_name = "settings/terminal_setting.html"
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
command_storage = utils.get_command_storage_setting() command_storage = utils.get_command_storage_setting()
...@@ -119,7 +119,7 @@ class TerminalSettingView(SuperUserRequiredMixin, TemplateView): ...@@ -119,7 +119,7 @@ class TerminalSettingView(SuperUserRequiredMixin, TemplateView):
class ReplayStorageCreateView(SuperUserRequiredMixin, TemplateView): class ReplayStorageCreateView(SuperUserRequiredMixin, TemplateView):
template_name = 'common/replay_storage_create.html' template_name = 'settings/replay_storage_create.html'
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = { context = {
...@@ -131,7 +131,7 @@ class ReplayStorageCreateView(SuperUserRequiredMixin, TemplateView): ...@@ -131,7 +131,7 @@ class ReplayStorageCreateView(SuperUserRequiredMixin, TemplateView):
class CommandStorageCreateView(SuperUserRequiredMixin, TemplateView): class CommandStorageCreateView(SuperUserRequiredMixin, TemplateView):
template_name = 'common/command_storage_create.html' template_name = 'settings/command_storage_create.html'
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = { context = {
...@@ -144,7 +144,7 @@ class CommandStorageCreateView(SuperUserRequiredMixin, TemplateView): ...@@ -144,7 +144,7 @@ class CommandStorageCreateView(SuperUserRequiredMixin, TemplateView):
class SecuritySettingView(SuperUserRequiredMixin, TemplateView): class SecuritySettingView(SuperUserRequiredMixin, TemplateView):
form_class = SecuritySettingForm form_class = SecuritySettingForm
template_name = "common/security_setting.html" template_name = "settings/security_setting.html"
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = { context = {
......
...@@ -6,7 +6,6 @@ import re ...@@ -6,7 +6,6 @@ import re
import pyotp import pyotp
import base64 import base64
import logging import logging
import uuid
import requests import requests
import ipaddress import ipaddress
...@@ -20,8 +19,6 @@ from datetime import datetime ...@@ -20,8 +19,6 @@ from datetime import datetime
from common.tasks import send_mail_async from common.tasks import send_mail_async
from common.utils import reverse, get_object_or_none from common.utils import reverse, get_object_or_none
from common.forms import SecuritySettingForm
from common.models import Setting
from .models import User, LoginLog from .models import User, LoginLog
......
# Minimal makefile for Sphinx documentation
#
# You can set these variables from the command line.
SPHINXOPTS =
SPHINXBUILD = sphinx-build
SPHINXPROJ = Jumpserver
SOURCEDIR = .
BUILDDIR = _build
# Put it first so that "make" without argument is like "make help".
help:
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
.PHONY: help Makefile
# Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
%: Makefile
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
\ No newline at end of file
资产管理模块
=============
这里介绍资产管理模块功能。
.. toctree::
:maxdepth: 1
asset_list
asset_admin_user
asset_system_user
asset_label
\ No newline at end of file
管理文档
=========
这里介绍管理员功能。
.. toctree::
:maxdepth: 1
admin_instruction
admin_user
admin_asset
admin_permission
admin_work_center
admin_session
admin_system_settings
架构说明
=================
.. image:: _static/img/structure.png
:alt: 组件架构图
组件说明
=================
Jumpserver
`````````````
现指 Jumpserver 管理后台,是核心组件(Core), 使用 Django Class Based View 风格开发,支持 Restful API。
`Github <https://github.com/jumpserver/jumpserver.git>`_
Coco
````````
实现了 SSH Server 和 Web Terminal Server 的组件,提供 SSH 和 WebSocket 接口, 使用 Paramiko 和 Flask 开发。
`Github <https://github.com/jumpserver/coco.git>`__
Luna
````````
现在是 Web Terminal 前端,计划前端页面都由该项目提供,Jumpserver 只提供 API,不再负责后台渲染html等。
`Github <https://github.com/jumpserver/luna.git>`__
Guacamole
```````````
Apache 跳板机项目,Jumpserver 使用其组件实现 RDP 功能,Jumpserver 并没有修改其代码而是添加了额外的插件,支持 Jumpserver 调用。
Jumpserver-Python-SDK
```````````````````````
Jumpserver API Python SDK,Coco 目前使用该 SDK 与 Jumpserver API 交互。
`Github <https://github.com/jumpserver/jumpserver-python-sdk.git>`__
权限管理模块
=============
这里介绍权限管理功能。
.. toctree::
:maxdepth: 1
permission_asset_authorized
会话管理模块
==============
这里介绍会话管理功能。
.. toctree::
:maxdepth: 1
session_history
session_online
session_command
session_web_terminal
session_terminal
\ No newline at end of file
系统设置
=============
这里介绍系统设置的功能。
.. contents:: Topics
.. _view_system_settings:
查看系统设置
`````````````
点击页面左侧“系统设置”按钮,进入系统设置页面,产看基本设置、邮件设置、LDAP 设置和终端设置等内容。
.. _basic_settings:
基本设置
`````````
点击页面上边的"基本设置" TAB ,进入基本设置页面,编辑当前站点 URL、用户想到 URL、Email 主题前缀等信息,点击“提交”按钮,基本设置完成。
.. _email_settings:
邮件设置
`````````
点击页面上边的"邮件设置" TAB ,进入邮件设置页面,编辑 SMTP 主机、SMTP 端口、SMTP 账号、SMTP 密码和使用 SSL 或者 TSL 等信息,点击“测试连接”按钮,测试是否正确设置,点击“提交”按钮,邮件设置完成。
.. _ladp_settings:
LDAP 设置
````````````
点击页面上边的" LDAP 设置" TAB ,进入 LDAP 设置页面,编辑 LDAP 地址、DN、用户 OU、用户过滤器、LDAP 属性映射和是否使用 SSL、是否启用 LDAP 认证等信息,点击“测试连接”按钮,测试是否正确设置,点击“提交”按钮,完成 LDAP 设置。
.. _terminal_settings:
终端设置
````````````
点击页面上边的“终端设置” TAB ,进入终端设置页面,编辑终端信息,点击“提交”按钮,终端设置完成。
\ No newline at end of file
用户管理模块
=============
这里介绍用户管理功能。
.. toctree::
:maxdepth: 1
user
user_group
login_log
\ No newline at end of file
作业中心模块
==============
这里介绍作业中心功能。
.. toctree::
:maxdepth: 1
work_center_list
\ No newline at end of file
REST API规范约定
----------------
这里仅考虑 REST API 的基本情况。参考
`RESTful API 设计指南`_
`Github API 文档`_
协议
~~~~
API 与用户的通信协议,总是使用 HTTPS 协议。
域名
~~~~
这版 API 相对简单, 没有前后端分离, 没有独立 APP, 所以放在主域名下
::
https://example.org/api/
版本
~~~~
将 API 的版本号放入 URL 中,由于一个项目多个 APP 所以 Jumpserver 使用以下风格,将版本号放到 APP 后面
::
https://example.com/api/:app:/:version:/:resource:
https://example.com/api/assets/v1.0/assets [GET, POST]
https://example.com/api/assets/v1.0/assets/1 [GET, PUT, DELETE]
路径
~~~~
路径又称“终点”(endpoint),表示 API 的具体网址。
在 RESTful 架构中,每个网址代表一种资源(Resource),所以网址中不能有动词,只能有名词,而且所用的名词往往与数据库的表格名对应。一般来说,数据库中的表都是同种记录的“集合”(Collection),所以 API 中的名词也应该使用复数。
举例来说 Cmdb 中的 Assets 列表, IDC 列表。
::
https://example.com/api/:app:/:version:/:resource:
https://example.com/api/assets/v1.0/assets [GET, POST]
https://example.com/api/assets/v1.0/assets/1 [GET, PUT, DELETE]
https://example.com/api/assets/v1.0/idcs [GET, POST]
一般性的增删查改(CRUD)API,完全使用 HTTP Method 加上 URL 提供的语义,URL 中的可变部分(比如上面提到的),一般用来传递该API操作的核心实体对象的唯一 ID,如果有更多的参数需要提供,GET 方法请使用 URL Parameter(例如:“?client_id=xxxxx&app_id=xxxxxx”),PUT/POST/DELETE 方法请使用请求体传递参数。
HTTP Method
~~~~~~~~~~~
对于资源的具体操作类型,由 HTTP 动词表示。
常用的HTTP动词有下面五个(括号里是对应的 SQL 命令)。
- GET(SELECT):从服务器取出资源(一项或多项)。
- POST(CREATE):在服务器新建一个资源。
- PUT(UPDATE):在服务器更新资源(客户端提供改变后的完整资源, 幂等
- PATCH(UPDATE):在服务器更新资源(客户端提供改变的属性)。
- DELETE(DELETE):从服务器删除资源。
.. _RESTful API 设计指南: http://www.ruanyifeng.com/blog/2014/05/restful_api.html
.. _Github API 文档: https://developer.github.com/v3/
过滤信息
~~~~~~~~
常见参数约定
::
?keyword=localhost 模糊搜索
?limit=10:指定返回记录的数量
?offset=10:指定返回记录的开始位置。
?page=2&per_page=100:指定第几页,以及每页的记录数。
?sort=name&order=asc:指定返回结果按照哪个属性排序,以及排序顺序。
?asset_id=1:指定筛选条件
状态码
~~~~~~
服务器向用户返回的状态码和提示信息,常见的有以下一些(方括号中是该状态码对应的HTTP动词)。
- 200 OK - [GET]:服务器成功返回用户请求的数据,该操作是幂等的(Idempotent)。
- 201 CREATED - [POST/PUT/PATCH]:用户新建或修改数据成功。
- 202 Accepted - [*]:表示一个请求已经进入后台排队(异步任务)
- 204 NO CONTENT - [DELETE]:用户删除数据成功。
- 400 INVALID REQUEST -
[POST/PUT/PATCH]:用户发出的请求有错误,服务器没有进行新建或修改数据的操作,该操作是幂等的。
- 401 Unauthorized - [*]:表示用户没有权限(令牌、用户名、密码错误)。
- 403 Forbidden - [*]
表示用户得到授权(与401错误相对),但是访问是被禁止的。
- 404 NOT FOUND -
[*]:用户发出的请求针对的是不存在的记录,服务器没有进行操作,该操作是幂等的。
- 406 Not Acceptable -
[GET]:用户请求的格式不可得(比如用户请求JSON格式,但是只有XML格式)。
- 410 Gone -[GET]:用户请求的资源被永久删除,且不会再得到的。
- 422 Unprocesable entity - [POST/PUT/PATCH]
当创建一个对象时,发生一个验证错误。
- 500 INTERNAL SERVER ERROR -
[*]:服务器发生错误,用户将无法判断发出的请求是否成功。
错误处理
~~~~~~~~
如果状态码是4xx,就应该向用户返回出错信息。一般来说,返回的信息中将 error 作为键名,出错信息作为键值即可。
::
{
error: "Invalid API key"
}
返回结果
~~~~~~~~
针对不同操作,服务器向用户返回的结果应该符合以下规范。
::
GET /collection:返回资源对象的列表(数组)
GET /collection/resource:返回单个资源对象
POST /collection:返回新生成的资源对象
PUT /collection/resource:返回完整的资源对象
PATCH /collection/resource:返回完整的资源对象
DELETE /collection/resource:返回一个空文档
Hypermedia API
~~~~~~~~~~~~~~
RESTful
API 最好做到 Hypermedia,即返回结果中提供链接,连向其他 API 方法,使得用户不查文档,也知道下一步应该做什么。
比如,当用户向 api.example.com 的根目录发出请求,会得到这样一个文档。
::
{"link": {
"rel": "collection https://www.example.com/zoos",
"href": "https://api.example.com/zoos",
"title": "List of zoos",
"type": "application/vnd.yourformat+json"
}}
上面代码表示,文档中有一个 Link 属性,用户读取这个属性就知道下一步该调用什么 API 了。
- rel 表示这个 API 与当前网址的关系(Collection 关系,并给出该 Collection 的网址)
- href 表示 API 的路径
- title 表示 API 的标题
- type 表示返回类型
Hypermedia API 的设计被称为 HATEOAS。 Github API 就是这种设计.
其它
~~~~
(1)API 的身份认证应该使用 OAuth 2.0 框架。
(2)服务器返回的数据格式,应该尽量使用 JSON。
\ No newline at end of file
管理用户
==========
这里介绍管理用户的功能。
.. contents:: Topics
.. _view_admin_user_list:
查看管理用户列表
````````````````
点击页面左侧“资产管理“菜单下的“管理用户”按钮,进入管理用户列表页面,查看管理用户的名称、资产数等信息。
.. _create_admin_user:
创建管理用户
````````````
点击页面左上角的“创建管理用户“按钮,进入创建管理用户界面,填写名称、用户名、密码、ssh私钥等信息,点击“提交”按钮,完成管理用户创建。
.. _update_admin_user:
更新管理用户
````````````
点击页面右边动作栏的“更新”按钮,进入更新管理用户页面,编辑管理用户的信息,点击“提交”按钮,完成管理用户更新。
.. _delete_admin_user:
删除管理用户
````````````
点击页面右边动作栏的“删除”按钮,弹出删除确认框,点击"确认"按钮,管理用户删除完成。
\ No newline at end of file
标签管理
============
这里介绍标签管理的功能。
.. contents:: Topics
.. _view_label_list:
查看标签列表
````````````````
点击页面左边“资产管理”菜单下的“标签管理”按钮,进入标签列表页面,产看标签的名称、值、资产数等信息。
.. _create_label:
创建标签
````````````
点击页面左上角“创建标签”按钮,进入创建标签页面,填写标签的名称、值等信息,选择资产,点击“提交”按钮,标签创建完成。
.. _update_label:
更新标签
````````````
点击页面右边动作栏的“更新”按钮,进入更新标签页面,编辑标签信息,点击“提交”按钮,标签更新完成。
.. _delete_label:
删除标签
`````````
点击页面右边动作栏的“删除”按钮,弹出删除确认框,点击“确认”按钮,完成标签删除。
\ No newline at end of file
资产列表
===========
这里介绍资产列表的功能。
.. contents:: Topics
.. _view_asset_list:
查看资产列表
`````````````
点击页面左侧的“资产管理”菜单下的“资产列表”按钮,查看当前所有的资产列表。
.. _create_asset:
创建资产
````````````
点击页面左上角的“创建资产”按钮,进入资产创建页面,填写资产信息,点击“提交”按钮,完成资产创建。
.. _update_asset:
更新资产
````````````
点击页面右边的“更新”按钮,进入编辑资产页面,更新资产信息,点击“提交”按钮,完成资产更新。
.. _delete_asset:
删除资产
`````````
点击页面右边的“删除”按钮,弹出删除确认框,点击“确认”按钮,完成资产删除。
.. _batch_operation:
批量操作
````````````
选中资产,选择页面左下角批量操作选项,点击“提交”按钮,完成批量操作。
\ No newline at end of file
系统用户
===========
这里介绍系统用户功能。
.. contents:: Topics
.. _view_admin_system_user:
查看系统用户
````````````
点击页面左侧“资产管理“菜单下的”系统用户“按钮,进入系统用户列表页面,查看系统用户的名称,资产数和连接数等信息。
.. _create_admin_system_user:
创建系统用户
````````````
点击页面左上角的“创建系统用户“按钮,进入创建系统用户页面,填写系统用户的基本信息、认证信息和其它信息,点击“提交“按钮,完成系统用户创建。
.. _update_admin_system_user:
更新系统用户
`````````````
点击页面动作栏的“更新”按钮,进入更新系统用户页面,编辑系统用户信息,点击“提交”按钮,系统用户更新完成。
.. _delete_admin_system_user:
删除系统用户
`````````````
点击页面动作栏的“删除”按钮,弹出删除确认框,点击“删除”按钮,完成删除系统用户。
\ No newline at end of file
# -*- coding: utf-8 -*-
#
# Configuration file for the Sphinx documentation builder.
#
# This file does only contain a selection of the most common options. For a
# full list see the documentation:
# http://www.sphinx-doc.org/en/stable/config
# -- Path setup --------------------------------------------------------------
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
#
# import os
# import sys
# sys.path.insert(0, os.path.abspath('.'))
import sphinx_rtd_theme
# -- Project information -----------------------------------------------------
project = 'Jumpserver'
copyright = '北京堆栈科技有限公司 © 2014-2018'
author = 'Jumpserver team'
# The short X.Y version
version = ''
# The full version, including alpha/beta/rc tags
release = '0.5.0'
# -- General configuration ---------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
#
# needs_sphinx = '1.0'
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [
'sphinx.ext.viewcode',
'sphinx.ext.githubpages',
]
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
# The suffix(es) of source filenames.
# You can specify multiple suffix as a list of string:
#
# source_suffix = ['.rst', '.md']
source_suffix = '.rst'
# The master toctree document.
master_doc = 'index'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#
# This is also used if you do content translation via gettext catalogs.
# Usually you set "language" from the command line for these cases.
language = 'zh_CN'
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This pattern also affects html_static_path and html_extra_path .
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'
html_show_sourcelink = False
# -- Options for HTML output -------------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
#
# html_theme = 'alabaster'
html_theme = "sphinx_rtd_theme"
html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
#
html_theme_options = {
'logo_only': True,
'display_version': True
}
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']
# Custom sidebar templates, must be a dictionary that maps document names
# to template names.
#
# The default sidebars (for documents that don't match any pattern) are
# defined by theme itself. Builtin themes are using these templates by
# default: ``['localtoc.html', 'relations.html', 'sourcelink.html',
# 'searchbox.html']``.
#
# html_sidebars = {}
# -- Options for HTMLHelp output ---------------------------------------------
# Output file base name for HTML help builder.
htmlhelp_basename = 'Jumpserver 文档'
# -- Options for LaTeX output ------------------------------------------------
latex_elements = {
# The paper size ('letterpaper' or 'a4paper').
#
# 'papersize': 'letterpaper',
# The font size ('10pt', '11pt' or '12pt').
#
# 'pointsize': '10pt',
# Additional stuff for the LaTeX preamble.
#
# 'preamble': '',
# Latex figure (float) alignment
#
# 'figure_align': 'htbp',
}
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title,
# author, documentclass [howto, manual, or own class]).
latex_documents = [
(master_doc, 'Jumpserver.tex', 'Jumpserver Documentation',
'Jumpserver team', 'manual'),
]
# -- Options for manual page output ------------------------------------------
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
(master_doc, 'Jumpserver', 'Jumpserver Documentation',
[author], 1)
]
# -- Options for Texinfo output ----------------------------------------------
# Grouping the document tree into Texinfo files. List of tuples
# (source start file, target name, title, author,
# dir menu entry, description, category)
texinfo_documents = [
(master_doc, 'Jumpserver', 'Jumpserver 文档',
author, 'Jumpserver', ' Jumpserver是全球首款完全开源的堡垒机,是符合 4A 的专业运维审计系统',
'Miscellaneous'),
]
# -- Extension configuration -------------------------------------------------
html_logo = '_static/img/logo-text.png'
联系方式
+++++++++++++++++++++++++
商业支持
~~~~~~~~~~~
`阿里云市场购买: <https://market.aliyun.com/products/53690006/cmgj026011.html>`_
QQ 群
~~~~~~~~
群1: 390139816 (推荐)
群2: 399218702 (满)
群3: 552054376 (满)
Github
~~~~~~~~
https://github.com/jumpserver/jumpserver.git
官网
~~~~~~~~
http://www.jumpserver.org
Demo
~~~~~~~~
http://demo.jumpserver.org
邮件
~~~~~~~~
support@fit2cloud.com (#替换为@)
\ No newline at end of file
贡献者
=============
感谢以下朋友为 Jumpserver 做出的贡献,世界因你们而不同,排名不分先后
- **小彧 <李磊>** Django 资深开发者,为用户模块贡献了很多代码
- **sofia <周小侠>** 资深前端工程师, 前端代码贡献者
- **liuz <刘正> 全栈工程师** 编写了 Web Terminal 大部分代码
- **jiaxiangkong <陈尚委>** Jumpserver 测试运营
- **halcyon <王墉>** DevOps 资深开发者, 0.3.2 核心开发者之一
- **yumaojun03 <喻茂峻>** DevOps 资深开发者,擅长 Python、Go 以及 PaaS 平台开发
- **kelianchun <柯连春>** DevOps 资产开发者,修复了很多 Bugs
- **q4speed <莫鹍>** 架构师,贡献了 0.5.0 Windows 远程桌面登录大部分代码
- **ZhangFengyi <张峰毅>** 贡献了 0.5.0 新版文档
- **Aaron3S <沈晨阳>** 贡献了 0.5.0 新版文档
- **liqiang-fit2cloud <张立强>** 0.5.0 版本测试,给资产树设计贡献了很多建议
\ No newline at end of file
开发文档
======================================
.. toctree::
:maxdepth: 1
:caption: 开发文档
api_style_guide
python_style_guide
project_structure
FAQ
==========
1. Windows 无法连接
::
(1). 如果白屏 可能是nginx设置的不对,也可能运行guacamole的docker容器有问题,总之请求到不了guacamole
(2). 如果显示没有权限 可能是你在 终端管理里没有接受 guacamole的注册,请接受一下,如果还是不行,就删除刚才的注册,重启guacamole的docker重新注册
(3). 如果显示未知问题 可能是你的资产填写的端口不对,或者授权的系统用户的协议不是rdp
2. 用户、系统用户、管理用户的关系
::
用户:每个公司的同事创建一个用户账号,用来登录Jumpserver
系统用户:使用来登录到服务器的用户,如 web, dba, root等
管理用户:是服务器上已存在的特权用户,Ansible用来获取硬件信息, 如 root, 或者其它拥有 sudo NOPASSWD: ALL权限的用户
.. jumpserver documentation master file, created by
sphinx-quickstart on Mon Feb 26 23:28:27 2018.
You can adapt this file completely to your liking, but it should at least
contain the root `toctree` directive.
Jumpserver 文档
======================================
目录:
.. toctree::
:maxdepth: 2
introduce
installation
admin_guide
user_guide
development
contributor
contact
snapshot
faq
安装文档
++++++++++++++++++++++++
.. toctree::
:maxdepth: 1
quickstart
step_by_step
upgrade
总体介绍
==================
欢迎来到 Jumpserver 文档。
Jumpserver 是全球首款完全开源的堡垒机,使用 GNU GPL v2.0 开源协议,是符合 4A 的专业运维审计系统。
Jumpserver 使用 Python / Django 进行开发,遵循 Web 2.0 规范,配备了业界领先的 Web Terminal 解决方案,交互界面美观、用户体验好。
Jumpserver 采纳分布式架构,支持多机房跨区域部署,中心节点提供 API,各机房部署登录节点,可横向扩展、无并发访问限制。
改变世界,从一点点开始。
\ No newline at end of file
登录日志
==========
这里介绍登录日志的功能。
点击页面左侧“用户管理”菜单下的“登录日志”按钮,进入登录日志页面。
\ No newline at end of file
资产授权
=========
这里介绍资产授权的相关的功能。
.. contents:: Topics
.. _view_asset_authorized:
查看资产授权规则列表
````````````````````
资产授权页面默认展示资产授权列表。点击左侧资产节点树下的节点,右侧展示此节点下的资产授权规则。
.. _create_asset_authorized:
创建授权规则
````````````
在左侧资产节点树下选择要创建授权规则的节点,点击页面右侧创建授权规则进入创建授权规则页面,填写授权规则信息,点击提交,完成创建授权规则。
.. _update_asset_authorized:
更新授权规则
````````````
在左侧资产节点树下选择要更新授权规则的节点,在右侧授权规则列表中找到要更新的授权规则,点击“动作”标题下的“更新”按钮进入授权规则更新页面,填写授权规则信息,点击提交,完成创建授权规则。
.. _delete_asset_authorized:
删除授权规则
````````````
在左侧资产节点树下选择要删除授权规则的节点,在右侧授权规则列表中找到要删除的授权规则,点击“动作”标题下的“删除”按钮,弹出确认删除页面,点击确认,完成删除授权规则。
项目骨架
--------
说明如下:
::
.
├── config-example.py // 配置文件样例
├── docs // 所有 DOC 文件放到该目录
│ └── README.md
├── LICENSE
├── README.md
├── install // 安装说明
├── logs // 日志目录
├── apps // 管理后台目录,也是各 APP 所在目录
│ └── assets // APP 目录
│ │ ├── admin.py
│ │ ├── apps.py // 新版本 Django APP 设置文件
│ │ ├── api.py // API 文件
│ │ ├── __init__.py // 对外暴露的接口,放到该文件中,方便别的 APP 引用
│ │ ├── migrations // Models Migrations 版本控制目录
│ │ │ └── __init__.py
│ │ ├── models.py // 数据模型目录
│ │ ├── static // APP 下静态资源目录,如果需要
│ │ │ └── assets // 多一层目录,防止资源重名
│ │ │ └── some_image.png
│ │ ├── templates // APP 下模板目录
│ │ │ └── assets // 多一层目录,防止资源重名
│ │ │ └── asset_list.html
│ │ ├── templatetags // 模板标签目录
│ │ ├── tests.py // 测试用例文件
│ │ ├── urls.py // Urlconf 文件
│ │ ├── utils.py // 将 Views 和 API 可复用的代码放在这里, API 和 Views 只是请求和返回不同
│ │ └── views.py // Views 文件
│ ├── common
│ │ ├── templatetags // 通用 Template Tag
│ │ ├── utils.py // 通用的函数方法
│ │ └── views.py
│ ├── fixtures // 初始化数据目录
│ │ ├── init.json // 初始化项目数据库
│ │ └── fake.json // 生成大量测试数据
│ ├── jumpserver // 项目设置目录
│ │ ├── __init__.py
│ │ ├── settings.py // 项目设置文件
│ │ ├── urls.py // 项目入口 Urlconf
│ │ └── wsgi.py
│ ├── manage.py
│ ├── static // 项目静态资源目录
│ ├── i18n // 项目多语言目录
│ └── templates // 项目模板目录
\ No newline at end of file
Jumpserver 项目规范(Draft)
============================
语言框架
----------
1. Python 3.6.1 (当前最新)
2. Django 1.11 (当前最新)
3. Flask 0.12 Luna (当前最新)
4. Paramiko 2.12 Coco (当前最新)
Django 规范
--------------
1. 尽量使用 Class Base View 编程,更少代码
2. 使用 Django Form
3. 每个 URL 独立命名,不要硬编码,同理 Static 也是
4. 数据库表名手动指定,不要使用默认
5. 代码优雅简洁
6. 注释明确优美
7. 测试案例尽可能完整
8. 尽可能利用 Django 造好的轮子
代码风格
-----------
Python 方面大致的风格,我们采用 pocoo 的\ `Style
Guidance`_\ ,但是有些细节部分会尽量放开 参考国内翻译
基本的代码布局
~~~~~~~~~~~~~~
缩进
^^^^^^^^
1. Python 严格采用4个空格的缩进,任何 Python 代码都都必须遵守此规定。
2. Web 部分代码(HTML、CSS、JavaScript),Node.js 采用2空格缩进,同样不使用 TAB。
之所以与 Python 不同,是因为 JS 中有大量回调式的写法,2空格可以显著降低视觉上的负担。
最大行长度
^^^^^^^^^^^^^
按 PEP8 规范,Python 一般限制最大79个字符,
但是 Django 的命名,URL 等通常比较长,
而且21世纪都是宽屏了,所以我们限制最大120字符
**补充说明:HTML 代码不受此规范约束。**
长语句缩进
^^^^^^^^^^^^
编写长语句时,可以使用换行符"\"换行。在这种情况下,下一行应该与上一行的最后一个“.”句点或“=”对齐,或者是缩进4个空格符。
::
this_is_a_very_long(function_call, 'with many parameters') \
.that_returns_an_object_with_an_attribute
MyModel.query.filter(MyModel.scalar > 120) \
.order_by(MyModel.name.desc()) \
.limit(10)
如果你使用括号“()”或花括号“{}”为长语句换行,那么下一行应与括号或花括号对齐:
::
this_is_a_very_long(function_call, 'with many parameters',
23, 42, 'and even more')
对于元素众多的列表或元组,在第一个“[”或“(”之后马上换行:
::
items = [
'this is the first', 'set of items', 'with more items',
'to come in this line', 'like this'
]
.. _Style Guidance: http://www.pocoo.org/internal/styleguide/
空行
^^^^^^
顶层函数与类之间空两行,此外都只空一行。不要在代码中使用太多的空行来区分不同的逻辑模块。
::
def hello(name):
print 'Hello %s!' % name
def goodbye(name):
print 'See you %s.' % name
class MyClass(object):
"""This is a simple docstring."""
def __init__(self, name):
self.name = name
def get_annoying_name(self):
return self.name.upper() + '!!!!111'
语句和表达式
~~~~~~~~~~~~
一般空格规则
^^^^^^^^^^^^
1. 单目运算符与运算对象之间不空格(例如,-,~等),即使单目运算符位于括号内部也一样。
2. 双目运算符与运算对象之间要空格。
::
exp = -1.05
value = (item_value / item_count) * offset / exp
value = my_list[index]
value = my_dict['key']
比较
^^^^
1. 任意类型之间的比较,使用“==”和“!=”。
2. 与单例(singletons)进行比较时,使用 is 和 is not。
3. 永远不要与True或False进行比较(例如,不要这样写:foo ==
False,而应该这样写:not foo)。
否定成员关系检查
^^^^^^^^^^^^^^^^
使用 foo not in bar,而不是 not foo in bar。
命名约定
~~~~~~~~
1. 类名称:采用骆驼拼写法(CamelCase),首字母缩略词保持大写不变(HTTPWriter,而不是 HttpWriter)。
2. 变量名:小写_以及_下划线(lowercase_with_underscores)。
3. 方法与函数名:小写_以及_下划线(lowercase_with_underscores)。
4. 常量:大写_以及_下划线(UPPERCASE_WITH_UNDERSCORES)。
5. 预编译的正则表达式:name_re。
6. 受保护的元素以一个下划线为前缀。双下划线前缀只有定义混入类(mixin classes)时才使用。
7. 如果使用关键词(keywords)作为类名称,应在名称后添加后置下划线(trailing underscore)。
允许与内建变量重名,不要在变量名后添加下划线进行区分。如果函数需要访问重名的内建变量,请将内建变量重新绑定为其他名称。
8. 命名要有寓意, 不使用拼音,不使用无意义简单字母命名 (循环中计数例外 for i in)
9. 命名缩写要谨慎, 尽量是大家认可的缩写
函数和方法的参数:
^^^^^^^^^^^^^^^^^^
1. 类方法:cls 为第一个参数。
2. 实例方法:self 为第一个参数。
3. property函数中使用匿名函数(lambdas)时,匿名函数的第一个参数可以用 x 替代,
例如:display_name = property(lambda x: x.real_name or x.username)。
文档注释(Docstring,即各方法,类的说明文档注释)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
所有文档字符串均以 reStructuredText 格式编写,方便 Sphinx 处理。文档字符串的行数不同,布局也不一样。
如果只有一行,代表字符串结束的三个引号与代表字符串开始的三个引号在同一行。
如果为多行,文档字符串中的文本紧接着代表字符串开始的三个引号编写,代表字符串结束的三个引号则自己独立成一行。
(有能力尽可能用英文, 否则请中文优雅注释)
::
def foo():
"""This is a simple docstring."""
def bar():
"""This is a longer docstring with so much information in there
that it spans three lines. In this case, the closing triple quote
is on its own line.
"""
文档字符串应分成简短摘要(尽量一行)和详细介绍。如果必要的话,摘要与详细介绍之间空一行。
模块头部
~~~~~~~~
模块文件的头部包含有 utf-8 编码声明(如果模块中使用了非 ASCII 编码的字符,建议进行声明),以及标准的文档字符串。
::
# -*- coding: utf-8 -*-
"""
package.module
~~~~~~~~~~~~~~
A brief description goes here.
:copyright: (c) YEAR by AUTHOR.
:license: LICENSE_NAME, see LICENSE_FILE for more details.
"""
注释(Comment)
~~~~~~~~~~~~~~~~
注释的规范与文档字符串编写规范类似。二者均以 reStructuredText 格式编写。
如果使用注释来编写类属性的文档,请在#符号后添加一个冒号“:”。
(有能力尽可能用英文, 否则请中文优雅注释)
::
class User(object):
#: the name of the user as unicode string
name = Column(String)
#: the sha1 hash of the password + inline salt
pw_hash = Column(String)
\ No newline at end of file
快速安装
==========================
Jumpserver 封装了一个 All in one Docker,可以快速启动。该镜像集成了所需要的组件(Windows组件未暂未集成),也支持使用外置 Database 和 Redis
Tips: 不建议在生产中使用, 生产中请使用 详细安装 `CentOS <step_by_step.html>`_ `Ubuntu <setup_by_ubuntu.html>`_
Docker 安装见: `Docker官方安装文档 <https://docs.docker.com/install/>`_
快速启动
```````````````
使用 root 命令行输入::
$ docker run -d -p 8080:80 -p 2222:2222 registry.jumpserver.org/public/jumpserver:1.0.0
访问
```````````````
浏览器访问: http://<容器所在服务器IP>:8080
SSH访问: ssh -p 2222 <容器所在服务器IP>
XShell等工具请添加connection连接
额外环境变量
```````````````
- DB_ENGINE = mysql
- DB_HOST = mysql_host
- DB_PORT = 3306
- DB_USER = xxx
- DB_PASSWORD = xxxx
- DB_NAME = jumpserver
- REDIS_HOST = <redis-host>
- REDIS_PORT = <redis-port>
- REDIS_PASSWORD = <
::
docker run -d -p 8080:80 -p 2222:2222 -e DB_ENGINE=mysql -e DB_HOST=192.168.1.1 -e DB_PORT=3306 -e DB_USER=root -e DB_PASSWORD=xxx -e DB_NAME=jumpserver registry.jumpserver.org/public/jumpserver:1.0.0
仓库地址
```````````````
https://github.com/jumpserver/Dockerfile
命令记录
=========
这里介绍命令记录功能。
点击页面左侧“会话管理”菜单下的“命令记录”,进入命令记录列表页面。
.. contents:: Topics
.. _view_command_session:
查看命令记录
`````````````
命令记录页面默认展示一周内命令记录,页面左上角提供起止时间、用户、资产、系统用户等搜索过滤条件。
.. _detial_command_invoke:
查看命令执行详情
````````````````
点击命令记录列表中需要查看命令执行详情的行,即可显示命令执行详情。
.. _detial_command_session:
转到会话详情
`````````````
在命令记录列表中找到需要转到会话详情的记录,点击“会话”标题下的“转到”按钮,完成转到会话详情。
历史会话
=========
这里介绍历史会话功能。
点击页面左侧“会话管理”菜单下的“历史会话”,进入历史会话列表页面。
.. contents:: Topics
.. _view_history_session:
查看历史会话
`````````````
历史会话页面默认展示一周内历史会话,页面左上角提供起止时间、用户、资产、系统用户等搜索过滤条件。
.. _playback_history_session:
历史会话回放
`````````````
在在线会话列表中找到要回放的历史会话,点击动作标签下的“回放”按钮,弹出回放页面,完成回放历史会话。
\ No newline at end of file
在线会话
=========
这里介绍在线会话功能。
点击页面左侧“会话管理”菜单下的“在线会话”,进入在线会话列表页面。
.. contents:: Topics
.. _view_online_session:
查看在线会话
`````````````
在线会话页面默认展示一周内在线会话,页面左上角提供起止时间、用户、资产、系统用户等搜索过滤条件。
.. _stop_online_session:
终断在线会话
`````````````
在在线会话列表中找到要终止的在线会话,点击动作标签下的“终断”按钮,完成终断在线会话。
终端管理
=========
这里介绍终端管理功能。
点击页面左侧“会话管理”菜单下的“终端管理”,进入终端列表页面。
.. contents:: Topics
.. _view_terminal_session:
查看终端列表
`````````````
终端列表页面默认展示全部终端列表。
.. _upate_terminal_session:
更新终端
`````````````
在在线会话列表中找到要更新的终端,点击动作标签下的“更新”按钮,在更新终端页面填写相关信息,点击提交,完成更新终端。
.. _delete_terminal_session:
删除终端
`````````````
在在线会话列表中找到要删除的终端,点击动作标签下的“删除”按钮,在弹出确认删除页面点击确认,完成删除终端。
Web终端
=========
这里介绍Web终端功能。
点击页面左侧“会话管理”菜单下的“Web终端”,进入Web终端页面。
.. contents:: Topics
.. _login:
主机登录
`````````````
点解页面左侧的”Web终端”,进入主机登录页,然后点击页面右侧的主机IP地址,连接主机,页面右侧会展示当前连接的终端信息。
.. _logout:
主机登出
`````````````
在主机登录页面,选择左上角的“服务器”按钮,出现两个选项,一个“断开链接“按钮,断开当前连接的主机;另一个”断开所有链接“,断开当前所有连接的主机。
Snapshot 截图
+++++++++++++++++
仪表盘
~~~~~~~~
.. image:: _static/img/dash_board.png
用户管理
~~~~~~~~~~
.. image:: _static/img/admin_user.png
资产管理
~~~~~~~~~~
.. image:: _static/img/admin_asset.png
Linux 终端
~~~~~~~~~~~~~
.. image:: _static/img/linux_terminal.png
Windows 终端
~~~~~~~~~~~~~~~~
.. image:: _static/img/windows_terminal.png
一步一步安装
--------------------------
环境
~~~~~~~
- 系统: CentOS 7
- IP: 192.168.244.144
- 关闭 selinux 和防火墙
::
# CentOS 7
$ setenforce 0 # 可以设置配置文件永久关闭
$ systemctl stop iptables.service
$ systemctl stop firewalld.service
# CentOS6
$ setenforce 0
$ service iptables stop
一. 准备 Python3 和 Python 虚拟环境
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
**1.1 安装依赖包**
::
$ yum -y install wget sqlite-devel xz gcc automake zlib-devel openssl-devel epel-release git
**1.2 编译安装**
::
$ wget https://www.python.org/ftp/python/3.6.1/Python-3.6.1.tar.xz
$ tar xvf Python-3.6.1.tar.xz && cd Python-3.6.1
$ ./configure && make && make install
**1.3 建立 Python 虚拟环境**
因为 CentOS 6/7 自带的是 Python2,而 Yum 等工具依赖原来的 Python,为了不扰乱原来的环境我们来使用 Python 虚拟环境
::
$ cd /opt
$ python3 -m venv py3
$ source /opt/py3/bin/activate
# 看到下面的提示符代表成功,以后运行 Jumpserver 都要先运行以上 source 命令,以下所有命令均在该虚拟环境中运行
(py3) [root@localhost py3]
二. 安装 Jumpserver 1.0.0
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
**2.1 下载或 Clone 项目**
项目提交较多 git clone 时较大,你可以选择去 Github 项目页面直接下载zip包。
::
$ cd /opt/
$ git clone --depth=1 https://github.com/jumpserver/jumpserver.git && cd jumpserver && git checkout master
**2.2 安装依赖 RPM 包**
::
$ cd /opt/jumpserver/requirements
$ yum -y install $(cat rpm_requirements.txt) # 如果没有任何报错请继续
**2.3 安装 Python 库依赖**
::
$ pip install -r requirements.txt # 不要指定-i参数,因为镜像上可能没有最新的包,如果没有任何报错请继续
**2.4 安装 Redis, Jumpserver 使用 Redis 做 cache 和 celery broke**
::
$ yum -y install redis
$ service redis start
**2.5 安装 MySQL**
本教程使用 Mysql 作为数据库,如果不使用 Mysql 可以跳过相关 Mysql 安装和配置
::
# centos7
$ yum -y install mariadb mariadb-devel mariadb-server # centos7下安装的是mariadb
$ service mariadb start
# centos6
$ yum -y install mysql mysql-devel mysql-server
$ service mysqld start
**2.6 创建数据库 Jumpserver 并授权**
::
$ mysql
> create database jumpserver default charset 'utf8';
> grant all on jumpserver.* to 'jumpserver'@'127.0.0.1' identified by 'somepassword';
**2.7 修改 Jumpserver 配置文件**
::
$ cd /opt/jumpserver
$ cp config_example.py config.py
$ vi config.py # 我们计划修改 DevelopmentConfig中的配置,因为默认jumpserver是使用该配置,它继承自Config
**注意: 配置文件是 Python 格式,不要用 TAB,而要用空格**
::
class DevelopmentConfig(Config):
DEBUG = True
DB_ENGINE = 'mysql'
DB_HOST = '127.0.0.1'
DB_PORT = 3306
DB_USER = 'jumpserver'
DB_PASSWORD = 'somepassword'
DB_NAME = 'jumpserver'
...
config = DevelopmentConfig() # 确保使用的是刚才设置的配置文件
**2.8 生成数据库表结构和初始化数据**
::
$ cd /opt/jumpserver/utils
$ bash make_migrations.sh
**2.9 运行 Jumpserver**
::
$ cd /opt/jumpserver
$ python run_server.py all
运行不报错,请浏览器访问 http://192.168.244.144:8080/
(这里只是 Jumpserver, 没有 Web Terminal,所以访问 Web Terminal 会报错)
账号: admin 密码: admin
三. 安装 SSH Server 和 WebSocket Server: Coco
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
**3.1 下载或 Clone 项目**
新开一个终端,连接测试机,别忘了 source /opt/py3/bin/activate
::
$ cd /opt
$ git clone https://github.com/jumpserver/coco.git && cd coco && git checkout master
**3.2 安装依赖**
::
$ cd /opt/coco/requirements
$ yum -y install $(cat rpm_requirements.txt)
$ pip install -r requirements.txt
**3.3 查看配置文件并运行**
::
$ cd /opt/coco
$ cp conf_example.py conf.py
$ python run_server.py
这时需要去 Jumpserver 管理后台-会话管理-终端管理(http://192.168.244.144:8080/terminal/terminal/)接受 Coco 的注册
::
Coco version 0.4.0, more see https://www.jumpserver.org
Starting ssh server at 0.0.0.0:2222
Quit the server with CONTROL-C.
**3.4 测试连接**
::
$ ssh -p2222 admin@192.168.244.144
密码: admin
如果是用在 Windows 下,Xshell Terminal 登录语法如下
$ssh admin@192.168.244.144 2222
密码: admin
如果能登陆代表部署成功
四. 安装 Web Terminal 前端: Luna
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Luna 已改为纯前端,需要 Nginx 来运行访问
访问(https://github.com/jumpserver/luna/releases)下载对应版本的 release 包,直接解压,不需要编译
4.1 解压 Luna
::
$ pwd
/opt/
$ tar xvf luna.tar.gz
$ ls /opt/luna
...
五. 安装 Windows 支持组件
~~~~~~~~~~~~~~~~~~~~~~~~~~
因为手动安装 guacamole 组件比较复杂,这里提供打包好的 docker 使用, 启动 guacamole
.. code:: shell
# 注意:这里一定要改写一下本机的IP地址, 否则会出错
docker run --name jms_guacamole -d \
-p 8081:8080 -v /opt/guacamole/key:/config/guacamole/key \
-e JUMPSERVER_KEY_DIR=/config/guacamole/key \
-e JUMPSERVER_SERVER=http://<填写本机的IP地址>:8080 \
registry.jumpserver.org/public/guacamole:latest
这里所需要注意的是 guacamole 暴露出来的端口是 8081,若与主机上其他端口冲突请自定义一下。
再次强调:修改 JUMPSERVER_SERVER 环境变量的配置,填上 Jumpserver 的内网地址, 这时
去 Jumpserver-会话管理-终端管理 接受[Gua]开头的一个注册
六. 配置 Nginx 整合各组件
~~~~~~~~~~~~~~~~~~~~~~~~~
6.1 安装 Nginx 根据喜好选择安装方式和版本
.. code:: shell
yum -y install nginx
6.2 准备配置文件 修改 /etc/nginx/nginx.conf
::
server {
listen 80;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
location /luna/ {
try_files $uri / /index.html;
alias /opt/luna/;
}
location /media/ {
add_header Content-Encoding gzip;
root /opt/jumpserver/data/;
}
location /static/ {
root /opt/jumpserver/data/;
}
location /socket.io/ {
proxy_pass http://localhost:5000/socket.io/;
proxy_buffering off;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
location /guacamole/ {
proxy_pass http://localhost:8081/;
proxy_buffering off;
proxy_http_version 1.1;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $http_connection;
access_log off;
}
location / {
proxy_pass http://localhost:8080;
}
}
6.3 运行 Nginx
::
nginx -t
service nginx start
6.4 访问 http://192.168.244.144
更新升级
-------------
1. 升级 Jumpserver
::
$ git pull && pip install -r requirements/requirements.txt && cd utils && sh make_migrations.sh
2. 升级 Coco
::
$ git pull && cd requirements && pip install -r requirements.txt # 不要指定 -i参数
3. 升级 Luna
重新下载 release 包(https://github.com/jumpserver/luna/releases)
4. 升级 guacamole
::
$ docker pull registry.jumpserver.org/public/guacamole:latest
$ docker stop jms_guacamole # 或者写guacamole的容器ID
$ docker run --name jms_guacamole -d \
-p 8081:8080 -v /opt/guacamole/key:/config/guacamole/key \
-e JUMPSERVER_KEY_DIR=/config/guacamole/key \
-e JUMPSERVER_SERVER=http://<填写本机的IP地址>:8080 \
registry.jumpserver.org/public/guacamole:latest
切换分支或离线升级
-------------------------------
**Jumpserver**
说明: 以下操作,都在jumpserver所在目录运行
1. 备份配置文件
::
$ jumpserver_backup=/tmp/jumpserver_backup
$ mkdir -p $jumpserver_backup
$ cp config.py $jumpserver_backup
2. 备份migrations migrations中存的是数据库表结构的变更,切换分支会丢失
::
$ for app in common users assets ops perms terminal;do
mkdir -p $jumpserver_backup/${app}_migrations
cp apps/${app}/migrations/*.py $jumpserver_backup/${app}_migrations
done
3. 备份数据库,已被不时之需
::
$ mysqldump -u你的数据库账号 -h数据库地址 -p 数据库名称 > $jumpserver_backup/db_backup.sql
4. 备份录像文件
::
$ cp -r data/media $jumpserver_backup/
5. 切换分支或下载离线包, 更新代码
::
$ git checkout master # or other branch
6. 还原配置文件
::
$ cp $jumpserver_backup/config.py .
7. 还原数据库表结构记录
::
$ for app in common users assets ops perms terminal;do
cp $jumpserver_backup/${app}_migrations/*.py ${app}/migrations/
done
8. 还原录像文件
::
$ cp -r $jumpserver_backup/media/* data/media/
9. 更新依赖或表结构
::
$ pip install -r requirements/requirements.txt && cd utils && sh make_migrations.sh
**Coco**
说明: 以下操作都在 coco 项目所在目录
coco是无状态的,备份 keys 目录即可
1. 备份keys
::
$ cp -r keys $jumpserver_backup/
2. 离线更新升级coco
3. 还原 keys目录
::
$ mv keys keys_backup
$ cp -r $jumpserver_backup/keys .
4. 升级依赖
::
$ git pull && cd requirements && pip install -r requirements.txt
**Luna**
直接下载最新Release包替换即可
**Guacamole**
直接参考上面的升级即可, 需要注意的是如果更换机器,请备份
用户列表
========
这里介绍用户列表的功能。
点击页面左侧“用户列表”菜单下的“用户列表“,进入用户列表页面。
.. contents:: Topics
.. _create_user:
创建用户
````````
点击页面左上角“创建用户”按钮,进入创建用户页面,填写账户,角色安全,个人等信息,点击“提交”按钮,用户创建完成。
.. _update_user:
更新用户
````````
点击页面右边的“更新”按钮,进入编辑用户页面,编辑用户信息,点击“提交”按钮,更新用户完成。
.. _delete_user:
删除用户
````````
点击页面右边的“删除”按钮,弹出是否删除确认框,点击“确定”按钮,删除用户完成。
.. _export_user:
导出用户
````````
选中用户,点击右上角的“导出”按钮,导出用户完成。
.. _import_user:
导入用户
````````
点击右上角的“导入”按钮,弹出导入对话框,选择要导入的CSV格式文件,点击“确认”按钮,导入用户完成。
.. _batch_user_operation:
批量操作
````````
选中用户,选择页面左下角的批量操作选项,点击”提交“按钮,批量操作完成。
\ No newline at end of file
个人资产
=========
这里介绍用户个人资产相关的功能。
.. contents:: Topics
.. _view_personal_assets:
查看个人资产
````````````
登录个人用户,默认展示个人资产列表。点击主机名,查看资产的详细信息。
.. _host_login:
主机登录
`````````
点解页面左侧的"Web终端",进入主机登录页,然后点击页面右侧的主机IP地址,连接主机,页面右侧会展示当前连接的终端信息。
.. _host_logout:
主机登出
`````````
在主机登录页面,选择左上角的“服务器”按钮,出现两个选项,一个“断开链接“按钮,断开当前连接的主机;另一个”断开所有链接“,断开当前所有连接的主机。
\ No newline at end of file
用户组列表
============
这里介绍用户组列表的功能。
点击页面左侧“用户管理”菜单下的”用户组“,进入用户组列表页面。
.. contents:: Topics
.. _create_user_group:
创建用户组
``````````
点击页面左上角“创建用户组”按钮,进入创建用户组页面,填写用户组信息,点击“提交”按钮,创建用户完成。
.. _update_user_group:
更新用户组
``````````
点击页面右边的“更新”按钮,进入编辑用户组页面,编辑用户组信息,点击“确认”按钮,更新用户组完成。
.. _delete_user_group:
删除用户组
````````````
点击页面右边的“删除”按钮,弹出删除确认框,点击“确认”按钮,删除用户组完成。
\ No newline at end of file
用户使用文档
=============
这部分给您介绍Jumpserver的用户管理模块的使用方法。
.. toctree::
:maxdepth: 1
user_asset
user_info
\ No newline at end of file
个人信息
=========
这里介绍个人信息相关的功能。
.. contents:: Topics
.. _view_personal_info:
查看个人信息
````````````
点击页面左侧的“个人信息”,查看用户的个人信息、SSH密钥。
.. _modify_personal_info:
修改个人信息
````````````
在个人信息页,点击页面右上角的“设置”按钮,进入个人信息修改页面,填写个人信息,点击“提交”按钮,完成个人信息修改。
.. _update_password:
更新密码
`````````
在个人信息页,点击页面右上角的“重置密码“按钮,进入密码更新页面,填写原来密码、新密码等信息,点击“提交”按钮,完成密码更新。
.. _update_ssh_key:
密钥更新
`````````
在个人信息页,点击页面左上角的“重置SSH密钥“按钮,进入密钥更新页面,填写SSH公钥,点击“提交”按钮,完成密钥更新。
\ No newline at end of file
任务列表
=========
这里介绍任务列表的相关的功能。
.. contents:: Topics
.. _view_asset_works:
查看任务列表
````````````
任务列表页面默认展示一周内所有任务。点击标题可根据当前字段进行排序。
.. _invoke_asset_work:
手动执行任务
````````````
在任务列表中找到要手动执行的任务,点击“动作”标题下的“执行”按钮,完成手动执行当前任务。
.. _delete_asset_work:
删除任务
`````````
在任务列表中找到要删除的任务,点击“动作”标题下的“删除”按钮,完成删除当前任务。
.. _detial_asset_work:
查看任务详情
`````````````
在任务列表中找到要查看的任务,点击要查看的任务名称,即可进入任务详情页面。
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