找回密码
 注册
查看: 433|回复: 1

jumpserver二次开发模块

[复制链接]

1

主题

0

回帖

12

积分

管理员

积分
12
QQ
发表于 2023-3-23 17:00:05 | 显示全部楼层 |阅读模式
Jumpserver是全球首款完全开源的堡垒机,是符合 4A 的专业运维审计系统。 http://www.jumpserver.org
* ~. d( X6 i4 p' z! w2 O' d6 @& g/ c! I- z, y- e, {
jms很好用,但是引入公司内部,也会有一些问题,比如资产导入,资产和项目关联,如何和现有cmdb打通等问题。我这里只是抛砖引玉,介绍下jms如何二次开发,做企业定制化版的jumpserver,打通DevOps系统的方法。
" B8 m4 c9 ]. @* Q- X首先,以增加项目管理 这个功能为例,说说堡垒机如何二次开发的。
7 \7 ~0 ^% _2 n# O8 M# Z0 P: v8 S, u: i) a8 _) \- \* f' K
基于版本 Version 1.4.10-2 GPLv2.3 f7 O( a; y0 Q) H  P! I% O
; y8 C- g" s9 m7 z
一、概述* p! b! A/ g( D* M
Jumpserver用的是python的django框架开发,API 采用了DRF(Django REST framework),基于CBV(Class-based views)开发模式,因此,二次开发也是比较简单的,一般先定义model,然后写api,再使用template渲染,前端基本用datatables较多。
( v: {; s# y$ I8 @" C$ B% e3 O$ F# u* y! y8 s3 c; I
实现效果:' m7 Y5 }3 P. M3 O  `
项目管理
1 G' ~* p+ c+ r1 `. g项目详情7 p- F1 P% ?8 {/ N
项目详情2
5 ^) d# E1 `. }, U' P/ j+ P7 J, M" _7 G0 m& L& o: ]4 Y
二、实战
, i1 \1 c8 G5 r# a项目(project)管理或者说是服务(service)管理,服务和主机是有关系的,我这里一个主机可以部署多个服务,同时一个服务也可以部署在多台主机上,所以是多堆多的关系$ s0 i$ i0 F6 M- }
) x) O$ B, ?6 u5 [& v7 J, J
2.1 定义project model* O  f) w  M4 \# ~- F8 G+ H/ X
jumpserver/apps/assets/models/project.py
; O3 [$ s: z* g" s5 ]  X" a# \" t, a4 P4 o. }& O* ?
6 y! r2 l& ]5 s! m8 m3 b

7 J* ~/ l9 y2 ]. a; a#!/usr/bin/env python
6 S; j2 c$ J4 y9 r" `# -*- coding: utf-8 -*-
$ A9 V8 j1 ~. Q! d% m0 ?1 B
' @! M3 c# `/ `. Q$ l$ ~  s# M- afrom __future__ import unicode_literals
& E" ?6 t5 n& N+ ~5 ^4 r# @/ _1 e4 X6 m7 i( w* J/ `: N
from django.db import models
  d8 w2 `5 P9 E/ W# zimport logging
% X9 U, ^$ Q- _. f' n* Lfrom django.utils.translation import ugettext_lazy as _" b' z# A, j0 T$ V$ E# T
import uuid- `9 G  x; K' h5 J
from django.core.cache import cache4 h* @! j! j9 K

9 C- V0 C0 j  ^4 h__all__ = ['Project', 'TYPE_CHOICES'], `  s8 m0 ]" O' N
logger = logging.getLogger(__name__)  {8 r' \) o8 Q; b4 p4 b6 @( ?5 f
4 b: H5 |' {5 R- o) g
TYPE_CHOICES = (
, ]6 k% Y0 ]% U4 f) j5 W        ('Java', 'java'),6 s+ X! J5 @+ ]% D
        ('React', 'react'),* p5 A" Z+ C, B* {: {; \
        ('Cpp', 'c++'),
3 {3 e- O( l) b8 \+ e( F) ~( x        ('Python', 'python')," V) r: r/ m5 C) H+ z& U& F! m
        ('Middleware', 'Middleware'),3 t* ]$ \7 a( B. |# c$ Y
        ('Other', 'Other'),
5 K5 b+ g4 a  p( j    )* q: v7 G) g3 d9 V  i% e2 h7 ]
& b: `+ m/ k' R6 t

* }# A5 d! H' J0 j7 E4 P3 fclass Project(models.Model):/ x/ D2 i8 H. M" o1 P# S8 B$ G0 w
    TYPE_CHOICES =TYPE_CHOICES, J% I  P, b5 K% ?9 m: X

" G6 R4 _; r6 C+ V, n+ L# v+ f0 P    id = models.UUIDField(default=uuid.uuid4, primary_key=True)
0 j' Y" C& @$ z; e8 T, f0 ?1 U) k    name = models.CharField(max_length=64, unique=True, verbose_name=_('Name'))" [  W" P# T" Z+ k

! ^; \7 s. d% e. `3 m* ^2 A0 J    parent = models.ForeignKey('self', null=True, blank=True, related_name="children", on_delete=models.SET_NULL)
6 f3 I) v" N/ u  [8 |* o: @3 {' B& M9 ]- c/ X
    domain_name = models.CharField(max_length=64, blank=True, verbose_name=_('Domain Name'))2 q2 ?* K9 m3 S. W( G" ~2 j. f4 R
    git_address = models.CharField(max_length=128, blank=True, verbose_name=_('Git Address'))! o) @5 s3 g: U/ h& N( k1 W, Z* c
    port = models.IntegerField(blank=True, null=True, verbose_name=_('Open port'))/ h0 V  e& d5 @5 W8 N2 b
    type = models.CharField(choices=TYPE_CHOICES, max_length=16, blank=True, null=True,
( M5 k5 W- p, [! ^! V                            default='Java', verbose_name=_('Project type'), )- i- O; f1 f2 p% v. d% K% W4 A
    dev_users = models.ManyToManyField(3 U; v" c7 k+ s: \# x9 q
        'users.User', related_name='users1',
# ^$ y# x! h2 v        blank=True, verbose_name=_('Dev Users')
) _/ [$ r! r+ m: O    )) L& ~9 N/ ]: ~* ?% C9 D  K5 y1 O
    sa_users = models.ManyToManyField(% Z1 h) F& Y! x
        'users.User', related_name='users2',
3 N9 K2 ~! e  X9 W        blank=True, verbose_name=_('Sa Users')
( ?( r5 Q5 ?# V$ e9 }    )
9 k3 |: I3 I1 x4 {    scrum_master = models.ForeignKey('users.User', on_delete=models.SET_NULL, null=True, verbose_name=_('Scrum Master'))! W: ^3 `% F  U" x1 ?% J2 x
    created_by = models.CharField(max_length=32, blank=True, verbose_name=_('Created by'))2 `5 l6 A* z- D9 v: S) T
    updated_by = models.CharField(max_length=32, blank=True, verbose_name=_('Updated by'))
; {8 V, X, F& G9 m    date_created = models.DateTimeField(auto_now_add=True, null=True, verbose_name=_('Date created'))
8 L+ U8 l% e) K    date_updated = models.DateTimeField(auto_now=True, null=True, verbose_name=_('Date updated'))
, i) D8 {4 J; ?8 T    comment = models.TextField(blank=True, verbose_name=_('Comment')), z1 L$ ]1 s0 |2 q& j/ t
    level = models.IntegerField(default=0, verbose_name=_('Level'))
6 M/ X  @# B- c5 U
. N$ P/ U; ]4 x3 D    def __unicode__(self):3 F) h3 u* {0 A# z3 |% B" l' J0 c
        return self.name  z* R5 i  h& a
    __str__ = __unicode__9 g8 J6 Y/ b* q
# ?3 K0 V: @4 C
    @property- p2 z: V5 i' c0 C! ^' Y; f! L0 e2 ~
    def all_sa_users(self):; F  M7 Y( u2 z. A4 |* P
        return [user.name for user in self.sa_users.all()]
# z/ D1 @% K+ Z0 Q3 Q$ B
* ^% R7 l* G6 ]/ V- }9 j    @property' S- h( f4 N. C2 m+ r0 w
    def all_dev_users(self):
, a6 w/ h& b5 f+ s+ z6 a+ {6 _        return [user.name for user in self.dev_users.all()]
( B( E1 R+ b: N$ N& A1 m: w4 v5 Y* b: B& `' o: V9 c
    def get_related_assets(self):
0 L, V: f% c( `& m        assets = self.assets.all()
* ~0 B$ _7 U8 j/ z7 M2 U4 j' _$ n        return assets& q* c# Q! s  e  y2 y& M7 J

, W  g/ G+ G4 l( i' x    def get_related_assets_ip(self):
1 p# `# X4 f1 @8 ^) l. W+ l0 ^        assets = self.assets.all()
. Z) g. V0 W; R) g4 k6 v( `5 R        return [{'ip': asset.ip, 'environment': asset.environment} for asset in assets]9 b8 D4 p) {: T
0 w3 u# m( T! K. z, ~) H# d4 a
    @property
- i+ |, R7 B* Z8 ~    def all_assets_ip(self):
1 E' e  _" K) `. Z; y$ G* _        return self.get_related_assets_ip(). X# ~$ X; ~8 ?. Z# F

- f+ h/ m: f' _( Z    class Meta:
9 j' s6 D- N0 c4 V        ordering = ['name']( f' [7 @. k* E! m$ x8 o
2.2 修改asset model4 n0 d% y3 A0 @/ \1 a4 \; P
jumpserver/apps/assets/models/asset.py  a, X) X, J  t7 Z
4 [/ o/ `( A4 a& z% v7 v; |
4 b5 r& q1 R3 U* [1 n" ~% r- D

9 G+ t* U: \; N* K. l# 增加环境定义+ H2 u1 U; d5 X# s' N1 m4 Y
ENV_CHOICES = (
+ W9 Q5 q5 {* z+ h' J5 E% e$ Y    ('DEV', 'dev'),
2 r' d  N5 r% U2 S$ ]; {    ('TEST', 'test'),
! q& |: G. e% F    ('DEMO', 'demo'),9 K0 M/ @5 ~* C* O- m
    ('TDEMO', 'tdemo'),
8 I# h0 s- L2 k9 m4 l    ('PROD', 'prod'),
5 R3 S' i+ |, {& S    ('Other', 'other'),, P$ J) O+ a) w7 l/ ~, k$ W0 E  r
)
$ b% w8 j9 r4 z' Benvironment = models.CharField(max_length=32, choices=ENV_CHOICES, default='DEV', verbose_name=_('Environment'))
) x6 M! @$ t& v. p" T, i% x, k# Project 和 asset 多对多关系
3 ~- I% o5 M0 q% w# kprojects = models.ManyToManyField(Project, blank=True, verbose_name=_('Projects'), related_name='assets', )
. [/ t( J, S2 A. _2.3 加入全局变量中init
4 {& w' H/ T/ b/ }% ?2 ^7 {jumpserver/apps/assets/models/init.py 末尾增加
3 w1 ]/ h; u  _  T8 r; o/ c
- d- ?3 A9 D' g1
- n! b4 Z9 a; pfrom .project import *
, b# J# @7 I# d4 ]2.4 定义DRF的serializers
6 l3 f) }% Y1 d! I5 D2 Cjumpserver/apps/assets/serializers/project.py
: `) {! Z& i- q* c2 A) }& l6 h& q& E) \

& V- _9 g/ v) \+ x1 L
" `$ x) {: U7 ^7 z0 \5 I' b" b- j* M, hfrom rest_framework_bulk import BulkListSerializer, BulkSerializerMixin$ O, a4 C3 Q7 q. E
from rest_framework import serializers
: S2 v* }) ?- @* P; k) w$ Zfrom ..models import Project, Asset
5 p+ o7 ^+ {; l5 Z" C. `7 |& I
# S8 A* U, `% s% B+ g7 B, v
* v; E0 c" Y! b" p6 o) s# D5 wclass ProjectSerializer(serializers.ModelSerializer):2 b# l* z4 b, t* Q  n
    asset_count = serializers.SerializerMethodField()$ o' K/ d9 x0 q8 \& ?5 }
; I, y6 W' ~- P7 P& H
    class Meta:( ?. C9 K6 W0 ~+ E3 F! ]
        model = Project
* T0 {$ R  C) b$ @6 O! U        fields = '__all__'
8 W8 I  k; i( U, U! w
$ k1 m' {6 Y% t4 x3 X5 ?    @staticmethod
9 b7 z' p" c. M8 R3 O6 C1 n    def get_asset_count(obj):6 l1 H9 Y$ P' |3 r$ x# P
        return obj.assets.count()/ d. B3 y  Z  b1 c8 R$ a

& |3 O0 x% k  z2 d4 ~( C4 A' a: Z; r1 Y' `  j+ E3 ?
class ProjectAssetsSerializer(serializers.ModelSerializer):9 v9 J1 i0 P  z+ s
    class Meta:5 N5 m" Z! F4 Y! E/ h! r: p' m
        fields = ['id', 'name', 'port']
5 x& q  {" H) n  q3 @0 M        model = Project
3 O' ?: Q- g4 ^7 K, }: M: |! V' d) z
    def get_field_names(self, declared_fields, info):! f+ h: f5 U$ o. y" P4 ]
        fields = super().get_field_names(declared_fields, info)
+ X* C  x4 W0 `: E/ G! O$ Z0 \! }        fields.extend([% R# t2 S0 E* r0 G' B
            'all_assets_ip'
! N6 d; v( y  N( @/ \  j  k        ])
) p. r( q) A) G        return fields7 }6 J7 D% O) P8 i% `" ?

- P+ u( u, e2 C) ]/ u2 {
. U/ r$ L2 u: L# f4 S$ B& Bclass ProjectUpdateSerializer(serializers.ModelSerializer):  l6 }- [$ o* X) a
    """update the project, and add or delete the asset to the project"""
$ u; x" m2 z3 l* E    assets = serializers.PrimaryKeyRelatedField(many=True, queryset=Asset.objects.all())( u9 m' m2 ]: A& _7 d) N
) C" T$ E- K3 I
    class Meta:
4 b1 m* C$ X! _% x( @        model = Project# l9 q" [0 y& c
        fields = ['id', 'assets']4 o- l6 K; a* I1 W
/ E; ]' t" \. O3 R* _* [
" _; E* X% X* A* j
class AssetUpdateProjectSerializer(serializers.ModelSerializer):7 y$ V, y. l' E  q) K! O$ i/ |
    projects = serializers.PrimaryKeyRelatedField(many=True, queryset=Project.objects.all())- G8 j5 q8 K6 @& J) F7 a
7 m9 D0 I/ ]/ r; y
    class Meta:3 J% B5 l2 N4 o0 i) E! x- l
        model = Asset
( a( q' t6 ?: W- S  k: i        fields = ['id', 'projects']. P6 W! l2 O* R0 s: B3 a0 p2 d! Z
2.5 增加URl支持
! T( o9 t# K8 F6 d6 \8 Q  K. E+ I/jumpserver/apps/assets/urls/api_urls.py2 H1 v9 }  ^7 n+ Y7 z* }

" P. }6 N# w) s: Q1 ^
/ n, x( {. ^" C3 [" r; ^, L0 A: z$ J
router.register(r'projects', api.ProjectViewSet, 'project')
& O; R+ L# ?! ?, erouter.register(r'projects-assets', api.ProjectAssetsViewSet, 'project-assets-view')
$ }$ R( a0 W1 {: |; ]4 j# \! T3 A( u% R
# urlpatterns 增加:# }( S5 |/ {2 ^$ i6 d& J
    path(r'assets/<uuid:pk>/projects/',: c: Z3 v8 ?9 d
            api.AssetUpdateProjectApi.as_view(), name='asset-update-project'),- u9 a6 {3 Q3 k! n& W
    path(r'projects/<uuid:pk>/assets/',
: \7 o: n9 ?* D1 O# c) b1 b  J            api.ProjectUpdateApi.as_view(), name='project-update-assets'),5 h. b# K1 f/ @
    path('project/<uuid:pk>/assets/',. B: @5 e  R3 d, @' h
            api.ProjectAssetsListView.as_view(), name='project-assets'),
. H' R, \+ x" d; {API到这里基本写完了,至于前端的一些,都是调用这些api,进行增删改查操作,详情见github
' E  B3 ~, q: p
/ l8 ~7 J. j" f/ d3 数据库迁移
4 o8 y: O3 x6 J; k$ j定义了模型以后,需要执行migrate命令进行数据库变更,省了自己执行sql
) R" ?+ G& H# _3 {' d7 Y) T5 v! u7 @0 k
- N4 [# V0 b2 O: e% U; P

# v! S% S  v  c$ F9 D! \cd jumpserver/utils
, _" t+ n# F0 H2 ?# v9 |bash make_migrations.sh. a; W3 K2 g( j" \! T! R) ~7 a
4 国际化6 _$ Q9 W% w- A# f. k% K0 D6 b( ~
因为我们定义的好多变量都是英文的,这里可以通过国际化手段支持中英文
9 F+ @& R/ J, d6 B) n; m2 y
* N* P; k5 W; c3 K  C" Q5 ]4 @" a

) }, H: w9 V, }- K6 ]  n* j2 |. `# 先执行
6 ], q( p9 y. r  [7 ?cd jumpserver/apps7 v5 A1 y' N* ?9 K3 v5 A  q
python manage.py makemessages -l zh
& n! |. Z5 ?0 I# s1 \1 F# 修改jumpserver/apps/locale/zh/LC_MESSAGES/django.po 的中英文对应
: R! j4 a+ R  [# N) qvim jumpserver/apps/locale/zh/LC_MESSAGES/django.po( M. a) u! u& E# t: @
# 修改完成后生效,完全生效需要服务重启" q" V( V5 y0 y* L
python manage.py compilemessages
4 i, t# D- D' M( w' L5 Z& I
/ _2 \! Z2 w  a" A) N
* G6 @. x) F/ x# [& a$ ?+ m
8 v  v; s9 N, X' j

1

主题

0

回帖

12

积分

管理员

积分
12
QQ
 楼主| 发表于 2023-3-23 17:00:06 | 显示全部楼层
您需要登录后才可以回帖 登录 | 注册

本版积分规则

返回首页|Archiver|手机版|小黑屋|易陆发现技术论坛 ( 蜀ICP备2026014127号-1 )

GMT+8, 2026-6-12 00:41 , Processed in 0.018731 second(s), 22 queries .

Powered by Discuz! X5.0

© 2001-2026 Discuz! Team.

快速回复 返回顶部 返回列表