mirror of
https://github.com/jumpserver/jumpserver.git
synced 2025-12-21 03:26:03 +00:00
Compare commits
11 Commits
rdp-patch-
...
v3.9.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
49c10dea12 | ||
|
|
73bed4d33d | ||
|
|
4c389c05c1 | ||
|
|
e744c4c8af | ||
|
|
06d1c9f420 | ||
|
|
a92023840a | ||
|
|
2d1bf866fa | ||
|
|
c606c3eb21 | ||
|
|
dabbb45f6e | ||
|
|
ce24c1c3fd | ||
|
|
3c54c82ce9 |
11
.github/ISSUE_TEMPLATE/----.md
vendored
Normal file
11
.github/ISSUE_TEMPLATE/----.md
vendored
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
---
|
||||||
|
name: 需求建议
|
||||||
|
about: 提出针对本项目的想法和建议
|
||||||
|
title: "[Feature] "
|
||||||
|
labels: 类型:需求
|
||||||
|
assignees:
|
||||||
|
- ibuler
|
||||||
|
- baijiangjie
|
||||||
|
---
|
||||||
|
|
||||||
|
**请描述您的需求或者改进建议.**
|
||||||
72
.github/ISSUE_TEMPLATE/1_bug_report.yml
vendored
72
.github/ISSUE_TEMPLATE/1_bug_report.yml
vendored
@@ -1,72 +0,0 @@
|
|||||||
name: '🐛 Bug Report'
|
|
||||||
description: 'Report an Bug'
|
|
||||||
title: '[Bug] '
|
|
||||||
labels: ['🐛 Bug']
|
|
||||||
assignees:
|
|
||||||
- baijiangjie
|
|
||||||
body:
|
|
||||||
- type: input
|
|
||||||
attributes:
|
|
||||||
label: 'Product Version'
|
|
||||||
description: The versions prior to v2.28 (inclusive) are no longer supported.
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
|
|
||||||
- type: checkboxes
|
|
||||||
attributes:
|
|
||||||
label: 'Product Edition'
|
|
||||||
options:
|
|
||||||
- label: 'Community Edition'
|
|
||||||
- label: 'Enterprise Edition'
|
|
||||||
- label: 'Enterprise Trial Edition'
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
|
|
||||||
- type: checkboxes
|
|
||||||
attributes:
|
|
||||||
label: 'Installation Method'
|
|
||||||
options:
|
|
||||||
- label: 'Online Installation (One-click command installation)'
|
|
||||||
- label: 'Offline Package Installation'
|
|
||||||
- label: 'All-in-One'
|
|
||||||
- label: '1Panel'
|
|
||||||
- label: 'Kubernetes'
|
|
||||||
- label: 'Source Code'
|
|
||||||
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: 'Environment Information'
|
|
||||||
description: Please provide a clear and concise description outlining your environment information.
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: '🐛 Bug Description'
|
|
||||||
description:
|
|
||||||
Please provide a clear and concise description of the defect. If the issue is complex, please provide detailed explanations. <br/>
|
|
||||||
Unclear descriptions will not be processed. Please ensure you provide enough detail and information to support replicating and fixing the defect.
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: 'Recurrence Steps'
|
|
||||||
description: Please provide a clear and concise description outlining how to reproduce the issue.
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: 'Expected Behavior'
|
|
||||||
description: Please provide a clear and concise description of what you expect to happen.
|
|
||||||
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: 'Additional Information'
|
|
||||||
description: Please add any additional background information about the issue here.
|
|
||||||
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: 'Attempted Solutions'
|
|
||||||
description: If you have already attempted to solve the issue, please list the solutions you have tried here.
|
|
||||||
72
.github/ISSUE_TEMPLATE/1_bug_report_cn.yml
vendored
72
.github/ISSUE_TEMPLATE/1_bug_report_cn.yml
vendored
@@ -1,72 +0,0 @@
|
|||||||
name: '🐛 反馈缺陷'
|
|
||||||
description: '反馈一个缺陷'
|
|
||||||
title: '[Bug] '
|
|
||||||
labels: ['🐛 Bug']
|
|
||||||
assignees:
|
|
||||||
- baijiangjie
|
|
||||||
body:
|
|
||||||
- type: input
|
|
||||||
attributes:
|
|
||||||
label: '产品版本'
|
|
||||||
description: 不再支持 v2.28(含)之前的版本。
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
|
|
||||||
- type: checkboxes
|
|
||||||
attributes:
|
|
||||||
label: '版本类型'
|
|
||||||
options:
|
|
||||||
- label: '社区版'
|
|
||||||
- label: '企业版'
|
|
||||||
- label: '企业试用版'
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
|
|
||||||
- type: checkboxes
|
|
||||||
attributes:
|
|
||||||
label: '安装方式'
|
|
||||||
options:
|
|
||||||
- label: '在线安装 (一键命令安装)'
|
|
||||||
- label: '离线包安装'
|
|
||||||
- label: 'All-in-One'
|
|
||||||
- label: '1Panel'
|
|
||||||
- label: 'Kubernetes'
|
|
||||||
- label: '源码安装'
|
|
||||||
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: '环境信息'
|
|
||||||
description: 请提供一个清晰且简洁的描述,说明你的环境信息。
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: '🐛 缺陷描述'
|
|
||||||
description: |
|
|
||||||
请提供一个清晰且简洁的缺陷描述,如果问题比较复杂,也请详细说明。<br/>
|
|
||||||
针对不清晰的描述信息将不予处理。请确保提供足够的细节和信息,以支持对缺陷进行复现和修复。
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: '复现步骤'
|
|
||||||
description: 请提供一个清晰且简洁的描述,说明如何复现问题。
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: '期望结果'
|
|
||||||
description: 请提供一个清晰且简洁的描述,说明你期望发生什么。
|
|
||||||
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: '补充信息'
|
|
||||||
description: 在这里添加关于问题的任何其他背景信息。
|
|
||||||
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: '尝试过的解决方案'
|
|
||||||
description: 如果你已经尝试解决问题,请在此列出你尝试过的解决方案。
|
|
||||||
56
.github/ISSUE_TEMPLATE/2_feature_request.yml
vendored
56
.github/ISSUE_TEMPLATE/2_feature_request.yml
vendored
@@ -1,56 +0,0 @@
|
|||||||
name: '⭐️ Feature Request'
|
|
||||||
description: 'Suggest an idea'
|
|
||||||
title: '[Feature] '
|
|
||||||
labels: ['⭐️ Feature Request']
|
|
||||||
assignees:
|
|
||||||
- baijiangjie
|
|
||||||
- ibuler
|
|
||||||
body:
|
|
||||||
- type: input
|
|
||||||
attributes:
|
|
||||||
label: 'Product Version'
|
|
||||||
description: The versions prior to v2.28 (inclusive) are no longer supported.
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
|
|
||||||
- type: checkboxes
|
|
||||||
attributes:
|
|
||||||
label: 'Product Edition'
|
|
||||||
options:
|
|
||||||
- label: 'Community Edition'
|
|
||||||
- label: 'Enterprise Edition'
|
|
||||||
- label: 'Enterprise Trial Edition'
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
|
|
||||||
- type: checkboxes
|
|
||||||
attributes:
|
|
||||||
label: 'Installation Method'
|
|
||||||
options:
|
|
||||||
- label: 'Online Installation (One-click command installation)'
|
|
||||||
- label: 'Offline Package Installation'
|
|
||||||
- label: 'All-in-One'
|
|
||||||
- label: '1Panel'
|
|
||||||
- label: 'Kubernetes'
|
|
||||||
- label: 'Source Code'
|
|
||||||
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: '⭐️ Feature Description'
|
|
||||||
description: |
|
|
||||||
Please add a clear and concise description of the problem you aim to solve with this feature request.<br/>
|
|
||||||
Unclear descriptions will not be processed.
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: 'Proposed Solution'
|
|
||||||
description: Please provide a clear and concise description of the solution you desire.
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: 'Additional Information'
|
|
||||||
description: Please add any additional background information about the issue here.
|
|
||||||
56
.github/ISSUE_TEMPLATE/2_feature_request_cn.yml
vendored
56
.github/ISSUE_TEMPLATE/2_feature_request_cn.yml
vendored
@@ -1,56 +0,0 @@
|
|||||||
name: '⭐️ 功能需求'
|
|
||||||
description: '提出需求或建议'
|
|
||||||
title: '[Feature] '
|
|
||||||
labels: ['⭐️ Feature Request']
|
|
||||||
assignees:
|
|
||||||
- baijiangjie
|
|
||||||
- ibuler
|
|
||||||
body:
|
|
||||||
- type: input
|
|
||||||
attributes:
|
|
||||||
label: '产品版本'
|
|
||||||
description: 不再支持 v2.28(含)之前的版本。
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
|
|
||||||
- type: checkboxes
|
|
||||||
attributes:
|
|
||||||
label: '版本类型'
|
|
||||||
options:
|
|
||||||
- label: '社区版'
|
|
||||||
- label: '企业版'
|
|
||||||
- label: '企业试用版'
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
|
|
||||||
- type: checkboxes
|
|
||||||
attributes:
|
|
||||||
label: '安装方式'
|
|
||||||
options:
|
|
||||||
- label: '在线安装 (一键命令安装)'
|
|
||||||
- label: '离线包安装'
|
|
||||||
- label: 'All-in-One'
|
|
||||||
- label: '1Panel'
|
|
||||||
- label: 'Kubernetes'
|
|
||||||
- label: '源码安装'
|
|
||||||
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: '⭐️ 需求描述'
|
|
||||||
description: |
|
|
||||||
请添加一个清晰且简洁的问题描述,阐述你希望通过这个功能需求解决的问题。<br/>
|
|
||||||
针对不清晰的描述信息将不予处理。
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: '解决方案'
|
|
||||||
description: 请清晰且简洁地描述你想要的解决方案。
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: '补充信息'
|
|
||||||
description: 在这里添加关于问题的任何其他背景信息。
|
|
||||||
60
.github/ISSUE_TEMPLATE/3_question.yml
vendored
60
.github/ISSUE_TEMPLATE/3_question.yml
vendored
@@ -1,60 +0,0 @@
|
|||||||
name: '🤔 Question'
|
|
||||||
description: 'Pose a question'
|
|
||||||
title: '[Question] '
|
|
||||||
labels: ['🤔 Question']
|
|
||||||
assignees:
|
|
||||||
- baijiangjie
|
|
||||||
body:
|
|
||||||
- type: input
|
|
||||||
attributes:
|
|
||||||
label: 'Product Version'
|
|
||||||
description: The versions prior to v2.28 (inclusive) are no longer supported.
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
|
|
||||||
- type: checkboxes
|
|
||||||
attributes:
|
|
||||||
label: 'Product Edition'
|
|
||||||
options:
|
|
||||||
- label: 'Community Edition'
|
|
||||||
- label: 'Enterprise Edition'
|
|
||||||
- label: 'Enterprise Trial Edition'
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
|
|
||||||
- type: checkboxes
|
|
||||||
attributes:
|
|
||||||
label: 'Installation Method'
|
|
||||||
options:
|
|
||||||
- label: 'Online Installation (One-click command installation)'
|
|
||||||
- label: 'Offline Package Installation'
|
|
||||||
- label: 'All-in-One'
|
|
||||||
- label: '1Panel'
|
|
||||||
- label: 'Kubernetes'
|
|
||||||
- label: 'Source Code'
|
|
||||||
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: 'Environment Information'
|
|
||||||
description: Please provide a clear and concise description outlining your environment information.
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: '🤔 Question Description'
|
|
||||||
description: |
|
|
||||||
Please provide a clear and concise description of the defect. If the issue is complex, please provide detailed explanations. <br/>
|
|
||||||
Unclear descriptions will not be processed.
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: 'Expected Behavior'
|
|
||||||
description: Please provide a clear and concise description of what you expect to happen.
|
|
||||||
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: 'Additional Information'
|
|
||||||
description: Please add any additional background information about the issue here.
|
|
||||||
61
.github/ISSUE_TEMPLATE/3_question_cn.yml
vendored
61
.github/ISSUE_TEMPLATE/3_question_cn.yml
vendored
@@ -1,61 +0,0 @@
|
|||||||
name: '🤔 问题咨询'
|
|
||||||
description: '提出一个问题'
|
|
||||||
title: '[Question] '
|
|
||||||
labels: ['🤔 Question']
|
|
||||||
assignees:
|
|
||||||
- baijiangjie
|
|
||||||
body:
|
|
||||||
- type: input
|
|
||||||
attributes:
|
|
||||||
label: '产品版本'
|
|
||||||
description: 不再支持 v2.28(含)之前的版本。
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
|
|
||||||
- type: checkboxes
|
|
||||||
attributes:
|
|
||||||
label: '版本类型'
|
|
||||||
options:
|
|
||||||
- label: '社区版'
|
|
||||||
- label: '企业版'
|
|
||||||
- label: '企业试用版'
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
|
|
||||||
- type: checkboxes
|
|
||||||
attributes:
|
|
||||||
label: '安装方式'
|
|
||||||
options:
|
|
||||||
- label: '在线安装 (一键命令安装)'
|
|
||||||
- label: '离线包安装'
|
|
||||||
- label: 'All-in-One'
|
|
||||||
- label: '1Panel'
|
|
||||||
- label: 'Kubernetes'
|
|
||||||
- label: '源码安装'
|
|
||||||
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: '环境信息'
|
|
||||||
description: 请在此详细描述你的环境信息,如操作系统、浏览器和部署架构等。
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: '🤔 问题描述'
|
|
||||||
description: |
|
|
||||||
请提供一个清晰且简洁的问题描述,如果问题比较复杂,也请详细说明。<br/>
|
|
||||||
针对不清晰的描述信息将不予处理。
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: '期望结果'
|
|
||||||
description: 请提供一个清晰且简洁的描述,说明你期望发生什么。
|
|
||||||
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: '补充信息'
|
|
||||||
description: 在这里添加关于问题的任何其他背景信息。
|
|
||||||
|
|
||||||
22
.github/ISSUE_TEMPLATE/bug---.md
vendored
Normal file
22
.github/ISSUE_TEMPLATE/bug---.md
vendored
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
---
|
||||||
|
name: Bug 提交
|
||||||
|
about: 提交产品缺陷帮助我们更好的改进
|
||||||
|
title: "[Bug] "
|
||||||
|
labels: 类型:Bug
|
||||||
|
assignees:
|
||||||
|
- baijiangjie
|
||||||
|
---
|
||||||
|
|
||||||
|
**JumpServer 版本( v2.28 之前的版本不再支持 )**
|
||||||
|
|
||||||
|
|
||||||
|
**浏览器版本**
|
||||||
|
|
||||||
|
|
||||||
|
**Bug 描述**
|
||||||
|
|
||||||
|
|
||||||
|
**Bug 重现步骤(有截图更好)**
|
||||||
|
1.
|
||||||
|
2.
|
||||||
|
3.
|
||||||
10
.github/ISSUE_TEMPLATE/question.md
vendored
Normal file
10
.github/ISSUE_TEMPLATE/question.md
vendored
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
---
|
||||||
|
name: 问题咨询
|
||||||
|
about: 提出针对本项目安装部署、使用及其他方面的相关问题
|
||||||
|
title: "[Question] "
|
||||||
|
labels: 类型:提问
|
||||||
|
assignees:
|
||||||
|
- baijiangjie
|
||||||
|
---
|
||||||
|
|
||||||
|
**请描述您的问题.**
|
||||||
4
.github/workflows/issue-close-require.yml
vendored
4
.github/workflows/issue-close-require.yml
vendored
@@ -12,9 +12,7 @@ jobs:
|
|||||||
uses: actions-cool/issues-helper@v2
|
uses: actions-cool/issues-helper@v2
|
||||||
with:
|
with:
|
||||||
actions: 'close-issues'
|
actions: 'close-issues'
|
||||||
labels: '⏳ Pending feedback'
|
labels: '状态:待反馈'
|
||||||
inactive-day: 30
|
inactive-day: 30
|
||||||
body: |
|
body: |
|
||||||
You haven't provided feedback for over 30 days.
|
|
||||||
We will close this issue. If you have any further needs, you can reopen it or submit a new issue.
|
|
||||||
您超过 30 天未反馈信息,我们将关闭该 issue,如有需求您可以重新打开或者提交新的 issue。
|
您超过 30 天未反馈信息,我们将关闭该 issue,如有需求您可以重新打开或者提交新的 issue。
|
||||||
|
|||||||
2
.github/workflows/issue-close.yml
vendored
2
.github/workflows/issue-close.yml
vendored
@@ -13,4 +13,4 @@ jobs:
|
|||||||
if: ${{ !github.event.issue.pull_request }}
|
if: ${{ !github.event.issue.pull_request }}
|
||||||
with:
|
with:
|
||||||
actions: 'remove-labels'
|
actions: 'remove-labels'
|
||||||
labels: '🔔 Pending processing,⏳ Pending feedback'
|
labels: '状态:待处理,状态:待反馈'
|
||||||
8
.github/workflows/issue-comment.yml
vendored
8
.github/workflows/issue-comment.yml
vendored
@@ -13,13 +13,13 @@ jobs:
|
|||||||
uses: actions-cool/issues-helper@v2
|
uses: actions-cool/issues-helper@v2
|
||||||
with:
|
with:
|
||||||
actions: 'add-labels'
|
actions: 'add-labels'
|
||||||
labels: '🔔 Pending processing'
|
labels: '状态:待处理'
|
||||||
|
|
||||||
- name: Remove require reply label
|
- name: Remove require reply label
|
||||||
uses: actions-cool/issues-helper@v2
|
uses: actions-cool/issues-helper@v2
|
||||||
with:
|
with:
|
||||||
actions: 'remove-labels'
|
actions: 'remove-labels'
|
||||||
labels: '⏳ Pending feedback'
|
labels: '状态:待反馈'
|
||||||
|
|
||||||
add-label-if-is-member:
|
add-label-if-is-member:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@@ -55,11 +55,11 @@ jobs:
|
|||||||
uses: actions-cool/issues-helper@v2
|
uses: actions-cool/issues-helper@v2
|
||||||
with:
|
with:
|
||||||
actions: 'add-labels'
|
actions: 'add-labels'
|
||||||
labels: '⏳ Pending feedback'
|
labels: '状态:待反馈'
|
||||||
|
|
||||||
- name: Remove require handle label
|
- name: Remove require handle label
|
||||||
if: contains(steps.member_names.outputs.data, github.event.comment.user.login)
|
if: contains(steps.member_names.outputs.data, github.event.comment.user.login)
|
||||||
uses: actions-cool/issues-helper@v2
|
uses: actions-cool/issues-helper@v2
|
||||||
with:
|
with:
|
||||||
actions: 'remove-labels'
|
actions: 'remove-labels'
|
||||||
labels: '🔔 Pending processing'
|
labels: '状态:待处理'
|
||||||
|
|||||||
2
.github/workflows/issue-open.yml
vendored
2
.github/workflows/issue-open.yml
vendored
@@ -13,4 +13,4 @@ jobs:
|
|||||||
if: ${{ !github.event.issue.pull_request }}
|
if: ${{ !github.event.issue.pull_request }}
|
||||||
with:
|
with:
|
||||||
actions: 'add-labels'
|
actions: 'add-labels'
|
||||||
labels: '🔔 Pending processing'
|
labels: '状态:待处理'
|
||||||
45
.github/workflows/jms-build-test.yml
vendored
45
.github/workflows/jms-build-test.yml
vendored
@@ -1,32 +1,26 @@
|
|||||||
name: "Run Build Test"
|
name: "Run Build Test"
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
paths:
|
branches:
|
||||||
- 'Dockerfile'
|
- pr@*
|
||||||
- 'Dockerfile-*'
|
- repr@*
|
||||||
- 'pyproject.toml'
|
|
||||||
- 'poetry.lock'
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- uses: docker/setup-qemu-action@v3
|
|
||||||
- uses: docker/setup-buildx-action@v3
|
|
||||||
|
|
||||||
- name: Check Dockerfile
|
- uses: docker/setup-qemu-action@v2
|
||||||
run: |
|
|
||||||
test -f Dockerfile-ce || cp -f Dockerfile Dockerfile-ce
|
|
||||||
|
|
||||||
- name: Build CE Image
|
- uses: docker/setup-buildx-action@v2
|
||||||
uses: docker/build-push-action@v5
|
|
||||||
|
- uses: docker/build-push-action@v3
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
push: false
|
push: false
|
||||||
file: Dockerfile-ce
|
|
||||||
tags: jumpserver/core-ce:test
|
tags: jumpserver/core-ce:test
|
||||||
platforms: linux/amd64
|
file: Dockerfile-ce
|
||||||
build-args: |
|
build-args: |
|
||||||
APT_MIRROR=http://deb.debian.org
|
APT_MIRROR=http://deb.debian.org
|
||||||
PIP_MIRROR=https://pypi.org/simple
|
PIP_MIRROR=https://pypi.org/simple
|
||||||
@@ -34,22 +28,9 @@ jobs:
|
|||||||
cache-from: type=gha
|
cache-from: type=gha
|
||||||
cache-to: type=gha,mode=max
|
cache-to: type=gha,mode=max
|
||||||
|
|
||||||
- name: Prepare EE Image
|
- uses: LouisBrunner/checks-action@v1.5.0
|
||||||
run: |
|
if: always()
|
||||||
sed -i 's@^FROM registry.fit2cloud.com@# FROM registry.fit2cloud.com@g' Dockerfile-ee
|
|
||||||
sed -i 's@^COPY --from=build-xpack@# COPY --from=build-xpack@g' Dockerfile-ee
|
|
||||||
|
|
||||||
- name: Build EE Image
|
|
||||||
uses: docker/build-push-action@v5
|
|
||||||
with:
|
with:
|
||||||
context: .
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
push: false
|
name: Check Build
|
||||||
file: Dockerfile-ee
|
conclusion: ${{ job.status }}
|
||||||
tags: jumpserver/core-ee:test
|
|
||||||
platforms: linux/amd64
|
|
||||||
build-args: |
|
|
||||||
APT_MIRROR=http://deb.debian.org
|
|
||||||
PIP_MIRROR=https://pypi.org/simple
|
|
||||||
PIP_JMS_MIRROR=https://pypi.org/simple
|
|
||||||
cache-from: type=gha
|
|
||||||
cache-to: type=gha,mode=max
|
|
||||||
|
|||||||
@@ -10,4 +10,3 @@ jobs:
|
|||||||
- uses: jumpserver/action-generic-handler@master
|
- uses: jumpserver/action-generic-handler@master
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.PRIVATE_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.PRIVATE_TOKEN }}
|
||||||
I18N_TOKEN: ${{ secrets.I18N_TOKEN }}
|
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -43,4 +43,3 @@ releashe
|
|||||||
data/*
|
data/*
|
||||||
test.py
|
test.py
|
||||||
.history/
|
.history/
|
||||||
.test/
|
|
||||||
|
|||||||
137
Dockerfile
137
Dockerfile
@@ -1,137 +0,0 @@
|
|||||||
FROM python:3.11-slim-bullseye AS stage-1
|
|
||||||
ARG TARGETARCH
|
|
||||||
|
|
||||||
ARG VERSION
|
|
||||||
ENV VERSION=$VERSION
|
|
||||||
|
|
||||||
WORKDIR /opt/jumpserver
|
|
||||||
ADD . .
|
|
||||||
RUN echo > /opt/jumpserver/config.yml \
|
|
||||||
&& cd utils && bash -ixeu build.sh
|
|
||||||
|
|
||||||
FROM python:3.11-slim-bullseye as stage-2
|
|
||||||
ARG TARGETARCH
|
|
||||||
|
|
||||||
ARG BUILD_DEPENDENCIES=" \
|
|
||||||
g++ \
|
|
||||||
make \
|
|
||||||
pkg-config"
|
|
||||||
|
|
||||||
ARG DEPENDENCIES=" \
|
|
||||||
freetds-dev \
|
|
||||||
libffi-dev \
|
|
||||||
libjpeg-dev \
|
|
||||||
libkrb5-dev \
|
|
||||||
libldap2-dev \
|
|
||||||
libpq-dev \
|
|
||||||
libsasl2-dev \
|
|
||||||
libssl-dev \
|
|
||||||
libxml2-dev \
|
|
||||||
libxmlsec1-dev \
|
|
||||||
libxmlsec1-openssl \
|
|
||||||
freerdp2-dev \
|
|
||||||
libaio-dev"
|
|
||||||
|
|
||||||
ARG TOOLS=" \
|
|
||||||
ca-certificates \
|
|
||||||
curl \
|
|
||||||
default-libmysqlclient-dev \
|
|
||||||
default-mysql-client \
|
|
||||||
git \
|
|
||||||
git-lfs \
|
|
||||||
unzip \
|
|
||||||
xz-utils \
|
|
||||||
wget"
|
|
||||||
|
|
||||||
ARG APT_MIRROR=http://mirrors.ustc.edu.cn
|
|
||||||
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked,id=core-apt \
|
|
||||||
--mount=type=cache,target=/var/lib/apt,sharing=locked,id=core-apt \
|
|
||||||
sed -i "s@http://.*.debian.org@${APT_MIRROR}@g" /etc/apt/sources.list \
|
|
||||||
&& rm -f /etc/apt/apt.conf.d/docker-clean \
|
|
||||||
&& ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
|
|
||||||
&& apt-get update \
|
|
||||||
&& apt-get -y install --no-install-recommends ${BUILD_DEPENDENCIES} \
|
|
||||||
&& apt-get -y install --no-install-recommends ${DEPENDENCIES} \
|
|
||||||
&& apt-get -y install --no-install-recommends ${TOOLS} \
|
|
||||||
&& echo "no" | dpkg-reconfigure dash
|
|
||||||
|
|
||||||
WORKDIR /opt/jumpserver
|
|
||||||
|
|
||||||
ARG PIP_MIRROR=https://pypi.tuna.tsinghua.edu.cn/simple
|
|
||||||
RUN --mount=type=cache,target=/root/.cache \
|
|
||||||
--mount=type=bind,source=poetry.lock,target=/opt/jumpserver/poetry.lock \
|
|
||||||
--mount=type=bind,source=pyproject.toml,target=/opt/jumpserver/pyproject.toml \
|
|
||||||
set -ex \
|
|
||||||
&& python3 -m venv /opt/py3 \
|
|
||||||
&& pip install poetry -i ${PIP_MIRROR} \
|
|
||||||
&& poetry config virtualenvs.create false \
|
|
||||||
&& . /opt/py3/bin/activate \
|
|
||||||
&& poetry install
|
|
||||||
|
|
||||||
FROM python:3.11-slim-bullseye
|
|
||||||
ARG TARGETARCH
|
|
||||||
ENV LANG=zh_CN.UTF-8 \
|
|
||||||
PATH=/opt/py3/bin:$PATH
|
|
||||||
|
|
||||||
ARG DEPENDENCIES=" \
|
|
||||||
libjpeg-dev \
|
|
||||||
libpq-dev \
|
|
||||||
libx11-dev \
|
|
||||||
freerdp2-dev \
|
|
||||||
libxmlsec1-openssl"
|
|
||||||
|
|
||||||
ARG TOOLS=" \
|
|
||||||
ca-certificates \
|
|
||||||
curl \
|
|
||||||
default-libmysqlclient-dev \
|
|
||||||
default-mysql-client \
|
|
||||||
iputils-ping \
|
|
||||||
locales \
|
|
||||||
netcat-openbsd \
|
|
||||||
nmap \
|
|
||||||
openssh-client \
|
|
||||||
patch \
|
|
||||||
sshpass \
|
|
||||||
telnet \
|
|
||||||
vim \
|
|
||||||
bubblewrap \
|
|
||||||
wget"
|
|
||||||
|
|
||||||
ARG APT_MIRROR=http://mirrors.ustc.edu.cn
|
|
||||||
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked,id=core-apt \
|
|
||||||
--mount=type=cache,target=/var/lib/apt,sharing=locked,id=core-apt \
|
|
||||||
sed -i "s@http://.*.debian.org@${APT_MIRROR}@g" /etc/apt/sources.list \
|
|
||||||
&& rm -f /etc/apt/apt.conf.d/docker-clean \
|
|
||||||
&& ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
|
|
||||||
&& apt-get update \
|
|
||||||
&& apt-get -y install --no-install-recommends ${DEPENDENCIES} \
|
|
||||||
&& apt-get -y install --no-install-recommends ${TOOLS} \
|
|
||||||
&& mkdir -p /root/.ssh/ \
|
|
||||||
&& echo "Host *\n\tStrictHostKeyChecking no\n\tUserKnownHostsFile /dev/null\n\tCiphers +aes128-cbc\n\tKexAlgorithms +diffie-hellman-group1-sha1\n\tHostKeyAlgorithms +ssh-rsa" > /root/.ssh/config \
|
|
||||||
&& echo "no" | dpkg-reconfigure dash \
|
|
||||||
&& echo "zh_CN.UTF-8" | dpkg-reconfigure locales \
|
|
||||||
&& sed -i "s@# export @export @g" ~/.bashrc \
|
|
||||||
&& sed -i "s@# alias @alias @g" ~/.bashrc
|
|
||||||
|
|
||||||
ARG RECEPTOR_VERSION=v1.4.5
|
|
||||||
RUN set -ex \
|
|
||||||
&& wget -O /opt/receptor.tar.gz https://github.com/ansible/receptor/releases/download/${RECEPTOR_VERSION}/receptor_${RECEPTOR_VERSION/v/}_linux_${TARGETARCH}.tar.gz \
|
|
||||||
&& tar -xf /opt/receptor.tar.gz -C /usr/local/bin/ \
|
|
||||||
&& chown root:root /usr/local/bin/receptor \
|
|
||||||
&& chmod 755 /usr/local/bin/receptor \
|
|
||||||
&& rm -f /opt/receptor.tar.gz
|
|
||||||
|
|
||||||
COPY --from=stage-2 /opt/py3 /opt/py3
|
|
||||||
COPY --from=stage-1 /opt/jumpserver/release/jumpserver /opt/jumpserver
|
|
||||||
COPY --from=stage-1 /opt/jumpserver/release/jumpserver/apps/libs/ansible/ansible.cfg /etc/ansible/
|
|
||||||
|
|
||||||
WORKDIR /opt/jumpserver
|
|
||||||
|
|
||||||
ARG VERSION
|
|
||||||
ENV VERSION=$VERSION
|
|
||||||
|
|
||||||
VOLUME /opt/jumpserver/data
|
|
||||||
|
|
||||||
EXPOSE 8080
|
|
||||||
|
|
||||||
ENTRYPOINT ["./entrypoint.sh"]
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
FROM python:3.11-slim-bullseye AS stage-1
|
FROM python:3.11-slim-bullseye as stage-1
|
||||||
ARG TARGETARCH
|
ARG TARGETARCH
|
||||||
|
|
||||||
ARG VERSION
|
ARG VERSION
|
||||||
@@ -19,11 +19,11 @@ ARG BUILD_DEPENDENCIES=" \
|
|||||||
|
|
||||||
ARG DEPENDENCIES=" \
|
ARG DEPENDENCIES=" \
|
||||||
freetds-dev \
|
freetds-dev \
|
||||||
|
libpq-dev \
|
||||||
libffi-dev \
|
libffi-dev \
|
||||||
libjpeg-dev \
|
libjpeg-dev \
|
||||||
libkrb5-dev \
|
libkrb5-dev \
|
||||||
libldap2-dev \
|
libldap2-dev \
|
||||||
libpq-dev \
|
|
||||||
libsasl2-dev \
|
libsasl2-dev \
|
||||||
libssl-dev \
|
libssl-dev \
|
||||||
libxml2-dev \
|
libxml2-dev \
|
||||||
@@ -75,9 +75,7 @@ ENV LANG=zh_CN.UTF-8 \
|
|||||||
|
|
||||||
ARG DEPENDENCIES=" \
|
ARG DEPENDENCIES=" \
|
||||||
libjpeg-dev \
|
libjpeg-dev \
|
||||||
libpq-dev \
|
|
||||||
libx11-dev \
|
libx11-dev \
|
||||||
freerdp2-dev \
|
|
||||||
libxmlsec1-openssl"
|
libxmlsec1-openssl"
|
||||||
|
|
||||||
ARG TOOLS=" \
|
ARG TOOLS=" \
|
||||||
@@ -87,14 +85,12 @@ ARG TOOLS=" \
|
|||||||
default-mysql-client \
|
default-mysql-client \
|
||||||
iputils-ping \
|
iputils-ping \
|
||||||
locales \
|
locales \
|
||||||
netcat-openbsd \
|
|
||||||
nmap \
|
nmap \
|
||||||
openssh-client \
|
openssh-client \
|
||||||
patch \
|
patch \
|
||||||
sshpass \
|
sshpass \
|
||||||
telnet \
|
telnet \
|
||||||
vim \
|
vim \
|
||||||
bubblewrap \
|
|
||||||
wget"
|
wget"
|
||||||
|
|
||||||
ARG APT_MIRROR=http://mirrors.ustc.edu.cn
|
ARG APT_MIRROR=http://mirrors.ustc.edu.cn
|
||||||
@@ -113,17 +109,8 @@ RUN --mount=type=cache,target=/var/cache/apt,sharing=locked,id=core-apt \
|
|||||||
&& sed -i "s@# export @export @g" ~/.bashrc \
|
&& sed -i "s@# export @export @g" ~/.bashrc \
|
||||||
&& sed -i "s@# alias @alias @g" ~/.bashrc
|
&& sed -i "s@# alias @alias @g" ~/.bashrc
|
||||||
|
|
||||||
ARG RECEPTOR_VERSION=v1.4.5
|
|
||||||
RUN set -ex \
|
|
||||||
&& wget -O /opt/receptor.tar.gz https://github.com/ansible/receptor/releases/download/${RECEPTOR_VERSION}/receptor_${RECEPTOR_VERSION/v/}_linux_${TARGETARCH}.tar.gz \
|
|
||||||
&& tar -xf /opt/receptor.tar.gz -C /usr/local/bin/ \
|
|
||||||
&& chown root:root /usr/local/bin/receptor \
|
|
||||||
&& chmod 755 /usr/local/bin/receptor \
|
|
||||||
&& rm -f /opt/receptor.tar.gz
|
|
||||||
|
|
||||||
COPY --from=stage-2 /opt/py3 /opt/py3
|
COPY --from=stage-2 /opt/py3 /opt/py3
|
||||||
COPY --from=stage-1 /opt/jumpserver/release/jumpserver /opt/jumpserver
|
COPY --from=stage-1 /opt/jumpserver/release/jumpserver /opt/jumpserver
|
||||||
COPY --from=stage-1 /opt/jumpserver/release/jumpserver/apps/libs/ansible/ansible.cfg /etc/ansible/
|
|
||||||
|
|
||||||
WORKDIR /opt/jumpserver
|
WORKDIR /opt/jumpserver
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
ARG VERSION
|
ARG VERSION
|
||||||
FROM registry.fit2cloud.com/jumpserver/xpack:${VERSION} AS build-xpack
|
FROM registry.fit2cloud.com/jumpserver/xpack:${VERSION} as build-xpack
|
||||||
FROM registry.fit2cloud.com/jumpserver/core-ce:${VERSION}
|
FROM registry.fit2cloud.com/jumpserver/core-ce:${VERSION}
|
||||||
|
|
||||||
COPY --from=build-xpack /opt/xpack /opt/jumpserver/apps/xpack
|
COPY --from=build-xpack /opt/xpack /opt/jumpserver/apps/xpack
|
||||||
@@ -94,8 +94,7 @@ JumpServer 堡垒机帮助企业以更安全的方式管控和登录各种类型
|
|||||||
| [KoKo](https://github.com/jumpserver/koko) | <a href="https://github.com/jumpserver/koko/releases"><img alt="Koko release" src="https://img.shields.io/github/release/jumpserver/koko.svg" /></a> | JumpServer 字符协议 Connector 项目 |
|
| [KoKo](https://github.com/jumpserver/koko) | <a href="https://github.com/jumpserver/koko/releases"><img alt="Koko release" src="https://img.shields.io/github/release/jumpserver/koko.svg" /></a> | JumpServer 字符协议 Connector 项目 |
|
||||||
| [Lion](https://github.com/jumpserver/lion-release) | <a href="https://github.com/jumpserver/lion-release/releases"><img alt="Lion release" src="https://img.shields.io/github/release/jumpserver/lion-release.svg" /></a> | JumpServer 图形协议 Connector 项目,依赖 [Apache Guacamole](https://guacamole.apache.org/) |
|
| [Lion](https://github.com/jumpserver/lion-release) | <a href="https://github.com/jumpserver/lion-release/releases"><img alt="Lion release" src="https://img.shields.io/github/release/jumpserver/lion-release.svg" /></a> | JumpServer 图形协议 Connector 项目,依赖 [Apache Guacamole](https://guacamole.apache.org/) |
|
||||||
| [Razor](https://github.com/jumpserver/razor) | <img alt="Chen" src="https://img.shields.io/badge/release-私有发布-red" /> | JumpServer RDP 代理 Connector 项目 |
|
| [Razor](https://github.com/jumpserver/razor) | <img alt="Chen" src="https://img.shields.io/badge/release-私有发布-red" /> | JumpServer RDP 代理 Connector 项目 |
|
||||||
| [Tinker](https://github.com/jumpserver/tinker) | <img alt="Tinker" src="https://img.shields.io/badge/release-私有发布-red" /> | JumpServer 远程应用 Connector 项目 (Windows) |
|
| [Tinker](https://github.com/jumpserver/tinker) | <img alt="Tinker" src="https://img.shields.io/badge/release-私有发布-red" /> | JumpServer 远程应用 Connector 项目 |
|
||||||
| [Panda](https://github.com/jumpserver/Panda) | <img alt="Panda" src="https://img.shields.io/badge/release-私有发布-red" /> | JumpServer 远程应用 Connector 项目 (Linux) |
|
|
||||||
| [Magnus](https://github.com/jumpserver/magnus-release) | <a href="https://github.com/jumpserver/magnus-release/releases"><img alt="Magnus release" src="https://img.shields.io/github/release/jumpserver/magnus-release.svg" /> | JumpServer 数据库代理 Connector 项目 |
|
| [Magnus](https://github.com/jumpserver/magnus-release) | <a href="https://github.com/jumpserver/magnus-release/releases"><img alt="Magnus release" src="https://img.shields.io/github/release/jumpserver/magnus-release.svg" /> | JumpServer 数据库代理 Connector 项目 |
|
||||||
| [Chen](https://github.com/jumpserver/chen-release) | <a href="https://github.com/jumpserver/chen-release/releases"><img alt="Chen release" src="https://img.shields.io/github/release/jumpserver/chen-release.svg" /> | JumpServer Web DB 项目,替代原来的 OmniDB |
|
| [Chen](https://github.com/jumpserver/chen-release) | <a href="https://github.com/jumpserver/chen-release/releases"><img alt="Chen release" src="https://img.shields.io/github/release/jumpserver/chen-release.svg" /> | JumpServer Web DB 项目,替代原来的 OmniDB |
|
||||||
| [Kael](https://github.com/jumpserver/kael) | <a href="https://github.com/jumpserver/kael/releases"><img alt="Kael release" src="https://img.shields.io/github/release/jumpserver/kael.svg" /> | JumpServer 连接 GPT 资产的组件项目 |
|
| [Kael](https://github.com/jumpserver/kael) | <a href="https://github.com/jumpserver/kael/releases"><img alt="Kael release" src="https://img.shields.io/github/release/jumpserver/kael.svg" /> | JumpServer 连接 GPT 资产的组件项目 |
|
||||||
@@ -113,7 +112,7 @@ JumpServer是一款安全产品,请参考 [基本安全建议](https://docs.ju
|
|||||||
|
|
||||||
## License & Copyright
|
## License & Copyright
|
||||||
|
|
||||||
Copyright (c) 2014-2024 飞致云 FIT2CLOUD, All rights reserved.
|
Copyright (c) 2014-2023 飞致云 FIT2CLOUD, All rights reserved.
|
||||||
|
|
||||||
Licensed under The GNU General Public License version 3 (GPLv3) (the "License"); you may not use this file except in
|
Licensed under The GNU General Public License version 3 (GPLv3) (the "License"); you may not use this file except in
|
||||||
compliance with the License. You may obtain a copy of the License at
|
compliance with the License. You may obtain a copy of the License at
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ If you find a security problem, please contact us directly:
|
|||||||
- 400-052-0755
|
- 400-052-0755
|
||||||
|
|
||||||
### License & Copyright
|
### License & Copyright
|
||||||
Copyright (c) 2014-2024 FIT2CLOUD Tech, Inc., All rights reserved.
|
Copyright (c) 2014-2022 FIT2CLOUD Tech, Inc., All rights reserved.
|
||||||
|
|
||||||
Licensed under The GNU General Public License version 3 (GPLv3) (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
|
Licensed under The GNU General Public License version 3 (GPLv3) (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,9 @@
|
|||||||
from django.db.models import Q
|
|
||||||
from rest_framework.generics import CreateAPIView
|
from rest_framework.generics import CreateAPIView
|
||||||
|
from rest_framework.response import Response
|
||||||
|
|
||||||
from accounts import serializers
|
from accounts import serializers
|
||||||
from accounts.models import Account
|
from accounts.tasks import verify_accounts_connectivity_task, push_accounts_to_assets_task
|
||||||
from accounts.permissions import AccountTaskActionPermission
|
from assets.exceptions import NotSupportedTemporarilyError
|
||||||
from accounts.tasks import (
|
|
||||||
remove_accounts_task, verify_accounts_connectivity_task, push_accounts_to_assets_task
|
|
||||||
)
|
|
||||||
from authentication.permissions import UserConfirmation, ConfirmType
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'AccountsTaskCreateAPI',
|
'AccountsTaskCreateAPI',
|
||||||
@@ -16,48 +12,40 @@ __all__ = [
|
|||||||
|
|
||||||
class AccountsTaskCreateAPI(CreateAPIView):
|
class AccountsTaskCreateAPI(CreateAPIView):
|
||||||
serializer_class = serializers.AccountTaskSerializer
|
serializer_class = serializers.AccountTaskSerializer
|
||||||
permission_classes = (AccountTaskActionPermission,)
|
|
||||||
|
|
||||||
def get_permissions(self):
|
def check_permissions(self, request):
|
||||||
act = self.request.data.get('action')
|
act = request.data.get('action')
|
||||||
if act == 'remove':
|
if act == 'push':
|
||||||
self.permission_classes = [
|
code = 'accounts.push_account'
|
||||||
AccountTaskActionPermission,
|
else:
|
||||||
UserConfirmation.require(ConfirmType.PASSWORD)
|
code = 'accounts.verify_account'
|
||||||
]
|
has = request.user.has_perm(code)
|
||||||
return super().get_permissions()
|
if not has:
|
||||||
|
self.permission_denied(request)
|
||||||
@staticmethod
|
|
||||||
def get_account_ids(data, action):
|
|
||||||
account_type = 'gather_accounts' if action == 'remove' else 'accounts'
|
|
||||||
accounts = data.get(account_type, [])
|
|
||||||
account_ids = [str(a.id) for a in accounts]
|
|
||||||
|
|
||||||
if action == 'remove':
|
|
||||||
return account_ids
|
|
||||||
|
|
||||||
assets = data.get('assets', [])
|
|
||||||
asset_ids = [str(a.id) for a in assets]
|
|
||||||
ids = Account.objects.filter(
|
|
||||||
Q(id__in=account_ids) | Q(asset_id__in=asset_ids)
|
|
||||||
).distinct().values_list('id', flat=True)
|
|
||||||
return [str(_id) for _id in ids]
|
|
||||||
|
|
||||||
def perform_create(self, serializer):
|
def perform_create(self, serializer):
|
||||||
data = serializer.validated_data
|
data = serializer.validated_data
|
||||||
action = data['action']
|
accounts = data.get('accounts', [])
|
||||||
ids = self.get_account_ids(data, action)
|
params = data.get('params')
|
||||||
|
account_ids = [str(a.id) for a in accounts]
|
||||||
|
|
||||||
if action == 'push':
|
if data['action'] == 'push':
|
||||||
task = push_accounts_to_assets_task.delay(ids, data.get('params'))
|
task = push_accounts_to_assets_task.delay(account_ids, params)
|
||||||
elif action == 'remove':
|
|
||||||
task = remove_accounts_task.delay(ids)
|
|
||||||
elif action == 'verify':
|
|
||||||
task = verify_accounts_connectivity_task.delay(ids)
|
|
||||||
else:
|
else:
|
||||||
raise ValueError(f"Invalid action: {action}")
|
account = accounts[0]
|
||||||
|
asset = account.asset
|
||||||
|
if not asset.auto_config['ansible_enabled'] or \
|
||||||
|
not asset.auto_config['ping_enabled']:
|
||||||
|
raise NotSupportedTemporarilyError()
|
||||||
|
task = verify_accounts_connectivity_task.delay(account_ids)
|
||||||
|
|
||||||
data = getattr(serializer, '_data', {})
|
data = getattr(serializer, '_data', {})
|
||||||
data["task"] = task.id
|
data["task"] = task.id
|
||||||
setattr(serializer, '_data', data)
|
setattr(serializer, '_data', data)
|
||||||
return task
|
return task
|
||||||
|
|
||||||
|
def get_exception_handler(self):
|
||||||
|
def handler(e, context):
|
||||||
|
return Response({"error": str(e)}, status=401)
|
||||||
|
|
||||||
|
return handler
|
||||||
|
|||||||
@@ -18,8 +18,9 @@ __all__ = [
|
|||||||
|
|
||||||
class AccountBackupPlanViewSet(OrgBulkModelViewSet):
|
class AccountBackupPlanViewSet(OrgBulkModelViewSet):
|
||||||
model = AccountBackupAutomation
|
model = AccountBackupAutomation
|
||||||
filterset_fields = ('name',)
|
filter_fields = ('name',)
|
||||||
search_fields = filterset_fields
|
search_fields = filter_fields
|
||||||
|
ordering = ('name',)
|
||||||
serializer_class = serializers.AccountBackupSerializer
|
serializer_class = serializers.AccountBackupSerializer
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -20,8 +20,8 @@ __all__ = [
|
|||||||
class AutomationAssetsListApi(generics.ListAPIView):
|
class AutomationAssetsListApi(generics.ListAPIView):
|
||||||
model = BaseAutomation
|
model = BaseAutomation
|
||||||
serializer_class = serializers.AutomationAssetsSerializer
|
serializer_class = serializers.AutomationAssetsSerializer
|
||||||
filterset_fields = ("name", "address")
|
filter_fields = ("name", "address")
|
||||||
search_fields = filterset_fields
|
search_fields = filter_fields
|
||||||
|
|
||||||
def get_object(self):
|
def get_object(self):
|
||||||
pk = self.kwargs.get('pk')
|
pk = self.kwargs.get('pk')
|
||||||
|
|||||||
@@ -6,12 +6,9 @@ from rest_framework.response import Response
|
|||||||
|
|
||||||
from accounts import serializers
|
from accounts import serializers
|
||||||
from accounts.const import AutomationTypes
|
from accounts.const import AutomationTypes
|
||||||
from accounts.filters import ChangeSecretRecordFilterSet
|
|
||||||
from accounts.models import ChangeSecretAutomation, ChangeSecretRecord
|
from accounts.models import ChangeSecretAutomation, ChangeSecretRecord
|
||||||
from accounts.tasks import execute_automation_record_task
|
from accounts.tasks import execute_automation_record_task
|
||||||
from authentication.permissions import UserConfirmation, ConfirmType
|
|
||||||
from orgs.mixins.api import OrgBulkModelViewSet, OrgGenericViewSet
|
from orgs.mixins.api import OrgBulkModelViewSet, OrgGenericViewSet
|
||||||
from rbac.permissions import RBACPermission
|
|
||||||
from .base import (
|
from .base import (
|
||||||
AutomationAssetsListApi, AutomationRemoveAssetApi, AutomationAddAssetApi,
|
AutomationAssetsListApi, AutomationRemoveAssetApi, AutomationAddAssetApi,
|
||||||
AutomationNodeAddRemoveApi, AutomationExecutionViewSet
|
AutomationNodeAddRemoveApi, AutomationExecutionViewSet
|
||||||
@@ -27,54 +24,35 @@ __all__ = [
|
|||||||
|
|
||||||
class ChangeSecretAutomationViewSet(OrgBulkModelViewSet):
|
class ChangeSecretAutomationViewSet(OrgBulkModelViewSet):
|
||||||
model = ChangeSecretAutomation
|
model = ChangeSecretAutomation
|
||||||
filterset_fields = ('name', 'secret_type', 'secret_strategy')
|
filter_fields = ('name', 'secret_type', 'secret_strategy')
|
||||||
search_fields = filterset_fields
|
search_fields = filter_fields
|
||||||
serializer_class = serializers.ChangeSecretAutomationSerializer
|
serializer_class = serializers.ChangeSecretAutomationSerializer
|
||||||
|
|
||||||
|
|
||||||
class ChangeSecretRecordViewSet(mixins.ListModelMixin, OrgGenericViewSet):
|
class ChangeSecretRecordViewSet(mixins.ListModelMixin, OrgGenericViewSet):
|
||||||
filterset_class = ChangeSecretRecordFilterSet
|
serializer_class = serializers.ChangeSecretRecordSerializer
|
||||||
|
filterset_fields = ('asset_id', 'execution_id')
|
||||||
search_fields = ('asset__address',)
|
search_fields = ('asset__address',)
|
||||||
tp = AutomationTypes.change_secret
|
tp = AutomationTypes.change_secret
|
||||||
serializer_classes = {
|
|
||||||
'default': serializers.ChangeSecretRecordSerializer,
|
|
||||||
'secret': serializers.ChangeSecretRecordViewSecretSerializer,
|
|
||||||
}
|
|
||||||
rbac_perms = {
|
rbac_perms = {
|
||||||
'execute': 'accounts.add_changesecretexecution',
|
'execute': 'accounts.add_changesecretexecution',
|
||||||
'secret': 'accounts.view_changesecretrecord',
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def get_permissions(self):
|
|
||||||
if self.action == 'secret':
|
|
||||||
self.permission_classes = [
|
|
||||||
RBACPermission,
|
|
||||||
UserConfirmation.require(ConfirmType.MFA)
|
|
||||||
]
|
|
||||||
return super().get_permissions()
|
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
return ChangeSecretRecord.objects.all()
|
return ChangeSecretRecord.objects.all()
|
||||||
|
|
||||||
@action(methods=['post'], detail=False, url_path='execute')
|
@action(methods=['post'], detail=False, url_path='execute')
|
||||||
def execute(self, request, *args, **kwargs):
|
def execute(self, request, *args, **kwargs):
|
||||||
record_ids = request.data.get('record_ids')
|
record_id = request.data.get('record_id')
|
||||||
records = self.get_queryset().filter(id__in=record_ids)
|
record = self.get_queryset().filter(pk=record_id)
|
||||||
execution_count = records.values_list('execution_id', flat=True).distinct().count()
|
if not record:
|
||||||
if execution_count != 1:
|
|
||||||
return Response(
|
return Response(
|
||||||
{'detail': 'Only one execution is allowed to execute'},
|
{'detail': 'record not found'},
|
||||||
status=status.HTTP_400_BAD_REQUEST
|
status=status.HTTP_404_NOT_FOUND
|
||||||
)
|
)
|
||||||
task = execute_automation_record_task.delay(record_ids, self.tp)
|
task = execute_automation_record_task.delay(record_id, self.tp)
|
||||||
return Response({'task': task.id}, status=status.HTTP_200_OK)
|
return Response({'task': task.id}, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
@action(methods=['get'], detail=True, url_path='secret')
|
|
||||||
def secret(self, request, *args, **kwargs):
|
|
||||||
instance = self.get_object()
|
|
||||||
serializer = self.get_serializer(instance)
|
|
||||||
return Response(serializer.data)
|
|
||||||
|
|
||||||
|
|
||||||
class ChangSecretExecutionViewSet(AutomationExecutionViewSet):
|
class ChangSecretExecutionViewSet(AutomationExecutionViewSet):
|
||||||
rbac_perms = (
|
rbac_perms = (
|
||||||
|
|||||||
@@ -20,8 +20,8 @@ __all__ = [
|
|||||||
|
|
||||||
class GatherAccountsAutomationViewSet(OrgBulkModelViewSet):
|
class GatherAccountsAutomationViewSet(OrgBulkModelViewSet):
|
||||||
model = GatherAccountsAutomation
|
model = GatherAccountsAutomation
|
||||||
filterset_fields = ('name',)
|
filter_fields = ('name',)
|
||||||
search_fields = filterset_fields
|
search_fields = filter_fields
|
||||||
serializer_class = serializers.GatherAccountAutomationSerializer
|
serializer_class = serializers.GatherAccountAutomationSerializer
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -20,8 +20,8 @@ __all__ = [
|
|||||||
|
|
||||||
class PushAccountAutomationViewSet(OrgBulkModelViewSet):
|
class PushAccountAutomationViewSet(OrgBulkModelViewSet):
|
||||||
model = PushAccountAutomation
|
model = PushAccountAutomation
|
||||||
filterset_fields = ('name', 'secret_type', 'secret_strategy')
|
filter_fields = ('name', 'secret_type', 'secret_strategy')
|
||||||
search_fields = filterset_fields
|
search_fields = filter_fields
|
||||||
serializer_class = serializers.PushAccountAutomationSerializer
|
serializer_class = serializers.PushAccountAutomationSerializer
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -3,13 +3,13 @@ import time
|
|||||||
from collections import defaultdict, OrderedDict
|
from collections import defaultdict, OrderedDict
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from openpyxl import Workbook
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
from xlsxwriter import Workbook
|
|
||||||
|
|
||||||
from accounts.const import AccountBackupType
|
from accounts.const.automation import AccountBackupType
|
||||||
from accounts.models.automations.backup_account import AccountBackupAutomation
|
|
||||||
from accounts.notifications import AccountBackupExecutionTaskMsg, AccountBackupByObjStorageExecutionTaskMsg
|
from accounts.notifications import AccountBackupExecutionTaskMsg, AccountBackupByObjStorageExecutionTaskMsg
|
||||||
from accounts.serializers import AccountSecretSerializer
|
from accounts.serializers import AccountSecretSerializer
|
||||||
|
from accounts.models.automations.backup_account import AccountBackupAutomation
|
||||||
from assets.const import AllTypes
|
from assets.const import AllTypes
|
||||||
from common.utils.file import encrypt_and_compress_zip_file, zip_files
|
from common.utils.file import encrypt_and_compress_zip_file, zip_files
|
||||||
from common.utils.timezone import local_now_filename, local_now_display
|
from common.utils.timezone import local_now_filename, local_now_display
|
||||||
@@ -144,11 +144,10 @@ class AccountBackupHandler:
|
|||||||
|
|
||||||
wb = Workbook(filename)
|
wb = Workbook(filename)
|
||||||
for sheet, data in data_map.items():
|
for sheet, data in data_map.items():
|
||||||
ws = wb.add_worksheet(str(sheet))
|
ws = wb.create_sheet(str(sheet))
|
||||||
for row_index, row_data in enumerate(data):
|
for row in data:
|
||||||
for col_index, col_data in enumerate(row_data):
|
ws.append(row)
|
||||||
ws.write_string(row_index, col_index, col_data)
|
wb.save(filename)
|
||||||
wb.close()
|
|
||||||
files.append(filename)
|
files.append(filename)
|
||||||
timedelta = round((time.time() - time_start), 2)
|
timedelta = round((time.time() - time_start), 2)
|
||||||
print('创建备份文件完成: 用时 {}s'.format(timedelta))
|
print('创建备份文件完成: 用时 {}s'.format(timedelta))
|
||||||
@@ -168,8 +167,9 @@ class AccountBackupHandler:
|
|||||||
if not user.secret_key:
|
if not user.secret_key:
|
||||||
attachment_list = []
|
attachment_list = []
|
||||||
else:
|
else:
|
||||||
|
password = user.secret_key.encode('utf8')
|
||||||
attachment = os.path.join(PATH, f'{plan_name}-{local_now_filename()}-{time.time()}.zip')
|
attachment = os.path.join(PATH, f'{plan_name}-{local_now_filename()}-{time.time()}.zip')
|
||||||
encrypt_and_compress_zip_file(attachment, user.secret_key, files)
|
encrypt_and_compress_zip_file(attachment, password, files)
|
||||||
attachment_list = [attachment, ]
|
attachment_list = [attachment, ]
|
||||||
AccountBackupExecutionTaskMsg(plan_name, user).publish(attachment_list)
|
AccountBackupExecutionTaskMsg(plan_name, user).publish(attachment_list)
|
||||||
print('邮件已发送至{}({})'.format(user, user.email))
|
print('邮件已发送至{}({})'.format(user, user.email))
|
||||||
@@ -190,6 +190,7 @@ class AccountBackupHandler:
|
|||||||
attachment = os.path.join(PATH, f'{plan_name}-{local_now_filename()}-{time.time()}.zip')
|
attachment = os.path.join(PATH, f'{plan_name}-{local_now_filename()}-{time.time()}.zip')
|
||||||
if password:
|
if password:
|
||||||
print('\033[32m>>> 使用加密密码对文件进行加密中\033[0m')
|
print('\033[32m>>> 使用加密密码对文件进行加密中\033[0m')
|
||||||
|
password = password.encode('utf8')
|
||||||
encrypt_and_compress_zip_file(attachment, password, files)
|
encrypt_and_compress_zip_file(attachment, password, files)
|
||||||
else:
|
else:
|
||||||
zip_files(attachment, files)
|
zip_files(attachment, files)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
- hosts: custom
|
- hosts: custom
|
||||||
gather_facts: no
|
gather_facts: no
|
||||||
vars:
|
vars:
|
||||||
|
asset_port: "{{ jms_asset.protocols | selectattr('name', 'equalto', 'ssh') | map(attribute='port') | first }}"
|
||||||
ansible_connection: local
|
ansible_connection: local
|
||||||
ansible_become: false
|
ansible_become: false
|
||||||
|
|
||||||
@@ -8,18 +9,16 @@
|
|||||||
- name: Test privileged account (paramiko)
|
- name: Test privileged account (paramiko)
|
||||||
ssh_ping:
|
ssh_ping:
|
||||||
login_host: "{{ jms_asset.address }}"
|
login_host: "{{ jms_asset.address }}"
|
||||||
login_port: "{{ jms_asset.port }}"
|
login_port: "{{ asset_port }}"
|
||||||
login_user: "{{ jms_account.username }}"
|
login_user: "{{ jms_account.username }}"
|
||||||
login_password: "{{ jms_account.secret }}"
|
login_password: "{{ jms_account.secret }}"
|
||||||
login_secret_type: "{{ jms_account.secret_type }}"
|
login_secret_type: "{{ jms_account.secret_type }}"
|
||||||
login_private_key_path: "{{ jms_account.private_key_path }}"
|
login_private_key_path: "{{ jms_account.private_key_path }}"
|
||||||
become: "{{ jms_custom_become | default(False) }}"
|
become: "{{ custom_become | default(False) }}"
|
||||||
become_method: "{{ jms_custom_become_method | default('su') }}"
|
become_method: "{{ custom_become_method | default('su') }}"
|
||||||
become_user: "{{ jms_custom_become_user | default('') }}"
|
become_user: "{{ custom_become_user | default('') }}"
|
||||||
become_password: "{{ jms_custom_become_password | default('') }}"
|
become_password: "{{ custom_become_password | default('') }}"
|
||||||
become_private_key_path: "{{ jms_custom_become_private_key_path | default(None) }}"
|
become_private_key_path: "{{ custom_become_private_key_path | default(None) }}"
|
||||||
old_ssh_version: "{{ jms_asset.old_ssh_version | default(False) }}"
|
|
||||||
gateway_args: "{{ jms_asset.ansible_ssh_common_args | default(None) }}"
|
|
||||||
register: ping_info
|
register: ping_info
|
||||||
delegate_to: localhost
|
delegate_to: localhost
|
||||||
|
|
||||||
@@ -28,14 +27,14 @@
|
|||||||
login_user: "{{ jms_account.username }}"
|
login_user: "{{ jms_account.username }}"
|
||||||
login_password: "{{ jms_account.secret }}"
|
login_password: "{{ jms_account.secret }}"
|
||||||
login_host: "{{ jms_asset.address }}"
|
login_host: "{{ jms_asset.address }}"
|
||||||
login_port: "{{ jms_asset.port }}"
|
login_port: "{{ asset_port }}"
|
||||||
login_secret_type: "{{ jms_account.secret_type }}"
|
login_secret_type: "{{ jms_account.secret_type }}"
|
||||||
login_private_key_path: "{{ jms_account.private_key_path }}"
|
login_private_key_path: "{{ jms_account.private_key_path }}"
|
||||||
become: "{{ jms_custom_become | default(False) }}"
|
become: "{{ custom_become | default(False) }}"
|
||||||
become_method: "{{ jms_custom_become_method | default('su') }}"
|
become_method: "{{ custom_become_method | default('su') }}"
|
||||||
become_user: "{{ jms_custom_become_user | default('') }}"
|
become_user: "{{ custom_become_user | default('') }}"
|
||||||
become_password: "{{ jms_custom_become_password | default('') }}"
|
become_password: "{{ custom_become_password | default('') }}"
|
||||||
become_private_key_path: "{{ jms_custom_become_private_key_path | default(None) }}"
|
become_private_key_path: "{{ custom_become_private_key_path | default(None) }}"
|
||||||
name: "{{ account.username }}"
|
name: "{{ account.username }}"
|
||||||
password: "{{ account.secret }}"
|
password: "{{ account.secret }}"
|
||||||
commands: "{{ params.commands }}"
|
commands: "{{ params.commands }}"
|
||||||
@@ -50,12 +49,10 @@
|
|||||||
login_user: "{{ account.username }}"
|
login_user: "{{ account.username }}"
|
||||||
login_password: "{{ account.secret }}"
|
login_password: "{{ account.secret }}"
|
||||||
login_host: "{{ jms_asset.address }}"
|
login_host: "{{ jms_asset.address }}"
|
||||||
login_port: "{{ jms_asset.port }}"
|
login_port: "{{ asset_port }}"
|
||||||
become: "{{ account.become.ansible_become | default(False) }}"
|
become: "{{ account.become.ansible_become | default(False) }}"
|
||||||
become_method: su
|
become_method: su
|
||||||
become_user: "{{ account.become.ansible_user | default('') }}"
|
become_user: "{{ account.become.ansible_user | default('') }}"
|
||||||
become_password: "{{ account.become.ansible_password | default('') }}"
|
become_password: "{{ account.become.ansible_password | default('') }}"
|
||||||
become_private_key_path: "{{ account.become.ansible_ssh_private_key_file | default(None) }}"
|
become_private_key_path: "{{ account.become.ansible_ssh_private_key_file | default(None) }}"
|
||||||
old_ssh_version: "{{ jms_asset.old_ssh_version | default(False) }}"
|
|
||||||
gateway_args: "{{ jms_asset.ansible_ssh_common_args | default(None) }}"
|
|
||||||
delegate_to: localhost
|
delegate_to: localhost
|
||||||
|
|||||||
@@ -6,27 +6,15 @@ category:
|
|||||||
type:
|
type:
|
||||||
- all
|
- all
|
||||||
method: change_secret
|
method: change_secret
|
||||||
protocol: ssh
|
|
||||||
priority: 50
|
|
||||||
params:
|
params:
|
||||||
- name: commands
|
- name: commands
|
||||||
type: list
|
type: list
|
||||||
label: "{{ 'Params commands label' | trans }}"
|
label: '自定义命令'
|
||||||
default: [ '' ]
|
default: [ '' ]
|
||||||
help_text: "{{ 'Params commands help text' | trans }}"
|
help_text: '自定义命令中如需包含账号的 账号、密码、SSH 连接的用户密码 字段,<br />请使用 {username}、{password}、{login_password}格式,执行任务时会进行替换 。<br />比如针对 Cisco 主机进行改密,一般需要配置五条命令:<br />1. enable<br />2. {login_password}<br />3. configure terminal<br />4. username {username} privilege 0 password {password} <br />5. end'
|
||||||
|
|
||||||
i18n:
|
i18n:
|
||||||
SSH account change secret:
|
SSH account change secret:
|
||||||
zh: '使用 SSH 命令行自定义改密'
|
zh: 使用 SSH 命令行自定义改密
|
||||||
ja: 'SSH コマンドライン方式でカスタムパスワード変更'
|
ja: SSH コマンドライン方式でカスタムパスワード変更
|
||||||
en: 'Custom password change by SSH command line'
|
en: Custom password change by SSH command line
|
||||||
|
|
||||||
Params commands help text:
|
|
||||||
zh: '自定义命令中如需包含账号的 账号、密码、SSH 连接的用户密码 字段,<br />请使用 {username}、{password}、{login_password}格式,执行任务时会进行替换 。<br />比如针对 Cisco 主机进行改密,一般需要配置五条命令:<br />1. enable<br />2. {login_password}<br />3. configure terminal<br />4. username {username} privilege 0 password {password} <br />5. end'
|
|
||||||
ja: 'カスタム コマンドに SSH 接続用のアカウント番号、パスワード、ユーザー パスワード フィールドを含める必要がある場合は、<br />{ユーザー名}、{パスワード}、{login_password& を使用してください。 # 125; 形式。タスクの実行時に置き換えられます。 <br />たとえば、Cisco ホストのパスワードを変更するには、通常、次の 5 つのコマンドを設定する必要があります:<br />1.enable<br />2.{login_password}<br />3 .ターミナルの設定<br / >4. ユーザー名 {ユーザー名} 権限 0 パスワード {パスワード} <br />5. 終了'
|
|
||||||
en: 'If the custom command needs to include the account number, password, and user password field for SSH connection,<br />Please use {username}, {password}, {login_password&# 125; format, which will be replaced when executing the task. <br />For example, to change the password of a Cisco host, you generally need to configure five commands:<br />1. enable<br />2. {login_password}<br />3. configure terminal<br / >4. username {username} privilege 0 password {password} <br />5. end'
|
|
||||||
|
|
||||||
Params commands label:
|
|
||||||
zh: '自定义命令'
|
|
||||||
ja: 'カスタムコマンド'
|
|
||||||
en: 'Custom command'
|
|
||||||
|
|||||||
@@ -53,5 +53,3 @@
|
|||||||
ssl_certfile: "{{ jms_asset.secret_info.client_key | default('') }}"
|
ssl_certfile: "{{ jms_asset.secret_info.client_key | default('') }}"
|
||||||
connection_options:
|
connection_options:
|
||||||
- tlsAllowInvalidHostnames: "{{ jms_asset.spec_info.allow_invalid_cert}}"
|
- tlsAllowInvalidHostnames: "{{ jms_asset.spec_info.allow_invalid_cert}}"
|
||||||
register: result
|
|
||||||
failed_when: not result.is_available
|
|
||||||
|
|||||||
@@ -36,8 +36,7 @@
|
|||||||
name: "{{ account.username }}"
|
name: "{{ account.username }}"
|
||||||
password: "{{ account.secret }}"
|
password: "{{ account.secret }}"
|
||||||
host: "%"
|
host: "%"
|
||||||
priv: "{{ omit if db_name == '' else db_name + '.*:ALL' }}"
|
priv: "{{ account.username + '.*:USAGE' if db_name == '' else db_name + '.*:ALL' }}"
|
||||||
append_privs: "{{ db_name != '' | bool }}"
|
|
||||||
ignore_errors: true
|
ignore_errors: true
|
||||||
when: db_info is succeeded
|
when: db_info is succeeded
|
||||||
|
|
||||||
|
|||||||
@@ -39,4 +39,3 @@
|
|||||||
login_host: "{{ jms_asset.address }}"
|
login_host: "{{ jms_asset.address }}"
|
||||||
login_port: "{{ jms_asset.port }}"
|
login_port: "{{ jms_asset.port }}"
|
||||||
login_database: "{{ jms_asset.spec_info.db_name }}"
|
login_database: "{{ jms_asset.spec_info.db_name }}"
|
||||||
mode: "{{ account.mode }}"
|
|
||||||
|
|||||||
@@ -39,5 +39,3 @@
|
|||||||
login_host: "{{ jms_asset.address }}"
|
login_host: "{{ jms_asset.address }}"
|
||||||
login_port: "{{ jms_asset.port }}"
|
login_port: "{{ jms_asset.port }}"
|
||||||
db: "{{ jms_asset.spec_info.db_name }}"
|
db: "{{ jms_asset.spec_info.db_name }}"
|
||||||
register: result
|
|
||||||
failed_when: not result.is_available
|
|
||||||
@@ -35,24 +35,12 @@
|
|||||||
- user_info.failed
|
- user_info.failed
|
||||||
- params.groups
|
- params.groups
|
||||||
|
|
||||||
- name: "Set {{ account.username }} sudo setting"
|
|
||||||
ansible.builtin.lineinfile:
|
|
||||||
dest: /etc/sudoers
|
|
||||||
state: present
|
|
||||||
regexp: "^{{ account.username }} ALL="
|
|
||||||
line: "{{ account.username + ' ALL=(ALL) NOPASSWD: ' + params.sudo }}"
|
|
||||||
validate: visudo -cf %s
|
|
||||||
when:
|
|
||||||
- user_info.failed or params.modify_sudo
|
|
||||||
- params.sudo
|
|
||||||
|
|
||||||
- name: "Change {{ account.username }} password"
|
- name: "Change {{ account.username }} password"
|
||||||
ansible.builtin.user:
|
ansible.builtin.user:
|
||||||
name: "{{ account.username }}"
|
name: "{{ account.username }}"
|
||||||
password: "{{ account.secret | password_hash('des') }}"
|
password: "{{ account.secret | password_hash('des') }}"
|
||||||
update_password: always
|
update_password: always
|
||||||
ignore_errors: true
|
ignore_errors: true
|
||||||
register: change_secret_result
|
|
||||||
when: account.secret_type == "password"
|
when: account.secret_type == "password"
|
||||||
|
|
||||||
- name: remove jumpserver ssh key
|
- name: remove jumpserver ssh key
|
||||||
@@ -69,9 +57,19 @@
|
|||||||
user: "{{ account.username }}"
|
user: "{{ account.username }}"
|
||||||
key: "{{ account.secret }}"
|
key: "{{ account.secret }}"
|
||||||
exclusive: "{{ ssh_params.exclusive }}"
|
exclusive: "{{ ssh_params.exclusive }}"
|
||||||
register: change_secret_result
|
|
||||||
when: account.secret_type == "ssh_key"
|
when: account.secret_type == "ssh_key"
|
||||||
|
|
||||||
|
- name: "Set {{ account.username }} sudo setting"
|
||||||
|
ansible.builtin.lineinfile:
|
||||||
|
dest: /etc/sudoers
|
||||||
|
state: present
|
||||||
|
regexp: "^{{ account.username }} ALL="
|
||||||
|
line: "{{ account.username + ' ALL=(ALL) NOPASSWD: ' + params.sudo }}"
|
||||||
|
validate: visudo -cf %s
|
||||||
|
when:
|
||||||
|
- user_info.failed
|
||||||
|
- params.sudo
|
||||||
|
|
||||||
- name: Refresh connection
|
- name: Refresh connection
|
||||||
ansible.builtin.meta: reset_connection
|
ansible.builtin.meta: reset_connection
|
||||||
|
|
||||||
@@ -87,10 +85,7 @@
|
|||||||
become_user: "{{ account.become.ansible_user | default('') }}"
|
become_user: "{{ account.become.ansible_user | default('') }}"
|
||||||
become_password: "{{ account.become.ansible_password | default('') }}"
|
become_password: "{{ account.become.ansible_password | default('') }}"
|
||||||
become_private_key_path: "{{ account.become.ansible_ssh_private_key_file | default(None) }}"
|
become_private_key_path: "{{ account.become.ansible_ssh_private_key_file | default(None) }}"
|
||||||
old_ssh_version: "{{ jms_asset.old_ssh_version | default(False) }}"
|
when: account.secret_type == "password"
|
||||||
when:
|
|
||||||
- account.secret_type == "password"
|
|
||||||
- check_conn_after_change or change_secret_result.failed | default(false)
|
|
||||||
delegate_to: localhost
|
delegate_to: localhost
|
||||||
|
|
||||||
- name: "Verify {{ account.username }} SSH KEY (paramiko)"
|
- name: "Verify {{ account.username }} SSH KEY (paramiko)"
|
||||||
@@ -100,8 +95,5 @@
|
|||||||
login_user: "{{ account.username }}"
|
login_user: "{{ account.username }}"
|
||||||
login_private_key_path: "{{ account.private_key_path }}"
|
login_private_key_path: "{{ account.private_key_path }}"
|
||||||
gateway_args: "{{ jms_asset.ansible_ssh_common_args | default('') }}"
|
gateway_args: "{{ jms_asset.ansible_ssh_common_args | default('') }}"
|
||||||
old_ssh_version: "{{ jms_asset.old_ssh_version | default(False) }}"
|
when: account.secret_type == "ssh_key"
|
||||||
when:
|
|
||||||
- account.secret_type == "ssh_key"
|
|
||||||
- check_conn_after_change or change_secret_result.failed | default(false)
|
|
||||||
delegate_to: localhost
|
delegate_to: localhost
|
||||||
|
|||||||
@@ -5,12 +5,6 @@ type:
|
|||||||
- AIX
|
- AIX
|
||||||
method: change_secret
|
method: change_secret
|
||||||
params:
|
params:
|
||||||
- name: modify_sudo
|
|
||||||
type: bool
|
|
||||||
label: "{{ 'Modify sudo label' | trans }}"
|
|
||||||
default: False
|
|
||||||
help_text: "{{ 'Modify params sudo help text' | trans }}"
|
|
||||||
|
|
||||||
- name: sudo
|
- name: sudo
|
||||||
type: str
|
type: str
|
||||||
label: 'Sudo'
|
label: 'Sudo'
|
||||||
@@ -40,11 +34,6 @@ i18n:
|
|||||||
ja: 'Ansible user モジュールを使用してアカウントのパスワード変更 (DES)'
|
ja: 'Ansible user モジュールを使用してアカウントのパスワード変更 (DES)'
|
||||||
en: 'Using Ansible module user to change account secret (DES)'
|
en: 'Using Ansible module user to change account secret (DES)'
|
||||||
|
|
||||||
Modify params sudo help text:
|
|
||||||
zh: '如果用户存在,可以修改sudo权限'
|
|
||||||
ja: 'ユーザーが存在する場合、sudo権限を変更できます'
|
|
||||||
en: 'If the user exists, sudo permissions can be modified'
|
|
||||||
|
|
||||||
Params sudo help text:
|
Params sudo help text:
|
||||||
zh: '使用逗号分隔多个命令,如: /bin/whoami,/sbin/ifconfig'
|
zh: '使用逗号分隔多个命令,如: /bin/whoami,/sbin/ifconfig'
|
||||||
ja: 'コンマで区切って複数のコマンドを入力してください。例: /bin/whoami,/sbin/ifconfig'
|
ja: 'コンマで区切って複数のコマンドを入力してください。例: /bin/whoami,/sbin/ifconfig'
|
||||||
@@ -60,11 +49,6 @@ i18n:
|
|||||||
ja: 'グループを入力してください。複数のグループはコンマで区切ってください(既存のグループを入力してください)'
|
ja: 'グループを入力してください。複数のグループはコンマで区切ってください(既存のグループを入力してください)'
|
||||||
en: 'Please enter the group. Multiple groups are separated by commas (please enter the existing group)'
|
en: 'Please enter the group. Multiple groups are separated by commas (please enter the existing group)'
|
||||||
|
|
||||||
Modify sudo label:
|
|
||||||
zh: '修改 sudo 权限'
|
|
||||||
ja: 'sudo 権限を変更'
|
|
||||||
en: 'Modify sudo'
|
|
||||||
|
|
||||||
Params home label:
|
Params home label:
|
||||||
zh: '家目录'
|
zh: '家目录'
|
||||||
ja: 'ホームディレクトリ'
|
ja: 'ホームディレクトリ'
|
||||||
|
|||||||
@@ -35,24 +35,12 @@
|
|||||||
- user_info.failed
|
- user_info.failed
|
||||||
- params.groups
|
- params.groups
|
||||||
|
|
||||||
- name: "Set {{ account.username }} sudo setting"
|
|
||||||
ansible.builtin.lineinfile:
|
|
||||||
dest: /etc/sudoers
|
|
||||||
state: present
|
|
||||||
regexp: "^{{ account.username }} ALL="
|
|
||||||
line: "{{ account.username + ' ALL=(ALL) NOPASSWD: ' + params.sudo }}"
|
|
||||||
validate: visudo -cf %s
|
|
||||||
when:
|
|
||||||
- user_info.failed or params.modify_sudo
|
|
||||||
- params.sudo
|
|
||||||
|
|
||||||
- name: "Change {{ account.username }} password"
|
- name: "Change {{ account.username }} password"
|
||||||
ansible.builtin.user:
|
ansible.builtin.user:
|
||||||
name: "{{ account.username }}"
|
name: "{{ account.username }}"
|
||||||
password: "{{ account.secret | password_hash('sha512') }}"
|
password: "{{ account.secret | password_hash('sha512') }}"
|
||||||
update_password: always
|
update_password: always
|
||||||
ignore_errors: true
|
ignore_errors: true
|
||||||
register: change_secret_result
|
|
||||||
when: account.secret_type == "password"
|
when: account.secret_type == "password"
|
||||||
|
|
||||||
- name: remove jumpserver ssh key
|
- name: remove jumpserver ssh key
|
||||||
@@ -69,9 +57,19 @@
|
|||||||
user: "{{ account.username }}"
|
user: "{{ account.username }}"
|
||||||
key: "{{ account.secret }}"
|
key: "{{ account.secret }}"
|
||||||
exclusive: "{{ ssh_params.exclusive }}"
|
exclusive: "{{ ssh_params.exclusive }}"
|
||||||
register: change_secret_result
|
|
||||||
when: account.secret_type == "ssh_key"
|
when: account.secret_type == "ssh_key"
|
||||||
|
|
||||||
|
- name: "Set {{ account.username }} sudo setting"
|
||||||
|
ansible.builtin.lineinfile:
|
||||||
|
dest: /etc/sudoers
|
||||||
|
state: present
|
||||||
|
regexp: "^{{ account.username }} ALL="
|
||||||
|
line: "{{ account.username + ' ALL=(ALL) NOPASSWD: ' + params.sudo }}"
|
||||||
|
validate: visudo -cf %s
|
||||||
|
when:
|
||||||
|
- user_info.failed
|
||||||
|
- params.sudo
|
||||||
|
|
||||||
- name: Refresh connection
|
- name: Refresh connection
|
||||||
ansible.builtin.meta: reset_connection
|
ansible.builtin.meta: reset_connection
|
||||||
|
|
||||||
@@ -87,10 +85,7 @@
|
|||||||
become_user: "{{ account.become.ansible_user | default('') }}"
|
become_user: "{{ account.become.ansible_user | default('') }}"
|
||||||
become_password: "{{ account.become.ansible_password | default('') }}"
|
become_password: "{{ account.become.ansible_password | default('') }}"
|
||||||
become_private_key_path: "{{ account.become.ansible_ssh_private_key_file | default(None) }}"
|
become_private_key_path: "{{ account.become.ansible_ssh_private_key_file | default(None) }}"
|
||||||
old_ssh_version: "{{ jms_asset.old_ssh_version | default(False) }}"
|
when: account.secret_type == "password"
|
||||||
when:
|
|
||||||
- account.secret_type == "password"
|
|
||||||
- check_conn_after_change or change_secret_result.failed | default(false)
|
|
||||||
delegate_to: localhost
|
delegate_to: localhost
|
||||||
|
|
||||||
- name: "Verify {{ account.username }} SSH KEY (paramiko)"
|
- name: "Verify {{ account.username }} SSH KEY (paramiko)"
|
||||||
@@ -100,8 +95,5 @@
|
|||||||
login_user: "{{ account.username }}"
|
login_user: "{{ account.username }}"
|
||||||
login_private_key_path: "{{ account.private_key_path }}"
|
login_private_key_path: "{{ account.private_key_path }}"
|
||||||
gateway_args: "{{ jms_asset.ansible_ssh_common_args | default('') }}"
|
gateway_args: "{{ jms_asset.ansible_ssh_common_args | default('') }}"
|
||||||
old_ssh_version: "{{ jms_asset.old_ssh_version | default(False) }}"
|
when: account.secret_type == "ssh_key"
|
||||||
when:
|
|
||||||
- account.secret_type == "ssh_key"
|
|
||||||
- check_conn_after_change or change_secret_result.failed | default(false)
|
|
||||||
delegate_to: localhost
|
delegate_to: localhost
|
||||||
|
|||||||
@@ -6,12 +6,6 @@ type:
|
|||||||
- linux
|
- linux
|
||||||
method: change_secret
|
method: change_secret
|
||||||
params:
|
params:
|
||||||
- name: modify_sudo
|
|
||||||
type: bool
|
|
||||||
label: "{{ 'Modify sudo label' | trans }}"
|
|
||||||
default: False
|
|
||||||
help_text: "{{ 'Modify params sudo help text' | trans }}"
|
|
||||||
|
|
||||||
- name: sudo
|
- name: sudo
|
||||||
type: str
|
type: str
|
||||||
label: 'Sudo'
|
label: 'Sudo'
|
||||||
@@ -42,11 +36,6 @@ i18n:
|
|||||||
ja: 'Ansible user モジュールを使用して アカウントのパスワード変更 (SHA512)'
|
ja: 'Ansible user モジュールを使用して アカウントのパスワード変更 (SHA512)'
|
||||||
en: 'Using Ansible module user to change account secret (SHA512)'
|
en: 'Using Ansible module user to change account secret (SHA512)'
|
||||||
|
|
||||||
Modify params sudo help text:
|
|
||||||
zh: '如果用户存在,可以修改sudo权限'
|
|
||||||
ja: 'ユーザーが存在する場合、sudo権限を変更できます'
|
|
||||||
en: 'If the user exists, sudo permissions can be modified'
|
|
||||||
|
|
||||||
Params sudo help text:
|
Params sudo help text:
|
||||||
zh: '使用逗号分隔多个命令,如: /bin/whoami,/sbin/ifconfig'
|
zh: '使用逗号分隔多个命令,如: /bin/whoami,/sbin/ifconfig'
|
||||||
ja: 'コンマで区切って複数のコマンドを入力してください。例: /bin/whoami,/sbin/ifconfig'
|
ja: 'コンマで区切って複数のコマンドを入力してください。例: /bin/whoami,/sbin/ifconfig'
|
||||||
@@ -62,11 +51,6 @@ i18n:
|
|||||||
ja: 'グループを入力してください。複数のグループはコンマで区切ってください(既存のグループを入力してください)'
|
ja: 'グループを入力してください。複数のグループはコンマで区切ってください(既存のグループを入力してください)'
|
||||||
en: 'Please enter the group. Multiple groups are separated by commas (please enter the existing group)'
|
en: 'Please enter the group. Multiple groups are separated by commas (please enter the existing group)'
|
||||||
|
|
||||||
Modify sudo label:
|
|
||||||
zh: '修改 sudo 权限'
|
|
||||||
ja: 'sudo 権限を変更'
|
|
||||||
en: 'Modify sudo'
|
|
||||||
|
|
||||||
Params home label:
|
Params home label:
|
||||||
zh: '家目录'
|
zh: '家目录'
|
||||||
ja: 'ホームディレクトリ'
|
ja: 'ホームディレクトリ'
|
||||||
|
|||||||
@@ -28,6 +28,4 @@
|
|||||||
vars:
|
vars:
|
||||||
ansible_user: "{{ account.username }}"
|
ansible_user: "{{ account.username }}"
|
||||||
ansible_password: "{{ account.secret }}"
|
ansible_password: "{{ account.secret }}"
|
||||||
when:
|
when: account.secret_type == "password"
|
||||||
- account.secret_type == "password"
|
|
||||||
- check_conn_after_change
|
|
||||||
|
|||||||
@@ -31,7 +31,5 @@
|
|||||||
login_password: "{{ account.secret }}"
|
login_password: "{{ account.secret }}"
|
||||||
login_secret_type: "{{ account.secret_type }}"
|
login_secret_type: "{{ account.secret_type }}"
|
||||||
login_private_key_path: "{{ account.private_key_path }}"
|
login_private_key_path: "{{ account.private_key_path }}"
|
||||||
when:
|
when: account.secret_type == "password"
|
||||||
- account.secret_type == "password"
|
|
||||||
- check_conn_after_change
|
|
||||||
delegate_to: localhost
|
delegate_to: localhost
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ method: change_secret
|
|||||||
category: host
|
category: host
|
||||||
type:
|
type:
|
||||||
- windows
|
- windows
|
||||||
priority: 49
|
|
||||||
params:
|
params:
|
||||||
- name: groups
|
- name: groups
|
||||||
type: str
|
type: str
|
||||||
|
|||||||
@@ -4,12 +4,11 @@ from copy import deepcopy
|
|||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.utils.translation import gettext_lazy as _
|
from openpyxl import Workbook
|
||||||
from xlsxwriter import Workbook
|
|
||||||
|
|
||||||
from accounts.const import AutomationTypes, SecretType, SSHKeyStrategy, SecretStrategy, ChangeSecretRecordStatusChoice
|
from accounts.const import AutomationTypes, SecretType, SSHKeyStrategy, SecretStrategy
|
||||||
from accounts.models import ChangeSecretRecord
|
from accounts.models import ChangeSecretRecord
|
||||||
from accounts.notifications import ChangeSecretExecutionTaskMsg, ChangeSecretFailedMsg
|
from accounts.notifications import ChangeSecretExecutionTaskMsg
|
||||||
from accounts.serializers import ChangeSecretRecordBackUpSerializer
|
from accounts.serializers import ChangeSecretRecordBackUpSerializer
|
||||||
from assets.const import HostTypes
|
from assets.const import HostTypes
|
||||||
from common.utils import get_logger
|
from common.utils import get_logger
|
||||||
@@ -27,7 +26,7 @@ class ChangeSecretManager(AccountBasePlaybookManager):
|
|||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.record_map = self.execution.snapshot.get('record_map', {})
|
self.record_id = self.execution.snapshot.get('record_id')
|
||||||
self.secret_type = self.execution.snapshot.get('secret_type')
|
self.secret_type = self.execution.snapshot.get('secret_type')
|
||||||
self.secret_strategy = self.execution.snapshot.get(
|
self.secret_strategy = self.execution.snapshot.get(
|
||||||
'secret_strategy', SecretStrategy.custom
|
'secret_strategy', SecretStrategy.custom
|
||||||
@@ -119,25 +118,14 @@ class ChangeSecretManager(AccountBasePlaybookManager):
|
|||||||
else:
|
else:
|
||||||
new_secret = self.get_secret(secret_type)
|
new_secret = self.get_secret(secret_type)
|
||||||
|
|
||||||
if new_secret is None:
|
if self.record_id is None:
|
||||||
print(f'new_secret is None, account: {account}')
|
|
||||||
continue
|
|
||||||
|
|
||||||
asset_account_id = f'{asset.id}-{account.id}'
|
|
||||||
if asset_account_id not in self.record_map:
|
|
||||||
recorder = ChangeSecretRecord(
|
recorder = ChangeSecretRecord(
|
||||||
asset=asset, account=account, execution=self.execution,
|
asset=asset, account=account, execution=self.execution,
|
||||||
old_secret=account.secret, new_secret=new_secret,
|
old_secret=account.secret, new_secret=new_secret,
|
||||||
)
|
)
|
||||||
records.append(recorder)
|
records.append(recorder)
|
||||||
else:
|
else:
|
||||||
record_id = self.record_map[asset_account_id]
|
recorder = ChangeSecretRecord.objects.get(id=self.record_id)
|
||||||
try:
|
|
||||||
recorder = ChangeSecretRecord.objects.get(id=record_id)
|
|
||||||
new_secret = recorder.new_secret
|
|
||||||
except ChangeSecretRecord.DoesNotExist:
|
|
||||||
print(f"Record {record_id} not found")
|
|
||||||
continue
|
|
||||||
|
|
||||||
self.name_recorder_mapper[h['name']] = recorder
|
self.name_recorder_mapper[h['name']] = recorder
|
||||||
|
|
||||||
@@ -151,7 +139,7 @@ class ChangeSecretManager(AccountBasePlaybookManager):
|
|||||||
'name': account.name,
|
'name': account.name,
|
||||||
'username': account.username,
|
'username': account.username,
|
||||||
'secret_type': secret_type,
|
'secret_type': secret_type,
|
||||||
'secret': account.escape_jinja2_syntax(new_secret),
|
'secret': new_secret,
|
||||||
'private_key_path': private_key_path,
|
'private_key_path': private_key_path,
|
||||||
'become': account.get_ansible_become_auth(),
|
'become': account.get_ansible_become_auth(),
|
||||||
}
|
}
|
||||||
@@ -165,43 +153,24 @@ class ChangeSecretManager(AccountBasePlaybookManager):
|
|||||||
recorder = self.name_recorder_mapper.get(host)
|
recorder = self.name_recorder_mapper.get(host)
|
||||||
if not recorder:
|
if not recorder:
|
||||||
return
|
return
|
||||||
recorder.status = ChangeSecretRecordStatusChoice.success.value
|
recorder.status = 'success'
|
||||||
recorder.date_finished = timezone.now()
|
recorder.date_finished = timezone.now()
|
||||||
|
recorder.save()
|
||||||
account = recorder.account
|
account = recorder.account
|
||||||
if not account:
|
if not account:
|
||||||
print("Account not found, deleted ?")
|
print("Account not found, deleted ?")
|
||||||
return
|
return
|
||||||
account.secret = recorder.new_secret
|
account.secret = recorder.new_secret
|
||||||
account.date_updated = timezone.now()
|
account.save(update_fields=['secret'])
|
||||||
|
|
||||||
max_retries = 3
|
|
||||||
retry_count = 0
|
|
||||||
|
|
||||||
while retry_count < max_retries:
|
|
||||||
try:
|
|
||||||
recorder.save()
|
|
||||||
account.save(update_fields=['secret', 'version', 'date_updated'])
|
|
||||||
break
|
|
||||||
except Exception as e:
|
|
||||||
retry_count += 1
|
|
||||||
if retry_count == max_retries:
|
|
||||||
self.on_host_error(host, str(e), result)
|
|
||||||
else:
|
|
||||||
print(f'retry {retry_count} times for {host} recorder save error: {e}')
|
|
||||||
time.sleep(1)
|
|
||||||
|
|
||||||
def on_host_error(self, host, error, result):
|
def on_host_error(self, host, error, result):
|
||||||
recorder = self.name_recorder_mapper.get(host)
|
recorder = self.name_recorder_mapper.get(host)
|
||||||
if not recorder:
|
if not recorder:
|
||||||
return
|
return
|
||||||
recorder.status = ChangeSecretRecordStatusChoice.failed.value
|
recorder.status = 'failed'
|
||||||
recorder.date_finished = timezone.now()
|
recorder.date_finished = timezone.now()
|
||||||
recorder.error = error
|
recorder.error = error
|
||||||
try:
|
recorder.save()
|
||||||
recorder.save()
|
|
||||||
except Exception as e:
|
|
||||||
print(f"\033[31m Save {host} recorder error: {e} \033[0m\n")
|
|
||||||
|
|
||||||
def on_runner_failed(self, runner, e):
|
def on_runner_failed(self, runner, e):
|
||||||
logger.error("Account error: ", e)
|
logger.error("Account error: ", e)
|
||||||
@@ -213,56 +182,23 @@ class ChangeSecretManager(AccountBasePlaybookManager):
|
|||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_summary(recorders):
|
|
||||||
total, succeed, failed = 0, 0, 0
|
|
||||||
for recorder in recorders:
|
|
||||||
if recorder.status == ChangeSecretRecordStatusChoice.success.value:
|
|
||||||
succeed += 1
|
|
||||||
else:
|
|
||||||
failed += 1
|
|
||||||
total += 1
|
|
||||||
|
|
||||||
summary = _('Success: %s, Failed: %s, Total: %s') % (succeed, failed, total)
|
|
||||||
return summary
|
|
||||||
|
|
||||||
def run(self, *args, **kwargs):
|
def run(self, *args, **kwargs):
|
||||||
if self.secret_type and not self.check_secret():
|
if self.secret_type and not self.check_secret():
|
||||||
return
|
return
|
||||||
super().run(*args, **kwargs)
|
super().run(*args, **kwargs)
|
||||||
recorders = list(self.name_recorder_mapper.values())
|
if self.record_id:
|
||||||
summary = self.get_summary(recorders)
|
|
||||||
print(summary, end='')
|
|
||||||
|
|
||||||
if self.record_map:
|
|
||||||
return
|
return
|
||||||
|
recorders = self.name_recorder_mapper.values()
|
||||||
|
recorders = list(recorders)
|
||||||
|
self.send_recorder_mail(recorders)
|
||||||
|
|
||||||
failed_recorders = [
|
def send_recorder_mail(self, recorders):
|
||||||
r for r in recorders
|
|
||||||
if r.status == ChangeSecretRecordStatusChoice.failed.value
|
|
||||||
]
|
|
||||||
|
|
||||||
recipients = self.execution.recipients
|
recipients = self.execution.recipients
|
||||||
|
if not recorders or not recipients:
|
||||||
|
return
|
||||||
|
|
||||||
recipients = User.objects.filter(id__in=list(recipients.keys()))
|
recipients = User.objects.filter(id__in=list(recipients.keys()))
|
||||||
if not recipients:
|
|
||||||
return
|
|
||||||
|
|
||||||
if failed_recorders:
|
|
||||||
name = self.execution.snapshot.get('name')
|
|
||||||
execution_id = str(self.execution.id)
|
|
||||||
_ids = [r.id for r in failed_recorders]
|
|
||||||
asset_account_errors = ChangeSecretRecord.objects.filter(
|
|
||||||
id__in=_ids).values_list('asset__name', 'account__username', 'error')
|
|
||||||
|
|
||||||
for user in recipients:
|
|
||||||
ChangeSecretFailedMsg(name, execution_id, user, asset_account_errors).publish()
|
|
||||||
|
|
||||||
if not recorders:
|
|
||||||
return
|
|
||||||
|
|
||||||
self.send_recorder_mail(recipients, recorders, summary)
|
|
||||||
|
|
||||||
def send_recorder_mail(self, recipients, recorders, summary):
|
|
||||||
name = self.execution.snapshot['name']
|
name = self.execution.snapshot['name']
|
||||||
path = os.path.join(os.path.dirname(settings.BASE_DIR), 'tmp')
|
path = os.path.join(os.path.dirname(settings.BASE_DIR), 'tmp')
|
||||||
filename = os.path.join(path, f'{name}-{local_now_filename()}-{time.time()}.xlsx')
|
filename = os.path.join(path, f'{name}-{local_now_filename()}-{time.time()}.xlsx')
|
||||||
@@ -272,10 +208,11 @@ class ChangeSecretManager(AccountBasePlaybookManager):
|
|||||||
for user in recipients:
|
for user in recipients:
|
||||||
attachments = []
|
attachments = []
|
||||||
if user.secret_key:
|
if user.secret_key:
|
||||||
|
password = user.secret_key.encode('utf8')
|
||||||
attachment = os.path.join(path, f'{name}-{local_now_filename()}-{time.time()}.zip')
|
attachment = os.path.join(path, f'{name}-{local_now_filename()}-{time.time()}.zip')
|
||||||
encrypt_and_compress_zip_file(attachment, user.secret_key, [filename])
|
encrypt_and_compress_zip_file(attachment, password, [filename])
|
||||||
attachments = [attachment]
|
attachments = [attachment]
|
||||||
ChangeSecretExecutionTaskMsg(name, user, summary).publish(attachments)
|
ChangeSecretExecutionTaskMsg(name, user).publish(attachments)
|
||||||
os.remove(filename)
|
os.remove(filename)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@@ -290,9 +227,8 @@ class ChangeSecretManager(AccountBasePlaybookManager):
|
|||||||
|
|
||||||
rows.insert(0, header)
|
rows.insert(0, header)
|
||||||
wb = Workbook(filename)
|
wb = Workbook(filename)
|
||||||
ws = wb.add_worksheet('Sheet1')
|
ws = wb.create_sheet('Sheet1')
|
||||||
for row_index, row_data in enumerate(rows):
|
for row in rows:
|
||||||
for col_index, col_data in enumerate(row_data):
|
ws.append(row)
|
||||||
ws.write_string(row_index, col_index, col_data)
|
wb.save(filename)
|
||||||
wb.close()
|
|
||||||
return True
|
return True
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
from .backup_account.manager import AccountBackupManager
|
|
||||||
from .change_secret.manager import ChangeSecretManager
|
|
||||||
from .gather_accounts.manager import GatherAccountsManager
|
|
||||||
from .push_account.manager import PushAccountManager
|
from .push_account.manager import PushAccountManager
|
||||||
from .remove_account.manager import RemoveAccountManager
|
from .change_secret.manager import ChangeSecretManager
|
||||||
from .verify_account.manager import VerifyAccountManager
|
from .verify_account.manager import VerifyAccountManager
|
||||||
|
from .backup_account.manager import AccountBackupManager
|
||||||
|
from .gather_accounts.manager import GatherAccountsManager
|
||||||
from .verify_gateway_account.manager import VerifyGatewayAccountManager
|
from .verify_gateway_account.manager import VerifyGatewayAccountManager
|
||||||
from ..const import AutomationTypes
|
from ..const import AutomationTypes
|
||||||
|
|
||||||
@@ -13,7 +12,6 @@ class ExecutionManager:
|
|||||||
AutomationTypes.push_account: PushAccountManager,
|
AutomationTypes.push_account: PushAccountManager,
|
||||||
AutomationTypes.change_secret: ChangeSecretManager,
|
AutomationTypes.change_secret: ChangeSecretManager,
|
||||||
AutomationTypes.verify_account: VerifyAccountManager,
|
AutomationTypes.verify_account: VerifyAccountManager,
|
||||||
AutomationTypes.remove_account: RemoveAccountManager,
|
|
||||||
AutomationTypes.gather_accounts: GatherAccountsManager,
|
AutomationTypes.gather_accounts: GatherAccountsManager,
|
||||||
AutomationTypes.verify_gateway_account: VerifyGatewayAccountManager,
|
AutomationTypes.verify_gateway_account: VerifyGatewayAccountManager,
|
||||||
# TODO 后期迁移到自动化策略中
|
# TODO 后期迁移到自动化策略中
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
- hosts: demo
|
- hosts: demo
|
||||||
gather_facts: no
|
gather_facts: no
|
||||||
tasks:
|
tasks:
|
||||||
- name: Gather windows account
|
- name: Gather posix account
|
||||||
ansible.builtin.win_shell: net user
|
ansible.builtin.win_shell: net user
|
||||||
register: result
|
register: result
|
||||||
ignore_errors: true
|
|
||||||
|
|
||||||
- name: Define info by set_fact
|
- name: Define info by set_fact
|
||||||
ansible.builtin.set_fact:
|
ansible.builtin.set_fact:
|
||||||
|
|||||||
@@ -51,22 +51,14 @@ class GatherAccountsManager(AccountBasePlaybookManager):
|
|||||||
data = self.generate_data(asset, result)
|
data = self.generate_data(asset, result)
|
||||||
self.asset_account_info[asset] = data
|
self.asset_account_info[asset] = data
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_nested_info(data, *keys):
|
|
||||||
for key in keys:
|
|
||||||
data = data.get(key, {})
|
|
||||||
if not data:
|
|
||||||
break
|
|
||||||
return data
|
|
||||||
|
|
||||||
def on_host_success(self, host, result):
|
def on_host_success(self, host, result):
|
||||||
info = self.get_nested_info(result, 'debug', 'res', 'info')
|
info = result.get('debug', {}).get('res', {}).get('info', {})
|
||||||
asset = self.host_asset_mapper.get(host)
|
asset = self.host_asset_mapper.get(host)
|
||||||
if asset and info:
|
if asset and info:
|
||||||
result = self.filter_success_result(asset.type, info)
|
result = self.filter_success_result(asset.type, info)
|
||||||
self.collect_asset_account_info(asset, result)
|
self.collect_asset_account_info(asset, result)
|
||||||
else:
|
else:
|
||||||
print(f'\033[31m Not found {host} info \033[0m\n')
|
logger.error(f'Not found {host} info')
|
||||||
|
|
||||||
def update_or_create_accounts(self):
|
def update_or_create_accounts(self):
|
||||||
for asset, data in self.asset_account_info.items():
|
for asset, data in self.asset_account_info.items():
|
||||||
@@ -80,7 +72,7 @@ class GatherAccountsManager(AccountBasePlaybookManager):
|
|||||||
)
|
)
|
||||||
gathered_accounts.append(gathered_account)
|
gathered_accounts.append(gathered_account)
|
||||||
if not self.is_sync_account:
|
if not self.is_sync_account:
|
||||||
continue
|
return
|
||||||
GatheredAccount.sync_accounts(gathered_accounts)
|
GatheredAccount.sync_accounts(gathered_accounts)
|
||||||
|
|
||||||
def run(self, *args, **kwargs):
|
def run(self, *args, **kwargs):
|
||||||
|
|||||||
@@ -53,5 +53,3 @@
|
|||||||
ssl_certfile: "{{ jms_asset.secret_info.client_key | default('') }}"
|
ssl_certfile: "{{ jms_asset.secret_info.client_key | default('') }}"
|
||||||
connection_options:
|
connection_options:
|
||||||
- tlsAllowInvalidHostnames: "{{ jms_asset.spec_info.allow_invalid_cert}}"
|
- tlsAllowInvalidHostnames: "{{ jms_asset.spec_info.allow_invalid_cert}}"
|
||||||
register: result
|
|
||||||
failed_when: not result.is_available
|
|
||||||
|
|||||||
@@ -36,8 +36,7 @@
|
|||||||
name: "{{ account.username }}"
|
name: "{{ account.username }}"
|
||||||
password: "{{ account.secret }}"
|
password: "{{ account.secret }}"
|
||||||
host: "%"
|
host: "%"
|
||||||
priv: "{{ omit if db_name == '' else db_name + '.*:ALL' }}"
|
priv: "{{ account.username + '.*:USAGE' if db_name == '' else db_name + '.*:ALL' }}"
|
||||||
append_privs: "{{ db_name != '' | bool }}"
|
|
||||||
ignore_errors: true
|
ignore_errors: true
|
||||||
when: db_info is succeeded
|
when: db_info is succeeded
|
||||||
|
|
||||||
|
|||||||
@@ -39,4 +39,3 @@
|
|||||||
login_host: "{{ jms_asset.address }}"
|
login_host: "{{ jms_asset.address }}"
|
||||||
login_port: "{{ jms_asset.port }}"
|
login_port: "{{ jms_asset.port }}"
|
||||||
login_database: "{{ jms_asset.spec_info.db_name }}"
|
login_database: "{{ jms_asset.spec_info.db_name }}"
|
||||||
mode: "{{ account.mode }}"
|
|
||||||
|
|||||||
@@ -31,6 +31,7 @@
|
|||||||
role_attr_flags: LOGIN
|
role_attr_flags: LOGIN
|
||||||
ignore_errors: true
|
ignore_errors: true
|
||||||
when: result is succeeded
|
when: result is succeeded
|
||||||
|
register: change_info
|
||||||
|
|
||||||
- name: Verify password
|
- name: Verify password
|
||||||
community.postgresql.postgresql_ping:
|
community.postgresql.postgresql_ping:
|
||||||
@@ -39,5 +40,8 @@
|
|||||||
login_host: "{{ jms_asset.address }}"
|
login_host: "{{ jms_asset.address }}"
|
||||||
login_port: "{{ jms_asset.port }}"
|
login_port: "{{ jms_asset.port }}"
|
||||||
db: "{{ jms_asset.spec_info.db_name }}"
|
db: "{{ jms_asset.spec_info.db_name }}"
|
||||||
|
when:
|
||||||
|
- result is succeeded
|
||||||
|
- change_info is succeeded
|
||||||
register: result
|
register: result
|
||||||
failed_when: not result.is_available
|
failed_when: not result.is_available
|
||||||
|
|||||||
@@ -35,24 +35,12 @@
|
|||||||
- user_info.failed
|
- user_info.failed
|
||||||
- params.groups
|
- params.groups
|
||||||
|
|
||||||
- name: "Set {{ account.username }} sudo setting"
|
|
||||||
ansible.builtin.lineinfile:
|
|
||||||
dest: /etc/sudoers
|
|
||||||
state: present
|
|
||||||
regexp: "^{{ account.username }} ALL="
|
|
||||||
line: "{{ account.username + ' ALL=(ALL) NOPASSWD: ' + params.sudo }}"
|
|
||||||
validate: visudo -cf %s
|
|
||||||
when:
|
|
||||||
- user_info.failed or params.modify_sudo
|
|
||||||
- params.sudo
|
|
||||||
|
|
||||||
- name: "Change {{ account.username }} password"
|
- name: "Change {{ account.username }} password"
|
||||||
ansible.builtin.user:
|
ansible.builtin.user:
|
||||||
name: "{{ account.username }}"
|
name: "{{ account.username }}"
|
||||||
password: "{{ account.secret | password_hash('des') }}"
|
password: "{{ account.secret | password_hash('des') }}"
|
||||||
update_password: always
|
update_password: always
|
||||||
ignore_errors: true
|
ignore_errors: true
|
||||||
register: change_secret_result
|
|
||||||
when: account.secret_type == "password"
|
when: account.secret_type == "password"
|
||||||
|
|
||||||
- name: remove jumpserver ssh key
|
- name: remove jumpserver ssh key
|
||||||
@@ -69,9 +57,19 @@
|
|||||||
user: "{{ account.username }}"
|
user: "{{ account.username }}"
|
||||||
key: "{{ account.secret }}"
|
key: "{{ account.secret }}"
|
||||||
exclusive: "{{ ssh_params.exclusive }}"
|
exclusive: "{{ ssh_params.exclusive }}"
|
||||||
register: change_secret_result
|
|
||||||
when: account.secret_type == "ssh_key"
|
when: account.secret_type == "ssh_key"
|
||||||
|
|
||||||
|
- name: "Set {{ account.username }} sudo setting"
|
||||||
|
ansible.builtin.lineinfile:
|
||||||
|
dest: /etc/sudoers
|
||||||
|
state: present
|
||||||
|
regexp: "^{{ account.username }} ALL="
|
||||||
|
line: "{{ account.username + ' ALL=(ALL) NOPASSWD: ' + params.sudo }}"
|
||||||
|
validate: visudo -cf %s
|
||||||
|
when:
|
||||||
|
- user_info.failed
|
||||||
|
- params.sudo
|
||||||
|
|
||||||
- name: Refresh connection
|
- name: Refresh connection
|
||||||
ansible.builtin.meta: reset_connection
|
ansible.builtin.meta: reset_connection
|
||||||
|
|
||||||
@@ -87,10 +85,7 @@
|
|||||||
become_user: "{{ account.become.ansible_user | default('') }}"
|
become_user: "{{ account.become.ansible_user | default('') }}"
|
||||||
become_password: "{{ account.become.ansible_password | default('') }}"
|
become_password: "{{ account.become.ansible_password | default('') }}"
|
||||||
become_private_key_path: "{{ account.become.ansible_ssh_private_key_file | default(None) }}"
|
become_private_key_path: "{{ account.become.ansible_ssh_private_key_file | default(None) }}"
|
||||||
old_ssh_version: "{{ jms_asset.old_ssh_version | default(False) }}"
|
when: account.secret_type == "password"
|
||||||
when:
|
|
||||||
- account.secret_type == "password"
|
|
||||||
- check_conn_after_change or change_secret_result.failed | default(false)
|
|
||||||
delegate_to: localhost
|
delegate_to: localhost
|
||||||
|
|
||||||
- name: "Verify {{ account.username }} SSH KEY (paramiko)"
|
- name: "Verify {{ account.username }} SSH KEY (paramiko)"
|
||||||
@@ -100,9 +95,6 @@
|
|||||||
login_user: "{{ account.username }}"
|
login_user: "{{ account.username }}"
|
||||||
login_private_key_path: "{{ account.private_key_path }}"
|
login_private_key_path: "{{ account.private_key_path }}"
|
||||||
gateway_args: "{{ jms_asset.ansible_ssh_common_args | default('') }}"
|
gateway_args: "{{ jms_asset.ansible_ssh_common_args | default('') }}"
|
||||||
old_ssh_version: "{{ jms_asset.old_ssh_version | default(False) }}"
|
when: account.secret_type == "ssh_key"
|
||||||
when:
|
|
||||||
- account.secret_type == "ssh_key"
|
|
||||||
- check_conn_after_change or change_secret_result.failed | default(false)
|
|
||||||
delegate_to: localhost
|
delegate_to: localhost
|
||||||
|
|
||||||
|
|||||||
@@ -5,17 +5,11 @@ type:
|
|||||||
- AIX
|
- AIX
|
||||||
method: push_account
|
method: push_account
|
||||||
params:
|
params:
|
||||||
- name: modify_sudo
|
|
||||||
type: bool
|
|
||||||
label: "{{ 'Modify sudo label' | trans }}"
|
|
||||||
default: False
|
|
||||||
help_text: "{{ 'Modify params sudo help text' | trans }}"
|
|
||||||
|
|
||||||
- name: sudo
|
- name: sudo
|
||||||
type: str
|
type: str
|
||||||
label: 'Sudo'
|
label: 'Sudo'
|
||||||
default: '/bin/whoami'
|
default: '/bin/whoami'
|
||||||
help_text: "{{ 'Params sudo help text' | trans }}"
|
help_text: '使用逗号分隔多个命令,如: /bin/whoami,/sbin/ifconfig'
|
||||||
|
|
||||||
- name: shell
|
- name: shell
|
||||||
type: str
|
type: str
|
||||||
@@ -24,54 +18,19 @@ params:
|
|||||||
|
|
||||||
- name: home
|
- name: home
|
||||||
type: str
|
type: str
|
||||||
label: "{{ 'Params home label' | trans }}"
|
label: '家目录'
|
||||||
default: ''
|
default: ''
|
||||||
help_text: "{{ 'Params home help text' | trans }}"
|
help_text: '默认家目录 /home/系统用户名: /home/username'
|
||||||
|
|
||||||
- name: groups
|
- name: groups
|
||||||
type: str
|
type: str
|
||||||
label: "{{ 'Params groups label' | trans }}"
|
label: '用户组'
|
||||||
default: ''
|
default: ''
|
||||||
help_text: "{{ 'Params groups help text' | trans }}"
|
help_text: '请输入用户组,多个用户组使用逗号分隔(需填写已存在的用户组)'
|
||||||
|
|
||||||
i18n:
|
i18n:
|
||||||
Aix account push:
|
Aix account push:
|
||||||
zh: '使用 Ansible 模块 user 执行 Aix 账号推送 (DES)'
|
zh: 使用 Ansible 模块 user 执行 Aix 账号推送 (DES)
|
||||||
ja: 'Ansible user モジュールを使用して Aix アカウントをプッシュする (DES)'
|
ja: Ansible user モジュールを使用して Aix アカウントをプッシュする (DES)
|
||||||
en: 'Using Ansible module user to push account (DES)'
|
en: Using Ansible module user to push account (DES)
|
||||||
|
|
||||||
Modify params sudo help text:
|
|
||||||
zh: '如果用户存在,可以修改sudo权限'
|
|
||||||
ja: 'ユーザーが存在する場合、sudo権限を変更できます'
|
|
||||||
en: 'If the user exists, sudo permissions can be modified'
|
|
||||||
|
|
||||||
Params sudo help text:
|
|
||||||
zh: '使用逗号分隔多个命令,如: /bin/whoami,/sbin/ifconfig'
|
|
||||||
ja: 'コンマで区切って複数のコマンドを入力してください。例: /bin/whoami,/sbin/ifconfig'
|
|
||||||
en: 'Use commas to separate multiple commands, such as: /bin/whoami,/sbin/ifconfig'
|
|
||||||
|
|
||||||
Params home help text:
|
|
||||||
zh: '默认家目录 /home/{账号用户名}'
|
|
||||||
ja: 'デフォルトのホームディレクトリ /home/{アカウントユーザ名}'
|
|
||||||
en: 'Default home directory /home/{account username}'
|
|
||||||
|
|
||||||
Params groups help text:
|
|
||||||
zh: '请输入用户组,多个用户组使用逗号分隔(需填写已存在的用户组)'
|
|
||||||
ja: 'グループを入力してください。複数のグループはコンマで区切ってください(既存のグループを入力してください)'
|
|
||||||
en: 'Please enter the group. Multiple groups are separated by commas (please enter the existing group)'
|
|
||||||
|
|
||||||
Modify sudo label:
|
|
||||||
zh: '修改 sudo 权限'
|
|
||||||
ja: 'sudo 権限を変更'
|
|
||||||
en: 'Modify sudo'
|
|
||||||
|
|
||||||
Params home label:
|
|
||||||
zh: '家目录'
|
|
||||||
ja: 'ホームディレクトリ'
|
|
||||||
en: 'Home'
|
|
||||||
|
|
||||||
Params groups label:
|
|
||||||
zh: '用户组'
|
|
||||||
ja: 'グループ'
|
|
||||||
en: 'Groups'
|
|
||||||
|
|
||||||
|
|||||||
@@ -35,24 +35,12 @@
|
|||||||
- user_info.failed
|
- user_info.failed
|
||||||
- params.groups
|
- params.groups
|
||||||
|
|
||||||
- name: "Set {{ account.username }} sudo setting"
|
|
||||||
ansible.builtin.lineinfile:
|
|
||||||
dest: /etc/sudoers
|
|
||||||
state: present
|
|
||||||
regexp: "^{{ account.username }} ALL="
|
|
||||||
line: "{{ account.username + ' ALL=(ALL) NOPASSWD: ' + params.sudo }}"
|
|
||||||
validate: visudo -cf %s
|
|
||||||
when:
|
|
||||||
- user_info.failed or params.modify_sudo
|
|
||||||
- params.sudo
|
|
||||||
|
|
||||||
- name: "Change {{ account.username }} password"
|
- name: "Change {{ account.username }} password"
|
||||||
ansible.builtin.user:
|
ansible.builtin.user:
|
||||||
name: "{{ account.username }}"
|
name: "{{ account.username }}"
|
||||||
password: "{{ account.secret | password_hash('sha512') }}"
|
password: "{{ account.secret | password_hash('sha512') }}"
|
||||||
update_password: always
|
update_password: always
|
||||||
ignore_errors: true
|
ignore_errors: true
|
||||||
register: change_secret_result
|
|
||||||
when: account.secret_type == "password"
|
when: account.secret_type == "password"
|
||||||
|
|
||||||
- name: remove jumpserver ssh key
|
- name: remove jumpserver ssh key
|
||||||
@@ -69,9 +57,19 @@
|
|||||||
user: "{{ account.username }}"
|
user: "{{ account.username }}"
|
||||||
key: "{{ account.secret }}"
|
key: "{{ account.secret }}"
|
||||||
exclusive: "{{ ssh_params.exclusive }}"
|
exclusive: "{{ ssh_params.exclusive }}"
|
||||||
register: change_secret_result
|
|
||||||
when: account.secret_type == "ssh_key"
|
when: account.secret_type == "ssh_key"
|
||||||
|
|
||||||
|
- name: "Set {{ account.username }} sudo setting"
|
||||||
|
ansible.builtin.lineinfile:
|
||||||
|
dest: /etc/sudoers
|
||||||
|
state: present
|
||||||
|
regexp: "^{{ account.username }} ALL="
|
||||||
|
line: "{{ account.username + ' ALL=(ALL) NOPASSWD: ' + params.sudo }}"
|
||||||
|
validate: visudo -cf %s
|
||||||
|
when:
|
||||||
|
- user_info.failed
|
||||||
|
- params.sudo
|
||||||
|
|
||||||
- name: Refresh connection
|
- name: Refresh connection
|
||||||
ansible.builtin.meta: reset_connection
|
ansible.builtin.meta: reset_connection
|
||||||
|
|
||||||
@@ -87,10 +85,7 @@
|
|||||||
become_user: "{{ account.become.ansible_user | default('') }}"
|
become_user: "{{ account.become.ansible_user | default('') }}"
|
||||||
become_password: "{{ account.become.ansible_password | default('') }}"
|
become_password: "{{ account.become.ansible_password | default('') }}"
|
||||||
become_private_key_path: "{{ account.become.ansible_ssh_private_key_file | default(None) }}"
|
become_private_key_path: "{{ account.become.ansible_ssh_private_key_file | default(None) }}"
|
||||||
old_ssh_version: "{{ jms_asset.old_ssh_version | default(False) }}"
|
when: account.secret_type == "password"
|
||||||
when:
|
|
||||||
- account.secret_type == "password"
|
|
||||||
- check_conn_after_change or change_secret_result.failed | default(false)
|
|
||||||
delegate_to: localhost
|
delegate_to: localhost
|
||||||
|
|
||||||
- name: "Verify {{ account.username }} SSH KEY (paramiko)"
|
- name: "Verify {{ account.username }} SSH KEY (paramiko)"
|
||||||
@@ -100,9 +95,6 @@
|
|||||||
login_user: "{{ account.username }}"
|
login_user: "{{ account.username }}"
|
||||||
login_private_key_path: "{{ account.private_key_path }}"
|
login_private_key_path: "{{ account.private_key_path }}"
|
||||||
gateway_args: "{{ jms_asset.ansible_ssh_common_args | default('') }}"
|
gateway_args: "{{ jms_asset.ansible_ssh_common_args | default('') }}"
|
||||||
old_ssh_version: "{{ jms_asset.old_ssh_version | default(False) }}"
|
when: account.secret_type == "ssh_key"
|
||||||
when:
|
|
||||||
- account.secret_type == "ssh_key"
|
|
||||||
- check_conn_after_change or change_secret_result.failed | default(false)
|
|
||||||
delegate_to: localhost
|
delegate_to: localhost
|
||||||
|
|
||||||
|
|||||||
@@ -6,17 +6,11 @@ type:
|
|||||||
- linux
|
- linux
|
||||||
method: push_account
|
method: push_account
|
||||||
params:
|
params:
|
||||||
- name: modify_sudo
|
|
||||||
type: bool
|
|
||||||
label: "{{ 'Modify sudo label' | trans }}"
|
|
||||||
default: False
|
|
||||||
help_text: "{{ 'Modify params sudo help text' | trans }}"
|
|
||||||
|
|
||||||
- name: sudo
|
- name: sudo
|
||||||
type: str
|
type: str
|
||||||
label: 'Sudo'
|
label: 'Sudo'
|
||||||
default: '/bin/whoami'
|
default: '/bin/whoami'
|
||||||
help_text: "{{ 'Params sudo help text' | trans }}"
|
help_text: '使用逗号分隔多个命令,如: /bin/whoami,/sbin/ifconfig'
|
||||||
|
|
||||||
- name: shell
|
- name: shell
|
||||||
type: str
|
type: str
|
||||||
@@ -26,53 +20,18 @@ params:
|
|||||||
|
|
||||||
- name: home
|
- name: home
|
||||||
type: str
|
type: str
|
||||||
label: "{{ 'Params home label' | trans }}"
|
label: '家目录'
|
||||||
default: ''
|
default: ''
|
||||||
help_text: "{{ 'Params home help text' | trans }}"
|
help_text: '默认家目录 /home/系统用户名: /home/username'
|
||||||
|
|
||||||
- name: groups
|
- name: groups
|
||||||
type: str
|
type: str
|
||||||
label: "{{ 'Params groups label' | trans }}"
|
label: '用户组'
|
||||||
default: ''
|
default: ''
|
||||||
help_text: "{{ 'Params groups help text' | trans }}"
|
help_text: '请输入用户组,多个用户组使用逗号分隔(需填写已存在的用户组)'
|
||||||
|
|
||||||
i18n:
|
i18n:
|
||||||
Posix account push:
|
Posix account push:
|
||||||
zh: '使用 Ansible 模块 user 执行账号推送 (sha512)'
|
zh: 使用 Ansible 模块 user 执行账号推送 (sha512)
|
||||||
ja: 'Ansible user モジュールを使用してアカウントをプッシュする (sha512)'
|
ja: Ansible user モジュールを使用してアカウントをプッシュする (sha512)
|
||||||
en: 'Using Ansible module user to push account (sha512)'
|
en: Using Ansible module user to push account (sha512)
|
||||||
|
|
||||||
Modify params sudo help text:
|
|
||||||
zh: '如果用户存在,可以修改sudo权限'
|
|
||||||
ja: 'ユーザーが存在する場合、sudo権限を変更できます'
|
|
||||||
en: 'If the user exists, sudo permissions can be modified'
|
|
||||||
|
|
||||||
Params sudo help text:
|
|
||||||
zh: '使用逗号分隔多个命令,如: /bin/whoami,/sbin/ifconfig'
|
|
||||||
ja: 'コンマで区切って複数のコマンドを入力してください。例: /bin/whoami,/sbin/ifconfig'
|
|
||||||
en: 'Use commas to separate multiple commands, such as: /bin/whoami,/sbin/ifconfig'
|
|
||||||
|
|
||||||
Params home help text:
|
|
||||||
zh: '默认家目录 /home/{账号用户名}'
|
|
||||||
ja: 'デフォルトのホームディレクトリ /home/{アカウントユーザ名}'
|
|
||||||
en: 'Default home directory /home/{account username}'
|
|
||||||
|
|
||||||
Params groups help text:
|
|
||||||
zh: '请输入用户组,多个用户组使用逗号分隔(需填写已存在的用户组)'
|
|
||||||
ja: 'グループを入力してください。複数のグループはコンマで区切ってください(既存のグループを入力してください)'
|
|
||||||
en: 'Please enter the group. Multiple groups are separated by commas (please enter the existing group)'
|
|
||||||
|
|
||||||
Modify sudo label:
|
|
||||||
zh: '修改 sudo 权限'
|
|
||||||
ja: 'sudo 権限を変更'
|
|
||||||
en: 'Modify sudo'
|
|
||||||
|
|
||||||
Params home label:
|
|
||||||
zh: '家目录'
|
|
||||||
ja: 'ホームディレクトリ'
|
|
||||||
en: 'Home'
|
|
||||||
|
|
||||||
Params groups label:
|
|
||||||
zh: '用户组'
|
|
||||||
ja: 'グループ'
|
|
||||||
en: 'Groups'
|
|
||||||
|
|||||||
@@ -28,6 +28,4 @@
|
|||||||
vars:
|
vars:
|
||||||
ansible_user: "{{ account.username }}"
|
ansible_user: "{{ account.username }}"
|
||||||
ansible_password: "{{ account.secret }}"
|
ansible_password: "{{ account.secret }}"
|
||||||
when:
|
when: account.secret_type == "password"
|
||||||
- account.secret_type == "password"
|
|
||||||
- check_conn_after_change
|
|
||||||
|
|||||||
@@ -10,15 +10,10 @@ params:
|
|||||||
type: str
|
type: str
|
||||||
label: '用户组'
|
label: '用户组'
|
||||||
default: 'Users,Remote Desktop Users'
|
default: 'Users,Remote Desktop Users'
|
||||||
help_text: "{{ 'Params groups help text' | trans }}"
|
help_text: '请输入用户组,多个用户组使用逗号分隔(需填写已存在的用户组)'
|
||||||
|
|
||||||
i18n:
|
i18n:
|
||||||
Windows account push:
|
Windows account push:
|
||||||
zh: '使用 Ansible 模块 win_user 执行 Windows 账号推送'
|
zh: 使用 Ansible 模块 win_user 执行 Windows 账号推送
|
||||||
ja: 'Ansible win_user モジュールを使用して Windows アカウントをプッシュする'
|
ja: Ansible win_user モジュールを使用して Windows アカウントをプッシュする
|
||||||
en: 'Using Ansible module win_user to push account'
|
en: Using Ansible module win_user to push account
|
||||||
|
|
||||||
Params groups help text:
|
|
||||||
zh: '请输入用户组,多个用户组使用逗号分隔(需填写已存在的用户组)'
|
|
||||||
ja: 'グループを入力してください。複数のグループはコンマで区切ってください(既存のグループを入力してください)'
|
|
||||||
en: 'Please enter the group. Multiple groups are separated by commas (please enter the existing group)'
|
|
||||||
|
|||||||
@@ -31,7 +31,5 @@
|
|||||||
login_password: "{{ account.secret }}"
|
login_password: "{{ account.secret }}"
|
||||||
login_secret_type: "{{ account.secret_type }}"
|
login_secret_type: "{{ account.secret_type }}"
|
||||||
login_private_key_path: "{{ account.private_key_path }}"
|
login_private_key_path: "{{ account.private_key_path }}"
|
||||||
when:
|
when: account.secret_type == "password"
|
||||||
- account.secret_type == "password"
|
|
||||||
- check_conn_after_change
|
|
||||||
delegate_to: localhost
|
delegate_to: localhost
|
||||||
|
|||||||
@@ -5,21 +5,15 @@ method: push_account
|
|||||||
category: host
|
category: host
|
||||||
type:
|
type:
|
||||||
- windows
|
- windows
|
||||||
priority: 49
|
|
||||||
params:
|
params:
|
||||||
- name: groups
|
- name: groups
|
||||||
type: str
|
type: str
|
||||||
label: '用户组'
|
label: '用户组'
|
||||||
default: 'Users,Remote Desktop Users'
|
default: 'Users,Remote Desktop Users'
|
||||||
help_text: "{{ 'Params groups help text' | trans }}"
|
help_text: '请输入用户组,多个用户组使用逗号分隔(需填写已存在的用户组)'
|
||||||
|
|
||||||
i18n:
|
i18n:
|
||||||
Windows account push rdp verify:
|
Windows account push rdp verify:
|
||||||
zh: '使用 Ansible 模块 win_user 执行 Windows 账号推送(最后使用 Python 模块 pyfreerdp 验证账号的可连接性)'
|
zh: 使用 Ansible 模块 win_user 执行 Windows 账号推送 RDP 协议测试最后的可连接性
|
||||||
ja: 'Ansible モジュール win_user を使用して Windows アカウントのプッシュを実行します (最後に Python モジュール pyfreerdp を使用してアカウントの接続性を確認します)'
|
ja: Ansibleモジュールwin_userがWindowsアカウントプッシュRDPプロトコルテストを実行する最後の接続性
|
||||||
en: 'Use the Ansible module win_user to perform Windows account push (finally use the Python module pyfreerdp to verify the connectability of the account)'
|
en: Using the Ansible module win_user performs Windows account push RDP protocol testing for final connectivity
|
||||||
|
|
||||||
Params groups help text:
|
|
||||||
zh: '请输入用户组,多个用户组使用逗号分隔(需填写已存在的用户组)'
|
|
||||||
ja: 'グループを入力してください。複数のグループはコンマで区切ってください(既存のグループを入力してください)'
|
|
||||||
en: 'Please enter the group. Multiple groups are separated by commas (please enter the existing group)'
|
|
||||||
|
|||||||
@@ -1,21 +0,0 @@
|
|||||||
- hosts: mongodb
|
|
||||||
gather_facts: no
|
|
||||||
vars:
|
|
||||||
ansible_python_interpreter: /opt/py3/bin/python
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
- name: "Remove account"
|
|
||||||
mongodb_user:
|
|
||||||
login_user: "{{ jms_account.username }}"
|
|
||||||
login_password: "{{ jms_account.secret }}"
|
|
||||||
login_host: "{{ jms_asset.address }}"
|
|
||||||
login_port: "{{ jms_asset.port }}"
|
|
||||||
login_database: "{{ jms_asset.spec_info.db_name }}"
|
|
||||||
ssl: "{{ jms_asset.spec_info.use_ssl }}"
|
|
||||||
ssl_ca_certs: "{{ jms_asset.secret_info.ca_cert | default('') }}"
|
|
||||||
ssl_certfile: "{{ jms_asset.secret_info.client_key | default('') }}"
|
|
||||||
connection_options:
|
|
||||||
- tlsAllowInvalidHostnames: "{{ jms_asset.spec_info.allow_invalid_cert}}"
|
|
||||||
db: "{{ jms_asset.spec_info.db_name }}"
|
|
||||||
name: "{{ account.username }}"
|
|
||||||
state: absent
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
id: remove_account_mongodb
|
|
||||||
name: "{{ 'MongoDB account remove' | trans }}"
|
|
||||||
category: database
|
|
||||||
type:
|
|
||||||
- mongodb
|
|
||||||
method: remove_account
|
|
||||||
|
|
||||||
i18n:
|
|
||||||
MongoDB account remove:
|
|
||||||
zh: 使用 Ansible 模块 mongodb 删除账号
|
|
||||||
ja: Ansible モジュール mongodb を使用してアカウントを削除する
|
|
||||||
en: Delete account using Ansible module mongodb
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
- hosts: mysql
|
|
||||||
gather_facts: no
|
|
||||||
vars:
|
|
||||||
ansible_python_interpreter: /opt/py3/bin/python
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
- name: "Remove account"
|
|
||||||
community.mysql.mysql_user:
|
|
||||||
login_user: "{{ jms_account.username }}"
|
|
||||||
login_password: "{{ jms_account.secret }}"
|
|
||||||
login_host: "{{ jms_asset.address }}"
|
|
||||||
login_port: "{{ jms_asset.port }}"
|
|
||||||
check_hostname: "{{ check_ssl if check_ssl else omit }}"
|
|
||||||
ca_cert: "{{ jms_asset.secret_info.ca_cert | default(omit) if check_ssl else omit }}"
|
|
||||||
client_cert: "{{ jms_asset.secret_info.client_cert | default(omit) if check_ssl else omit }}"
|
|
||||||
client_key: "{{ jms_asset.secret_info.client_key | default(omit) if check_ssl else omit }}"
|
|
||||||
name: "{{ account.username }}"
|
|
||||||
state: absent
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
id: remove_account_mysql
|
|
||||||
name: "{{ 'MySQL account remove' | trans }}"
|
|
||||||
category: database
|
|
||||||
type:
|
|
||||||
- mysql
|
|
||||||
- mariadb
|
|
||||||
method: remove_account
|
|
||||||
|
|
||||||
i18n:
|
|
||||||
MySQL account remove:
|
|
||||||
zh: 使用 Ansible 模块 mysql_user 删除账号
|
|
||||||
ja: Ansible モジュール mysql_user を使用してアカウントを削除します
|
|
||||||
en: Use the Ansible module mysql_user to delete the account
|
|
||||||
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
- hosts: oracle
|
|
||||||
gather_facts: no
|
|
||||||
vars:
|
|
||||||
ansible_python_interpreter: /opt/py3/bin/python
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
- name: "Remove account"
|
|
||||||
oracle_user:
|
|
||||||
login_user: "{{ jms_account.username }}"
|
|
||||||
login_password: "{{ jms_account.secret }}"
|
|
||||||
login_host: "{{ jms_asset.address }}"
|
|
||||||
login_port: "{{ jms_asset.port }}"
|
|
||||||
login_database: "{{ jms_asset.spec_info.db_name }}"
|
|
||||||
mode: "{{ jms_account.mode }}"
|
|
||||||
name: "{{ account.username }}"
|
|
||||||
state: absent
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
id: remove_account_oracle
|
|
||||||
name: "{{ 'Oracle account remove' | trans }}"
|
|
||||||
category: database
|
|
||||||
type:
|
|
||||||
- oracle
|
|
||||||
method: remove_account
|
|
||||||
|
|
||||||
i18n:
|
|
||||||
Oracle account remove:
|
|
||||||
zh: 使用 Python 模块 oracledb 删除账号
|
|
||||||
ja: Python モジュール oracledb を使用してアカウントを検証する
|
|
||||||
en: Using Python module oracledb to verify account
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
- hosts: postgresql
|
|
||||||
gather_facts: no
|
|
||||||
vars:
|
|
||||||
ansible_python_interpreter: /opt/py3/bin/python
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
- name: "Remove account"
|
|
||||||
community.postgresql.postgresql_user:
|
|
||||||
login_user: "{{ jms_account.username }}"
|
|
||||||
login_password: "{{ jms_account.secret }}"
|
|
||||||
login_host: "{{ jms_asset.address }}"
|
|
||||||
login_port: "{{ jms_asset.port }}"
|
|
||||||
db: "{{ jms_asset.spec_info.db_name }}"
|
|
||||||
name: "{{ account.username }}"
|
|
||||||
state: absent
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
id: remove_account_postgresql
|
|
||||||
name: "{{ 'PostgreSQL account remove' | trans }}"
|
|
||||||
category: database
|
|
||||||
type:
|
|
||||||
- postgresql
|
|
||||||
method: remove_account
|
|
||||||
|
|
||||||
i18n:
|
|
||||||
PostgreSQL account remove:
|
|
||||||
zh: 使用 Ansible 模块 postgresql_user 删除账号
|
|
||||||
ja: Ansible モジュール postgresql_user を使用してアカウントを削除します
|
|
||||||
en: Use the Ansible module postgresql_user to delete the account
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
- hosts: sqlserver
|
|
||||||
gather_facts: no
|
|
||||||
vars:
|
|
||||||
ansible_python_interpreter: /opt/py3/bin/python
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
- name: "Remove account"
|
|
||||||
community.general.mssql_script:
|
|
||||||
login_user: "{{ jms_account.username }}"
|
|
||||||
login_password: "{{ jms_account.secret }}"
|
|
||||||
login_host: "{{ jms_asset.address }}"
|
|
||||||
login_port: "{{ jms_asset.port }}"
|
|
||||||
name: "{{ jms_asset.spec_info.db_name }}"
|
|
||||||
script: "DROP USER {{ account.username }}"
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
id: remove_account_sqlserver
|
|
||||||
name: "{{ 'SQLServer account remove' | trans }}"
|
|
||||||
category: database
|
|
||||||
type:
|
|
||||||
- sqlserver
|
|
||||||
method: remove_account
|
|
||||||
|
|
||||||
i18n:
|
|
||||||
SQLServer account remove:
|
|
||||||
zh: 使用 Ansible 模块 mssql 删除账号
|
|
||||||
ja: Ansible モジュール mssql を使用してアカウントを削除する
|
|
||||||
en: Use Ansible module mssql to delete account
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
- hosts: demo
|
|
||||||
gather_facts: no
|
|
||||||
tasks:
|
|
||||||
- name: "Get user home directory path"
|
|
||||||
ansible.builtin.shell:
|
|
||||||
cmd: "getent passwd {{ account.username }} | cut -d: -f6"
|
|
||||||
register: user_home_dir
|
|
||||||
ignore_errors: yes
|
|
||||||
|
|
||||||
- name: "Check if user home directory exists"
|
|
||||||
ansible.builtin.stat:
|
|
||||||
path: "{{ user_home_dir.stdout }}"
|
|
||||||
register: home_dir
|
|
||||||
when: user_home_dir.stdout != ""
|
|
||||||
ignore_errors: yes
|
|
||||||
|
|
||||||
- name: "Rename user home directory if it exists"
|
|
||||||
ansible.builtin.command:
|
|
||||||
cmd: "mv {{ user_home_dir.stdout }} {{ user_home_dir.stdout }}.bak"
|
|
||||||
when: home_dir.stat | default(false) and user_home_dir.stdout != ""
|
|
||||||
ignore_errors: yes
|
|
||||||
|
|
||||||
- name: "Remove account"
|
|
||||||
ansible.builtin.user:
|
|
||||||
name: "{{ account.username }}"
|
|
||||||
state: absent
|
|
||||||
remove: "{{ home_dir.stat.exists }}"
|
|
||||||
when: home_dir.stat | default(false)
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
id: remove_account_posix
|
|
||||||
name: "{{ 'Posix account remove' | trans }}"
|
|
||||||
category: host
|
|
||||||
type:
|
|
||||||
- linux
|
|
||||||
- unix
|
|
||||||
method: remove_account
|
|
||||||
|
|
||||||
i18n:
|
|
||||||
Posix account remove:
|
|
||||||
zh: 使用 Ansible 模块 user 删除账号
|
|
||||||
ja: Ansible モジュール ユーザーを使用してアカウントを削除します
|
|
||||||
en: Use the Ansible module user to delete the account
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
- hosts: windows
|
|
||||||
gather_facts: no
|
|
||||||
tasks:
|
|
||||||
- name: "Remove account"
|
|
||||||
ansible.windows.win_user:
|
|
||||||
name: "{{ account.username }}"
|
|
||||||
state: absent
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
id: remove_account_windows
|
|
||||||
name: "{{ 'Windows account remove' | trans }}"
|
|
||||||
version: 1
|
|
||||||
method: remove_account
|
|
||||||
category: host
|
|
||||||
type:
|
|
||||||
- windows
|
|
||||||
|
|
||||||
i18n:
|
|
||||||
Windows account remove:
|
|
||||||
zh: 使用 Ansible 模块 win_user 删除账号
|
|
||||||
ja: Ansible モジュール win_user を使用してアカウントを削除する
|
|
||||||
en: Use the Ansible module win_user to delete an account
|
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
import os
|
|
||||||
from copy import deepcopy
|
|
||||||
|
|
||||||
from django.db.models import QuerySet
|
|
||||||
|
|
||||||
from accounts.const import AutomationTypes
|
|
||||||
from accounts.models import Account
|
|
||||||
from common.utils import get_logger
|
|
||||||
from ..base.manager import AccountBasePlaybookManager
|
|
||||||
|
|
||||||
logger = get_logger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class RemoveAccountManager(AccountBasePlaybookManager):
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
super().__init__(*args, **kwargs)
|
|
||||||
self.host_account_mapper = {}
|
|
||||||
|
|
||||||
def prepare_runtime_dir(self):
|
|
||||||
path = super().prepare_runtime_dir()
|
|
||||||
ansible_config_path = os.path.join(path, 'ansible.cfg')
|
|
||||||
|
|
||||||
with open(ansible_config_path, 'w') as f:
|
|
||||||
f.write('[ssh_connection]\n')
|
|
||||||
f.write('ssh_args = -o ControlMaster=no -o ControlPersist=no\n')
|
|
||||||
return path
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def method_type(cls):
|
|
||||||
return AutomationTypes.remove_account
|
|
||||||
|
|
||||||
def get_gather_accounts(self, privilege_account, gather_accounts: QuerySet):
|
|
||||||
gather_account_ids = self.execution.snapshot['gather_accounts']
|
|
||||||
gather_accounts = gather_accounts.filter(id__in=gather_account_ids)
|
|
||||||
gather_accounts = gather_accounts.exclude(
|
|
||||||
username__in=[privilege_account.username, 'root', 'Administrator']
|
|
||||||
)
|
|
||||||
return gather_accounts
|
|
||||||
|
|
||||||
def host_callback(self, host, asset=None, account=None, automation=None, path_dir=None, **kwargs):
|
|
||||||
if host.get('error'):
|
|
||||||
return host
|
|
||||||
|
|
||||||
gather_accounts = asset.gatheredaccount_set.all()
|
|
||||||
gather_accounts = self.get_gather_accounts(account, gather_accounts)
|
|
||||||
|
|
||||||
inventory_hosts = []
|
|
||||||
|
|
||||||
for gather_account in gather_accounts:
|
|
||||||
h = deepcopy(host)
|
|
||||||
h['name'] += '(' + gather_account.username + ')'
|
|
||||||
self.host_account_mapper[h['name']] = (asset, gather_account)
|
|
||||||
h['account'] = {'username': gather_account.username}
|
|
||||||
inventory_hosts.append(h)
|
|
||||||
return inventory_hosts
|
|
||||||
|
|
||||||
def on_host_success(self, host, result):
|
|
||||||
tuple_asset_gather_account = self.host_account_mapper.get(host)
|
|
||||||
if not tuple_asset_gather_account:
|
|
||||||
return
|
|
||||||
asset, gather_account = tuple_asset_gather_account
|
|
||||||
try:
|
|
||||||
Account.objects.filter(
|
|
||||||
asset_id=asset.id,
|
|
||||||
username=gather_account.username
|
|
||||||
).delete()
|
|
||||||
gather_account.delete()
|
|
||||||
except Exception as e:
|
|
||||||
print(f'\033[31m Delete account {gather_account.username} failed: {e} \033[0m\n')
|
|
||||||
@@ -3,13 +3,12 @@
|
|||||||
vars:
|
vars:
|
||||||
ansible_shell_type: sh
|
ansible_shell_type: sh
|
||||||
ansible_connection: local
|
ansible_connection: local
|
||||||
ansible_python_interpreter: /opt/py3/bin/python
|
|
||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
- name: Verify account (pyfreerdp)
|
- name: Verify account (pyfreerdp)
|
||||||
rdp_ping:
|
rdp_ping:
|
||||||
login_host: "{{ jms_asset.address }}"
|
login_host: "{{ jms_asset.address }}"
|
||||||
login_port: "{{ jms_asset.port }}"
|
login_port: "{{ jms_asset.protocols | selectattr('name', 'equalto', 'rdp') | map(attribute='port') | first }}"
|
||||||
login_user: "{{ account.username }}"
|
login_user: "{{ account.username }}"
|
||||||
login_password: "{{ account.secret }}"
|
login_password: "{{ account.secret }}"
|
||||||
login_secret_type: "{{ account.secret_type }}"
|
login_secret_type: "{{ account.secret_type }}"
|
||||||
|
|||||||
@@ -5,11 +5,9 @@ category:
|
|||||||
type:
|
type:
|
||||||
- windows
|
- windows
|
||||||
method: verify_account
|
method: verify_account
|
||||||
protocol: rdp
|
|
||||||
priority: 1
|
|
||||||
|
|
||||||
i18n:
|
i18n:
|
||||||
Windows rdp account verify:
|
Windows rdp account verify:
|
||||||
zh: '使用 Python 模块 pyfreerdp 验证账号'
|
zh: 使用 Python 模块 pyfreerdp 验证账号
|
||||||
ja: 'Python モジュール pyfreerdp を使用してアカウントを検証する'
|
ja: Python モジュール pyfreerdp を使用してアカウントを検証する
|
||||||
en: 'Using Python module pyfreerdp to verify account'
|
en: Using Python module pyfreerdp to verify account
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
- name: Verify account (paramiko)
|
- name: Verify account (paramiko)
|
||||||
ssh_ping:
|
ssh_ping:
|
||||||
login_host: "{{ jms_asset.address }}"
|
login_host: "{{ jms_asset.address }}"
|
||||||
login_port: "{{ jms_asset.port }}"
|
login_port: "{{ jms_asset.protocols | selectattr('name', 'equalto', 'ssh') | map(attribute='port') | first }}"
|
||||||
login_user: "{{ account.username }}"
|
login_user: "{{ account.username }}"
|
||||||
login_password: "{{ account.secret }}"
|
login_password: "{{ account.secret }}"
|
||||||
login_secret_type: "{{ account.secret_type }}"
|
login_secret_type: "{{ account.secret_type }}"
|
||||||
@@ -19,5 +19,3 @@
|
|||||||
become_user: "{{ account.become.ansible_user | default('') }}"
|
become_user: "{{ account.become.ansible_user | default('') }}"
|
||||||
become_password: "{{ account.become.ansible_password | default('') }}"
|
become_password: "{{ account.become.ansible_password | default('') }}"
|
||||||
become_private_key_path: "{{ account.become.ansible_ssh_private_key_file | default(None) }}"
|
become_private_key_path: "{{ account.become.ansible_ssh_private_key_file | default(None) }}"
|
||||||
old_ssh_version: "{{ jms_asset.old_ssh_version | default(False) }}"
|
|
||||||
gateway_args: "{{ jms_asset.ansible_ssh_common_args | default(None) }}"
|
|
||||||
|
|||||||
@@ -6,11 +6,9 @@ category:
|
|||||||
type:
|
type:
|
||||||
- all
|
- all
|
||||||
method: verify_account
|
method: verify_account
|
||||||
protocol: ssh
|
|
||||||
priority: 50
|
|
||||||
|
|
||||||
i18n:
|
i18n:
|
||||||
SSH account verify:
|
SSH account verify:
|
||||||
zh: '使用 Python 模块 paramiko 验证账号'
|
zh: 使用 Python 模块 paramiko 验证账号
|
||||||
ja: 'Python モジュール paramiko を使用してアカウントを検証する'
|
ja: Python モジュール paramiko を使用してアカウントを検証する
|
||||||
en: 'Using Python module paramiko to verify account'
|
en: Using Python module paramiko to verify account
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
- hosts: mongodb
|
- hosts: mongdb
|
||||||
gather_facts: no
|
gather_facts: no
|
||||||
vars:
|
vars:
|
||||||
ansible_python_interpreter: /opt/py3/bin/python
|
ansible_python_interpreter: /opt/py3/bin/python
|
||||||
@@ -16,5 +16,3 @@
|
|||||||
ssl_certfile: "{{ jms_asset.secret_info.client_key | default('') }}"
|
ssl_certfile: "{{ jms_asset.secret_info.client_key | default('') }}"
|
||||||
connection_options:
|
connection_options:
|
||||||
- tlsAllowInvalidHostnames: "{{ jms_asset.spec_info.allow_invalid_cert }}"
|
- tlsAllowInvalidHostnames: "{{ jms_asset.spec_info.allow_invalid_cert }}"
|
||||||
register: result
|
|
||||||
failed_when: not result.is_available
|
|
||||||
|
|||||||
@@ -8,7 +8,6 @@
|
|||||||
ansible_user: "{{ account.username }}"
|
ansible_user: "{{ account.username }}"
|
||||||
ansible_password: "{{ account.secret }}"
|
ansible_password: "{{ account.secret }}"
|
||||||
ansible_ssh_private_key_file: "{{ account.private_key_path }}"
|
ansible_ssh_private_key_file: "{{ account.private_key_path }}"
|
||||||
ansible_timeout: 30
|
|
||||||
when: not account.become.ansible_become
|
when: not account.become.ansible_become
|
||||||
|
|
||||||
- name: Verify account connectivity(Switch)
|
- name: Verify account connectivity(Switch)
|
||||||
@@ -21,5 +20,4 @@
|
|||||||
ansible_become_method: "{{ account.become.ansible_become_method }}"
|
ansible_become_method: "{{ account.become.ansible_become_method }}"
|
||||||
ansible_become_user: "{{ account.become.ansible_become_user }}"
|
ansible_become_user: "{{ account.become.ansible_become_user }}"
|
||||||
ansible_become_password: "{{ account.become.ansible_become_password }}"
|
ansible_become_password: "{{ account.become.ansible_become_password }}"
|
||||||
ansible_timeout: 30
|
|
||||||
when: account.become.ansible_become
|
when: account.become.ansible_become
|
||||||
|
|||||||
@@ -9,4 +9,3 @@
|
|||||||
vars:
|
vars:
|
||||||
ansible_user: "{{ account.username }}"
|
ansible_user: "{{ account.username }}"
|
||||||
ansible_password: "{{ account.secret }}"
|
ansible_password: "{{ account.secret }}"
|
||||||
ansible_timeout: 30
|
|
||||||
|
|||||||
@@ -51,9 +51,6 @@ class VerifyAccountManager(AccountBasePlaybookManager):
|
|||||||
h['name'] += '(' + account.username + ')'
|
h['name'] += '(' + account.username + ')'
|
||||||
self.host_account_mapper[h['name']] = account
|
self.host_account_mapper[h['name']] = account
|
||||||
secret = account.secret
|
secret = account.secret
|
||||||
if secret is None:
|
|
||||||
print(f'account {account.name} secret is None')
|
|
||||||
continue
|
|
||||||
|
|
||||||
private_key_path = None
|
private_key_path = None
|
||||||
if account.secret_type == SecretType.SSH_KEY:
|
if account.secret_type == SecretType.SSH_KEY:
|
||||||
@@ -65,7 +62,7 @@ class VerifyAccountManager(AccountBasePlaybookManager):
|
|||||||
'name': account.name,
|
'name': account.name,
|
||||||
'username': account.username,
|
'username': account.username,
|
||||||
'secret_type': account.secret_type,
|
'secret_type': account.secret_type,
|
||||||
'secret': account.escape_jinja2_syntax(secret),
|
'secret': secret,
|
||||||
'private_key_path': private_key_path,
|
'private_key_path': private_key_path,
|
||||||
'become': account.get_ansible_become_auth(),
|
'become': account.get_ansible_become_auth(),
|
||||||
}
|
}
|
||||||
@@ -76,14 +73,8 @@ class VerifyAccountManager(AccountBasePlaybookManager):
|
|||||||
|
|
||||||
def on_host_success(self, host, result):
|
def on_host_success(self, host, result):
|
||||||
account = self.host_account_mapper.get(host)
|
account = self.host_account_mapper.get(host)
|
||||||
try:
|
account.set_connectivity(Connectivity.OK)
|
||||||
account.set_connectivity(Connectivity.OK)
|
|
||||||
except Exception as e:
|
|
||||||
print(f'\033[31m Update account {account.name} connectivity failed: {e} \033[0m\n')
|
|
||||||
|
|
||||||
def on_host_error(self, host, error, result):
|
def on_host_error(self, host, error, result):
|
||||||
account = self.host_account_mapper.get(host)
|
account = self.host_account_mapper.get(host)
|
||||||
try:
|
account.set_connectivity(Connectivity.ERR)
|
||||||
account.set_connectivity(Connectivity.ERR)
|
|
||||||
except Exception as e:
|
|
||||||
print(f'\033[31m Update account {account.name} connectivity failed: {e} \033[0m\n')
|
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ class AliasAccount(TextChoices):
|
|||||||
INPUT = '@INPUT', _('Manual input')
|
INPUT = '@INPUT', _('Manual input')
|
||||||
USER = '@USER', _('Dynamic user')
|
USER = '@USER', _('Dynamic user')
|
||||||
ANON = '@ANON', _('Anonymous account')
|
ANON = '@ANON', _('Anonymous account')
|
||||||
SPEC = '@SPEC', _('Specified account')
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def virtual_choices(cls):
|
def virtual_choices(cls):
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ DEFAULT_PASSWORD_RULES = {
|
|||||||
__all__ = [
|
__all__ = [
|
||||||
'AutomationTypes', 'SecretStrategy', 'SSHKeyStrategy', 'Connectivity',
|
'AutomationTypes', 'SecretStrategy', 'SSHKeyStrategy', 'Connectivity',
|
||||||
'DEFAULT_PASSWORD_LENGTH', 'DEFAULT_PASSWORD_RULES', 'TriggerChoice',
|
'DEFAULT_PASSWORD_LENGTH', 'DEFAULT_PASSWORD_RULES', 'TriggerChoice',
|
||||||
'PushAccountActionChoice', 'AccountBackupType', 'ChangeSecretRecordStatusChoice',
|
'PushAccountActionChoice', 'AccountBackupType'
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@@ -24,7 +24,6 @@ class AutomationTypes(models.TextChoices):
|
|||||||
push_account = 'push_account', _('Push account')
|
push_account = 'push_account', _('Push account')
|
||||||
change_secret = 'change_secret', _('Change secret')
|
change_secret = 'change_secret', _('Change secret')
|
||||||
verify_account = 'verify_account', _('Verify account')
|
verify_account = 'verify_account', _('Verify account')
|
||||||
remove_account = 'remove_account', _('Remove account')
|
|
||||||
gather_accounts = 'gather_accounts', _('Gather accounts')
|
gather_accounts = 'gather_accounts', _('Gather accounts')
|
||||||
verify_gateway_account = 'verify_gateway_account', _('Verify gateway account')
|
verify_gateway_account = 'verify_gateway_account', _('Verify gateway account')
|
||||||
|
|
||||||
@@ -103,9 +102,3 @@ class AccountBackupType(models.TextChoices):
|
|||||||
email = 'email', _('Email')
|
email = 'email', _('Email')
|
||||||
# 目前只支持sftp方式
|
# 目前只支持sftp方式
|
||||||
object_storage = 'object_storage', _('SFTP')
|
object_storage = 'object_storage', _('SFTP')
|
||||||
|
|
||||||
|
|
||||||
class ChangeSecretRecordStatusChoice(models.TextChoices):
|
|
||||||
failed = 'failed', _('Failed')
|
|
||||||
success = 'success', _('Success')
|
|
||||||
pending = 'pending', _('Pending')
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ from django_filters import rest_framework as drf_filters
|
|||||||
|
|
||||||
from assets.models import Node
|
from assets.models import Node
|
||||||
from common.drf.filters import BaseFilterSet
|
from common.drf.filters import BaseFilterSet
|
||||||
from .models import Account, GatheredAccount, ChangeSecretRecord
|
from .models import Account, GatheredAccount
|
||||||
|
|
||||||
|
|
||||||
class AccountFilterSet(BaseFilterSet):
|
class AccountFilterSet(BaseFilterSet):
|
||||||
@@ -51,8 +51,6 @@ class AccountFilterSet(BaseFilterSet):
|
|||||||
|
|
||||||
class GatheredAccountFilterSet(BaseFilterSet):
|
class GatheredAccountFilterSet(BaseFilterSet):
|
||||||
node_id = drf_filters.CharFilter(method='filter_nodes')
|
node_id = drf_filters.CharFilter(method='filter_nodes')
|
||||||
asset_id = drf_filters.CharFilter(field_name='asset_id', lookup_expr='exact')
|
|
||||||
asset_name = drf_filters.CharFilter(field_name='asset__name', lookup_expr='icontains')
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def filter_nodes(queryset, name, value):
|
def filter_nodes(queryset, name, value):
|
||||||
@@ -60,14 +58,4 @@ class GatheredAccountFilterSet(BaseFilterSet):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = GatheredAccount
|
model = GatheredAccount
|
||||||
fields = ['id', 'username']
|
fields = ['id', 'asset_id', 'username']
|
||||||
|
|
||||||
|
|
||||||
class ChangeSecretRecordFilterSet(BaseFilterSet):
|
|
||||||
asset_name = drf_filters.CharFilter(field_name='asset__name', lookup_expr='icontains')
|
|
||||||
account_username = drf_filters.CharFilter(field_name='account__username', lookup_expr='icontains')
|
|
||||||
execution_id = drf_filters.CharFilter(field_name='execution_id', lookup_expr='exact')
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = ChangeSecretRecord
|
|
||||||
fields = ['id', 'status', 'asset_id', 'execution']
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ from django.db import migrations
|
|||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('accounts', '0006_gatheredaccount'),
|
('accounts', '0006_gatheredaccount'),
|
||||||
]
|
]
|
||||||
@@ -11,13 +12,6 @@ class Migration(migrations.Migration):
|
|||||||
operations = [
|
operations = [
|
||||||
migrations.AlterModelOptions(
|
migrations.AlterModelOptions(
|
||||||
name='account',
|
name='account',
|
||||||
options={'permissions': [
|
options={'permissions': [('view_accountsecret', 'Can view asset account secret'), ('view_historyaccount', 'Can view asset history account'), ('view_historyaccountsecret', 'Can view asset history account secret'), ('verify_account', 'Can verify account'), ('push_account', 'Can push account')], 'verbose_name': 'Account'},
|
||||||
('view_accountsecret', 'Can view asset account secret'),
|
|
||||||
('view_historyaccount', 'Can view asset history account'),
|
|
||||||
('view_historyaccountsecret', 'Can view asset history account secret'),
|
|
||||||
('verify_account', 'Can verify account'),
|
|
||||||
('push_account', 'Can push account'),
|
|
||||||
('remove_account', 'Can remove account'),
|
|
||||||
], 'verbose_name': 'Account'},
|
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
# Generated by Django 4.1.10 on 2023-08-01 09:12
|
# Generated by Django 4.1.10 on 2023-08-01 09:12
|
||||||
|
|
||||||
import uuid
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
@@ -21,7 +20,7 @@ class Migration(migrations.Migration):
|
|||||||
('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')),
|
('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')),
|
||||||
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
|
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
|
||||||
('org_id', models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')),
|
('org_id', models.CharField(blank=True, db_index=True, default='', max_length=36, verbose_name='Organization')),
|
||||||
('alias', models.CharField(choices=[('@INPUT', 'Manual input'), ('@USER', 'Dynamic user'), ('@ANON', 'Anonymous account'), ('@SPEC', 'Specified account')], max_length=128, verbose_name='Alias')),
|
('alias', models.CharField(choices=[('@INPUT', 'Manual input'), ('@USER', 'Dynamic user'), ('@ANON', 'Anonymous account')], max_length=128, verbose_name='Alias')),
|
||||||
('secret_from_login', models.BooleanField(default=None, null=True, verbose_name='Secret from login')),
|
('secret_from_login', models.BooleanField(default=None, null=True, verbose_name='Secret from login')),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ from simple_history.models import HistoricalRecords
|
|||||||
|
|
||||||
from assets.models.base import AbsConnectivity
|
from assets.models.base import AbsConnectivity
|
||||||
from common.utils import lazyproperty
|
from common.utils import lazyproperty
|
||||||
from labels.mixins import LabeledMixin
|
|
||||||
from .base import BaseAccount
|
from .base import BaseAccount
|
||||||
from .mixins import VaultModelMixin
|
from .mixins import VaultModelMixin
|
||||||
from ..const import Source
|
from ..const import Source
|
||||||
@@ -43,7 +42,7 @@ class AccountHistoricalRecords(HistoricalRecords):
|
|||||||
return super().create_history_model(model, inherited)
|
return super().create_history_model(model, inherited)
|
||||||
|
|
||||||
|
|
||||||
class Account(AbsConnectivity, LabeledMixin, BaseAccount):
|
class Account(AbsConnectivity, BaseAccount):
|
||||||
asset = models.ForeignKey(
|
asset = models.ForeignKey(
|
||||||
'assets.Asset', related_name='accounts',
|
'assets.Asset', related_name='accounts',
|
||||||
on_delete=models.CASCADE, verbose_name=_('Asset')
|
on_delete=models.CASCADE, verbose_name=_('Asset')
|
||||||
@@ -53,8 +52,7 @@ class Account(AbsConnectivity, LabeledMixin, BaseAccount):
|
|||||||
on_delete=models.SET_NULL, verbose_name=_("Su from")
|
on_delete=models.SET_NULL, verbose_name=_("Su from")
|
||||||
)
|
)
|
||||||
version = models.IntegerField(default=0, verbose_name=_('Version'))
|
version = models.IntegerField(default=0, verbose_name=_('Version'))
|
||||||
history = AccountHistoricalRecords(included_fields=['id', '_secret', 'secret_type', 'version'],
|
history = AccountHistoricalRecords(included_fields=['id', '_secret', 'secret_type', 'version'])
|
||||||
verbose_name=_("historical Account"))
|
|
||||||
source = models.CharField(max_length=30, default=Source.LOCAL, verbose_name=_('Source'))
|
source = models.CharField(max_length=30, default=Source.LOCAL, verbose_name=_('Source'))
|
||||||
source_id = models.CharField(max_length=128, null=True, blank=True, verbose_name=_('Source ID'))
|
source_id = models.CharField(max_length=128, null=True, blank=True, verbose_name=_('Source ID'))
|
||||||
|
|
||||||
@@ -70,15 +68,10 @@ class Account(AbsConnectivity, LabeledMixin, BaseAccount):
|
|||||||
('view_historyaccountsecret', _('Can view asset history account secret')),
|
('view_historyaccountsecret', _('Can view asset history account secret')),
|
||||||
('verify_account', _('Can verify account')),
|
('verify_account', _('Can verify account')),
|
||||||
('push_account', _('Can push account')),
|
('push_account', _('Can push account')),
|
||||||
('remove_account', _('Can remove account')),
|
|
||||||
]
|
]
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
if self.asset_id:
|
return '{}'.format(self.username)
|
||||||
host = self.asset.name
|
|
||||||
else:
|
|
||||||
host = 'Dynamic'
|
|
||||||
return '{}({})'.format(self.name, host)
|
|
||||||
|
|
||||||
@lazyproperty
|
@lazyproperty
|
||||||
def platform(self):
|
def platform(self):
|
||||||
@@ -102,13 +95,14 @@ class Account(AbsConnectivity, LabeledMixin, BaseAccount):
|
|||||||
""" 排除自己和以自己为 su-from 的账号 """
|
""" 排除自己和以自己为 su-from 的账号 """
|
||||||
return self.asset.accounts.exclude(id=self.id).exclude(su_from=self)
|
return self.asset.accounts.exclude(id=self.id).exclude(su_from=self)
|
||||||
|
|
||||||
def make_account_ansible_vars(self, su_from):
|
@staticmethod
|
||||||
|
def make_account_ansible_vars(su_from):
|
||||||
var = {
|
var = {
|
||||||
'ansible_user': su_from.username,
|
'ansible_user': su_from.username,
|
||||||
}
|
}
|
||||||
if not su_from.secret:
|
if not su_from.secret:
|
||||||
return var
|
return var
|
||||||
var['ansible_password'] = self.escape_jinja2_syntax(su_from.secret)
|
var['ansible_password'] = su_from.secret
|
||||||
var['ansible_ssh_private_key_file'] = su_from.private_key_path
|
var['ansible_ssh_private_key_file'] = su_from.private_key_path
|
||||||
return var
|
return var
|
||||||
|
|
||||||
@@ -120,31 +114,14 @@ class Account(AbsConnectivity, LabeledMixin, BaseAccount):
|
|||||||
return auth
|
return auth
|
||||||
|
|
||||||
auth.update(self.make_account_ansible_vars(su_from))
|
auth.update(self.make_account_ansible_vars(su_from))
|
||||||
|
become_method = platform.su_method if platform.su_method else 'sudo'
|
||||||
become_method = platform.ansible_become_method
|
|
||||||
password = su_from.secret if become_method == 'sudo' else self.secret
|
password = su_from.secret if become_method == 'sudo' else self.secret
|
||||||
auth['ansible_become'] = True
|
auth['ansible_become'] = True
|
||||||
auth['ansible_become_method'] = become_method
|
auth['ansible_become_method'] = become_method
|
||||||
auth['ansible_become_user'] = self.username
|
auth['ansible_become_user'] = self.username
|
||||||
auth['ansible_become_password'] = self.escape_jinja2_syntax(password)
|
auth['ansible_become_password'] = password
|
||||||
return auth
|
return auth
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def escape_jinja2_syntax(value):
|
|
||||||
if not isinstance(value, str):
|
|
||||||
return value
|
|
||||||
|
|
||||||
def escape(v):
|
|
||||||
v = v.replace('{{', '__TEMP_OPEN_BRACES__') \
|
|
||||||
.replace('}}', '__TEMP_CLOSE_BRACES__')
|
|
||||||
|
|
||||||
v = v.replace('__TEMP_OPEN_BRACES__', '{{ "{{" }}') \
|
|
||||||
.replace('__TEMP_CLOSE_BRACES__', '{{ "}}" }}')
|
|
||||||
|
|
||||||
return v.replace('{%', '{{ "{%" }}').replace('%}', '{{ "%}" }}')
|
|
||||||
|
|
||||||
return escape(value)
|
|
||||||
|
|
||||||
|
|
||||||
def replace_history_model_with_mixin():
|
def replace_history_model_with_mixin():
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ from django.db import models
|
|||||||
from django.db.models import F
|
from django.db.models import F
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from accounts.const import AccountBackupType
|
from accounts.const.automation import AccountBackupType
|
||||||
from common.const.choices import Trigger
|
from common.const.choices import Trigger
|
||||||
from common.db import fields
|
from common.db import fields
|
||||||
from common.db.encoder import ModelJSONFieldEncoder
|
from common.db.encoder import ModelJSONFieldEncoder
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ from django.db import models
|
|||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from accounts.const import (
|
from accounts.const import (
|
||||||
AutomationTypes, ChangeSecretRecordStatusChoice
|
AutomationTypes
|
||||||
)
|
)
|
||||||
from common.db import fields
|
from common.db import fields
|
||||||
from common.db.models import JMSBaseModel
|
from common.db.models import JMSBaseModel
|
||||||
@@ -40,10 +40,7 @@ class ChangeSecretRecord(JMSBaseModel):
|
|||||||
new_secret = fields.EncryptTextField(blank=True, null=True, verbose_name=_('New secret'))
|
new_secret = fields.EncryptTextField(blank=True, null=True, verbose_name=_('New secret'))
|
||||||
date_started = models.DateTimeField(blank=True, null=True, verbose_name=_('Date started'))
|
date_started = models.DateTimeField(blank=True, null=True, verbose_name=_('Date started'))
|
||||||
date_finished = models.DateTimeField(blank=True, null=True, verbose_name=_('Date finished'))
|
date_finished = models.DateTimeField(blank=True, null=True, verbose_name=_('Date finished'))
|
||||||
status = models.CharField(
|
status = models.CharField(max_length=16, default='pending', verbose_name=_('Status'))
|
||||||
max_length=16, verbose_name=_('Status'),
|
|
||||||
default=ChangeSecretRecordStatusChoice.pending.value
|
|
||||||
)
|
|
||||||
error = models.TextField(blank=True, null=True, verbose_name=_('Error'))
|
error = models.TextField(blank=True, null=True, verbose_name=_('Error'))
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|||||||
@@ -137,13 +137,16 @@ class BaseAccount(VaultModelMixin, JMSOrgBaseModel):
|
|||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def get_private_key_path(self, path):
|
@property
|
||||||
|
def private_key_path(self):
|
||||||
if self.secret_type != SecretType.SSH_KEY \
|
if self.secret_type != SecretType.SSH_KEY \
|
||||||
or not self.secret \
|
or not self.secret \
|
||||||
or not self.private_key:
|
or not self.private_key:
|
||||||
return None
|
return None
|
||||||
|
project_dir = settings.PROJECT_DIR
|
||||||
|
tmp_dir = os.path.join(project_dir, 'tmp')
|
||||||
key_name = '.' + md5(self.private_key.encode('utf-8')).hexdigest()
|
key_name = '.' + md5(self.private_key.encode('utf-8')).hexdigest()
|
||||||
key_path = os.path.join(path, key_name)
|
key_path = os.path.join(tmp_dir, key_name)
|
||||||
if not os.path.exists(key_path):
|
if not os.path.exists(key_path):
|
||||||
# https://github.com/ansible/ansible-runner/issues/544
|
# https://github.com/ansible/ansible-runner/issues/544
|
||||||
# ssh requires OpenSSH format keys to have a full ending newline.
|
# ssh requires OpenSSH format keys to have a full ending newline.
|
||||||
@@ -155,12 +158,6 @@ class BaseAccount(VaultModelMixin, JMSOrgBaseModel):
|
|||||||
os.chmod(key_path, 0o400)
|
os.chmod(key_path, 0o400)
|
||||||
return key_path
|
return key_path
|
||||||
|
|
||||||
@property
|
|
||||||
def private_key_path(self):
|
|
||||||
project_dir = settings.PROJECT_DIR
|
|
||||||
tmp_dir = os.path.join(project_dir, 'tmp')
|
|
||||||
return self.get_private_key_path(tmp_dir)
|
|
||||||
|
|
||||||
def get_private_key(self):
|
def get_private_key(self):
|
||||||
if not self.private_key:
|
if not self.private_key:
|
||||||
return None
|
return None
|
||||||
|
|||||||
@@ -3,14 +3,13 @@ from django.db.models import Count, Q
|
|||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from labels.mixins import LabeledMixin
|
|
||||||
from .account import Account
|
from .account import Account
|
||||||
from .base import BaseAccount, SecretWithRandomMixin
|
from .base import BaseAccount, SecretWithRandomMixin
|
||||||
|
|
||||||
__all__ = ['AccountTemplate', ]
|
__all__ = ['AccountTemplate', ]
|
||||||
|
|
||||||
|
|
||||||
class AccountTemplate(LabeledMixin, BaseAccount, SecretWithRandomMixin):
|
class AccountTemplate(BaseAccount, SecretWithRandomMixin):
|
||||||
su_from = models.ForeignKey(
|
su_from = models.ForeignKey(
|
||||||
'self', related_name='su_to', null=True,
|
'self', related_name='su_to', null=True,
|
||||||
on_delete=models.SET_NULL, verbose_name=_("Su from")
|
on_delete=models.SET_NULL, verbose_name=_("Su from")
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
from django.template.loader import render_to_string
|
from django.template.loader import render_to_string
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from accounts.models import ChangeSecretRecord
|
|
||||||
from common.tasks import send_mail_attachment_async, upload_backup_to_obj_storage
|
from common.tasks import send_mail_attachment_async, upload_backup_to_obj_storage
|
||||||
from notifications.notifications import UserMessage
|
from notifications.notifications import UserMessage
|
||||||
from terminal.models.component.storage import ReplayStorage
|
|
||||||
from users.models import User
|
from users.models import User
|
||||||
|
from terminal.models.component.storage import ReplayStorage
|
||||||
|
|
||||||
|
|
||||||
class AccountBackupExecutionTaskMsg(object):
|
class AccountBackupExecutionTaskMsg(object):
|
||||||
@@ -24,8 +23,8 @@ class AccountBackupExecutionTaskMsg(object):
|
|||||||
else:
|
else:
|
||||||
return _("{} - The account backup passage task has been completed: "
|
return _("{} - The account backup passage task has been completed: "
|
||||||
"the encryption password has not been set - "
|
"the encryption password has not been set - "
|
||||||
"please go to personal information -> Basic file encryption password for preference settings"
|
"please go to personal information -> file encryption password "
|
||||||
).format(name)
|
"to set the encryption password").format(name)
|
||||||
|
|
||||||
def publish(self, attachment_list=None):
|
def publish(self, attachment_list=None):
|
||||||
send_mail_attachment_async(
|
send_mail_attachment_async(
|
||||||
@@ -55,23 +54,20 @@ class AccountBackupByObjStorageExecutionTaskMsg(object):
|
|||||||
class ChangeSecretExecutionTaskMsg(object):
|
class ChangeSecretExecutionTaskMsg(object):
|
||||||
subject = _('Notification of implementation result of encryption change plan')
|
subject = _('Notification of implementation result of encryption change plan')
|
||||||
|
|
||||||
def __init__(self, name: str, user: User, summary):
|
def __init__(self, name: str, user: User):
|
||||||
self.name = name
|
self.name = name
|
||||||
self.user = user
|
self.user = user
|
||||||
self.summary = summary
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def message(self):
|
def message(self):
|
||||||
name = self.name
|
name = self.name
|
||||||
if self.user.secret_key:
|
if self.user.secret_key:
|
||||||
default_message = _('{} - The encryption change task has been completed. '
|
return _('{} - The encryption change task has been completed. '
|
||||||
'See the attachment for details').format(name)
|
'See the attachment for details').format(name)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
default_message = _("{} - The encryption change task has been completed: the encryption "
|
return _("{} - The encryption change task has been completed: the encryption "
|
||||||
"password has not been set - please go to personal information -> "
|
"password has not been set - please go to personal information -> "
|
||||||
"set encryption password in preferences").format(name)
|
"file encryption password to set the encryption password").format(name)
|
||||||
return self.summary + '\n' + default_message
|
|
||||||
|
|
||||||
def publish(self, attachments=None):
|
def publish(self, attachments=None):
|
||||||
send_mail_attachment_async(
|
send_mail_attachment_async(
|
||||||
@@ -99,35 +95,3 @@ class GatherAccountChangeMsg(UserMessage):
|
|||||||
def gen_test_msg(cls):
|
def gen_test_msg(cls):
|
||||||
user = User.objects.first()
|
user = User.objects.first()
|
||||||
return cls(user, {})
|
return cls(user, {})
|
||||||
|
|
||||||
|
|
||||||
class ChangeSecretFailedMsg(UserMessage):
|
|
||||||
subject = _('Change secret or push account failed information')
|
|
||||||
|
|
||||||
def __init__(self, name, execution_id, user, asset_account_errors: list):
|
|
||||||
self.name = name
|
|
||||||
self.execution_id = execution_id
|
|
||||||
self.asset_account_errors = asset_account_errors
|
|
||||||
super().__init__(user)
|
|
||||||
|
|
||||||
def get_html_msg(self) -> dict:
|
|
||||||
context = {
|
|
||||||
'name': self.name,
|
|
||||||
'recipient': self.user,
|
|
||||||
'execution_id': self.execution_id,
|
|
||||||
'asset_account_errors': self.asset_account_errors
|
|
||||||
}
|
|
||||||
message = render_to_string('accounts/change_secret_failed_info.html', context)
|
|
||||||
|
|
||||||
return {
|
|
||||||
'subject': str(self.subject),
|
|
||||||
'message': message
|
|
||||||
}
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def gen_test_msg(cls):
|
|
||||||
name = 'test'
|
|
||||||
user = User.objects.first()
|
|
||||||
record = ChangeSecretRecord.objects.first()
|
|
||||||
execution_id = str(record.execution_id)
|
|
||||||
return cls(name, execution_id, user, [])
|
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
from rest_framework import permissions
|
|
||||||
|
|
||||||
|
|
||||||
def check_permissions(request):
|
|
||||||
act = request.data.get('action')
|
|
||||||
if act == 'push':
|
|
||||||
code = 'accounts.push_account'
|
|
||||||
elif act == 'remove':
|
|
||||||
code = 'accounts.remove_account'
|
|
||||||
else:
|
|
||||||
code = 'accounts.verify_account'
|
|
||||||
return request.user.has_perm(code)
|
|
||||||
|
|
||||||
|
|
||||||
class AccountTaskActionPermission(permissions.IsAuthenticated):
|
|
||||||
|
|
||||||
def has_permission(self, request, view):
|
|
||||||
return super().has_permission(request, view) \
|
|
||||||
and check_permissions(request)
|
|
||||||
@@ -10,7 +10,7 @@ from rest_framework.generics import get_object_or_404
|
|||||||
from rest_framework.validators import UniqueTogetherValidator
|
from rest_framework.validators import UniqueTogetherValidator
|
||||||
|
|
||||||
from accounts.const import SecretType, Source, AccountInvalidPolicy
|
from accounts.const import SecretType, Source, AccountInvalidPolicy
|
||||||
from accounts.models import Account, AccountTemplate, GatheredAccount
|
from accounts.models import Account, AccountTemplate
|
||||||
from accounts.tasks import push_accounts_to_assets_task
|
from accounts.tasks import push_accounts_to_assets_task
|
||||||
from assets.const import Category, AllTypes
|
from assets.const import Category, AllTypes
|
||||||
from assets.models import Asset
|
from assets.models import Asset
|
||||||
@@ -58,7 +58,7 @@ class AccountCreateUpdateSerializerMixin(serializers.Serializer):
|
|||||||
for data in initial_data:
|
for data in initial_data:
|
||||||
if not data.get('asset') and not self.instance:
|
if not data.get('asset') and not self.instance:
|
||||||
raise serializers.ValidationError({'asset': UniqueTogetherValidator.missing_message})
|
raise serializers.ValidationError({'asset': UniqueTogetherValidator.missing_message})
|
||||||
asset = data.get('asset') or getattr(self.instance, 'asset', None)
|
asset = data.get('asset') or self.instance.asset
|
||||||
self.from_template_if_need(data)
|
self.from_template_if_need(data)
|
||||||
self.set_uniq_name_if_need(data, asset)
|
self.set_uniq_name_if_need(data, asset)
|
||||||
|
|
||||||
@@ -66,9 +66,6 @@ class AccountCreateUpdateSerializerMixin(serializers.Serializer):
|
|||||||
name = initial_data.get('name')
|
name = initial_data.get('name')
|
||||||
if name is not None:
|
if name is not None:
|
||||||
return
|
return
|
||||||
request = self.context.get('request')
|
|
||||||
if request and request.method == 'PATCH':
|
|
||||||
return
|
|
||||||
if not name:
|
if not name:
|
||||||
name = initial_data.get('username')
|
name = initial_data.get('username')
|
||||||
if self.instance and self.instance.name == name:
|
if self.instance and self.instance.name == name:
|
||||||
@@ -79,28 +76,18 @@ class AccountCreateUpdateSerializerMixin(serializers.Serializer):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_template_attr_for_account(template):
|
def get_template_attr_for_account(template):
|
||||||
|
# Set initial data from template
|
||||||
field_names = [
|
field_names = [
|
||||||
'name', 'username',
|
'name', 'username', 'secret',
|
||||||
'secret_type', 'secret',
|
'secret_type', 'privileged', 'is_active'
|
||||||
'privileged', 'is_active'
|
|
||||||
]
|
]
|
||||||
|
|
||||||
field_map = {
|
|
||||||
'push_params': 'params',
|
|
||||||
'auto_push': 'push_now'
|
|
||||||
}
|
|
||||||
|
|
||||||
field_names.extend(field_map.keys())
|
|
||||||
|
|
||||||
attrs = {}
|
attrs = {}
|
||||||
for name in field_names:
|
for name in field_names:
|
||||||
value = getattr(template, name, None)
|
value = getattr(template, name, None)
|
||||||
if value is None:
|
if value is None:
|
||||||
continue
|
continue
|
||||||
|
attrs[name] = value
|
||||||
attr_name = field_map.get(name, name)
|
|
||||||
attrs[attr_name] = value
|
|
||||||
|
|
||||||
attrs['secret'] = template.get_secret()
|
attrs['secret'] = template.get_secret()
|
||||||
return attrs
|
return attrs
|
||||||
|
|
||||||
@@ -183,8 +170,7 @@ class AccountCreateUpdateSerializerMixin(serializers.Serializer):
|
|||||||
params = validated_data.pop('params', None)
|
params = validated_data.pop('params', None)
|
||||||
self.clean_auth_fields(validated_data)
|
self.clean_auth_fields(validated_data)
|
||||||
instance, stat = self.do_create(validated_data)
|
instance, stat = self.do_create(validated_data)
|
||||||
if instance.source == Source.LOCAL:
|
self.push_account_if_need(instance, push_now, params, stat)
|
||||||
self.push_account_if_need(instance, push_now, params, stat)
|
|
||||||
return instance
|
return instance
|
||||||
|
|
||||||
def update(self, instance, validated_data):
|
def update(self, instance, validated_data):
|
||||||
@@ -252,7 +238,7 @@ class AccountSerializer(AccountCreateUpdateSerializerMixin, BaseAccountSerialize
|
|||||||
queryset = queryset.prefetch_related(
|
queryset = queryset.prefetch_related(
|
||||||
'asset', 'asset__platform',
|
'asset', 'asset__platform',
|
||||||
'asset__platform__automation'
|
'asset__platform__automation'
|
||||||
).prefetch_related('labels', 'labels__label')
|
)
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
|
|
||||||
@@ -286,8 +272,8 @@ class AssetAccountBulkSerializer(
|
|||||||
fields = [
|
fields = [
|
||||||
'name', 'username', 'secret', 'secret_type', 'passphrase',
|
'name', 'username', 'secret', 'secret_type', 'passphrase',
|
||||||
'privileged', 'is_active', 'comment', 'template',
|
'privileged', 'is_active', 'comment', 'template',
|
||||||
'on_invalid', 'push_now', 'params', 'assets',
|
'on_invalid', 'push_now', 'assets', 'su_from_username',
|
||||||
'su_from_username', 'source', 'source_id',
|
'source', 'source_id',
|
||||||
]
|
]
|
||||||
extra_kwargs = {
|
extra_kwargs = {
|
||||||
'name': {'required': False},
|
'name': {'required': False},
|
||||||
@@ -425,23 +411,16 @@ class AssetAccountBulkSerializer(
|
|||||||
return results
|
return results
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def push_accounts_if_need(results, push_now, params):
|
def push_accounts_if_need(results, push_now):
|
||||||
if not push_now:
|
if not push_now:
|
||||||
return
|
return
|
||||||
|
accounts = [str(v['instance']) for v in results if v.get('instance')]
|
||||||
account_ids = [v['instance'] for v in results if v.get('instance')]
|
push_accounts_to_assets_task.delay(accounts)
|
||||||
accounts = Account.objects.filter(id__in=account_ids, source=Source.LOCAL)
|
|
||||||
if not accounts.exists():
|
|
||||||
return
|
|
||||||
|
|
||||||
account_ids = [str(_id) for _id in accounts.values_list('id', flat=True)]
|
|
||||||
push_accounts_to_assets_task.delay(account_ids, params)
|
|
||||||
|
|
||||||
def create(self, validated_data):
|
def create(self, validated_data):
|
||||||
params = validated_data.pop('params', None)
|
|
||||||
push_now = validated_data.pop('push_now', False)
|
push_now = validated_data.pop('push_now', False)
|
||||||
results = self.perform_bulk_create(validated_data)
|
results = self.perform_bulk_create(validated_data)
|
||||||
self.push_accounts_if_need(results, push_now, params)
|
self.push_accounts_if_need(results, push_now)
|
||||||
for res in results:
|
for res in results:
|
||||||
res['asset'] = str(res['asset'])
|
res['asset'] = str(res['asset'])
|
||||||
return results
|
return results
|
||||||
@@ -449,11 +428,8 @@ class AssetAccountBulkSerializer(
|
|||||||
|
|
||||||
class AccountSecretSerializer(SecretReadableMixin, AccountSerializer):
|
class AccountSecretSerializer(SecretReadableMixin, AccountSerializer):
|
||||||
class Meta(AccountSerializer.Meta):
|
class Meta(AccountSerializer.Meta):
|
||||||
fields = AccountSerializer.Meta.fields + ['spec_info']
|
|
||||||
extra_kwargs = {
|
extra_kwargs = {
|
||||||
**AccountSerializer.Meta.extra_kwargs,
|
|
||||||
'secret': {'write_only': False},
|
'secret': {'write_only': False},
|
||||||
'spec_info': {'label': _('Spec info')},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -476,20 +452,14 @@ class AccountHistorySerializer(serializers.ModelSerializer):
|
|||||||
|
|
||||||
class AccountTaskSerializer(serializers.Serializer):
|
class AccountTaskSerializer(serializers.Serializer):
|
||||||
ACTION_CHOICES = (
|
ACTION_CHOICES = (
|
||||||
|
('test', 'test'),
|
||||||
('verify', 'verify'),
|
('verify', 'verify'),
|
||||||
('push', 'push'),
|
('push', 'push'),
|
||||||
('remove', 'remove'),
|
|
||||||
)
|
)
|
||||||
action = serializers.ChoiceField(choices=ACTION_CHOICES, write_only=True)
|
action = serializers.ChoiceField(choices=ACTION_CHOICES, write_only=True)
|
||||||
assets = serializers.PrimaryKeyRelatedField(
|
|
||||||
queryset=Asset.objects, required=False, allow_empty=True, many=True
|
|
||||||
)
|
|
||||||
accounts = serializers.PrimaryKeyRelatedField(
|
accounts = serializers.PrimaryKeyRelatedField(
|
||||||
queryset=Account.objects, required=False, allow_empty=True, many=True
|
queryset=Account.objects, required=False, allow_empty=True, many=True
|
||||||
)
|
)
|
||||||
gather_accounts = serializers.PrimaryKeyRelatedField(
|
|
||||||
queryset=GatheredAccount.objects, required=False, allow_empty=True, many=True
|
|
||||||
)
|
|
||||||
task = serializers.CharField(read_only=True)
|
task = serializers.CharField(read_only=True)
|
||||||
params = serializers.JSONField(
|
params = serializers.JSONField(
|
||||||
decoder=None, encoder=None, required=False,
|
decoder=None, encoder=None, required=False,
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ from rest_framework import serializers
|
|||||||
from accounts.const import SecretType
|
from accounts.const import SecretType
|
||||||
from accounts.models import BaseAccount
|
from accounts.models import BaseAccount
|
||||||
from accounts.utils import validate_password_for_ansible, validate_ssh_key
|
from accounts.utils import validate_password_for_ansible, validate_ssh_key
|
||||||
from common.serializers import ResourceLabelsMixin
|
|
||||||
from common.serializers.fields import EncryptedField, LabeledChoiceField
|
from common.serializers.fields import EncryptedField, LabeledChoiceField
|
||||||
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
|
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
|
||||||
|
|
||||||
@@ -61,20 +60,22 @@ class AuthValidateMixin(serializers.Serializer):
|
|||||||
return super().update(instance, validated_data)
|
return super().update(instance, validated_data)
|
||||||
|
|
||||||
|
|
||||||
class BaseAccountSerializer(AuthValidateMixin, ResourceLabelsMixin, BulkOrgResourceModelSerializer):
|
class BaseAccountSerializer(AuthValidateMixin, BulkOrgResourceModelSerializer):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = BaseAccount
|
model = BaseAccount
|
||||||
fields_mini = ['id', 'name', 'username']
|
fields_mini = ['id', 'name', 'username']
|
||||||
fields_small = fields_mini + [
|
fields_small = fields_mini + [
|
||||||
'secret_type', 'secret', 'passphrase',
|
'secret_type', 'secret', 'passphrase',
|
||||||
'privileged', 'is_active',
|
'privileged', 'is_active', 'spec_info',
|
||||||
]
|
]
|
||||||
fields_other = ['created_by', 'date_created', 'date_updated', 'comment']
|
fields_other = ['created_by', 'date_created', 'date_updated', 'comment']
|
||||||
fields = fields_small + fields_other + ['labels']
|
fields = fields_small + fields_other
|
||||||
read_only_fields = [
|
read_only_fields = [
|
||||||
'date_verified', 'created_by', 'date_created',
|
'spec_info', 'date_verified', 'created_by', 'date_created',
|
||||||
]
|
]
|
||||||
extra_kwargs = {
|
extra_kwargs = {
|
||||||
|
'spec_info': {'label': _('Spec info')},
|
||||||
'username': {'help_text': _(
|
'username': {'help_text': _(
|
||||||
"Tip: If no username is required for authentication, fill in `null`, "
|
"Tip: If no username is required for authentication, fill in `null`, "
|
||||||
"If AD account, like `username@domain`"
|
"If AD account, like `username@domain`"
|
||||||
|
|||||||
@@ -15,9 +15,6 @@ class PasswordRulesSerializer(serializers.Serializer):
|
|||||||
uppercase = serializers.BooleanField(default=True, label=_('Uppercase'))
|
uppercase = serializers.BooleanField(default=True, label=_('Uppercase'))
|
||||||
digit = serializers.BooleanField(default=True, label=_('Digit'))
|
digit = serializers.BooleanField(default=True, label=_('Digit'))
|
||||||
symbol = serializers.BooleanField(default=True, label=_('Special symbol'))
|
symbol = serializers.BooleanField(default=True, label=_('Special symbol'))
|
||||||
exclude_symbols = serializers.CharField(
|
|
||||||
default='', allow_blank=True, max_length=16, label=_('Exclude symbol')
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class AccountTemplateSerializer(BaseAccountSerializer):
|
class AccountTemplateSerializer(BaseAccountSerializer):
|
||||||
@@ -35,7 +32,6 @@ class AccountTemplateSerializer(BaseAccountSerializer):
|
|||||||
'su_from'
|
'su_from'
|
||||||
]
|
]
|
||||||
extra_kwargs = {
|
extra_kwargs = {
|
||||||
**BaseAccountSerializer.Meta.extra_kwargs,
|
|
||||||
'secret_strategy': {'help_text': _('Secret generation strategy for account creation')},
|
'secret_strategy': {'help_text': _('Secret generation strategy for account creation')},
|
||||||
'auto_push': {'help_text': _('Whether to automatically push the account to the asset')},
|
'auto_push': {'help_text': _('Whether to automatically push the account to the asset')},
|
||||||
'platforms': {
|
'platforms': {
|
||||||
@@ -65,9 +61,6 @@ class AccountTemplateSerializer(BaseAccountSerializer):
|
|||||||
|
|
||||||
class AccountTemplateSecretSerializer(SecretReadableMixin, AccountTemplateSerializer):
|
class AccountTemplateSecretSerializer(SecretReadableMixin, AccountTemplateSerializer):
|
||||||
class Meta(AccountTemplateSerializer.Meta):
|
class Meta(AccountTemplateSerializer.Meta):
|
||||||
fields = AccountTemplateSerializer.Meta.fields + ['spec_info']
|
|
||||||
extra_kwargs = {
|
extra_kwargs = {
|
||||||
**AccountTemplateSerializer.Meta.extra_kwargs,
|
|
||||||
'secret': {'write_only': False},
|
'secret': {'write_only': False},
|
||||||
'spec_info': {'label': _('Spec info')},
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ __all__ = [
|
|||||||
class BaseAutomationSerializer(PeriodTaskSerializerMixin, BulkOrgResourceModelSerializer):
|
class BaseAutomationSerializer(PeriodTaskSerializerMixin, BulkOrgResourceModelSerializer):
|
||||||
assets = ObjectRelatedField(many=True, required=False, queryset=Asset.objects, label=_('Assets'))
|
assets = ObjectRelatedField(many=True, required=False, queryset=Asset.objects, label=_('Assets'))
|
||||||
nodes = ObjectRelatedField(many=True, required=False, queryset=Node.objects, label=_('Nodes'))
|
nodes = ObjectRelatedField(many=True, required=False, queryset=Node.objects, label=_('Nodes'))
|
||||||
is_periodic = serializers.BooleanField(default=False, required=False, label=_("Periodic perform"))
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
read_only_fields = [
|
read_only_fields = [
|
||||||
|
|||||||
@@ -4,8 +4,7 @@ from django.utils.translation import gettext_lazy as _
|
|||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from accounts.const import (
|
from accounts.const import (
|
||||||
AutomationTypes, SecretType, SecretStrategy,
|
AutomationTypes, SecretType, SecretStrategy, SSHKeyStrategy
|
||||||
SSHKeyStrategy, ChangeSecretRecordStatusChoice
|
|
||||||
)
|
)
|
||||||
from accounts.models import (
|
from accounts.models import (
|
||||||
Account, ChangeSecretAutomation,
|
Account, ChangeSecretAutomation,
|
||||||
@@ -22,7 +21,6 @@ logger = get_logger(__file__)
|
|||||||
__all__ = [
|
__all__ = [
|
||||||
'ChangeSecretAutomationSerializer',
|
'ChangeSecretAutomationSerializer',
|
||||||
'ChangeSecretRecordSerializer',
|
'ChangeSecretRecordSerializer',
|
||||||
'ChangeSecretRecordViewSecretSerializer',
|
|
||||||
'ChangeSecretRecordBackUpSerializer',
|
'ChangeSecretRecordBackUpSerializer',
|
||||||
'ChangeSecretUpdateAssetSerializer',
|
'ChangeSecretUpdateAssetSerializer',
|
||||||
'ChangeSecretUpdateNodeSerializer',
|
'ChangeSecretUpdateNodeSerializer',
|
||||||
@@ -106,10 +104,7 @@ class ChangeSecretAutomationSerializer(AuthValidateMixin, BaseAutomationSerializ
|
|||||||
class ChangeSecretRecordSerializer(serializers.ModelSerializer):
|
class ChangeSecretRecordSerializer(serializers.ModelSerializer):
|
||||||
is_success = serializers.SerializerMethodField(label=_('Is success'))
|
is_success = serializers.SerializerMethodField(label=_('Is success'))
|
||||||
asset = ObjectRelatedField(queryset=Asset.objects, label=_('Asset'))
|
asset = ObjectRelatedField(queryset=Asset.objects, label=_('Asset'))
|
||||||
account = ObjectRelatedField(
|
account = ObjectRelatedField(queryset=Account.objects, label=_('Account'))
|
||||||
queryset=Account.objects, label=_('Account'),
|
|
||||||
attrs=("id", "name", "username")
|
|
||||||
)
|
|
||||||
execution = ObjectRelatedField(
|
execution = ObjectRelatedField(
|
||||||
queryset=AutomationExecution.objects, label=_('Automation task execution')
|
queryset=AutomationExecution.objects, label=_('Automation task execution')
|
||||||
)
|
)
|
||||||
@@ -124,16 +119,7 @@ class ChangeSecretRecordSerializer(serializers.ModelSerializer):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_is_success(obj):
|
def get_is_success(obj):
|
||||||
return obj.status == ChangeSecretRecordStatusChoice.success.value
|
return obj.status == 'success'
|
||||||
|
|
||||||
|
|
||||||
class ChangeSecretRecordViewSecretSerializer(serializers.ModelSerializer):
|
|
||||||
class Meta:
|
|
||||||
model = ChangeSecretRecord
|
|
||||||
fields = [
|
|
||||||
'id', 'old_secret', 'new_secret',
|
|
||||||
]
|
|
||||||
read_only_fields = fields
|
|
||||||
|
|
||||||
|
|
||||||
class ChangeSecretRecordBackUpSerializer(serializers.ModelSerializer):
|
class ChangeSecretRecordBackUpSerializer(serializers.ModelSerializer):
|
||||||
@@ -159,7 +145,7 @@ class ChangeSecretRecordBackUpSerializer(serializers.ModelSerializer):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_is_success(obj):
|
def get_is_success(obj):
|
||||||
if obj.status == ChangeSecretRecordStatusChoice.success.value:
|
if obj.status == 'success':
|
||||||
return _("Success")
|
return _("Success")
|
||||||
return _("Failed")
|
return _("Failed")
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ from django.dispatch import receiver
|
|||||||
from django.utils.translation import gettext_noop
|
from django.utils.translation import gettext_noop
|
||||||
|
|
||||||
from accounts.backends import vault_client
|
from accounts.backends import vault_client
|
||||||
from accounts.const import Source
|
|
||||||
from audits.const import ActivityChoices
|
from audits.const import ActivityChoices
|
||||||
from audits.signal_handlers import create_activities
|
from audits.signal_handlers import create_activities
|
||||||
from common.decorators import merge_delay_run
|
from common.decorators import merge_delay_run
|
||||||
@@ -22,8 +21,7 @@ def on_account_pre_save(sender, instance, **kwargs):
|
|||||||
if instance.version == 0:
|
if instance.version == 0:
|
||||||
instance.version = 1
|
instance.version = 1
|
||||||
else:
|
else:
|
||||||
history_account = instance.history.first()
|
instance.version = instance.history.count()
|
||||||
instance.version = history_account.version + 1 if history_account else 0
|
|
||||||
|
|
||||||
|
|
||||||
@merge_delay_run(ttl=5)
|
@merge_delay_run(ttl=5)
|
||||||
@@ -33,7 +31,7 @@ def push_accounts_if_need(accounts=()):
|
|||||||
template_accounts = defaultdict(list)
|
template_accounts = defaultdict(list)
|
||||||
for ac in accounts:
|
for ac in accounts:
|
||||||
# 再强调一次吧
|
# 再强调一次吧
|
||||||
if ac.source != Source.TEMPLATE:
|
if ac.source != 'template':
|
||||||
continue
|
continue
|
||||||
template_accounts[ac.source_id].append(ac)
|
template_accounts[ac.source_id].append(ac)
|
||||||
|
|
||||||
@@ -62,9 +60,9 @@ def create_accounts_activities(account, action='create'):
|
|||||||
|
|
||||||
@receiver(post_save, sender=Account)
|
@receiver(post_save, sender=Account)
|
||||||
def on_account_create_by_template(sender, instance, created=False, **kwargs):
|
def on_account_create_by_template(sender, instance, created=False, **kwargs):
|
||||||
if not created:
|
if not created or instance.source != 'template':
|
||||||
return
|
return
|
||||||
push_accounts_if_need.delay(accounts=(instance,))
|
push_accounts_if_need(accounts=(instance,))
|
||||||
create_accounts_activities(instance, action='create')
|
create_accounts_activities(instance, action='create')
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,5 @@ from .automation import *
|
|||||||
from .backup_account import *
|
from .backup_account import *
|
||||||
from .gather_accounts import *
|
from .gather_accounts import *
|
||||||
from .push_account import *
|
from .push_account import *
|
||||||
from .remove_account import *
|
|
||||||
from .template import *
|
from .template import *
|
||||||
from .verify_account import *
|
from .verify_account import *
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user