易陆发现互联网技术论坛

 找回密码
 开始注册
查看: 432|回复: 1
收起左侧

jumpserver二次开发模块

[复制链接]
发表于 2023-3-23 17:00:05 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?开始注册

x
Jumpserver是全球首款完全开源的堡垒机,是符合 4A 的专业运维审计系统。 http://www.jumpserver.org  @; C: I% U& B/ |$ E5 t8 p# S" T

9 ?! L* i# `7 T8 n: F$ x/ njms很好用,但是引入公司内部,也会有一些问题,比如资产导入,资产和项目关联,如何和现有cmdb打通等问题。我这里只是抛砖引玉,介绍下jms如何二次开发,做企业定制化版的jumpserver,打通DevOps系统的方法。
; j9 V9 P- z  E( s首先,以增加项目管理 这个功能为例,说说堡垒机如何二次开发的。
1 v, h: N' M/ P. T
4 A  Q6 X7 s- J, h0 `# z基于版本 Version 1.4.10-2 GPLv2.
( L9 |) D- B# r: Z1 K0 z+ C) w) Z; ^; A7 J, Z& F5 B
一、概述$ G' J2 q" J6 q* Y" Q' c5 P5 c, ~
Jumpserver用的是python的django框架开发,API 采用了DRF(Django REST framework),基于CBV(Class-based views)开发模式,因此,二次开发也是比较简单的,一般先定义model,然后写api,再使用template渲染,前端基本用datatables较多。5 o3 j. }6 |0 {& N% k+ B
. h6 x. R  d& k
实现效果:
' r$ }9 Z. v+ A4 F1 ?: |+ k1 s项目管理
2 a* @" i+ g2 G. Q" g! e7 ~项目详情3 Z" S4 I9 ^3 ]+ R/ U9 `
项目详情2
5 d  B7 u4 R  S# ?( x
) b) `: ]/ \- I7 U" M; J二、实战) H1 y  Q5 V+ l# d: H# n
项目(project)管理或者说是服务(service)管理,服务和主机是有关系的,我这里一个主机可以部署多个服务,同时一个服务也可以部署在多台主机上,所以是多堆多的关系
  Y, h7 M5 r6 o) |6 N
: @* W7 R% Z0 i2.1 定义project model
2 D; o+ M8 i1 D' Mjumpserver/apps/assets/models/project.py
8 Q% R" c+ g, m% W8 x1 x* T+ `/ _0 b) L6 y4 T$ u

% f/ a( M5 `0 V- G* ]8 C' j9 Z) s
#!/usr/bin/env python
$ U& I" y- c0 _% l; y0 n& l: \# -*- coding: utf-8 -*-
) e& d) \6 u+ U1 O7 _$ Q" u( Z8 Q& l- \9 I1 P0 p7 {; c  L' X
from __future__ import unicode_literals
6 m  B/ e7 x0 Y7 C6 i2 q' l$ q8 p' `
from django.db import models+ ?% f2 p: n2 c5 ~
import logging
; e. A' s8 O( j0 Vfrom django.utils.translation import ugettext_lazy as _
( K7 K  d) t( @! t  U$ H4 |2 z. Limport uuid8 r1 l# }8 l6 \* p0 e- b
from django.core.cache import cache
: H$ g/ ~* l7 i9 Y* Y
. A! o* t$ P% D7 F! g( h__all__ = ['Project', 'TYPE_CHOICES']$ ?6 P3 S' ~- j- K% l" X
logger = logging.getLogger(__name__)# Z, d1 ~- a& U5 I7 q0 |8 e( h
% Q4 S( B+ ]$ L( z8 M) g
TYPE_CHOICES = (
+ i! O* `2 Q) i4 l0 ^7 r        ('Java', 'java'),
. f- O3 t" v) m6 O# g        ('React', 'react')," h2 S+ |: d5 b$ _7 C5 y# l& G
        ('Cpp', 'c++'),
/ R! L2 ~8 E& P+ _4 ?, b* }  I+ L7 V        ('Python', 'python')," o9 j2 ~: R1 E( v4 b, B, I
        ('Middleware', 'Middleware'),4 ^5 D" N' y* j- l
        ('Other', 'Other'),; z8 `: B4 D5 |$ K2 V" g  ^  d5 x
    )
& G; Y; a- Q) ]2 x" F
2 ]; l' L% b4 `+ t% ?
! @% |0 v- q/ X4 V; w6 D9 w! pclass Project(models.Model):
0 m: E" U' f: U2 F    TYPE_CHOICES =TYPE_CHOICES) z3 t9 Q. }" W# |% F7 c

! p) N7 o$ o, o: M" ^; h7 I    id = models.UUIDField(default=uuid.uuid4, primary_key=True)
" v. l7 N# m9 v4 }6 k7 h    name = models.CharField(max_length=64, unique=True, verbose_name=_('Name'))7 o# C& Z0 }+ B3 s( p1 ]  K) w
9 \& D! f  ?% ~/ e$ b8 S2 E8 }2 O
    parent = models.ForeignKey('self', null=True, blank=True, related_name="children", on_delete=models.SET_NULL)
( O! s* t2 y9 s8 v8 p! @
7 f! Z7 K: Y1 D1 t, `& \    domain_name = models.CharField(max_length=64, blank=True, verbose_name=_('Domain Name'))4 r& G# z) h8 w0 r' n8 ?" B. I
    git_address = models.CharField(max_length=128, blank=True, verbose_name=_('Git Address'))
, m; Z' C8 g1 u1 K$ h" e  L    port = models.IntegerField(blank=True, null=True, verbose_name=_('Open port'))
0 ~* f& [1 ^1 N  O0 e! `1 ]    type = models.CharField(choices=TYPE_CHOICES, max_length=16, blank=True, null=True,
/ |5 Z; r4 {# p( p7 S                            default='Java', verbose_name=_('Project type'), )
2 k* ^4 z) v" C% v; ^! |    dev_users = models.ManyToManyField(( e& Y( S2 J) f6 `
        'users.User', related_name='users1',
9 J* e, w6 P3 p, {        blank=True, verbose_name=_('Dev Users')
9 m9 u3 w2 o/ Y    )/ h. n  h9 b# p- A0 u( V9 P3 E
    sa_users = models.ManyToManyField(
7 U6 H- s& |; j: B        'users.User', related_name='users2',0 b  x+ [9 C' r( E
        blank=True, verbose_name=_('Sa Users')
9 u! g! d9 Z, y/ c8 ^( v& A, T    )
% S8 T" u9 {* p0 @+ }2 X    scrum_master = models.ForeignKey('users.User', on_delete=models.SET_NULL, null=True, verbose_name=_('Scrum Master'))
: z, f$ N) q) ^- V" A/ `    created_by = models.CharField(max_length=32, blank=True, verbose_name=_('Created by'))
  [; K3 p2 ~* q3 b+ p    updated_by = models.CharField(max_length=32, blank=True, verbose_name=_('Updated by'))+ A/ n* g0 g! u/ |) S% A! a3 ]% W
    date_created = models.DateTimeField(auto_now_add=True, null=True, verbose_name=_('Date created'))1 L& o# T8 u0 p# Y
    date_updated = models.DateTimeField(auto_now=True, null=True, verbose_name=_('Date updated'))! r6 s- Z4 H. D- `) B" K' U
    comment = models.TextField(blank=True, verbose_name=_('Comment'))
. J1 _' Q1 w3 ^+ M- B0 l; L    level = models.IntegerField(default=0, verbose_name=_('Level'))5 X4 B5 e, V5 L/ A" c

7 K% g# A$ J- l( E% L5 w    def __unicode__(self):2 q4 F$ Z3 Z/ b. s8 T$ O" h
        return self.name
8 [7 A( p* e5 W    __str__ = __unicode__
8 f$ Z/ Z: v6 t2 }6 @) f! i, r/ A% j3 B& M% l' I" ~( s: P( S9 [* ?
    @property: M, k: o3 w$ A9 h
    def all_sa_users(self):
/ f4 ~" m: M) C" I8 l  g0 d* k        return [user.name for user in self.sa_users.all()]
* X! i  [1 V5 J+ q' X/ D2 [# X0 h6 X. S  R
    @property
) u) P  U0 _' S. u1 [5 C6 L    def all_dev_users(self):: d2 e2 i5 }& D
        return [user.name for user in self.dev_users.all()]# a( H8 ?( C6 O' B- v/ V4 N
$ N$ v% X% |" U& L0 a) A
    def get_related_assets(self):
5 h! }" D$ q2 Z' |        assets = self.assets.all()' N2 k6 g& c* b7 P1 K  K
        return assets7 a) Z! T' {' N: v
# n/ |( {" s& o* V3 k7 Y2 `& B* V/ B
    def get_related_assets_ip(self):9 r. |" p( P% `; B
        assets = self.assets.all()
: k, M* \" K9 K& ~        return [{'ip': asset.ip, 'environment': asset.environment} for asset in assets]
; a6 ^* ]3 `7 V2 t4 E
, C# s% t% b  u* l+ a+ e    @property
. k! C( R! V8 n% a+ M0 N    def all_assets_ip(self):) S' m/ a2 Y- g& u2 X8 O, u. p
        return self.get_related_assets_ip()
: z% e' W2 P; Z' T5 d2 [! ^  r& l: @9 G' B& i! q8 {
    class Meta:
! G" |# r8 s7 Q1 U        ordering = ['name']
+ Q/ S( b5 o  c% N2.2 修改asset model
+ ?9 K' l( K( v+ R9 ^; a' Ijumpserver/apps/assets/models/asset.py
/ e# B9 S# p. r# x& H7 @% n! S2 j2 [0 |5 P; O$ F9 d& _8 I) ?

) d( _5 }7 Q+ t/ b/ m0 q/ K7 c' ~5 \! r
# 增加环境定义) b/ _  e1 C' O5 B- C* s1 D
ENV_CHOICES = (
, _9 ^6 G/ L) ]  D& C( I    ('DEV', 'dev'),( A" d2 [7 g9 C- N9 z
    ('TEST', 'test'),& @( J9 {/ B+ h+ j* f4 g  @
    ('DEMO', 'demo'),
. U7 l! ?, t3 f    ('TDEMO', 'tdemo'),* @! g3 K! V! K6 N! E2 t" ~' F
    ('PROD', 'prod'),4 x  f+ K4 a9 Y" r& O+ F
    ('Other', 'other'),, z5 `4 o* J% ~6 [
)( x! e3 y3 F+ D9 i. q; [9 g2 Y
environment = models.CharField(max_length=32, choices=ENV_CHOICES, default='DEV', verbose_name=_('Environment')): r% g5 w6 g: o+ h3 r, A
# Project 和 asset 多对多关系
$ d" x/ Y" Q- B4 r' r* U7 N8 xprojects = models.ManyToManyField(Project, blank=True, verbose_name=_('Projects'), related_name='assets', ). }. p/ q* _6 E8 ?$ k+ `4 g5 U( S4 D$ g
2.3 加入全局变量中init* i4 f- u6 _4 G9 t" w' I) N
jumpserver/apps/assets/models/init.py 末尾增加
/ w0 F: V; x; z0 d! q  i+ J; k" f$ L8 M% j6 {
1
% Q& |% h3 D3 }2 m0 `from .project import *
- ?6 E, d2 p/ S% ~) U2.4 定义DRF的serializers' ]% B  n- m4 {8 L" m$ }: {3 v
jumpserver/apps/assets/serializers/project.py
- ~) w- A& D; x9 ?: _" q5 t6 t4 I! ]
1 a1 k! S7 E% B3 P- e! U% M( b" I/ Z% [+ L9 p- ]- n

# P2 F( ?9 z  w3 [% n) Nfrom rest_framework_bulk import BulkListSerializer, BulkSerializerMixin4 @4 d0 q7 }6 K% R9 b. T3 B
from rest_framework import serializers& j( t0 a2 n4 v+ |' r. _
from ..models import Project, Asset
0 q6 o5 l: R( G+ k+ {8 y9 `# M2 q( u8 y0 V$ o. P( J

" i5 O; k: D+ n% ?) t! }' Iclass ProjectSerializer(serializers.ModelSerializer):
2 V1 b2 d# R1 r5 |/ d" {    asset_count = serializers.SerializerMethodField()9 j  C* S$ n) x1 u3 p8 r
0 e& s% {, ]. Z  o" u
    class Meta:& y7 O' E7 `2 k8 p; W
        model = Project
* N1 E2 l% Y) k7 @0 F: R        fields = '__all__'& _+ ?, Y+ o; E3 a8 n  p
5 J8 e$ e8 g" @+ P: ]: ^( Z8 P
    @staticmethod
- d' O7 S( z/ g9 `    def get_asset_count(obj):
/ ]2 S' y$ U4 l$ k/ y9 U        return obj.assets.count()2 Y$ c3 `6 \  s3 j3 m

" l, k- U2 t4 b  Z- ]) k' h: `) j# D/ O
class ProjectAssetsSerializer(serializers.ModelSerializer):$ C/ C" M4 s, _- ]) e
    class Meta:0 X3 p, h+ l( m1 s* L
        fields = ['id', 'name', 'port']. y6 y7 S+ u/ z; ^3 n- {3 y1 |
        model = Project
9 L2 `) Q0 V5 f. j( ~- P( Y* |% A) B* V
    def get_field_names(self, declared_fields, info):5 H- B, Y: c9 U- h
        fields = super().get_field_names(declared_fields, info)
: w* U9 ?6 U. ~, [( n) I        fields.extend([% [/ n' ^0 @! j: W
            'all_assets_ip'! Q1 w( P% n7 p: _+ u; B8 v
        ])' Z$ X* C5 B- T7 d
        return fields$ u9 ]# o% E: m# ]* x( M& W
% R1 t- w5 x$ M! @' y" c. n

; r* v# H6 N/ }8 c" Aclass ProjectUpdateSerializer(serializers.ModelSerializer):  s* i  m/ U. V# D
    """update the project, and add or delete the asset to the project"""
, j# {% n1 }& s1 b, u0 ]    assets = serializers.PrimaryKeyRelatedField(many=True, queryset=Asset.objects.all())
% n! V4 }( ~$ {: F1 w$ V, c# j, S8 [! q' |9 k
    class Meta:% H; }/ w$ ^1 |3 W
        model = Project/ s6 M, R% `, P: b; u& z' z
        fields = ['id', 'assets']0 d. _7 T! e: d2 M: a3 V
+ x- N6 t& f- T4 b2 K* a

2 v2 W: W! ]# y" o* yclass AssetUpdateProjectSerializer(serializers.ModelSerializer):
$ p. L2 r: `& j( S7 O* l2 v    projects = serializers.PrimaryKeyRelatedField(many=True, queryset=Project.objects.all())
7 [4 a/ l$ x; F% Z" Z$ o1 I* N  A) V$ ]+ R3 I, _, B( b
    class Meta:9 h$ i3 v# g' y% R
        model = Asset
" R4 |+ @) c) b6 p& g5 I8 [        fields = ['id', 'projects']
' {6 \! }4 C; i; Q2 B2.5 增加URl支持
7 ^, N3 y7 N* C+ N" v/ d2 o& N/jumpserver/apps/assets/urls/api_urls.py
! f& y; m2 o0 E, j; ]' A' e; i
5 W+ w3 b( B/ F. k7 S* c+ T5 \) [3 \5 n; c# U+ N: R
, h% R, V+ {# Z- j; h$ s, y
router.register(r'projects', api.ProjectViewSet, 'project'), X( i% ^2 t0 Z6 G+ u2 D
router.register(r'projects-assets', api.ProjectAssetsViewSet, 'project-assets-view')
9 D) g, b( a6 c
  I+ |' h+ m8 V* k# urlpatterns 增加:
- n$ n& A7 B( I7 B- z8 S; I. S- a    path(r'assets/<uuid:pk>/projects/',  w* i) d' f/ f3 }
            api.AssetUpdateProjectApi.as_view(), name='asset-update-project'),
6 T& [! Z- M5 P$ N    path(r'projects/<uuid:pk>/assets/',) }+ c; m/ Y8 C/ I( m' Y+ M; Z
            api.ProjectUpdateApi.as_view(), name='project-update-assets'),8 q5 L9 W7 `, X5 x
    path('project/<uuid:pk>/assets/',8 Y) [0 ~" B$ \7 i5 R1 p% M
            api.ProjectAssetsListView.as_view(), name='project-assets'),
' N9 Q$ Z3 Z, x7 B0 x* F1 OAPI到这里基本写完了,至于前端的一些,都是调用这些api,进行增删改查操作,详情见github/ C0 q7 c; {+ _$ ]9 }: i' T

, p" d" M- H2 @& s8 O# X3 i& l3 数据库迁移
, h- M/ b0 t. g3 v& ?0 J* A" e( [定义了模型以后,需要执行migrate命令进行数据库变更,省了自己执行sql: i4 d$ f$ {% {- Z

! d: f: z2 t  |
9 x7 x9 J2 d* F# y2 x1 c
' u  R: A2 |3 z8 `! s! D: [* [cd jumpserver/utils. v; x( P! V! \* i
bash make_migrations.sh
& C, T" P8 g3 Q3 a" v4 国际化
7 b9 W# J4 @# s9 t0 N9 g! R! |2 I因为我们定义的好多变量都是英文的,这里可以通过国际化手段支持中英文& f- T7 g! ?. S9 H% D5 U# \9 k' D, G
1 V+ n0 e  G; j3 `( s- g5 \

' `; J: [& ~2 m9 ]4 o7 B) u, f; F& b7 {: f
# 先执行
1 X& }. h2 Y4 F) \- \cd jumpserver/apps2 S4 P, J. }7 u
python manage.py makemessages -l zh
& h3 b! h. L: j6 B! b" b* i# 修改jumpserver/apps/locale/zh/LC_MESSAGES/django.po 的中英文对应
: s; E! r6 d: z0 U+ a( kvim jumpserver/apps/locale/zh/LC_MESSAGES/django.po) ]. d  R# A- a) K7 T' B& \
# 修改完成后生效,完全生效需要服务重启# V* }( E8 G* ^% T) @
python manage.py compilemessages
/ D1 o  E$ |* Z, n) {1 u
1 z. V$ Z1 O; C/ z4 ^9 e6 x+ F8 s6 F5 d: e! D* k7 ]$ Q7 ~: R

* C9 p1 y5 g$ q, G* N! a! |
 楼主| 发表于 2023-3-23 17:00:06 | 显示全部楼层
您需要登录后才可以回帖 登录 | 开始注册

本版积分规则

关闭

站长推荐上一条 /4 下一条

北京云银创陇科技有限公司以云计算运维,代码开发

QQ|返回首页|Archiver|小黑屋|易陆发现技术论坛 ( 蜀ICP备2026014127号-1 )点击这里给我发消息

GMT+8, 2026-4-8 21:40 , Processed in 0.054019 second(s), 22 queries .

Powered by Discuz! X3.4 Licensed

© 2012-2025 Discuz! Team.

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