diff --git a/.github/ISSUE_TEMPLATE/bug.yml b/.github/ISSUE_TEMPLATE/bug.yml new file mode 100644 index 0000000..64a4803 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug.yml @@ -0,0 +1,70 @@ +name: Bug 反馈 +description: 提交一个 issue 帮助改进这个项目 +title: "[Bug] 在这里输入你的标题" +labels: ["bug"] +assignees: + - teamssix +body: +- type: markdown + attributes: + value: | + 感谢您花时间提交这份 issue +- type: textarea + id: what-happened + attributes: + label: 描述你遇到的问题 + value: "详细描述你所遇到的问题" + validations: + required: true +- type: textarea + attributes: + label: 复现步骤 + description: 复现这个问题的步骤 + placeholder: | + 1. 在 xxx 情况下 + 2. 执行了 xxx 命令 + 3. 出现了 xxx 错误 + validations: + required: true +- type: dropdown + id: system + attributes: + label: 操作系统 + description: 你在哪个操作系统下运行的云鉴 ? + options: + - MacOS + - Linux + - Windows + validations: + required: true +- type: dropdown + id: system-type + attributes: + label: 系统类型 + description: 你在哪个系统类型下运行的云鉴 ? + options: + - amd64 + - amd32 + - arm64 + - arm32 + validations: + required: true +- type: dropdown + id: cloudsword-version + attributes: + label: 云鉴版本 + description: 你运行的是云鉴的哪个版本? + options: + - 最新的 (Latest) + - 0.0.1 + validations: + required: true +- type: textarea + attributes: + label: 补充信息 + description: | + 链接?参考资料?任何可以给我们提供更多关于你所遇到的问题的背景资料的东西 + + 提示:你可以通过点击这个区域来突出显示它,然后将文件拖入,从而附上图片或其他文件。 + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/feat.yml b/.github/ISSUE_TEMPLATE/feat.yml new file mode 100644 index 0000000..5b3f7c1 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feat.yml @@ -0,0 +1,27 @@ +name: 新功能反馈 +description: 提交一个功能需求帮助完善这个项目 +title: "[Feat] 在这里输入你的标题" +labels: ["enhancement"] +assignees: + - teamssix +body: +- type: markdown + attributes: + value: | + 感谢您花时间提交这份 issue +- type: textarea + id: new-features + attributes: + label: 描述你希望增加的功能 + value: "详细描述你希望增加的功能,并且描述为什么想要增加这个功能以及意义,描述的越完善该反馈越有可能被采纳。" + validations: + required: true +- type: textarea + attributes: + label: 补充信息 (Anything else?) + description: | + 链接?参考资料?任何可以给我们提供更多关于你所遇到的问题的背景资料的东西。 + + 提示:你可以通过点击这个区域来突出显示它,然后将文件拖入,从而附上图片或其他文件。 + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/perf.yml b/.github/ISSUE_TEMPLATE/perf.yml new file mode 100644 index 0000000..5776c1a --- /dev/null +++ b/.github/ISSUE_TEMPLATE/perf.yml @@ -0,0 +1,27 @@ +name: 功能优化反馈 +description: 提交一个功能优化需求帮助改进这个项目 +title: "[Perf] 在这里输入你的标题" +labels: ["performance"] +assignees: + - teamssix +body: +- type: markdown + attributes: + value: | + 感谢您花时间提交这份 issue +- type: textarea + id: performance + attributes: + label: 描述你希望优化的功能 + value: "详细描述你希望优化的功能,并且描述为什么想要对这个功能进行优化,描述的越完善该反馈越有可能被采纳。" + validations: + required: true +- type: textarea + attributes: + label: 补充信息 + description: | + 链接?参考资料?任何可以给我们提供更多关于你所遇到的问题的背景资料的东西 + + 提示:你可以通过点击这个区域来突出显示它,然后将文件拖入,从而附上图片或其他文件。 + validations: + required: false diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml new file mode 100644 index 0000000..0e5a6f5 --- /dev/null +++ b/.github/workflows/release.yaml @@ -0,0 +1,29 @@ +name: Release +on: + push: + tags: + - 'v*' +env: + GO_VERSION: 1.22.1 + +jobs: + goreleaser: + runs-on: ubuntu-latest + steps: + - name: Checkout Source Code + uses: actions/checkout@v3 + with: + fetch-depth: 0 + - name: Setup Go Environment + uses: actions/setup-go@v3 + with: + go-version: ${{ env.GO_VERSION }} + + - name: Run GoReleaser + uses: goreleaser/goreleaser-action@v6 + with: + version: 1.26.2 + args: release --clean + env: + CGO_ENABLED: 0 + GITHUB_TOKEN: ${{ secrets.RELEASE_GH_TOKEN }} \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3cbb53c --- /dev/null +++ b/.gitignore @@ -0,0 +1,29 @@ +# If you prefer the allow list template instead of the deny list, see community template: +# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore +# +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Dependency directories (remove the comment below to include it) +# vendor/ +tmp +test +misc +build + +# Go workspace file +go.work +.DS_Store +.idea +cloudsword +cloudsword.bak diff --git a/.goreleaser.yaml b/.goreleaser.yaml new file mode 100644 index 0000000..6e25e73 --- /dev/null +++ b/.goreleaser.yaml @@ -0,0 +1,54 @@ +project_name: cloudsword +before: + hooks: + - go mod tidy + - go generate ./... +builds: + - env: + - CGO_ENABLED=0 + goos: + - freebsd + - darwin + - windows + - linux + goarch: + - 386 + - amd64 + - arm + - arm64 +archives: + - name_template: "{{ .ProjectName }}_{{ .Tag }}_{{ .Os }}_{{ .Arch }}" + id: homebrew + format: zip + files: + - README.md + - CHANGELOG.md + - LICENSE + - static/* +checksum: + name_template: "checksums.txt" +snapshot: + name_template: "{{ incpatch .Version }}" +changelog: + sort: asc + filters: + exclude: + - "^docs:" + - "^doc:" + - "^ci:" + - "^Merge pull request" +brews: + - ids: + - homebrew + name: cloudsword + tap: + owner: wgpsec + name: homebrew-tap + branch: master + folder: Formula + url_template: "https://github.com/wgpsec/cloudsword/releases/download/{{ .Tag }}/{{ .ArtifactName }}" + homepage: "https://wiki.teamssix.com/cloudsword" + description: "CloudSword is a public cloud security exploitation tool." + skip_upload: auto + install: |- + bin.install "cloudsword" \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..66bf898 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,3 @@ +## [v0.0.1](https://github.com/wgpsec/cloudsword/releases/tag/v0.0.1) 2024.12.21 + +* 首次提交代码 \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..18c6a98 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,39 @@ +## 为云鉴贡献代码 + +首先很高兴你看到这个,非常欢迎您和我一起完善云鉴,让我们一起编写一个好用的、有价值的、有意义的工具。 + +### 是否发现了 bug? + +如果你发现了程序中的 bug,建议可以先在 [issue](https://github.com/wgpsec/cloudsword/issues) 中进行搜索,看看以前有没有人提交过相同的 bug。 + +### 发现了 bug,并准备自己为云鉴打补丁 + +如果你发现了 bug,而且准备自己去修补它的话,则可以先把云鉴 Fork 到自己的仓库中,然后修复完后,提交 PR 到云鉴的 dev 分支下,另外记得在 PR 中详细说明 bug 的产生条件以及你是如何修复的,这可以帮助你更快地使这个 PR 通过。 + +### 为云鉴增加新功能 + +如果你发现云鉴现在的功能不能满足自己的需求,并打算自己去增加上这个功能,则可以先把云鉴 Fork 到自己的仓库中,然后编写完相应的功能后,提交 PR 到云鉴 的 dev 分支下,另外记得在 PR 中详细说明增加的功能以及为什么要增加它,这可以帮助你更快地使这个 PR 通过。 + +建议师傅在增加新功能后,可以去完善对应的操作手册,操作手册项目地址:[github.com/teamssix/twiki](https://github.com/teamssix/twiki),先 fork 操作手册项目,然后在 [github. +com/teamssix/twiki/tree/main/docs/cloudsword](https://github.com/teamssix/twiki/tree/main/docs/cloudsword) 目录下编辑云鉴 操作手册的文档,最后提 pr 到 T Wiki 项目的 beta 分支下就行。 + +## 使你的 PR 更规范 + +### 规范 Git commit + +commit message 应尽可能的规范,为了保证和其他 commit 统一,建议 commit message 采用英文描述,并符合下面的格式: + +```yaml +type: subject +``` + +type 是 commit 的类别,subject 是对这个 commit 的英文描述,例如下面的示例: +* feat: add object download function +* perf: optimize the display of update progress bar + 关于 Git commit 的更多规范要求可以参见这篇文章:[如何规范你的Git commit?](https://zhuanlan.zhihu.com/p/182553920) + +### 规范代码格式 + +代码在编写完后,应使用 `go fmt` 和 `goimports`对代码进行格式化,从而使代码看起来更加整洁。 + +在 goland 中可以设置当代码保存时自动格式化代码,配置的步骤可以参见这篇文章:[GoLand:设置gofmt与goimports,保存时自动格式化代码](https://blog.csdn.net/qq_32907195/article/details/116755338) \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..e6de5b4 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [2024] [WgpSec] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md new file mode 100644 index 0000000..6c82a96 --- /dev/null +++ b/README.md @@ -0,0 +1,206 @@ +![header](./static/header.jpg) + +

云鉴,让您的公有云环境更安全

+ +

+ + + + + + + +

+ +--- + +云鉴 CloudSword 是一款帮助公有云租户快速发现云上风险、测试云上风险、增强云上防护能力的综合性开源工具。 + +作为一款面向安全人员的工具,云鉴可以帮助租户快速了解当前公有云环境中的资源信息、快速发现当前环境中可能存在的弱点从而方便安全人员进行修补,云鉴还预设了一些防御方法,方便安全人员快速部署从而增强云上的防御能力。 + +* 全局中文输出,没有使用压力。 +* 命令补全提示,方便易于使用。 +* MSF 使用逻辑,极低学习成本。 +* 凭证不用落地,避免二次泄露。 + +## 开始使用 + +### HomeBrew 安装 + +安装 + +```bash +brew tap wgpsec/tap +brew install wgpsec/tap/cloudsword +``` + +更新 + +```bash +brew update +brew upgrade cloudsword +``` + +### 下载二进制包 + +云鉴下载地址:[github.com/wgpsec/cloudsword/releases](https://github.com/wgpsec/cloudsword/releases) + +下载系统对应的压缩文件,解压后在命令行中运行即可。 + +## 使用手册 + +完整用法与介绍可以查看 [云鉴使用手册](https://wiki.teamssix.com/cloudsword) + +## 集成模块 + +以下是云鉴目前所支持使用的模块: + +| 序号 | ID | 云提供商 | 推荐评级 | 模块名称 | 描述 | +| :--- | :--- | :------- | :------- | :---------------------------- | :----------------------------------- | +| 1 | 1101 | 阿里云 | ★★★★ | list_cloud_assets | 列出 OSS、ECS、RAM、Domain 服务资产 | +| 2 | 1201 | 阿里云 | ★★ | oss_list_buckets | 列出阿里云 OSS 对象存储桶 | +| 3 | 1202 | 阿里云 | ★★★★ | oss_search_objects | 搜索阿里云 OSS 对象 | +| 4 | 1203 | 阿里云 | ★★★ | oss_bucket_only_upload_images | 使用云函数限制存储桶只允许上传图片 | +| 5 | 1301 | 阿里云 | ★★ | ecs_list_instances | 列出阿里云 ECS 弹性计算实例 | +| 6 | 1401 | 阿里云 | ★★ | ram_list_users | 列出阿里云 RAM 用户 | +| 7 | 1402 | 阿里云 | ★ | ram_list_roles | 列出阿里云 RAM 角色 | +| 8 | 1403 | 阿里云 | ★ | ram_create_user | 创建阿里云 RAM 用户 | +| 9 | 1404 | 阿里云 | ★ | ram_attach_policy_to_user | 为阿里云 RAM 用户添加策略 | +| 10 | 1405 | 阿里云 | ★★★ | ram_create_login_profile | 创建阿里云 RAM 用户 Web 登录配置 | +| 11 | 1406 | 阿里云 | ★ | ram_create_access_key | 创建阿里云 RAM 用户访问凭证 | +| 12 | 1501 | 阿里云 | ★ | domain_list_domains | 列出阿里云 Domains 域名资产 | +| 13 | 2101 | 腾讯云 | ★★★★ | list_cloud_assets | 列出 COS、EVM、LH、RAM 服务资产 | +| 14 | 2102 | 腾讯云 | ★★★★★ | create_honey_token | 创建腾讯云访问凭证蜜标 | +| 15 | 2201 | 腾讯云 | ★★ | cos_list_buckets | 列出腾讯云 COS 对象存储桶 | +| 16 | 2301 | 腾讯云 | ★★ | cvm_list_instances | 列出腾讯云 CVM 弹性计算实例 | +| 17 | 2302 | 腾讯云 | ★ | lh_list_instances | 列出腾讯云 LH 轻量应用服务器 | +| 18 | 2401 | 腾讯云 | ★★ | cam_list_users | 列出腾讯云 CAM 用户 | +| 19 | 2402 | 腾讯云 | ★ | cam_list_roles | 列出腾讯云 CAM 角色 | +| 20 | 2403 | 腾讯云 | ★ | cam_create_user | 创建腾讯云 CAM 用户 | +| 21 | 2404 | 腾讯云 | ★ | cam_attach_policy_to_user | 为腾讯云 CAM 用户添加策略 | +| 22 | 2405 | 腾讯云 | ★★★ | cam_create_login_profile | 创建腾讯云 CAM 用户 Web 登录配置 | +| 23 | 2406 | 腾讯云 | ★ | cam_create_access_key | 创建腾讯云 CAM 用户访问凭证 | +| 24 | 3201 | 华为云 | ★★ | obs_list_buckets | 列出华为云 OBS 对象存储桶 | +| 25 | 4201 | 百度云 | ★★ | bos_list_buckets | 列出百度云 BOS 对象存储桶 | + +> 推荐评级最高 5 颗星,推荐评级根据模块的复杂程度、受欢迎程度、价值程度等因素综合判定。 + +## 快速上手 + +查看帮助信息 + +```bash +CloudSword > help + +全局命令 +======== + + 全局命令 描述 + -------- ---- + help 查看帮助信息 + list 列出模块 + quit 退出程序 + search 搜索模块 + use 使用模块 + + +二级命令 +======== + + 二级命令 描述 + -------- ---- + info 查看模块使用方法 + run 运行模块 + set 设置运行参数 + unset 取消设置运行参数 + + +环境变量 +======== + + 环境变量 描述 + -------- ---- + CLOUD_SWORD_ACCESS_KEY_ID 访问凭证 ID + CLOUD_SWORD_ACCESS_KEY_SECRET 访问凭证 Secret + CLOUD_SWORD_SECURITY_TOKEN 可选,访问凭证的临时令牌部分 + CLOUD_SWORD_DETAIL 详细内容输出(设置 no 或者 yes) +``` + +列出阿里云 OSS 对象存储桶 + +```bash +CloudSword > use 1201_aliyun_oss_list_buckets + +CloudSword 阿里云 (1201_oss_list_buckets) > set ak_id XXXXXXXXXXXX +ak_id ==> XXXXXXXXXXXX + +CloudSword 阿里云 (1201_oss_list_buckets) > set ak_secret XXXXXXXXXXXX +ak_secret ==> XXXXXXXXXXXX + +CloudSword 阿里云 (1201_oss_list_buckets) > run +[INFO] 2024-12-20 23:23:23 正在运行 1201_aliyun_oss_list_buckets 模块。 +[INFO] 2024-12-20 23:23:23 找到以下存储桶: +XXXXXXXX +XXXXXXXX +``` + +![1201_aliyun_oss_list_buckets](./static/1201_aliyun_oss_list_buckets.gif) + +## 加入云鉴问题讨论群 + +在「WgpSec 狼组安全团队」公众号菜单栏点击「加群」,添加「WgpSecBot」微信。 + +

+ +添加「WgpSecBot」微信后,给机器人发送「云鉴」关键词,机器人就会自动给您发送进群链接了。 + +

+ +## 贡献者 + +十分感谢各位师傅对云鉴的贡献~,如果你也想对云鉴贡献代码或想法,请参见贡献说明:[CONTRIBUTING](https://github.com/wgpsec/cloudsword/blob/master/CONTRIBUTING.md) + + +
+ + + + + + +
+ TeamsSix
TeamsSix
+
+ Keac
Keac
+
+ shadowabi
shadowabi
+
+
+ +## 使用答疑 + +我在云鉴使用手册里介绍了为什么写云鉴、云鉴未来的计划以及大家可能关心的问题,感兴趣的师傅可以移步[云鉴使用答疑](https://wiki.teamssix.com/cloudsword/more)查看。 + +## 协议 + +云鉴在 [Apache-2.0](https://github.com/wgpsec/cloudsword?tab=Apache-2.0-1-ov-file#Apache-2.0-1-ov-file) 协议下授权使用。 + +## 更多 + +下面这个是我们狼组安全团队的公众号,欢迎师傅关注,有想法一起加入狼组的师傅也可以投递简历至 admin#wgpsec.org 加入我们。 + +> 发送邮件时,注意将 # 改为 @ + +

+ +如果你对云安全比较感兴趣,可以看我的另外一个项目 [Awesome Cloud Security](https://github.com/teamssix/awesome-cloud-security),这里收录了很多国内外的云安全资源,另外在我的[云安全文库](https://wiki.teamssix.com/)里有大量的云安全方向的笔记和文章,这应该是国内还不错的云安全学习资料。 + +下面这个是我的个人微信公众号,在 TeamsSix 公众号里可以与我进行联系,后续关于云鉴的动态我也会发布到我的公众号里。 + +

+ +如果您感觉这个项目还不错,也欢迎扫描下面打赏码进行赞赏。 + +

+ +
感谢您使用我的工具
\ No newline at end of file diff --git a/cmd/banner.go b/cmd/banner.go new file mode 100644 index 0000000..71e87dd --- /dev/null +++ b/cmd/banner.go @@ -0,0 +1,129 @@ +package cmd + +import ( + "fmt" + "github.com/wgpsec/cloudsword/utils/global" +) + +type EnvironmentVariables struct { + Text string + Description string + BasicOptions string +} + +var ( + banner = ` + (_) + |_| + |_| + |_| + ▗▄▄▖▗▖ ▗▄▖ ▗▖ ▗▖▗▄▄▄ o=========o ▗▄▄▖▗▖ ▗▖ ▗▄▖ ▗▄▄▖ ▗▄▄▄ + ▐▌ ▐▌ ▐▌ ▐▌▐▌ ▐▌▐▌ █ | | ▐▌ ▐▌ ▐▌▐▌ ▐▌▐▌ ▐▌▐▌ █ + ▐▌ ▐▌ ▐▌ ▐▌▐▌ ▐▌▐▌ █ | | ▝▀▚▖▐▌ ▐▌▐▌ ▐▌▐▛▀▚▖▐▌ █ + ▝▚▄▄▖▐▙▄▄▖▝▚▄▞▘▝▚▄▞▘▐▙▄▄▀ | | ▗▄▄▞▘▐▙█▟▌▝▚▄▞▘▐▌ ▐▌▐▙▄▄▀ + | | + | | + | | + | | + \ / + + <-- 云鉴,让您的公有云环境更安全 --> +` + projectInfo = fmt.Sprintf(` + %s + %s + 项目地址:https://github.com/wgpsec/cloudsword + 使用手册:https://wiki.teamssix.com/cloudsword +`, global.Version, global.UpdateDate) + helpMessageEnvironmentVariables = []EnvironmentVariables{ + { + Text: global.CloudSwordAccessKeyID, + BasicOptions: global.AKId, + Description: "访问凭证 ID", + }, + { + Text: global.CloudSwordAccessKeySecret, + BasicOptions: global.AKSecret, + Description: "访问凭证 Secret", + }, + { + Text: global.CloudSwordSecurityToken, + BasicOptions: global.AKToken, + Description: "可选,访问凭证的临时令牌部分", + }, + //{ + // Text: global.CloudSwordProxy, + // BasicOptions: global.Proxy, + // Description: "\t代理访问,支持 Socks5、HTTPS 和 HTTP", + //}, + //{ + // Text: global.CloudSwordUserAgent, + // BasicOptions: global.UserAgent, + // Description: "\t请求 UA 头", + //}, + { + Text: global.CloudSwordDetail, + BasicOptions: global.Detail, + Description: "\t详细内容输出(设置 no 或者 yes)", + }, + } +) + +func returnHelpInformation() string { + var ( + level1 string + level2 string + example string + environmentVariables string + helpInformation string + moreInformation string + ) + level1 = "\t全局命令\t描述\n\t--------\t----\n" + level2 = "\t二级命令\t描述\n\t--------\t----\n" + environmentVariables = "\t环境变量\t\t\t\t描述\n\t--------\t\t\t\t----\n" + example = fmt.Sprintf(` +列出阿里云 OSS 对象存储桶: + +%ssearch oss +%suse 1201_aliyun_oss_list_buckets +%sset ak_id xxxxxxx +%sset ak_secret xxxxxxx +%srun + + +创建腾讯云访问凭证蜜标: + +%sexport CLOUD_SWORD_ACCESS_KEY_ID="xxxxxxx" +%sexport CLOUD_SWORD_ACCESS_KEY_SECRET="xxxxxxx" +%suse 2101_tencent_cloud_list_cloud_assets +%sset webhook https://open.feishu.cn/open-apis/bot/v2/hook/xxxxxxxxxxxxxxxxx +%srun +`, global.Tab, global.Tab, global.Tab, global.Tab, global.Tab, global.Tab, global.Tab, global.Tab, global.Tab, global.Tab) + moreInformation = fmt.Sprintf(` +当前版本:%s +发布日期:%s +项目地址:https://github.com/wgpsec/cloudsword +使用手册:https://wiki.teamssix.com/cloudsword +项目团队公众号:WgpSec 狼组安全团队 +作者个人公众号:TeamsSix +============================================== + 建议关注公众号以获取云鉴相关的最新消息 +============================================== +`, global.Version, global.UpdateDate) + for _, help := range helpMessageLevel1 { + level1 += fmt.Sprintf("\t%s\t\t%s\n", help.Text, help.Description) + } + for _, help := range helpMessageLevel2 { + level2 += fmt.Sprintf("\t%s\t\t%s\n", help.Text, help.Description) + } + for _, help := range helpMessageEnvironmentVariables { + environmentVariables += fmt.Sprintf("\t%s\t\t%s\n", help.Text, help.Description) + } + + helpInformation = fmt.Sprintf("%s\n\n全局命令\n========\n\n%s\n\n二级命令\n========\n\n%s\n\n环境变量\n========\n\n%s\n\n使用示例\n========\n%s", moreInformation, + level1, + level2, + environmentVariables, + example) + return helpInformation +} diff --git a/cmd/flag.go b/cmd/flag.go new file mode 100644 index 0000000..d8ac046 --- /dev/null +++ b/cmd/flag.go @@ -0,0 +1,205 @@ +package cmd + +import ( + "fmt" + "github.com/c-bata/go-prompt" + "github.com/wgpsec/cloudsword/utils" + "github.com/wgpsec/cloudsword/utils/global" + "github.com/wgpsec/cloudsword/utils/logger" + "os" + "strings" +) + +type helpStruct struct { + Text string + Description string +} + +var ( + useID int + currentPrefix = global.DefaultPrefix + p *prompt.Prompt + helpMessageLevel1 = []helpStruct{ + {Text: "help", Description: "查看帮助信息"}, + {Text: "list", Description: "列出模块"}, + {Text: "quit", Description: "退出程序"}, + {Text: "search", Description: "搜索模块"}, + {Text: "use", Description: "使用模块"}, + } + helpMessageLevel2 = []helpStruct{ + {Text: "info", Description: "查看模块使用方法"}, + {Text: "run", Description: "运行模块"}, + {Text: "set", Description: "设置运行参数"}, + {Text: "unset", Description: "取消设置运行参数"}, + } +) + +func completer(in prompt.Document) []prompt.Suggest { + s := []prompt.Suggest{} + spaceCount := strings.Count(in.TextBeforeCursor(), " ") + // use + if strings.HasPrefix(in.TextBeforeCursor(), "use ") { + if spaceCount < 2 { + return returnAllModulesWithUse(in) + } else { + return nil + } + } + // u + if strings.HasPrefix(in.TextBeforeCursor(), "u ") { + if spaceCount < 2 { + return returnAllModulesWithU(in) + } else { + return nil + } + } + + // set + if strings.HasPrefix(in.TextBeforeCursor(), "set ") { + if spaceCount < 2 { + return returnModuleBasicOptionWithSet(in) + } else { + return nil + } + } + // s + if strings.HasPrefix(in.TextBeforeCursor(), "s ") { + if spaceCount < 2 { + return returnModuleBasicOptionWithS(in) + } else { + return nil + } + } + + // unset + if strings.HasPrefix(in.TextBeforeCursor(), "unset ") { + if spaceCount < 2 { + return returnModuleBasicOptionByUnset(in) + } else { + return nil + } + } + // other + if spaceCount < 1 { + if useID == 0 { + for _, h := range helpMessageLevel1 { + s = append(s, prompt.Suggest{Text: h.Text, Description: h.Description}) + } + } else { + for _, h := range helpMessageLevel1 { + s = append(s, prompt.Suggest{Text: h.Text, Description: h.Description}) + } + for _, h := range helpMessageLevel2 { + s = append(s, prompt.Suggest{Text: h.Text, Description: h.Description}) + } + } + return prompt.FilterHasPrefix(s, in.GetWordBeforeCursor(), true) + } + return nil +} + +func changingPrefixLivePrefix() (string, bool) { + return currentPrefix, true +} + +func executor(in string) { + in = strings.TrimSpace(in) + var blocks []string + blocks_ := strings.Split(in, " ") + for _, block := range blocks_ { + block = strings.Trim(block, " \"") + if len(block) > 0 { + blocks = append(blocks, block) + } + } + if len(blocks) > 0 { + switch blocks[0] { + case "help", "h": + fmt.Println(returnHelpInformation()) + case "info", "i": + if useID == 0 { + logger.Println.Warn("请使用 use 命令选择你要调用的模块。\n") + } else { + fmt.Println(getModuleByID(useID).Info) + } + case "list", "l": + printModule(Modules()) + case "quit", "q": + logger.Println.Warn("云鉴已退出。") + os.Exit(0) + case "run", "r": + if useID == 0 { + logger.Println.Warn("请使用 use 命令选择你要调用的模块。\n") + } else if global.GetBasicOptionValue(global.AKId) == global.NULL { + logger.Println.Warn("请配置云访问凭证后再运行。\n") + } else { + runModule(getModuleByID(useID)) + } + case "search": + if len(blocks) == 2 { + fuzzySearchModule(blocks[1]) + } else { + logger.Println.Warn("请检查你的命令。\n") + } + case "set", "s": + if useID == 0 { + logger.Println.Warn("请使用 use 命令选择你要调用的模块。\n") + } else { + if len(blocks) == 3 { + for _, b := range global.GetBasicOptionsWithId(useID) { + if blocks[1] == b.Key { + if b.Key == global.Detail { + value := strings.ToLower(blocks[2]) + if utils.Contains([]string{"0", "false", "f", "no", "n"}, value) { + global.UpdateBasicOptionValue(b.Key, global.False) + fmt.Println(b.Key + " ==> " + global.False) + } else if utils.Contains([]string{"1", "true", "t", "yes", "y"}, value) { + global.UpdateBasicOptionValue(b.Key, global.True) + fmt.Println(b.Key + " ==> " + global.True) + } else { + logger.Println.Error(fmt.Sprintf("%s 的值类型错误,请设置成 True 或者 False。", b.Key)) + } + } else { + global.UpdateBasicOptionValue(b.Key, blocks[2]) + fmt.Println(b.Key + " ==> " + blocks[2]) + } + } + } + } else { + logger.Println.Warn("请检查你的命令,正确格式:set 。\n") + } + } + case "unset": + if useID == 0 { + logger.Println.Warn("请使用 use 命令选择你要调用的模块。\n") + } else { + if len(blocks) == 2 { + for k, b := range global.GetBasicOptionsWithId(useID) { + if blocks[1] == b.Key { + global.BasicOptionsFull[k].Value = global.NULL + fmt.Println(b.Key + " ==> " + global.NULL) + } + } + } else { + logger.Println.Warn("请检查你的命令。\n") + } + } + case "use", "u": + if len(blocks) > 1 { + searchTerm := strings.Join(blocks[1:], " ") + searchTerm = strings.Trim(searchTerm, "\"") + result := preciseSearchModule(searchTerm) + if len(result) == 1 { + useID = result[0].ID + currentPrefix = fmt.Sprintf("%s %s (%d_%s) > ", global.Name, result[0].Provider.Name, result[0].ID, result[0].Name) + } else { + logger.Println.Warn(fmt.Sprintf("没有找到 ID 为 %s 的模块", searchTerm)) + } + } else { + logger.Println.Warn("请指定要使用的模块。\n") + } + default: + logger.Println.Warn("不存在该命令,请使用 help 命令查看帮助信息。\n") + } + } +} diff --git a/cmd/modules.go b/cmd/modules.go new file mode 100644 index 0000000..90ff45d --- /dev/null +++ b/cmd/modules.go @@ -0,0 +1,238 @@ +package cmd + +import ( + "fmt" + "github.com/c-bata/go-prompt" + "github.com/wgpsec/cloudsword/pkg/aliyun" + "github.com/wgpsec/cloudsword/pkg/baiduCloud" + "github.com/wgpsec/cloudsword/pkg/huaweiCloud" + "github.com/wgpsec/cloudsword/pkg/tencentCloud" + "github.com/wgpsec/cloudsword/utils" + "github.com/wgpsec/cloudsword/utils/global" + "github.com/wgpsec/cloudsword/utils/logger" + "os" + "strconv" + "strings" +) + +// init + +func init() { + for _, Environment := range helpMessageEnvironmentVariables { + value := os.Getenv(Environment.Text) + if value != "" { + for k, b := range global.BasicOptionsFull { + if Environment.BasicOptions == b.Key { + global.BasicOptionsFull[k].Value = value + } + } + } + } +} + +func Modules() []global.Module { + modules := []global.Module{} + modules = append(modules, aliyun.Modules()...) + modules = append(modules, tencentCloud.Modules()...) + modules = append(modules, huaweiCloud.Modules()...) + modules = append(modules, baiduCloud.Modules()...) + return modules +} + +// info + +func getModuleByID(id int) global.Module { + var mo global.Module + for _, m := range Modules() { + if m.ID == id { + mo = m + } + } + return mo +} + +// list + +func printModule(module []global.Module) { + header := []string{"ID", "云提供商", "推荐评级", "名称", "介绍"} + rows := [][]string{} + for _, m := range module { + rows = append(rows, []string{ + strconv.Itoa(m.ID), + m.Provider.Name, + strings.Repeat("★", m.Level), + m.Name, + m.Introduce, + }) + } + fmt.Println(utils.GenerateTable(header, rows, []int{4, 8, 10, 30, 50})) +} + +// run + +func runModule(m global.Module) { + var bool_ bool = true + for _, n := range m.BasicOptions { + if n.Required && n.Value == global.NULL { + logger.Println.Warn("未配置必选参数:" + n.Key) + bool_ = false + } + } + if bool_ { + logger.Println.Info(fmt.Sprintf("正在运行 %s 模块。", utils.GetModuleName(m))) + switch m.ID { + // 阿里云 + case 1101: + aliyun.ListCloudAssets() + case 1201: + aliyun.OSSListBuckets() + case 1202: + aliyun.OSSSearchObjects() + case 1203: + aliyun.OSSLimitBucketOnlyUploadImages() + case 1301: + aliyun.ECSListInstances() + case 1401: + aliyun.RAMListUsers() + case 1402: + aliyun.RAMListRoles() + case 1403: + aliyun.RAMCreateUser() + case 1404: + aliyun.RAMAttachPolicyToUser() + case 1405: + aliyun.RAMCreateLoginProfile() + case 1406: + aliyun.RAMCreateAccessKey() + case 1501: + aliyun.DomainListDomains() + + // 腾讯云 + case 2101: + tencentCloud.ListCloudAssets() + case 2102: + tencentCloud.CreateHoneyToken() + case 2201: + tencentCloud.COSListBuckets() + case 2301: + tencentCloud.CVMListInstances() + case 2302: + tencentCloud.LHListInstances() + case 2401: + tencentCloud.CAMListUsers() + case 2402: + tencentCloud.CAMListRoles() + case 2403: + tencentCloud.CAMCreateUser() + case 2404: + tencentCloud.CAMAttachPolicyToUser() + case 2405: + tencentCloud.CAMCreateLoginProfile() + case 2406: + tencentCloud.CAMCreateAccessKey() + // 华为云 + case 3201: + huaweiCloud.OBSListBuckets() + // 百度云 + case 4201: + baiduCloud.BOSListBuckets() + } + } +} + +// search + +func fuzzySearchModule(str string) { + var result []global.Module + for _, m := range Modules() { + if strings.Contains(strconv.Itoa(m.ID), str) || strings.Contains(m.Provider.Name, str) || strings.Contains(m.Provider.EnName, + str) || strings.Contains(m.Name, str) || strings.Contains(m.Introduce, str) { + result = append(result, m) + } + } + printModule(result) +} + +// set + +func returnModuleBasicOptionWithSet(d prompt.Document) []prompt.Suggest { + s := []prompt.Suggest{} + for _, m := range getModuleByID(useID).BasicOptions { + s = append(s, prompt.Suggest{Text: m.Key, Description: m.Introduce}) + } + input := strings.TrimPrefix(d.TextBeforeCursor(), "set ") + input = strings.TrimSpace(input) + return filterContains(s, input, true) +} + +func returnModuleBasicOptionWithS(d prompt.Document) []prompt.Suggest { + s := []prompt.Suggest{} + for _, m := range getModuleByID(useID).BasicOptions { + s = append(s, prompt.Suggest{Text: m.Key, Description: m.Introduce}) + } + input := strings.TrimPrefix(d.TextBeforeCursor(), "s ") + input = strings.TrimSpace(input) + return filterContains(s, input, true) +} + +// unset + +func returnModuleBasicOptionByUnset(d prompt.Document) []prompt.Suggest { + s := []prompt.Suggest{} + for _, m := range getModuleByID(useID).BasicOptions { + s = append(s, prompt.Suggest{Text: m.Key, Description: m.Introduce}) + } + input := strings.TrimPrefix(d.TextBeforeCursor(), "unset ") + input = strings.TrimSpace(input) + return filterContains(s, input, true) +} + +// use + +func preciseSearchModule(str string) []global.Module { + var result []global.Module + for _, m := range Modules() { + if utils.GetModuleName(m) == str { + result = append(result, m) + } + } + return result +} + +func returnAllModulesWithUse(d prompt.Document) []prompt.Suggest { + s := []prompt.Suggest{} + for _, m := range Modules() { + s = append(s, prompt.Suggest{Text: utils.GetModuleName(m), Description: m.Introduce}) + } + input := strings.TrimPrefix(d.TextBeforeCursor(), "use ") + input = strings.TrimSpace(input) + return filterContains(s, input, true) +} + +func returnAllModulesWithU(d prompt.Document) []prompt.Suggest { + s := []prompt.Suggest{} + for _, m := range Modules() { + s = append(s, prompt.Suggest{Text: utils.GetModuleName(m), Description: m.Introduce}) + } + input := strings.TrimPrefix(d.TextBeforeCursor(), "u ") + input = strings.TrimSpace(input) + return filterContains(s, input, true) +} + +func filterContains(suggestions []prompt.Suggest, word string, ignoreCase bool) []prompt.Suggest { + if len(word) == 0 { + return suggestions + } + filtered := make([]prompt.Suggest, 0, len(suggestions)) + for _, s := range suggestions { + text := s.Text + if ignoreCase { + text = strings.ToLower(text) + word = strings.ToLower(word) + } + if strings.Contains(text, word) { + filtered = append(filtered, s) + } + } + return filtered +} diff --git a/cmd/root.go b/cmd/root.go new file mode 100644 index 0000000..3e40c5b --- /dev/null +++ b/cmd/root.go @@ -0,0 +1,21 @@ +package cmd + +import ( + "fmt" + "github.com/c-bata/go-prompt" + "github.com/fatih/color" + "github.com/wgpsec/cloudsword/utils" +) + +func Run() { + color.Green(banner) + fmt.Println(projectInfo) + utils.CheckVersion() + + p = prompt.New( + executor, + completer, + prompt.OptionLivePrefix(changingPrefixLivePrefix), + ) + p.Run() +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..42c01b8 --- /dev/null +++ b/go.mod @@ -0,0 +1,77 @@ +module github.com/wgpsec/cloudsword + +go 1.23.2 + +require ( + github.com/AlecAivazis/survey/v2 v2.3.7 + github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.10 + github.com/alibabacloud-go/domain-20180129/v4 v4.2.0 + github.com/alibabacloud-go/fc-20230330/v4 v4.1.4 + github.com/alibabacloud-go/ram-20150501/v2 v2.1.0 + github.com/alibabacloud-go/tea v1.2.2 + github.com/alibabacloud-go/tea-utils/v2 v2.0.6 + github.com/aliyun/alibaba-cloud-sdk-go v1.63.59 + github.com/aliyun/alibabacloud-oss-go-sdk-v2 v1.1.2 + github.com/baidubce/bce-sdk-go v0.9.206 + github.com/c-bata/go-prompt v0.2.6 + github.com/charmbracelet/bubbles v0.20.0 + github.com/charmbracelet/lipgloss v0.13.1 + github.com/fatih/color v1.18.0 + github.com/google/go-github/v39 v39.2.0 + github.com/hashicorp/go-version v1.7.0 + github.com/huaweicloud/huaweicloud-sdk-go-obs v3.24.9+incompatible + github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cam v1.0.1056 + github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cloudaudit v1.0.1056 + github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cls v1.0.1056 + github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1056 + github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cvm v1.0.1056 + github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/lighthouse v1.0.1056 + github.com/tencentyun/cos-go-sdk-v5 v0.7.59 +) + +require ( + github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.5 // indirect + github.com/alibabacloud-go/debug v1.0.1 // indirect + github.com/alibabacloud-go/endpoint-util v1.1.0 // indirect + github.com/alibabacloud-go/openapi-util v0.1.0 // indirect + github.com/alibabacloud-go/tea-utils v1.3.1 // indirect + github.com/alibabacloud-go/tea-xml v1.1.3 // indirect + github.com/aliyun/credentials-go v1.3.10 // indirect + github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect + github.com/charmbracelet/bubbletea v1.1.2 // indirect + github.com/charmbracelet/x/ansi v0.4.0 // indirect + github.com/charmbracelet/x/term v0.2.0 // indirect + github.com/clbanning/mxj v1.8.4 // indirect + github.com/clbanning/mxj/v2 v2.5.5 // indirect + github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect + github.com/google/go-querystring v1.1.0 // indirect + github.com/jmespath/go-jmespath v0.4.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect + github.com/lucasb-eyer/go-colorful v1.2.0 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-localereader v0.0.1 // indirect + github.com/mattn/go-runewidth v0.0.16 // indirect + github.com/mattn/go-tty v0.0.3 // indirect + github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect + github.com/mitchellh/mapstructure v1.4.3 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/mozillazg/go-httpheader v0.2.1 // indirect + github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect + github.com/muesli/cancelreader v0.2.2 // indirect + github.com/muesli/termenv v0.15.2 // indirect + github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b // indirect + github.com/pkg/term v1.2.0-beta.2 // indirect + github.com/rivo/uniseg v0.4.7 // indirect + github.com/tjfoc/gmsm v1.4.1 // indirect + golang.org/x/crypto v0.21.0 // indirect + golang.org/x/net v0.23.0 // indirect + golang.org/x/sync v0.9.0 // indirect + golang.org/x/sys v0.27.0 // indirect + golang.org/x/term v0.18.0 // indirect + golang.org/x/text v0.20.0 // indirect + golang.org/x/time v0.4.0 // indirect + gopkg.in/ini.v1 v1.67.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..6460045 --- /dev/null +++ b/go.sum @@ -0,0 +1,424 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/AlecAivazis/survey/v2 v2.3.7 h1:6I/u8FvytdGsgonrYsVn2t8t4QiRnh6QSTqkkhIiSjQ= +github.com/AlecAivazis/survey/v2 v2.3.7/go.mod h1:xUTIdE4KCOIjsBAE1JYsUPoCqYdZ1reCfTwbto0Fduo= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/HdrHistogram/hdrhistogram-go v1.1.2/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo= +github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 h1:+vx7roKuyA63nhn5WAunQHLTznkw5W8b1Xc0dNjp83s= +github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDeC1lPdgDeDbhX8XFpy1jqjK0IBG8W5K+xYqA0w= +github.com/QcloudApi/qcloud_sign_golang v0.0.0-20141224014652-e4130a326409/go.mod h1:1pk82RBxDY/JZnPQrtqHlUFfCctgdorsd9M06fMynOM= +github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= +github.com/alibabacloud-go/alibabacloud-gateway-pop v0.0.6 h1:eIf+iGJxdU4U9ypaUfbtOWCsZSbTb8AUHvyPrxu6mAA= +github.com/alibabacloud-go/alibabacloud-gateway-pop v0.0.6/go.mod h1:4EUIoxs/do24zMOGGqYVWgw0s9NtiylnJglOeEB5UJo= +github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.4/go.mod h1:sCavSAvdzOjul4cEqeVtvlSaSScfNsTQ+46HwlTL1hc= +github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.5 h1:zE8vH9C7JiZLNJJQ5OwjU9mSi4T9ef9u3BURT6LCLC8= +github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.5/go.mod h1:tWnyE9AjF8J8qqLk645oUmVUnFybApTQWklQmi5tY6g= +github.com/alibabacloud-go/darabonba-array v0.1.0 h1:vR8s7b1fWAQIjEjWnuF0JiKsCvclSRTfDzZHTYqfufY= +github.com/alibabacloud-go/darabonba-array v0.1.0/go.mod h1:BLKxr0brnggqOJPqT09DFJ8g3fsDshapUD3C3aOEFaI= +github.com/alibabacloud-go/darabonba-encode-util v0.0.2 h1:1uJGrbsGEVqWcWxrS9MyC2NG0Ax+GpOM5gtupki31XE= +github.com/alibabacloud-go/darabonba-encode-util v0.0.2/go.mod h1:JiW9higWHYXm7F4PKuMgEUETNZasrDM6vqVr/Can7H8= +github.com/alibabacloud-go/darabonba-map v0.0.2 h1:qvPnGB4+dJbJIxOOfawxzF3hzMnIpjmafa0qOTp6udc= +github.com/alibabacloud-go/darabonba-map v0.0.2/go.mod h1:28AJaX8FOE/ym8OUFWga+MtEzBunJwQGceGQlvaPGPc= +github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.7/go.mod h1:CzQnh+94WDnJOnKZH5YRyouL+OOcdBnXY5VWAf0McgI= +github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.9/go.mod h1:bb+Io8Sn2RuM3/Rpme6ll86jMyFSrD1bxeV/+v61KeU= +github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.10 h1:GEYkMApgpKEVDn6z12DcH1EGYpDYRB8JxsazM4Rywak= +github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.10/go.mod h1:26a14FGhZVELuz2cc2AolvW4RHmIO3/HRwsdHhaIPDE= +github.com/alibabacloud-go/darabonba-signature-util v0.0.7 h1:UzCnKvsjPFzApvODDNEYqBHMFt1w98wC7FOo0InLyxg= +github.com/alibabacloud-go/darabonba-signature-util v0.0.7/go.mod h1:oUzCYV2fcCH797xKdL6BDH8ADIHlzrtKVjeRtunBNTQ= +github.com/alibabacloud-go/darabonba-string v1.0.2 h1:E714wms5ibdzCqGeYJ9JCFywE5nDyvIXIIQbZVFkkqo= +github.com/alibabacloud-go/darabonba-string v1.0.2/go.mod h1:93cTfV3vuPhhEwGGpKKqhVW4jLe7tDpo3LUM0i0g6mA= +github.com/alibabacloud-go/debug v0.0.0-20190504072949-9472017b5c68/go.mod h1:6pb/Qy8c+lqua8cFpEy7g39NRRqOWc3rOwAy8m5Y2BY= +github.com/alibabacloud-go/debug v1.0.0/go.mod h1:8gfgZCCAC3+SCzjWtY053FrOcd4/qlH6IHTI4QyICOc= +github.com/alibabacloud-go/debug v1.0.1 h1:MsW9SmUtbb1Fnt3ieC6NNZi6aEwrXfDksD4QA6GSbPg= +github.com/alibabacloud-go/debug v1.0.1/go.mod h1:8gfgZCCAC3+SCzjWtY053FrOcd4/qlH6IHTI4QyICOc= +github.com/alibabacloud-go/domain-20180129/v4 v4.2.0 h1:PAEt76VDoXbQODWeN9PTwYhA5NoEbJSK/yzGX2DZXrQ= +github.com/alibabacloud-go/domain-20180129/v4 v4.2.0/go.mod h1:q0n3wgGRndhuZsAXFVCFtiSR8+W0so85qYtKLzR2b18= +github.com/alibabacloud-go/endpoint-util v1.1.0 h1:r/4D3VSw888XGaeNpP994zDUaxdgTSHBbVfZlzf6b5Q= +github.com/alibabacloud-go/endpoint-util v1.1.0/go.mod h1:O5FuCALmCKs2Ff7JFJMudHs0I5EBgecXXxZRyswlEjE= +github.com/alibabacloud-go/fc-20230330/v4 v4.1.4 h1:asbQoCVvuJJyP8wDYwncBU8MYoaPqFKYls5zIcjoPJY= +github.com/alibabacloud-go/fc-20230330/v4 v4.1.4/go.mod h1:yuWHeS7dsdObjZQgnN81DGlOdPOVrRvwgQQfDkFWhos= +github.com/alibabacloud-go/openapi-util v0.1.0 h1:0z75cIULkDrdEhkLWgi9tnLe+KhAFE/r5Pb3312/eAY= +github.com/alibabacloud-go/openapi-util v0.1.0/go.mod h1:sQuElr4ywwFRlCCberQwKRFhRzIyG4QTP/P4y1CJ6Ws= +github.com/alibabacloud-go/ram-20150501/v2 v2.1.0 h1:l4ssQZwmqO3V0MUQeY3DRiIJSjoRrjvo9BcojlrRXYc= +github.com/alibabacloud-go/ram-20150501/v2 v2.1.0/go.mod h1:mSHwkEe0Qgxkhe/GVpHsuJ9SAI6AaZjxj3DuL5kX/ng= +github.com/alibabacloud-go/tea v1.1.0/go.mod h1:IkGyUSX4Ba1V+k4pCtJUc6jDpZLFph9QMy2VUPTwukg= +github.com/alibabacloud-go/tea v1.1.7/go.mod h1:/tmnEaQMyb4Ky1/5D+SE1BAsa5zj/KeGOFfwYm3N/p4= +github.com/alibabacloud-go/tea v1.1.8/go.mod h1:/tmnEaQMyb4Ky1/5D+SE1BAsa5zj/KeGOFfwYm3N/p4= +github.com/alibabacloud-go/tea v1.1.11/go.mod h1:/tmnEaQMyb4Ky1/5D+SE1BAsa5zj/KeGOFfwYm3N/p4= +github.com/alibabacloud-go/tea v1.1.17/go.mod h1:nXxjm6CIFkBhwW4FQkNrolwbfon8Svy6cujmKFUq98A= +github.com/alibabacloud-go/tea v1.1.20/go.mod h1:nXxjm6CIFkBhwW4FQkNrolwbfon8Svy6cujmKFUq98A= +github.com/alibabacloud-go/tea v1.2.1/go.mod h1:qbzof29bM/IFhLMtJPrgTGK3eauV5J2wSyEUo4OEmnA= +github.com/alibabacloud-go/tea v1.2.2 h1:aTsR6Rl3ANWPfqeQugPglfurloyBJY85eFy7Gc1+8oU= +github.com/alibabacloud-go/tea v1.2.2/go.mod h1:CF3vOzEMAG+bR4WOql8gc2G9H3EkH3ZLAQdpmpXMgwk= +github.com/alibabacloud-go/tea-utils v1.3.1 h1:iWQeRzRheqCMuiF3+XkfybB3kTgUXkXX+JMrqfLeB2I= +github.com/alibabacloud-go/tea-utils v1.3.1/go.mod h1:EI/o33aBfj3hETm4RLiAxF/ThQdSngxrpF8rKUDJjPE= +github.com/alibabacloud-go/tea-utils/v2 v2.0.5/go.mod h1:dL6vbUT35E4F4bFTHL845eUloqaerYBYPsdWR2/jhe4= +github.com/alibabacloud-go/tea-utils/v2 v2.0.6 h1:ZkmUlhlQbaDC+Eba/GARMPy6hKdCLiSke5RsN5LcyQ0= +github.com/alibabacloud-go/tea-utils/v2 v2.0.6/go.mod h1:qxn986l+q33J5VkialKMqT/TTs3E+U9MJpd001iWQ9I= +github.com/alibabacloud-go/tea-xml v1.1.3 h1:7LYnm+JbOq2B+T/B0fHC4Ies4/FofC4zHzYtqw7dgt0= +github.com/alibabacloud-go/tea-xml v1.1.3/go.mod h1:Rq08vgCcCAjHyRi/M7xlHKUykZCEtyBy9+DPF6GgEu8= +github.com/aliyun/alibaba-cloud-sdk-go v1.63.59 h1:swdLTTe5CjTdZZIFpMBk/9HxR5QSEnwt7em9G76Lo5k= +github.com/aliyun/alibaba-cloud-sdk-go v1.63.59/go.mod h1:SOSDHfe1kX91v3W5QiBsWSLqeLxImobbMX1mxrFHsVQ= +github.com/aliyun/alibabacloud-oss-go-sdk-v2 v1.1.2 h1:ov7op9h8dVKloivIxb+ca/eQzdoOXt2RjQ5+TxDMnWg= +github.com/aliyun/alibabacloud-oss-go-sdk-v2 v1.1.2/go.mod h1:FTzydeQVmR24FI0D6XWUOMKckjXehM/jgMn1xC+DA9M= +github.com/aliyun/credentials-go v1.1.2/go.mod h1:ozcZaMR5kLM7pwtCMEpVmQ242suV6qTJya2bDq4X1Tw= +github.com/aliyun/credentials-go v1.3.1/go.mod h1:8jKYhQuDawt8x2+fusqa1Y6mPxemTsBEN04dgcAcYz0= +github.com/aliyun/credentials-go v1.3.6/go.mod h1:1LxUuX7L5YrZUWzBrRyk0SwSdH4OmPrib8NVePL3fxM= +github.com/aliyun/credentials-go v1.3.10 h1:45Xxrae/evfzQL9V10zL3xX31eqgLWEaIdCoPipOEQA= +github.com/aliyun/credentials-go v1.3.10/go.mod h1:Jm6d+xIgwJVLVWT561vy67ZRP4lPTQxMbEYRuT2Ti1U= +github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= +github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= +github.com/aymanbagabas/go-udiff v0.2.0 h1:TK0fH4MteXUDspT88n8CKzvK0X9O2xu9yQjWpi6yML8= +github.com/aymanbagabas/go-udiff v0.2.0/go.mod h1:RE4Ex0qsGkTAJoQdQQCA0uG+nAzJO/pI/QwceO5fgrA= +github.com/baidubce/bce-sdk-go v0.9.206 h1:1nmKLHWCkPzpmVATiC15+4q/lYkx4PdXd2qKfYUzTes= +github.com/baidubce/bce-sdk-go v0.9.206/go.mod h1:zbYJMQwE4IZuyrJiFO8tO8NbtYiKTFTbwh4eIsqjVdg= +github.com/c-bata/go-prompt v0.2.6 h1:POP+nrHE+DfLYx370bedwNhsqmpCUynWPxuHi0C5vZI= +github.com/c-bata/go-prompt v0.2.6/go.mod h1:/LMAke8wD2FsNu9EXNdHxNLbd9MedkPnCdfpU9wwHfY= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/charmbracelet/bubbles v0.20.0 h1:jSZu6qD8cRQ6k9OMfR1WlM+ruM8fkPWkHvQWD9LIutE= +github.com/charmbracelet/bubbles v0.20.0/go.mod h1:39slydyswPy+uVOHZ5x/GjwVAFkCsV8IIVy+4MhzwwU= +github.com/charmbracelet/bubbletea v1.1.2 h1:naQXF2laRxyLyil/i7fxdpiz1/k06IKquhm4vBfHsIc= +github.com/charmbracelet/bubbletea v1.1.2/go.mod h1:9HIU/hBV24qKjlehyj8z1r/tR9TYTQEag+cWZnuXo8E= +github.com/charmbracelet/lipgloss v0.13.1 h1:Oik/oqDTMVA01GetT4JdEC033dNzWoQHdWnHnQmXE2A= +github.com/charmbracelet/lipgloss v0.13.1/go.mod h1:zaYVJ2xKSKEnTEEbX6uAHabh2d975RJ+0yfkFpRBz5U= +github.com/charmbracelet/x/ansi v0.4.0 h1:NqwHA4B23VwsDn4H3VcNX1W1tOmgnvY1NDx5tOXdnOU= +github.com/charmbracelet/x/ansi v0.4.0/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw= +github.com/charmbracelet/x/exp/golden v0.0.0-20240815200342-61de596daa2b h1:MnAMdlwSltxJyULnrYbkZpp4k58Co7Tah3ciKhSNo0Q= +github.com/charmbracelet/x/exp/golden v0.0.0-20240815200342-61de596daa2b/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U= +github.com/charmbracelet/x/term v0.2.0 h1:cNB9Ot9q8I711MyZ7myUR5HFWL/lc3OpU8jZ4hwm0x0= +github.com/charmbracelet/x/term v0.2.0/go.mod h1:GVxgxAbjUrmpvIINHIQnJJKpMlHiZ4cktEQCN6GWyF0= +github.com/clbanning/mxj v1.8.4 h1:HuhwZtbyvyOw+3Z1AowPkU87JkJUSv751ELWaiTpj8I= +github.com/clbanning/mxj v1.8.4/go.mod h1:BVjHeAH+rl9rs6f+QIpeRl0tfu10SXn1pUSa5PVGJng= +github.com/clbanning/mxj/v2 v2.5.5 h1:oT81vUeEiQQ/DcHbzSytRngP6Ky9O+L+0Bw0zSJag9E= +github.com/clbanning/mxj/v2 v2.5.5/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/creack/pty v1.1.17 h1:QeVUsEDNrLBW4tMgZHvxy18sKtr6VI492kBhUfhDJNI= +github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4= +github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM= +github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= +github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= +github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A= +github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-github/v39 v39.2.0 h1:rNNM311XtPOz5rDdsJXAp2o8F67X9FnROXTvto3aSnQ= +github.com/google/go-github/v39 v39.2.0/go.mod h1:C1s8C5aCC9L+JXIYpJM5GYytdX52vC1bLvHEF1IhBrE= +github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= +github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= +github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY= +github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec h1:qv2VnGeEQHchGaZ/u7lxST/RaJw+cv273q79D81Xbog= +github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec/go.mod h1:Q48J4R4DvxnHolD5P8pOtXigYlRuPLGl6moFx3ulM68= +github.com/huaweicloud/huaweicloud-sdk-go-obs v3.24.9+incompatible h1:XQVXdk+WAJ4fSNB6mMRuYNvFWou7BZs6SZB925hPrnk= +github.com/huaweicloud/huaweicloud-sdk-go-obs v3.24.9+incompatible/go.mod h1:l7VUhRbTKCzdOacdT4oWCwATKyvZqUOlOqr0Ous3k4s= +github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= +github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= +github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= +github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= +github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= +github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4= +github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88= +github.com/mattn/go-runewidth v0.0.6/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= +github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-tty v0.0.3 h1:5OfyWorkyO7xP52Mq7tB36ajHDG5OHrmBGIS/DtakQI= +github.com/mattn/go-tty v0.0.3/go.mod h1:ihxohKRERHTVzN+aSVRwACLCeqIoZAWpoICkkvrWyR0= +github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4= +github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= +github.com/mitchellh/mapstructure v1.4.3 h1:OVowDSCllw/YjdLkam3/sm7wEtOy59d8ndGgCcyj8cs= +github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/mozillazg/go-httpheader v0.2.1 h1:geV7TrjbL8KXSyvghnFm+NyTux/hxwueTSrwhe88TQQ= +github.com/mozillazg/go-httpheader v0.2.1/go.mod h1:jJ8xECTlalr6ValeXYdOF8fFUISeBAdw6E61aqQma60= +github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI= +github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo= +github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= +github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= +github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo= +github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b h1:FfH+VrHHk6Lxt9HdVS0PXzSXFyS2NbZKXv33FYPol0A= +github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b/go.mod h1:AC62GU6hc0BrNm+9RK9VSiwa/EUe1bkIeFORAMcHvJU= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/term v1.2.0-beta.2 h1:L3y/h2jkuBVFdWiJvNfYfKmzcCnILw7mJWm2JQuMppw= +github.com/pkg/term v1.2.0-beta.2/go.mod h1:E25nymQcrSllhX42Ok8MRm1+hyBdHY0dCeiKZ9jpNGw= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/assertions v1.1.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cam v1.0.1056 h1:NmAts73tvzaoo6wrc0eZQvi/2wRAUSABs2Q8ZzaMiII= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cam v1.0.1056/go.mod h1:AfhIJqhOKdRAuGvrq9JOWsKMr2WPtPm2mBLgXJlNMIo= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cloudaudit v1.0.1056 h1:y29c+VfVtFF5ddlH2lsj4RjKEokBILHjE0W0LSUP9Kc= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cloudaudit v1.0.1056/go.mod h1:5rZ9ZpK3CrglPRf44yzy1h6BDnm7ZyD2oro+IWBWg0Q= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cls v1.0.1056 h1:Bth0niGuBhMKMl2imXbyLijQQkw7QbW2J6ARxDzgK14= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cls v1.0.1056/go.mod h1:yHMvKldFZzgvUC16SZ880ivpX5xejGgxdjIATQrspdU= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.563/go.mod h1:7sCQWVkxcsR38nffDW057DRGk8mUjK1Ing/EFOK8s8Y= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1056 h1:bOSZ7cOXvBe8MCBqDBTtpyKIqx9q6woxqIPK5M9FhNY= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1056/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cvm v1.0.1056 h1:V4eiBVXkcSd3OfiTdnuQugvkD7KTY0uGB4c7ROuiS4o= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cvm v1.0.1056/go.mod h1:1ATX+0FFLEKi2is/zqsRxTaELyo+Mt+93e9AuSF8A2U= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/kms v1.0.563/go.mod h1:uom4Nvi9W+Qkom0exYiJ9VWJjXwyxtPYTkKkaLMlfE0= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/lighthouse v1.0.1056 h1:zkYAgYjCWDEDKXIhwgeyqY8P3QE0hGeDqWi+zVhBUNk= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/lighthouse v1.0.1056/go.mod h1:1nnsJrqVQSYEygyXkPc2xnKGDq3U7fgdJzufAugN1uo= +github.com/tencentyun/cos-go-sdk-v5 v0.7.59 h1:bzB/0fj+gKCUvEfe+c4CNGkqTnrenxzTsLM2rMn3mHE= +github.com/tencentyun/cos-go-sdk-v5 v0.7.59/go.mod h1:8+hG+mQMuRP/OIS9d83syAvXvrMj9HhkND6Q1fLghw0= +github.com/tjfoc/gmsm v1.3.2/go.mod h1:HaUcFuY0auTiaHB9MHFGCPx5IaLhTUd2atbCFBQXn9w= +github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho= +github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE= +github.com/uber/jaeger-client-go v2.30.0+incompatible h1:D6wyKGCecFaSRUpo8lCVbaOOb6ThwMmTEbhRwtKR97o= +github.com/uber/jaeger-client-go v2.30.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= +github.com/uber/jaeger-lib v2.4.1+incompatible h1:td4jdvLcExb4cBISKIpHuGoVXh+dVKhn2Um6rjCsSsg= +github.com/uber/jaeger-lib v2.4.1+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.30/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= +go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191219195013-becbf705a915/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I= +golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= +golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= +golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= +golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= +golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ= +golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200509044756-6aff5f38e54f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200918174421-af09f7315aff/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= +golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.9.0/go.mod h1:M6DEAAIenWoTxdKrOltXcmDY3rSplQUkrvaDU5FcQyo= +golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= +golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= +golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= +golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8= +golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug= +golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4= +golang.org/x/time v0.4.0 h1:Z81tqI5ddIoXDPvVQ7/7CC9TnLM7ubaFG2qXYd5BbYY= +golang.org/x/time v0.4.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200509030707-2212a7e161a5/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= +gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0= +gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= +gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/ini.v1 v1.56.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= diff --git a/main.go b/main.go new file mode 100644 index 0000000..50798d9 --- /dev/null +++ b/main.go @@ -0,0 +1,7 @@ +package main + +import "github.com/wgpsec/cloudsword/cmd" + +func main() { + cmd.Run() +} diff --git a/pkg/aliyun/1101_list_cloud_assets.go b/pkg/aliyun/1101_list_cloud_assets.go new file mode 100644 index 0000000..5159386 --- /dev/null +++ b/pkg/aliyun/1101_list_cloud_assets.go @@ -0,0 +1,11 @@ +package aliyun + +// list_cloud_assets + +func ListCloudAssets() { + OSSListBuckets() + ECSListInstances() + RAMListUsers() + RAMListRoles() + DomainListDomains() +} diff --git a/pkg/aliyun/1201_oss_list_buckets.go b/pkg/aliyun/1201_oss_list_buckets.go new file mode 100644 index 0000000..769c0d7 --- /dev/null +++ b/pkg/aliyun/1201_oss_list_buckets.go @@ -0,0 +1,51 @@ +package aliyun + +import ( + "fmt" + "github.com/aliyun/alibabacloud-oss-go-sdk-v2/oss" + "github.com/wgpsec/cloudsword/utils" + "github.com/wgpsec/cloudsword/utils/global" + "github.com/wgpsec/cloudsword/utils/logger" + "strings" +) + +// oss_list_buckets + +func OSSListBuckets() { + bucket := listBuckets() + if len(bucket) == 0 { + logger.Println.Info("没有找到存储桶。\n") + } else { + logger.Println.Info("找到以下存储桶:") + for _, b := range bucket { + if global.GetBasicOptionValue(global.Detail) == global.False { + fmt.Println(oss.ToString(b.Name)) + } else { + bucketsStat, err := getBucketStat(oss.ToString(b.Name), oss.ToString(b.Region)) + fmt.Printf("\n基础信息\n========\n") + fmt.Printf("名称:%v\n", oss.ToString(b.Name)) + fmt.Printf("区域:%v\n", oss.ToString(b.Region)) + if err == nil { + fmt.Printf("存储对象数量:%v\n", bucketsStat.ObjectCount) + fmt.Printf("存储容量:%v\n", utils.FormatBytes(bucketsStat.Storage)) + } + bucketTags, err := getBucketTags(oss.ToString(b.Name), oss.ToString(b.Region)) + if err == nil { + fmt.Printf("存储桶标签:%v\n", bucketTags) + } + + fmt.Printf("访问地址:https://%v.oss-%v.aliyuncs.com\n", oss.ToString(b.Name), oss.ToString(b.Region)) + + fmt.Printf("\n更多信息\n========\n") + fmt.Printf("存储类型:%v\n", oss.ToString(b.StorageClass)) + fmt.Printf("数据中心位置:%v\n", oss.ToString(b.Location)) + fmt.Printf("创建时间:%v\n", utils.FormatTime(b.CreationDate)) + fmt.Printf("公共端点:%v\n", oss.ToString(b.ExtranetEndpoint)) + fmt.Printf("内部端点:%v\n", oss.ToString(b.IntranetEndpoint)) + fmt.Printf("资源组 ID:%v\n", oss.ToString(b.ResourceGroupId)) + fmt.Println(strings.Repeat("=", 60)) + } + } + fmt.Println() + } +} diff --git a/pkg/aliyun/1202_oss_search_objects.go b/pkg/aliyun/1202_oss_search_objects.go new file mode 100644 index 0000000..fa514cd --- /dev/null +++ b/pkg/aliyun/1202_oss_search_objects.go @@ -0,0 +1,136 @@ +package aliyun + +import ( + "context" + "encoding/json" + "fmt" + "github.com/alibabacloud-go/tea/tea" + "github.com/aliyun/alibabacloud-oss-go-sdk-v2/oss" + "github.com/wgpsec/cloudsword/utils" + "github.com/wgpsec/cloudsword/utils/global" + "github.com/wgpsec/cloudsword/utils/logger" + "strconv" + "time" +) + +// oss_search_objects + +func OSSSearchObjects() { + // 1. 查询是否启用数据索引 + bucketName := global.GetBasicOptionValue(global.BucketName) + region, err := getBucketRegion(bucketName) + if err == nil { + status, err := getMetaQueryStatus(bucketName, region) + if err == nil { + // 2. 如果没有就启用数据索引 + if status == "MetaQueryNotExist" { + if utils.SurveyConfirm("查询 OSS 对象需要先创建数据索引,这需要一些时间,并且在查询过程中可能会产生一些费用,您要继续吗?") { + err = openMetaQuery(bucketName, region) + if err == nil { + logger.Printf.Infof("正在创建数据索引") + for { + status, err = getMetaQueryStatus(bucketName, region) + if err == nil { + if status == "Ready" { + fmt.Printf(".") + } else if status == "Running" { + fmt.Println() + break + } + time.Sleep(10 * time.Second) + } else { + break + } + } + } + } + } + + // 3. 数据索引创建完成,开始查询数据 + if status == "Running" { + logger.Println.Info("数据索引已建立,正在查询数据...") + var results []oss.MetaQueryFile + doMetaQueryRequest := &oss.DoMetaQueryRequest{ + MetaQuery: &oss.MetaQuery{}, + } + //doMetaQueryRequest.MetaQuery + doMetaQueryRequest.Bucket = tea.String(bucketName) + + maxResults, err := strconv.ParseInt(global.GetBasicOptionValue(global.MaxResults), 10, 64) + if err != nil { + logger.Println.Error("解析最大结果数量值时报错,详细信息如下:") + logger.Println.Error(err.Error()) + } else { + if maxResults == -1 { + doMetaQueryRequest.MetaQuery.MaxResults = tea.Int64(100) + } else { + doMetaQueryRequest.MetaQuery.MaxResults = tea.Int64(maxResults) + } + doMetaQueryRequest.MetaQuery.Query = tea.String(fmt.Sprintf("{\"Field\": \"Filename\",\"Value\": \"%v\",\"Operation\": \"match\"}", + global.GetBasicOptionValue(global.QueryValue))) + for { + responses, err := OSSClient(region).DoMetaQuery(context.TODO(), doMetaQueryRequest) + if err != nil { + logger.Println.Error(fmt.Sprintf("查询 %v 存储桶对象时报错错,详细信息如下:"), bucketName) + logger.Println.Error(err.Error()) + break + } else { + results = append(results, responses.Files...) + if responses.NextToken == nil { + break + } else if *responses.NextToken == global.NULL { + break + } else { + if maxResults == -1 { + doMetaQueryRequest.MetaQuery.NextToken = responses.NextToken + } else if len(results) > int(maxResults) { + break + } else { + doMetaQueryRequest.MetaQuery.NextToken = responses.NextToken + } + } + } + } + + if len(results) == 0 { + logger.Println.Info("未查询到数据。") + } else { + logger.Println.Info(fmt.Sprintf("查询到 %v 条数据,结果如下:", len(results))) + for _, file := range results { + if global.GetBasicOptionValue(global.Detail) == global.False { + fmt.Println(*file.Filename) + } else { + fmt.Printf("\n名称:%v\n", *file.Filename) + fmt.Printf("大小:%v\n", utils.FormatBytes(*file.Size)) + + jsonBytes, err := json.Marshal(file.OSSTagging) + if err != nil { + logger.Println.Error(fmt.Sprintf("解析 %v 对象标签信息时报错,详细信息如下:", *file.Filename)) + logger.Println.Error(err.Error()) + } else { + fmt.Printf("标签:%v\n", string(jsonBytes)) + } + + jsonBytes, err = json.Marshal(file.OSSUserMeta) + if err != nil { + logger.Println.Error(fmt.Sprintf("解析 %v 对象自定义元数据列表时报错,详细信息如下:", *file.Filename)) + logger.Println.Error(err.Error()) + } else { + fmt.Printf("自定义元数据列表:%v\n", string(jsonBytes)) + } + logger.Println.Error(err.Error()) + + fmt.Printf("访问地址:https://%v.oss-%v.aliyuncs.com/%v\n", bucketName, region, *file.Filename) + } + } + fmt.Println() + } + // 4. 关闭数据索引 + if utils.SurveyConfirm("需要关闭数据索引吗?(如果您当前没有搜索需求,建议选择关闭)") { + closeMetaQuery(bucketName, region) + } + } + } + } + } +} diff --git a/pkg/aliyun/1203_oss_bucket_only_upload_images.go b/pkg/aliyun/1203_oss_bucket_only_upload_images.go new file mode 100644 index 0000000..6ce74f8 --- /dev/null +++ b/pkg/aliyun/1203_oss_bucket_only_upload_images.go @@ -0,0 +1,193 @@ +package aliyun + +import ( + "fmt" + fc "github.com/alibabacloud-go/fc-20230330/v4/client" + ram "github.com/alibabacloud-go/ram-20150501/v2/client" + "github.com/alibabacloud-go/tea/tea" + "github.com/wgpsec/cloudsword/utils" + "github.com/wgpsec/cloudsword/utils/global" + "github.com/wgpsec/cloudsword/utils/logger" + "regexp" + "strings" +) + +func OSSLimitBucketOnlyUploadImages() { + // 1. 创建 RAM 角色 + ramClient, err := RAMClient() + if err == nil { + roleName := utils.GenerateRandomName("Role") + logger.Println.Info(fmt.Sprintf("正在创建 %v 角色。", roleName)) + createRoleRequest := &ram.CreateRoleRequest{} + createRoleRequest.RoleName = tea.String(roleName) + createRoleRequest.Description = tea.String("此角色由云鉴 1203_oss_bucket_only_upload_images 模块自动创建,此角色用于云函数读取和删除存储桶对象以及执行云函数使用。") + createRoleRequest.AssumeRolePolicyDocument = tea.String(` +{ + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": [ + "fc.aliyuncs.com" + ] + } + }, + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": [ + "oss.aliyuncs.com" + ] + } + } + ], + "Version": "1" +} +`) + createRoleResponse, err := ramClient.CreateRole(createRoleRequest) + if err != nil { + logger.Println.Error("创建 RAM 角色时报错,详细信息如下:") + logger.Println.Error(err.Error()) + } else { + // 2. 创建权限策略 + roleArn := *createRoleResponse.Body.Role.Arn + accountId := regexp.MustCompile(`::(\d+):`).FindStringSubmatch(roleArn)[1] + bucketName := global.GetBasicOptionValue(global.BucketName) + region, err := getBucketRegion(bucketName) + if err == nil { + policyName := utils.GenerateRandomName("Policy") + logger.Println.Info(fmt.Sprintf("正在创建 %v 策略。", policyName)) + createPolicyRequest := &ram.CreatePolicyRequest{} + createPolicyRequest.PolicyName = tea.String(policyName) + createPolicyRequest.Description = tea.String( + "此策略由云鉴 1203_oss_bucket_only_upload_images 模块自动创建,此策略所关联的角色用于云函数读取和删除存储桶对象以及执行云函数使用。") + createPolicyRequest.PolicyDocument = tea.String(fmt.Sprintf(` +{ + "Version": "1", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "oss:GetObject", + "oss:DeleteObject" + ], + "Resource": "acs:oss:oss-%s:%s:%s/*" + }, + { + "Effect": "Allow", + "Action": [ + "fc:InvokeFunction" + ], + "Resource": "*" + } + ] +} +`, region, accountId, bucketName)) + _, err := ramClient.CreatePolicy(createPolicyRequest) + if err != nil { + logger.Println.Error("创建 RAM 策略时报错,详细信息如下:") + logger.Println.Error(err.Error()) + logger.Println.Info(fmt.Sprintf(` +已经创建的资源如下: +角色:%v +`, roleName)) + } else { + // 3. 为 RAM 角色授予权限 + logger.Println.Info(fmt.Sprintf("正在将 %v 策略绑定到 %v 角色。", policyName, roleName)) + attachPolicyToRoleRequest := &ram.AttachPolicyToRoleRequest{} + attachPolicyToRoleRequest.PolicyName = tea.String(policyName) + attachPolicyToRoleRequest.RoleName = tea.String(roleName) + attachPolicyToRoleRequest.PolicyType = tea.String("Custom") + + _, err = ramClient.AttachPolicyToRole(attachPolicyToRoleRequest) + if err != nil { + logger.Println.Error(fmt.Sprintf("为 %v 角色绑定 %v 策略时报错,详细信息如下:", roleName, policyName)) + logger.Println.Error(err.Error()) + logger.Println.Info(fmt.Sprintf(` +已经创建的资源如下: +角色:%v +策略:%v +`, roleName, policyName)) + } else { + // 4. 创建云函数 + fcClient, err := FCClient(accountId, region) + if err == nil { + functionName := utils.GenerateRandomName("Function") + logger.Println.Info(fmt.Sprintf("正在创建 %v 函数。", functionName)) + createFunctionRequest := &fc.CreateFunctionRequest{} + createFunctionRequest.Body = &fc.CreateFunctionInput{} + createFunctionRequest.Body.FunctionName = tea.String(functionName) + createFunctionRequest.Body.Runtime = tea.String("python3.10") + createFunctionRequest.Body.Handler = tea.String("index.handler") + createFunctionRequest.Body.Cpu = tea.Float32(0.35) + createFunctionRequest.Body.MemorySize = tea.Int32(512) + createFunctionRequest.Body.DiskSize = tea.Int32(512) + createFunctionRequest.Body.Role = tea.String(roleArn) + createFunctionRequest.Body.Timeout = tea.Int32(60) + createFunctionRequest.Body.Description = tea.String( + "此函数由云鉴 1203_oss_bucket_only_upload_images 模块自动创建,此函数用于识别上传到存储桶里的对象是否是图片,如果不是则删除。") + createFunctionRequest.Body.Code = &fc.InputCodeLocation{} + createFunctionRequest.Body.Code.ZipFile = tea.String("UEsDBBQACAAIANS6hVkAAAAAAAAAAP0JAAAIACAAaW5kZXgucHlVVA0AB0HFUWcJz1NnB89TZ3V4CwABBPUBAAAEFAAAAI1VTU/bQBC951e46mETERygNyQOLfde2lsURcaeJAbHtrwbPoSQQgUFtZRyghaqFgqlSBWJ2iI+RFT+S4uDOfEXut61w8axgSiSdz0zb968nVnrVdtyiGThlM5X49gyw7WF8VC41qvliuakUikNSlJFMTUDnDRMgkmykmqZBKZJZjgl0d9jyVs9cd+vX54etTeOvcbF9UbDXTr0mvPu2bF3seXtrHiNPbpl3qoDGpZGQgzZ31NQXTEwsys1UqFmn4r8guCndJtmMbKiqoBxcQJmirqWlXpeYqCvSGigu5qjk5kisSbAzKQiVN9ctrbb60uX58dXmwtXW6fu4u/Li532fJP5wSQpGphQHr48smEpGubFZ0I7tQVeecQsGBXyAwVmHqupE0CKplIF7pZHtB5qR9zir3wj4u7W2DioxK8h4s0N/oraAmcwNdvSTT+/79aPpD4e40BZt0xUoHvUTx3AMRVDVgx9pmaqWFatKhLIhRI/Y7u0r3q2g50VK7hbOndt9apVv/p57n5+y/xqtq9WsaQbUMS1UkmfLpIZG1g+2VZIRca2oftHn76tO5MfLMgO2IaiQhrJKCshdE/e14tu40zIWwGa1QFcM/zaOH+ZveRpxGw9RFkzmiRkKmAxCHCwXKYqoVHu1/+S+t1DkC/cvaMEbYKUt1xpghiqVBVFS/cy5rRCwnxY5amKQtLPLROyUmUkJll++MlQIaBtO/Sg0yWU7ytInCsvwDtpun8WblqbtD2Gc7lZoRPmcrO3zOZQJgGnUzMFmU3gfEe00FBRAKGfkgEk8ZCiCOJBz92eoLs47zVOuQCds+Mc3OUNb+eAX02GYU2BxoL9KyyPxu2y36vjNrCnbbJHWS8FwxpGiGl5pF5VypALA/kuCOcbDhLwa+/W21++ibTaH5ru2r67tux9X2BOo1aVzpXCOmpAkIYrc7jrfjrgKJEqRSDpb32f/gNp9VJix5kW/UCY3YLwj4GYuL+nI0IG767rm97F0r/6qyAXGBh6AXpbigNEo2PYilfP3WzjGQtdmEi6l3g8+R6sOKCYGrpupUgRXR3VTSC2oHAqJH8qpDtLii8rtrQ41CTIaJMOdoZvefv64x4lEzuFYSt2QqNt0jX7911aCRzj2q8kqvdA3I6iN60VPnO8OK8ZDG+3LMHNr4EBBJK+U12MHj2ckXvyy/v6g+fnaVP/AVBLBwhBqwo4hAMAAP0JAABQSwECFAMUAAgACADUuoVZQasKOIQDAAD9CQAACAAgAAAAAAAAAAAApIEAAAAAaW5kZXgucHlVVA0AB0HFUWcJz1NnB89TZ3V4CwABBPUBAAAEFAAAAFBLBQYAAAAAAQABAFYAAADaAwAAAAA=") + createFunctionResponse, err := fcClient.CreateFunction(createFunctionRequest) + if err != nil { + logger.Println.Error("创建函数时报错,详细信息如下:") + logger.Println.Error(err.Error()) + logger.Println.Info(fmt.Sprintf(` +已经创建的资源如下: +角色:%v +策略:%v +`, roleName, policyName)) + } else { + // 5. 创建触发器 + triggerName := utils.GenerateRandomName("Trigger") + logger.Println.Info(fmt.Sprintf("正在创建 %v 触发器。", triggerName)) + createTriggerRequest := &fc.CreateTriggerRequest{} + createTriggerRequest.Body = &fc.CreateTriggerInput{} + createTriggerRequest.Body.Description = tea.String( + "此触发器由云鉴 1203_oss_bucket_only_upload_images 模块自动创建,此触发器用于识别当前是否存在存储桶文件上传动作。") + createTriggerRequest.Body.TriggerType = tea.String("oss") + createTriggerRequest.Body.TriggerName = tea.String(triggerName) + createTriggerRequest.Body.TriggerConfig = tea.String(`{"events":["oss:ObjectCreated:PutObject", +"oss:ObjectCreated:PostObject","oss:ObjectCreated:CompleteMultipartUpload","oss:ObjectCreated:PutSymlink"], +"filter":{"key":{"prefix":"","suffix":""}}}`) + createTriggerRequest.Body.InvocationRole = tea.String(roleArn) + createTriggerRequest.Body.SourceArn = tea.String(fmt.Sprintf("acs:oss:%s:%s:%s", region, accountId, bucketName)) + _, err := fcClient.CreateTrigger(createFunctionResponse.Body.FunctionName, createTriggerRequest) + if err != nil { + if strings.Contains(err.Error(), "Cannot specify overlapping prefix and suffix with same event type.") { + logger.Println.Error(fmt.Sprintf("在 %v 存储桶下已经存在相同前缀和后缀的触发器,此触发器无法创建。", bucketName)) + logger.Println.Info(fmt.Sprintf(` +已经创建的资源如下: +角色:%v +策略:%v +函数:%v +`, roleName, policyName, functionName)) + } else { + logger.Println.Error("创建触发器时报错,详细信息如下:") + logger.Println.Error(err.Error()) + logger.Println.Info(fmt.Sprintf(` +已经创建的资源如下: +角色:%v +策略:%v +函数:%v +`, roleName, policyName, functionName)) + } + } else { + logger.Println.Warn(fmt.Sprintf("限制存储桶只允许上传图片模块配置完成,现在向 %v 存储桶上传 jpg、jpeg、png、gif 之外格式的文件都会被自动删除。", bucketName)) + logger.Println.Warn(fmt.Sprintf(`已经创建的资源如下,如果想取消使用这个模块,请手动删除以下资源: +角色:%v +策略:%v +函数:%v +触发器:%v +`, roleName, policyName, functionName, triggerName)) + } + } + } + } + } + } + } + } +} diff --git a/pkg/aliyun/1301_ecs_list_instances.go b/pkg/aliyun/1301_ecs_list_instances.go new file mode 100644 index 0000000..0aa61c3 --- /dev/null +++ b/pkg/aliyun/1301_ecs_list_instances.go @@ -0,0 +1,180 @@ +package aliyun + +import ( + "encoding/json" + "fmt" + "github.com/aliyun/alibaba-cloud-sdk-go/services/ecs" + "github.com/wgpsec/cloudsword/utils" + "github.com/wgpsec/cloudsword/utils/global" + "github.com/wgpsec/cloudsword/utils/logger" + "strings" + "sync" +) + +var ecsInstances []ecs.Instance + +// ecs_list_instances + +func ECSListInstances() { + regions, err := describeRegions() + if err == nil { + ecsInstances = nil + var ( + wg sync.WaitGroup + threads = 3 + ) + // 获取 ECS 资源 + taskCh := make(chan string, threads) + for i := 0; i < threads; i++ { + wg.Add(1) + go func() { + err := describeEcsInstances(taskCh, &wg) + if err != nil { + return + } + }() + } + for _, item := range regions { + taskCh <- item + } + close(taskCh) + wg.Wait() + + // 输出 ECS 资源 + if len(ecsInstances) == 0 { + logger.Println.Warn("未找到 ECS 弹性计算实例,出现这种情况一般是由于没有实例或没有权限。\n") + } else { + logger.Println.Info("找到以下 ECS 弹性计算实例:") + for n, i := range ecsInstances { + fmt.Printf("\n=================== %d %v ===================\n", n+1, i.InstanceId) + // 提取 ECS 的 IP 地址 + var ( + PublicIpAddress string + PublicIpAddressList []string + PrivateIpAddress string + ) + if len(i.PublicIpAddress.IpAddress) > 0 { + PublicIpAddressList = append(PublicIpAddressList, i.PublicIpAddress.IpAddress...) + } + if len(i.EipAddress.IpAddress) > 0 { + PublicIpAddressList = append(PublicIpAddressList, i.EipAddress.IpAddress) + } + if len(PublicIpAddressList) > 0 { + PublicIpAddress = strings.Join(PublicIpAddressList, ",") + } + + if len(i.NetworkInterfaces.NetworkInterface[0].PrivateIpSets.PrivateIpSet) > 0 { + PrivateIpAddress = i.NetworkInterfaces.NetworkInterface[0].PrivateIpSets.PrivateIpSet[0].PrivateIpAddress + } + + if global.GetBasicOptionValue(global.Detail) == global.False { + // 输出 ECS 基础信息 + fmt.Printf("\n实例 ID:%v\n", i.InstanceId) + fmt.Printf("实例名称:%v\n", i.InstanceName) + fmt.Printf("实例描述:%v\n", i.Description) + fmt.Printf("实例区域:%v\n", i.RegionId) + fmt.Printf("实例状态:%v\n", i.Status) + fmt.Printf("操作系统类型:%v\n", i.OSType) + fmt.Printf("私有 IP 地址:%v\n", PrivateIpAddress) + fmt.Printf("公有 IP 地址:%v\n", PublicIpAddress) + } else { + // 输出 ECS 详细信息 + fmt.Printf("\n基础信息\n========\n") + fmt.Printf("实例 ID:%v\n", i.InstanceId) + fmt.Printf("实例名称:%v\n", i.InstanceName) + fmt.Printf("实例描述:%v\n", i.Description) + fmt.Printf("实例区域:%v\n", i.RegionId) + fmt.Printf("元数据 HttpTokens: %v\n", i.MetadataOptions.HttpTokens) + fmt.Printf("元数据 HttpEndpoint: %v\n", i.MetadataOptions.HttpEndpoint) + + ramRole, err := describeInstanceRamRole(i.RegionId, i.InstanceId) + if err == nil { + fmt.Printf("实例角色:%v\n", ramRole) + } + + userData, err := describeUserData(i.RegionId, i.InstanceId) + if err == nil { + fmt.Printf("实例用户数据:%v\n", userData) + } + + cloudAssistantStatus, err := describeCloudAssistantStatus(i.RegionId, i.InstanceId, i.OSType) + if err == nil { + fmt.Printf("实例云助手状态:%v\n", cloudAssistantStatus) + } + + fmt.Printf("\n操作系统信息\n============\n") + fmt.Printf("虚拟 CPU 数量:%v\n", i.Cpu) + fmt.Printf("内存大小(单位:MiB):%v\n", i.Memory) + fmt.Printf("实例规格族:%v\n", i.InstanceTypeFamily) + fmt.Printf("实例规格:%v\n", i.InstanceType) + fmt.Printf("操作系统类型:%v\n", i.OSType) + fmt.Printf("操作系统名称:%v\n", i.OSName) + fmt.Printf("操作系统镜像 ID:%v\n", i.ImageId) + + fmt.Printf("\n网络信息\n========\n") + fmt.Printf("网络类型:%v\n", i.InstanceNetworkType) + fmt.Printf("VPC ID:%v\n", i.VpcAttributes.VpcId) + fmt.Printf("安全组:%v\n", strings.Join(i.SecurityGroupIds.SecurityGroupId, ",")) + fmt.Printf("私有 IP 地址:%v\n", PrivateIpAddress) + fmt.Printf("公有 IP 地址:%v\n", PublicIpAddress) + fmt.Printf("公网入带宽最大值(单位:Mbit/s):%v\n", i.InternetMaxBandwidthIn) + fmt.Printf("公网出带宽最大值(单位:Mbit/s):%v\n", i.InternetMaxBandwidthOut) + + fmt.Printf("\n其他信息========\n") + fmt.Printf("删除保护:%v\n", i.DeletionProtection) + jsonBytes, err := json.Marshal(i.Tags.Tag) + logger.Println.Error(err.Error()) + fmt.Printf("实例标签:%v\n", jsonBytes) + fmt.Printf("计费类型:%v\n", i.InstanceChargeType) + fmt.Printf("主机名称:%v\n", i.Hostname) + fmt.Printf("资源组 ID:%v\n", i.ResourceGroupId) + fmt.Printf("实例所属可用区:%v\n", i.ZoneId) + fmt.Printf("实例序列号:%v\n", i.SerialNumber) + + creationTime, err := utils.GetUTC8TimeType1(i.CreationTime) + if err == nil { + fmt.Printf("创建时间:%v\n", creationTime) + } + + expiredTime, err := utils.GetUTC8TimeType1(i.ExpiredTime) + if err == nil { + fmt.Printf("过期时间:%v\n", expiredTime) + } + + fmt.Println(strings.Repeat("=", 60)) + } + } + fmt.Println() + } + } +} + +func describeEcsInstances(ch <-chan string, wg *sync.WaitGroup) error { + defer wg.Done() + var ( + err error + response *ecs.DescribeInstancesResponse + ) + for region := range ch { + logger.Println.Info(fmt.Sprintf("正在获取 %s 区域下的阿里云 ECS 资源信息", region)) + request := ecs.CreateDescribeInstancesRequest() + for { + client, err := ECSClient(region) + if err == nil { + response, err = client.DescribeInstances(request) + if err != nil { + break + } + if len(response.Instances.Instance) > 0 { + logger.Println.Warn(fmt.Sprintf("在 %s 区域下获取到 %d 条 ECS 资源", region, len(response.Instances.Instance))) + ecsInstances = append(ecsInstances, response.Instances.Instance...) + } + if response.NextToken == "" { + break + } + request.NextToken = response.NextToken + } + } + } + return err +} diff --git a/pkg/aliyun/1401_ram_list_users.go b/pkg/aliyun/1401_ram_list_users.go new file mode 100644 index 0000000..95168a0 --- /dev/null +++ b/pkg/aliyun/1401_ram_list_users.go @@ -0,0 +1,62 @@ +package aliyun + +import ( + "fmt" + ram "github.com/alibabacloud-go/ram-20150501/v2/client" + util "github.com/alibabacloud-go/tea-utils/v2/service" + "github.com/alibabacloud-go/tea/tea" + "github.com/wgpsec/cloudsword/utils" + "github.com/wgpsec/cloudsword/utils/global" + "github.com/wgpsec/cloudsword/utils/logger" +) + +// ram_list_users + +func RAMListUsers() { + var users []*ram.ListUsersResponseBodyUsersUser + listUsersRequest := &ram.ListUsersRequest{} + listUsersRequest.MaxItems = tea.Int32(1000) + runtime := &util.RuntimeOptions{} + + for { + client, err := RAMClient() + if err == nil { + response, err := client.ListUsersWithOptions(listUsersRequest, runtime) + if err != nil { + logger.Println.Error("列出 RAM 用户时报错,详细信息如下:") + logger.Println.Error(err.Error()) + break + } else { + users = append(users, response.Body.Users.User...) + if *response.Body.IsTruncated { + listUsersRequest.Marker = response.Body.Marker + } else { + break + } + } + } + } + if len(users) == 0 { + logger.Println.Info("未找到用户。\n") + } else { + logger.Println.Info("找到以下用户:") + for _, user := range users { + if global.GetBasicOptionValue(global.Detail) == global.False { + fmt.Println(*user.UserName) + } else { + fmt.Printf("\n用户登录名:%v\n", *user.UserName) + fmt.Printf("用户显示名称:%v\n", *user.DisplayName) + fmt.Printf("用户 ID:%v\n", *user.UserId) + fmt.Printf("邮箱:%v\n", utils.ConvertedNullPointer(user.Email)) + fmt.Printf("手机号:%v\n", utils.ConvertedNullPointer(user.MobilePhone)) + fmt.Printf("备注:%v\n", utils.ConvertedNullPointer(user.Comments)) + + createDate, err := utils.GetUTC8TimeType2(*user.CreateDate) + if err == nil { + fmt.Printf("创建时间:%v\n", createDate) + } + } + } + fmt.Println() + } +} diff --git a/pkg/aliyun/1402_ram_list_roles.go b/pkg/aliyun/1402_ram_list_roles.go new file mode 100644 index 0000000..893bf5e --- /dev/null +++ b/pkg/aliyun/1402_ram_list_roles.go @@ -0,0 +1,66 @@ +package aliyun + +import ( + "fmt" + ram "github.com/alibabacloud-go/ram-20150501/v2/client" + util "github.com/alibabacloud-go/tea-utils/v2/service" + "github.com/alibabacloud-go/tea/tea" + "github.com/wgpsec/cloudsword/utils" + "github.com/wgpsec/cloudsword/utils/global" + "github.com/wgpsec/cloudsword/utils/logger" +) + +// ram_list_roles + +func RAMListRoles() { + var roles []*ram.ListRolesResponseBodyRolesRole + listRolesRequest := &ram.ListRolesRequest{} + listRolesRequest.MaxItems = tea.Int32(1000) + runtime := &util.RuntimeOptions{} + + for { + client, err := RAMClient() + if err == nil { + response, err := client.ListRolesWithOptions(listRolesRequest, runtime) + if err != nil { + logger.Println.Error("列出 RAM 角色时报错,详细信息如下:") + logger.Println.Error(err.Error()) + break + } else { + roles = append(roles, response.Body.Roles.Role...) + if *response.Body.IsTruncated { + listRolesRequest.Marker = response.Body.Marker + } else { + break + } + } + } + } + if len(roles) == 0 { + logger.Println.Info("未找到角色。\n") + } else { + logger.Println.Info("找到以下角色:") + for _, role := range roles { + if global.GetBasicOptionValue(global.Detail) == global.False { + fmt.Println(*role.RoleName) + } else { + fmt.Printf("\n角色名称:%v\n", *role.RoleName) + fmt.Printf("角色 ID:%v\n", *role.RoleId) + fmt.Printf("角色资源描述符 ARN:%v\n", *role.Arn) + fmt.Printf("角色最大会话时间:%v\n", *role.MaxSessionDuration) + fmt.Printf("角色描述:%v\n", *role.Description) + + createDate, err := utils.GetUTC8TimeType2(*role.CreateDate) + if err == nil { + fmt.Printf("创建时间:%v\n", createDate) + } + + updateDate, err := utils.GetUTC8TimeType2(*role.UpdateDate) + if err == nil { + fmt.Printf("更新时间:%v\n", updateDate) + } + } + } + fmt.Println() + } +} diff --git a/pkg/aliyun/1403_ram_create_user.go b/pkg/aliyun/1403_ram_create_user.go new file mode 100644 index 0000000..fa647e1 --- /dev/null +++ b/pkg/aliyun/1403_ram_create_user.go @@ -0,0 +1,28 @@ +package aliyun + +import ( + "fmt" + ram "github.com/alibabacloud-go/ram-20150501/v2/client" + "github.com/alibabacloud-go/tea/tea" + "github.com/wgpsec/cloudsword/utils/global" + "github.com/wgpsec/cloudsword/utils/logger" +) + +// ram_create_user + +func RAMCreateUser() { + client, err := RAMClient() + if err == nil { + userName := global.GetBasicOptionValue(global.UserName) + createUserRequest := &ram.CreateUserRequest{} + createUserRequest.UserName = tea.String(userName) + createUserRequest.Comments = tea.String(global.GetBasicOptionValue(global.Description)) + _, err := client.CreateUser(createUserRequest) + if err != nil { + logger.Println.Error(fmt.Sprintf("创建 %v 用户时报错,详细信息如下:", userName)) + logger.Println.Error(err.Error()) + } else { + logger.Println.Info(fmt.Sprintf("%v 用户创建成功。", userName)) + } + } +} diff --git a/pkg/aliyun/1404_ram_attach_policy_to_user.go b/pkg/aliyun/1404_ram_attach_policy_to_user.go new file mode 100644 index 0000000..43e5bb9 --- /dev/null +++ b/pkg/aliyun/1404_ram_attach_policy_to_user.go @@ -0,0 +1,30 @@ +package aliyun + +import ( + "fmt" + ram "github.com/alibabacloud-go/ram-20150501/v2/client" + "github.com/alibabacloud-go/tea/tea" + "github.com/wgpsec/cloudsword/utils/global" + "github.com/wgpsec/cloudsword/utils/logger" +) + +// ram_attach_policy_to_user + +func RAMAttachPolicyToUser() { + client, err := RAMClient() + if err == nil { + userName := global.GetBasicOptionValue(global.UserName) + policyName := global.GetBasicOptionValue(global.PolicyName) + attachPolicyToUserRequest := &ram.AttachPolicyToUserRequest{} + attachPolicyToUserRequest.UserName = tea.String(userName) + attachPolicyToUserRequest.PolicyName = tea.String(policyName) + attachPolicyToUserRequest.PolicyType = tea.String("System") + _, err = client.AttachPolicyToUser(attachPolicyToUserRequest) + if err != nil { + logger.Println.Error(fmt.Sprintf("为 %v 用户添加 %v 策略时报错,详细信息如下:", userName, policyName)) + logger.Println.Error(err.Error()) + } else { + logger.Println.Info(fmt.Sprintf("为 %v 用户添加 %v 策略成功。", userName, policyName)) + } + } +} diff --git a/pkg/aliyun/1405_ram_create_login_profile.go b/pkg/aliyun/1405_ram_create_login_profile.go new file mode 100644 index 0000000..162c9ad --- /dev/null +++ b/pkg/aliyun/1405_ram_create_login_profile.go @@ -0,0 +1,34 @@ +package aliyun + +import ( + "fmt" + ram "github.com/alibabacloud-go/ram-20150501/v2/client" + "github.com/alibabacloud-go/tea/tea" + "github.com/wgpsec/cloudsword/utils" + "github.com/wgpsec/cloudsword/utils/global" + "github.com/wgpsec/cloudsword/utils/logger" +) + +// ram_create_login_profile + +func RAMCreateLoginProfile() { + client, err := RAMClient() + if err == nil { + userName := global.GetBasicOptionValue(global.UserName) + password := utils.GenerateRandomPasswords() + createLoginProfileRequest := &ram.CreateLoginProfileRequest{} + createLoginProfileRequest.UserName = tea.String(userName) + createLoginProfileRequest.Password = tea.String(password) + _, err = client.CreateLoginProfile(createLoginProfileRequest) + if err != nil { + logger.Println.Error(fmt.Sprintf("为 %v 用户创建 Web 控制台登录配置时报错,详细信息如下:", userName)) + logger.Println.Error(err.Error()) + } else { + alias, err := RAMGetAccountAlias() + if err == nil { + logger.Println.Info(fmt.Sprintf("为 %v 用户创建 Web 控制台登录配置成功,登录信息如下:", userName)) + fmt.Printf("用户名:%s@%s\n密码:%s\n登录地址:https://signin.aliyun.com\n", userName, alias, password) + } + } + } +} diff --git a/pkg/aliyun/1406_ram_create_access_key.go b/pkg/aliyun/1406_ram_create_access_key.go new file mode 100644 index 0000000..f20ba5a --- /dev/null +++ b/pkg/aliyun/1406_ram_create_access_key.go @@ -0,0 +1,29 @@ +package aliyun + +import ( + "fmt" + ram "github.com/alibabacloud-go/ram-20150501/v2/client" + "github.com/alibabacloud-go/tea/tea" + "github.com/wgpsec/cloudsword/utils/global" + "github.com/wgpsec/cloudsword/utils/logger" +) + +// ram_create_access_key + +func RAMCreateAccessKey() { + client, err := RAMClient() + if err == nil { + userName := global.GetBasicOptionValue(global.UserName) + createAccessKeyRequest := &ram.CreateAccessKeyRequest{} + createAccessKeyRequest.UserName = tea.String(userName) + createAccessKeyResponse, err := client.CreateAccessKey(createAccessKeyRequest) + if err != nil { + logger.Println.Error(fmt.Sprintf("为 %v 用户创建访问凭证时报错,详细信息如下:", userName)) + logger.Println.Error(err.Error()) + } else { + logger.Println.Info(fmt.Sprintf("为 %v 用户创建访问凭证如下:", userName)) + fmt.Printf("AccessKeyId: %v\nAccessKeySecret: %v\n", *createAccessKeyResponse.Body.AccessKey.AccessKeyId, + *createAccessKeyResponse.Body.AccessKey.AccessKeySecret) + } + } +} diff --git a/pkg/aliyun/1501_domain_list_domains.go b/pkg/aliyun/1501_domain_list_domains.go new file mode 100644 index 0000000..02f5575 --- /dev/null +++ b/pkg/aliyun/1501_domain_list_domains.go @@ -0,0 +1,100 @@ +package aliyun + +import ( + "encoding/json" + "fmt" + domain "github.com/alibabacloud-go/domain-20180129/v4/client" + util "github.com/alibabacloud-go/tea-utils/v2/service" + "github.com/alibabacloud-go/tea/tea" + "github.com/wgpsec/cloudsword/utils" + "github.com/wgpsec/cloudsword/utils/global" + "github.com/wgpsec/cloudsword/utils/logger" +) + +// domain_list_domains + +func DomainListDomains() { + var ( + page int32 = 1 + domains []*domain.QueryDomainListResponseBodyDataDomain + ) + queryDomainListRequest := &domain.QueryDomainListRequest{} + queryDomainListRequest.PageNum = tea.Int32(page) + queryDomainListRequest.PageSize = tea.Int32(10) + runtime := &util.RuntimeOptions{} + for { + client, err := DomainClient() + if err == nil { + response, err := client.QueryDomainListWithOptions(queryDomainListRequest, runtime) + if err != nil { + logger.Println.Error("列出域名时报错,详细信息如下:") + logger.Println.Error(err.Error()) + break + } else { + domains = append(domains, response.Body.Data.Domain...) + if *response.Body.NextPage { + page = page + 1 + queryDomainListRequest.PageNum = tea.Int32(page) + } else { + break + } + } + } + } + + if len(domains) == 0 { + logger.Println.Info("未找到域名。\n") + } else { + logger.Println.Info("找到以下域名:") + for _, domain_ := range domains { + if global.GetBasicOptionValue(global.Detail) == global.False { + fmt.Println(*domain_.DomainName) + } else { + utils.PrintfNotNilString("\n域名:", domain_.DomainName) + utils.PrintfNotNilString("公司名称:", domain_.Ccompany) + fmt.Printf("状态:%v\n", domainStatus(*domain_.DomainStatus)) + fmt.Printf("是否过期:%v\n", expirationDateStatus(*domain_.ExpirationDateStatus)) + utils.PrintfNotNilString("实名认证状态:", domain_.DomainAuditStatus) + utils.PrintfNotNilString("分组编号:", domain_.DomainGroupId) + utils.PrintfNotNilString("分组名称:", domain_.DomainGroupId) + utils.PrintfNotNilString("资源组 ID:", domain_.ResourceGroupId) + + jsonBytes, err := json.Marshal(&domain_.Tag.Tag) + if err != nil { + logger.Println.Error("解析域名标签信息时报错,详细信息如下:") + logger.Println.Error(err.Error()) + } else { + fmt.Printf("标签:%s\n", jsonBytes) + } + + utils.PrintfNotNilString("备注:", domain_.Remark) + utils.PrintfNotNilString("注册时间:", domain_.RegistrationDate) + utils.PrintfNotNilString("到期时间:", domain_.ExpirationDate) + utils.PrintfNotNilInt32("距离到期天数:", domain_.ExpirationCurrDateDiff) + } + } + fmt.Println() + } +} + +func expirationDateStatus(s string) string { + var status string + if s == "1" { + status = "未过期" + } else if s == "2" { + status = "已过期" + } + return status +} + +func domainStatus(s string) string { + var status string + if s == "1" { + status = "急需续费" + } else if s == "2" { + status = "急需赎回" + } else if s == "3" { + status = "正常" + } + return status +} diff --git a/pkg/aliyun/aliyun.go b/pkg/aliyun/aliyun.go new file mode 100644 index 0000000..1444bb0 --- /dev/null +++ b/pkg/aliyun/aliyun.go @@ -0,0 +1,274 @@ +package aliyun + +import ( + "github.com/wgpsec/cloudsword/utils" + "github.com/wgpsec/cloudsword/utils/global" +) + +func Modules() []global.Module { + var modules []global.Module + modules = append(modules, modules1()...) + modules = append(modules, modules2()...) + modules = append(modules, modules3()...) + modules = append(modules, modules4()...) + modules = append(modules, modules5()...) + return modules +} + +// 综合性模块 + +func modules1() []global.Module { + return []global.Module{ + utils.GetModule( + 1101, + 4, + global.Aliyun, + "list_cloud_assets", + global.TeamsSix, + "列出 OSS、ECS、RAM、Domain 服务资产", + ` +简洁输出所需权限: +1. oss:ListBuckets +2. ecs:DescribeRegions +3. ecs:DescribeInstances +4. ram:ListUsers +5. ram:ListRoles +6. domain:QueryDomainList + +详细输出所需权限: +1. oss:ListBuckets +2. oss:GetBucketStat +3. oss:GetBucketTags +4. ecs:DescribeRegions +5. ecs:DescribeInstances +6. ecs:DescribeCloudAssistantStatus +7. ecs:DescribeUserData +8. ram:ListUsers +9. ram:ListRoles +10. domain:QueryDomainList +`, + append(global.BasicOptionsDefault, global.BasicOptionDetail, global.BasicOptionRegion), + ), + } +} + +// OSS 模块 + +func modules2() []global.Module { + return []global.Module{ + utils.GetModule( + 1201, + 2, + global.Aliyun, + "oss_list_buckets", + global.TeamsSix, + "列出阿里云 OSS 对象存储桶", + ` +简洁输出模式所需权限: +1. oss:ListBuckets + +详细输出模式所需权限: +1. oss:ListBuckets +2. oss:GetBucketStat +3. oss:GetBucketTags +`, + append(global.BasicOptionsDefault, global.BasicOptionDetail), + ), + + utils.GetModule( + 1202, + 4, + global.Aliyun, + "oss_search_objects", + global.TeamsSix, + "搜索阿里云 OSS 对象", + ` +所需权限: +1. oss:GetBucketLocation +2. oss:GetMetaQueryStatus +3. oss:OpenMetaQuery +4. oss:DoMetaQuery +`, + append( + global.BasicOptionsDefault, + global.BasicOptionDetail, + global.BasicOptionQueryValue, + global.BasicOptionMaxResults, + global.BasicOptionBucketName, + ), + ), + + utils.GetModule( + 1203, + 3, + global.Aliyun, + "oss_bucket_only_upload_images", + global.TeamsSix, + "使用云函数限制存储桶只允许上传图片", + ` +所需权限: +1. ram:CreateRole +2. ram:CreatePolicy +3. ram:AttachPolicyToRole +4. fc:CreateFunction +5. fc:CreateTrigger + +使用说明: +1. 使用该功能前请仔细阅读《使用云函数限制存储桶上传类型》这篇文章,文章地址:https://wiki.teamssix.com/cloudservice/s3/limiting-bucket-upload-types-using-cloud-functions.html +2. 这个功能的运行逻辑是文件会先被上传到存储桶中,然后云函数判断刚刚上传的文件是否是预期类型,如果不是就会将这个文件删除。 +3. 这个功能设置后,所指定的存储桶只能上传 jpg、jpeg、png、gif 的文件,如果上传文件的文件头类型、文件后缀类型、Content Type 三个有任何一个不符合预期的都会被直接删除。 +4. 这个功能没办法解决在上传同名称文件时被覆盖的问题。 +`, + append( + global.BasicOptionsDefault, + global.BasicOptionBucketName, + ), + ), + } +} + +// ECS 模块 + +func modules3() []global.Module { + return []global.Module{ + utils.GetModule( + 1301, + 2, + global.Aliyun, + "ecs_list_instances", + global.TeamsSix, + "列出阿里云 ECS 弹性计算实例", + ` +简洁输出模式所需权限: +1. ecs:DescribeRegions +2. ecs:DescribeInstances + +详细输出模式所需权限: +1. ecs:DescribeRegions +2. ecs:DescribeInstances +3. ecs:DescribeCloudAssistantStatus +4. ecs:DescribeUserData +`, + append(global.BasicOptionsDefault, global.BasicOptionDetail, global.BasicOptionRegion), + ), + } +} + +// RAM 模块 + +func modules4() []global.Module { + return []global.Module{ + + utils.GetModule( + 1401, + 2, + global.Aliyun, + "ram_list_users", + global.TeamsSix, + "列出阿里云 RAM 用户", + ` +所需权限: +1. ram:ListUsers +`, + append(global.BasicOptionsDefault, global.BasicOptionDetail), + ), + utils.GetModule( + 1402, + 1, + global.Aliyun, + "ram_list_roles", + global.TeamsSix, + "列出阿里云 RAM 角色", + ` +所需权限: +1. ram:ListRoles +`, + append(global.BasicOptionsDefault, global.BasicOptionDetail), + ), + utils.GetModule( + 1403, + 1, + global.Aliyun, + "ram_create_user", + global.TeamsSix, + "创建阿里云 RAM 用户", + ` +所需权限: +1. ram:CreateUser +`, + append(global.BasicOptionsDefault, global.BasicOptionUserName, global.BasicOptionDescription), + ), + utils.GetModule( + 1404, + 1, + global.Aliyun, + "ram_attach_policy_to_user", + global.TeamsSix, + "为阿里云 RAM 用户添加策略", + ` +所需权限: +1. ram:AttachPolicyToUser + +使用频率较高的策略名称(仅供参考): +1. AdministratorAccess +2. ReadOnlyAccess +3. AliyunRAMFullAccess +4. AliyunOSSFullAccess +5. AliyunECSFullAccess + +注意事项: +1. 为用户添加 AdministratorAccess 或 AliyunRAMFullAccess 策略是一件高危操作,请根据实际情况酌量进行操作,此操作存在引发告警的风险。 +2. 此功能仅支持添加系统策略,自定义策略建议使用控制台或 aliyun cli 等工具进行添加,完整的权限策略清单可参考:https://ram.console.aliyun.com/policies +`, + append(global.BasicOptionsDefault, global.BasicOptionUserName, global.BasicOptionPolicyName), + ), + utils.GetModule( + 1405, + 3, + global.Aliyun, + "ram_create_login_profile", + global.TeamsSix, + "创建阿里云 RAM 用户 Web 登录配置", + ` +所需权限: +1. ram:CreateLoginProfile +2. ram:GetAccountAlias +`, + append(global.BasicOptionsDefault, global.BasicOptionUserName), + ), + utils.GetModule( + 1406, + 1, + global.Aliyun, + "ram_create_access_key", + global.TeamsSix, + "创建阿里云 RAM 用户访问凭证", + ` +所需权限: +1. ram:CreateAccessKey +`, + append(global.BasicOptionsDefault, global.BasicOptionUserName), + ), + } +} + +// 综合性模块 + +func modules5() []global.Module { + return []global.Module{ + // 其他模块 + utils.GetModule( + 1501, + 1, + global.Aliyun, + "domain_list_domains", + global.TeamsSix, + "列出阿里云 Domains 域名资产", + ` +所需权限: +1. domain:QueryDomainList +`, + append(global.BasicOptionsDefault, global.BasicOptionDetail), + ), + } +} diff --git a/pkg/aliyun/client.go b/pkg/aliyun/client.go new file mode 100644 index 0000000..f3f404e --- /dev/null +++ b/pkg/aliyun/client.go @@ -0,0 +1,141 @@ +package aliyun + +import ( + "fmt" + openapi "github.com/alibabacloud-go/darabonba-openapi/v2/client" + domain "github.com/alibabacloud-go/domain-20180129/v4/client" + fc "github.com/alibabacloud-go/fc-20230330/v4/client" + ram "github.com/alibabacloud-go/ram-20150501/v2/client" + "github.com/alibabacloud-go/tea/tea" + "github.com/aliyun/alibaba-cloud-sdk-go/sdk" + "github.com/aliyun/alibaba-cloud-sdk-go/sdk/auth/credentials" + "github.com/aliyun/alibaba-cloud-sdk-go/services/ecs" + "github.com/aliyun/alibabacloud-oss-go-sdk-v2/oss" + ossCredentials "github.com/aliyun/alibabacloud-oss-go-sdk-v2/oss/credentials" + "github.com/aliyun/alibabacloud-oss-go-sdk-v2/oss/transport" + "github.com/wgpsec/cloudsword/utils/global" + "github.com/wgpsec/cloudsword/utils/logger" + "net/http" +) + +// OSS + +func OSSClient(region string) *oss.Client { + var ( + //proxy string + cfg *oss.Config + customClient *http.Client + transConfig transport.Config + transports []func(*http.Transport) + ) + + cfg = oss.LoadDefaultConfig() + + cfg.WithCredentialsProvider( + ossCredentials.NewStaticCredentialsProvider( + global.GetBasicOptionValue(global.AKId), + global.GetBasicOptionValue(global.AKSecret), + global.GetBasicOptionValue(global.AKToken), + )) + cfg.WithRegion(region) + + // 自定义 HTTP 客户端 + //proxy = global.GetBasicOptionValue(global.Proxy) + //if proxy != global.NULL { + // transports = append(transports, utils.HttpTransport()) + //} + customClient = transport.NewHttpClient(&transConfig, transports...) + + // 自定义 User Agent + //customClient.Transport = &utils.UserAgentTransport{ + // Base: customClient.Transport, + // UserAgent: global.GetBasicOptionValue(global.UserAgent), + //} + + cfg.WithHttpClient(customClient) + client := oss.NewClient(cfg) + return client +} + +// ECS + +func ECSClient(region string) (*ecs.Client, error) { + var ( + ecsClient *ecs.Client + err error + ) + ecsConfig := sdk.NewConfig() + if global.GetBasicOptionValue(global.AKToken) == global.NULL { + credential := credentials.NewStsTokenCredential( + global.GetBasicOptionValue(global.AKId), + global.GetBasicOptionValue(global.AKSecret), + global.GetBasicOptionValue(global.AKToken), + ) + ecsClient, err = ecs.NewClientWithOptions(region, ecsConfig, credential) + if err != nil { + logger.Println.Error("创建 ECS 客户端时报错,详细信息如下:") + logger.Println.Error(err.Error()) + } + } else { + credential := credentials.NewAccessKeyCredential( + global.GetBasicOptionValue(global.AKId), + global.GetBasicOptionValue(global.AKSecret), + ) + ecsClient, err = ecs.NewClientWithOptions(region, ecsConfig, credential) + logger.Println.Error("创建 ECS 客户端时报错,详细信息如下:") + logger.Println.Error(err.Error()) + } + return ecsClient, err +} + +// RAM + +func RAMClient() (*ram.Client, error) { + credential := &openapi.Config{ + AccessKeyId: tea.String(global.GetBasicOptionValue(global.AKId)), + AccessKeySecret: tea.String(global.GetBasicOptionValue(global.AKSecret)), + SecurityToken: tea.String(global.GetBasicOptionValue(global.AKToken))} + ramClient, err := ram.NewClient(credential) + if err != nil { + logger.Println.Error("创建 RAM 客户端时报错,详细信息如下:") + logger.Println.Error(err.Error()) + return nil, err + } else { + return ramClient, err + } +} + +// Domains + +func DomainClient() (*domain.Client, error) { + credential := &openapi.Config{ + AccessKeyId: tea.String(global.GetBasicOptionValue(global.AKId)), + AccessKeySecret: tea.String(global.GetBasicOptionValue(global.AKSecret)), + SecurityToken: tea.String(global.GetBasicOptionValue(global.AKToken))} + domainClient, err := domain.NewClient(credential) + if err != nil { + logger.Println.Error("创建 Domain 客户端时报错,详细信息如下:") + logger.Println.Error(err.Error()) + return nil, err + } else { + return domainClient, err + } +} + +// FC + +func FCClient(accountId, region string) (*fc.Client, error) { + credential := &openapi.Config{ + AccessKeyId: tea.String(global.GetBasicOptionValue(global.AKId)), + AccessKeySecret: tea.String(global.GetBasicOptionValue(global.AKSecret)), + SecurityToken: tea.String(global.GetBasicOptionValue(global.AKToken))} + credential.Endpoint = tea.String(fmt.Sprintf("%s.%s.fc.aliyuncs.com", accountId, region)) + fcClient, err := fc.NewClient(credential) + if err != nil { + logger.Println.Error("创建 FC 客户端时报错,详细信息如下:") + logger.Println.Error(err.Error()) + return nil, err + } else { + return fcClient, err + } +} diff --git a/pkg/aliyun/utilsECS.go b/pkg/aliyun/utilsECS.go new file mode 100644 index 0000000..363ccf9 --- /dev/null +++ b/pkg/aliyun/utilsECS.go @@ -0,0 +1,108 @@ +package aliyun + +import ( + "fmt" + "github.com/aliyun/alibaba-cloud-sdk-go/services/ecs" + "github.com/wgpsec/cloudsword/utils/global" + "github.com/wgpsec/cloudsword/utils/logger" + "strings" +) + +func describeCloudAssistantStatus(region, instanceId, OSType string) (string, error) { + request := ecs.CreateDescribeCloudAssistantStatusRequest() + request.RegionId = region + request.OSType = OSType + request.InstanceId = &[]string{instanceId} + client, err := ECSClient(region) + if err == nil { + response, err := client.DescribeCloudAssistantStatus(request) + if err != nil { + logger.Println.Error(fmt.Sprintf("获取 %v 实例云助手状态时报错,详细信息如下:", instanceId)) + logger.Println.Error(err.Error()) + return global.NULL, err + } else { + if len(response.InstanceCloudAssistantStatusSet.InstanceCloudAssistantStatus) > 0 { + cloudAssistantStatus := response.InstanceCloudAssistantStatusSet.InstanceCloudAssistantStatus[0] + if cloudAssistantStatus.CloudAssistantStatus == "true" { + return "已安装", err + } else { + return "未安装", err + } + } else { + return "未安装", err + } + } + } else { + return global.NULL, err + } +} + +func describeInstanceRamRole(region, instanceId string) (string, error) { + var roles []string + request := ecs.CreateDescribeInstanceRamRoleRequest() + request.RegionId = region + request.InstanceIds = fmt.Sprintf("[\"%s\"]", instanceId) + client, err := ECSClient(region) + if err == nil { + response, err := client.DescribeInstanceRamRole(request) + if err != nil { + logger.Println.Error(fmt.Sprintf("获取 %v ECS 实例 RAM 角色时报错,详细信息如下:", instanceId)) + logger.Println.Error(err.Error()) + return global.NULL, err + } else { + for _, role := range response.InstanceRamRoleSets.InstanceRamRoleSet { + roles = append(roles, role.RamRoleName) + } + return strings.Join(roles, ","), err + } + } else { + return global.NULL, err + } +} + +func describeUserData(region, instanceId string) (string, error) { + request := ecs.CreateDescribeUserDataRequest() + request.RegionId = region + request.InstanceId = instanceId + client, err := ECSClient(region) + if err == nil { + response, err := client.DescribeUserData(request) + if err != nil { + logger.Println.Error(fmt.Sprintf("获取 %v 实例用户数据时报错,详细信息如下:", instanceId)) + logger.Println.Error(err.Error()) + return global.NULL, err + } else { + return response.UserData, err + } + } else { + return global.NULL, err + } +} + +// 获取 ECS 区域 + +func describeRegions() ([]string, error) { + var regions []string + globalRegion := global.GetBasicOptionValue(global.Region) + if globalRegion == global.All { + client, err := ECSClient(global.AliyunDefaultRegion) + if err == nil { + ecsRegions, err := client.DescribeRegions(ecs.CreateDescribeRegionsRequest()) + if err != nil { + logger.Println.Error("获取 ECS 弹性计算实例区域时报错,详细信息如下:") + logger.Println.Error(err.Error()) + return regions, err + } else { + for _, region := range ecsRegions.Regions.Region { + regions = append(regions, region.RegionId) + } + return regions, err + } + } else { + return regions, err + } + } else { + regions = append(regions, globalRegion) + return regions, nil + } +} diff --git a/pkg/aliyun/utilsOSS.go b/pkg/aliyun/utilsOSS.go new file mode 100644 index 0000000..4988113 --- /dev/null +++ b/pkg/aliyun/utilsOSS.go @@ -0,0 +1,138 @@ +package aliyun + +import ( + "context" + "encoding/json" + "fmt" + "github.com/alibabacloud-go/tea/tea" + "github.com/aliyun/alibabacloud-oss-go-sdk-v2/oss" + "github.com/wgpsec/cloudsword/utils/global" + "github.com/wgpsec/cloudsword/utils/logger" + "strings" +) + +// 列存储桶 + +func listBuckets() []oss.BucketProperties { + var buckets []oss.BucketProperties + listBucketsRequest := &oss.ListBucketsRequest{} + for { + response, err := OSSClient(global.AliyunDefaultRegion).ListBuckets(context.TODO(), listBucketsRequest) + if err != nil { + logger.Println.Error("列出存储桶时报错,详细信息如下:") + logger.Println.Error(err.Error()) + fmt.Println() + break + } else { + if response.IsTruncated { + listBucketsRequest.Marker = response.NextMarker + } else { + buckets = append(buckets, response.Buckets...) + break + } + } + } + return buckets +} + +// 查询 Bucket 容量信息 + +func getBucketStat(bucketName, region string) (*oss.GetBucketStatResult, error) { + getBucketStatRequest := &oss.GetBucketStatRequest{} + getBucketStatRequest.Bucket = tea.String(bucketName) + response, err := OSSClient(region).GetBucketStat(context.TODO(), getBucketStatRequest) + if err != nil { + logger.Println.Error(fmt.Sprintf("获取 %v 存储桶的容量信息时报错,详细信息如下:", bucketName)) + logger.Println.Error(err.Error()) + } + return response, err +} + +// 查询 Bucket 标签信息 + +func getBucketTags(BucketName, region string) (string, error) { + getBucketTagsRequest := &oss.GetBucketTagsRequest{} + getBucketTagsRequest.Bucket = tea.String(BucketName) + response, err := OSSClient(region).GetBucketTags(context.TODO(), getBucketTagsRequest) + if err != nil { + logger.Println.Error(fmt.Sprintf("获取 %v 存储桶的标签信息时报错,详细信息如下:", BucketName)) + logger.Println.Error(err.Error()) + return global.NULL, err + } else { + jsonBytes, err := json.Marshal(response.Tagging.TagSet.Tags) + if err != nil { + logger.Println.Error(fmt.Sprintf("解析 %v 存储桶的标签信息时报错,详细信息如下:", BucketName)) + logger.Println.Error(err.Error()) + return global.NULL, err + } else { + return string(jsonBytes), err + } + } +} + +// 查询 Bucket 数据索引状态 + +func getMetaQueryStatus(bucketName, region string) (string, error) { + getMetaQueryStatusRequest := &oss.GetMetaQueryStatusRequest{} + getMetaQueryStatusRequest.Bucket = tea.String(bucketName) + response, err := OSSClient(region).GetMetaQueryStatus(context.TODO(), getMetaQueryStatusRequest) + if err == nil { + return *response.MetaQueryStatus.State, err + } else if strings.Contains(err.Error(), "MetaQueryNotExist") { + return "MetaQueryNotExist", nil + } else { + logger.Println.Error(fmt.Sprintf("获取 %v 存储桶数据索引时报错,详细信息如下:", bucketName)) + logger.Println.Error(err.Error()) + return global.NULL, err + } +} + +// 开启数据索引 + +func openMetaQuery(bucketName, region string) error { + openMetaQueryRequest := &oss.OpenMetaQueryRequest{} + openMetaQueryRequest.Bucket = tea.String(bucketName) + _, err := OSSClient(region).OpenMetaQuery(context.TODO(), openMetaQueryRequest) + if err == nil { + logger.Println.Info("数据索引已开启。") + return err + } else if strings.Contains(err.Error(), "MetaQueryAlreadyExist") { + return nil + } else { + logger.Println.Error(fmt.Sprintf("开启 %v 存储桶的数据索引时报错,详细信息如下:", bucketName)) + logger.Println.Error(err.Error()) + return err + } +} + +// 关闭数据索引 + +func closeMetaQuery(bucketName, region string) { + closeMetaQueryRequest := &oss.CloseMetaQueryRequest{} + closeMetaQueryRequest.Bucket = tea.String(bucketName) + _, err := OSSClient(region).CloseMetaQuery(context.TODO(), closeMetaQueryRequest) + if err == nil { + logger.Println.Info("数据索引已关闭。") + } else { + logger.Println.Error(fmt.Sprintf("关闭 %v 存储桶的数据索引时报错,详细信息如下:", bucketName)) + logger.Println.Error(err.Error()) + } +} + +// 查询存储桶区域 + +func getBucketRegion(bucketName string) (string, error) { + getBucketLocationRequest := &oss.GetBucketLocationRequest{} + getBucketLocationRequest.Bucket = tea.String(bucketName) + response, err := OSSClient(global.AliyunDefaultRegion).GetBucketLocation(context.TODO(), getBucketLocationRequest) + if err == nil { + return strings.Replace(*response.LocationConstraint, "oss-", "", -1), err + } else if strings.Contains(err.Error(), "OperationInput.Bucket") { + logger.Println.Error(fmt.Sprintf("%v 存储桶不存在。", bucketName)) + return global.NULL, err + } else { + logger.Println.Error(fmt.Sprintf("获取 %v 存储桶的区域时报错,详细信息如下:", bucketName)) + logger.Println.Error(err.Error()) + return global.NULL, err + } +} diff --git a/pkg/aliyun/utilsRAM.go b/pkg/aliyun/utilsRAM.go new file mode 100644 index 0000000..9039188 --- /dev/null +++ b/pkg/aliyun/utilsRAM.go @@ -0,0 +1,24 @@ +package aliyun + +import ( + "github.com/wgpsec/cloudsword/utils/global" + "github.com/wgpsec/cloudsword/utils/logger" +) + +// 获取账号 ID + +func RAMGetAccountAlias() (string, error) { + client, err := RAMClient() + if err == nil { + GetAccountAliasResponse, err := client.GetAccountAlias() + if err != nil { + logger.Println.Error("查看云账号别名时报错,详细信息如下:") + logger.Println.Error(err.Error()) + return global.NULL, err + } else { + return *GetAccountAliasResponse.Body.AccountAlias, err + } + } else { + return global.NULL, err + } +} diff --git a/pkg/baiduCloud/4201_bos_list_buckets.go b/pkg/baiduCloud/4201_bos_list_buckets.go new file mode 100644 index 0000000..a783427 --- /dev/null +++ b/pkg/baiduCloud/4201_bos_list_buckets.go @@ -0,0 +1,35 @@ +package baiduCloud + +import ( + "fmt" + "github.com/wgpsec/cloudsword/utils" + "github.com/wgpsec/cloudsword/utils/global" + "github.com/wgpsec/cloudsword/utils/logger" + "strings" +) + +func BOSListBuckets() { + buckets, err := listBOSBuckets() + if err == nil { + if len(buckets) == 0 { + logger.Println.Info("没有找到存储桶。\n") + } else { + logger.Println.Info("找到以下存储桶:") + for _, b := range buckets { + if global.GetBasicOptionValue(global.Detail) == global.False { + fmt.Println(b.Name) + } else { + fmt.Printf("\n名称:%v\n", b.Name) + fmt.Printf("区域:%v\n", b.Location) + fmt.Printf("访问地址:https://%v.%v.bcebos.com\n", b.Name, b.Location) + creationDate, err := utils.GetUTC8TimeType2(b.CreationDate) + if err == nil { + fmt.Printf("创建时间:%v\n", creationDate) + } + fmt.Println(strings.Repeat("=", 60)) + } + } + fmt.Println() + } + } +} diff --git a/pkg/baiduCloud/baiduCloud.go b/pkg/baiduCloud/baiduCloud.go new file mode 100644 index 0000000..de403d4 --- /dev/null +++ b/pkg/baiduCloud/baiduCloud.go @@ -0,0 +1,35 @@ +package baiduCloud + +import ( + "github.com/wgpsec/cloudsword/utils" + "github.com/wgpsec/cloudsword/utils/global" +) + +func Modules() []global.Module { + var modules []global.Module + //modules = append(modules, module1()...) + modules = append(modules, module2()...) + //modules = append(modules, module3()...) + //modules = append(modules, module4()...) + return modules +} + +// OBS 模块 + +func module2() []global.Module { + return []global.Module{ + utils.GetModule( + 4201, + 2, + global.BaiduCloud, + "bos_list_buckets", + global.TeamsSix, + "列出百度云 BOS 对象存储桶", + ` +所需权限: +1. bos:ListBuckets +`, + append(global.BasicOptionsDefault, global.BasicOptionDetail), + ), + } +} diff --git a/pkg/baiduCloud/client.go b/pkg/baiduCloud/client.go new file mode 100644 index 0000000..ce658a0 --- /dev/null +++ b/pkg/baiduCloud/client.go @@ -0,0 +1,40 @@ +package baiduCloud + +import ( + "github.com/baidubce/bce-sdk-go/auth" + "github.com/baidubce/bce-sdk-go/services/bos" + "github.com/wgpsec/cloudsword/utils/global" + "github.com/wgpsec/cloudsword/utils/logger" +) + +// BOS + +func BOSClient() (*bos.Client, error) { + var ( + bosClient *bos.Client + err error + stsCredential *auth.BceCredentials + endpoint = "https://bj.bcebos.com" + ) + AKId := global.GetBasicOptionValue(global.AKId) + AKSecret := global.GetBasicOptionValue(global.AKSecret) + AKToken := global.GetBasicOptionValue(global.AKToken) + if AKToken != global.NULL { + bosClient, err = bos.NewClient(AKId, AKSecret, "") + stsCredential, err = auth.NewSessionBceCredentials(AKId, AKSecret, AKToken) + bosClient.Config.Credentials = stsCredential + } else { + clientConfig := bos.BosClientConfiguration{ + Ak: AKId, + Sk: AKSecret, + Endpoint: endpoint, + RedirectDisabled: false, + } + bosClient, err = bos.NewClientWithConfig(&clientConfig) + } + if err != nil { + logger.Println.Error("创建 BOS 客户端时报错,详细信息如下:") + logger.Println.Error(err.Error()) + } + return bosClient, err +} diff --git a/pkg/baiduCloud/utilsBOS.go b/pkg/baiduCloud/utilsBOS.go new file mode 100644 index 0000000..cdb5e50 --- /dev/null +++ b/pkg/baiduCloud/utilsBOS.go @@ -0,0 +1,29 @@ +package baiduCloud + +import ( + "fmt" + "github.com/baidubce/bce-sdk-go/services/bos" + "github.com/baidubce/bce-sdk-go/services/bos/api" + "github.com/wgpsec/cloudsword/utils/logger" +) + +func listBOSBuckets() ([]api.BucketSummaryType, error) { + var ( + err error + client *bos.Client + response *api.ListBucketsResult + buckets []api.BucketSummaryType + ) + client, err = BOSClient() + if err == nil { + response, err = client.ListBuckets() + if err != nil { + logger.Println.Error("列出存储桶时报错,详细信息如下:") + logger.Println.Error(err.Error()) + fmt.Println() + } else { + buckets = append(buckets, response.Buckets...) + } + } + return buckets, err +} diff --git a/pkg/huaweiCloud/3201_obs_list_buckets.go b/pkg/huaweiCloud/3201_obs_list_buckets.go new file mode 100644 index 0000000..b55940c --- /dev/null +++ b/pkg/huaweiCloud/3201_obs_list_buckets.go @@ -0,0 +1,35 @@ +package huaweiCloud + +import ( + "fmt" + "github.com/wgpsec/cloudsword/utils" + "github.com/wgpsec/cloudsword/utils/global" + "github.com/wgpsec/cloudsword/utils/logger" + "strings" +) + +func OBSListBuckets() { + buckets, err := listOBSBuckets() + if err == nil { + if len(buckets) == 0 { + logger.Println.Info("没有找到存储桶。\n") + } else { + logger.Println.Info("找到以下存储桶:") + for _, b := range buckets { + if global.GetBasicOptionValue(global.Detail) == global.False { + fmt.Println(b.Name) + } else { + fmt.Printf("\n名称:%v\n", b.Name) + fmt.Printf("区域:%v\n", b.Location) + fmt.Printf("访问地址:https://%v.obs.%v.myhuaweicloud.com\n", b.Name, b.Location) + creationDate := utils.FormatTime(&b.CreationDate) + if err == nil { + fmt.Printf("创建时间:%v\n", creationDate) + } + fmt.Println(strings.Repeat("=", 60)) + } + } + fmt.Println() + } + } +} diff --git a/pkg/huaweiCloud/client.go b/pkg/huaweiCloud/client.go new file mode 100644 index 0000000..2871f39 --- /dev/null +++ b/pkg/huaweiCloud/client.go @@ -0,0 +1,29 @@ +package huaweiCloud + +import ( + "github.com/huaweicloud/huaweicloud-sdk-go-obs/obs" + "github.com/wgpsec/cloudsword/utils/global" + "github.com/wgpsec/cloudsword/utils/logger" +) + +// OBS + +func OBSClient() (*obs.ObsClient, error) { + var ( + err error + obsClient *obs.ObsClient + ) + AKId := global.GetBasicOptionValue(global.AKId) + AKSecret := global.GetBasicOptionValue(global.AKSecret) + AKToken := global.GetBasicOptionValue(global.AKToken) + if AKToken == global.NULL { + obsClient, err = obs.New(AKId, AKSecret, "https://obs."+global.HuaweiCloudDefaultRegion+".myhuaweicloud.com") + } else { + obsClient, err = obs.New(AKId, AKSecret, "https://obs."+global.HuaweiCloudDefaultRegion+".myhuaweicloud.com", obs.WithSecurityToken(AKToken)) + } + if err != nil { + logger.Println.Error("创建 OBS 客户端时报错,详细信息如下:") + logger.Println.Error(err.Error()) + } + return obsClient, err +} diff --git a/pkg/huaweiCloud/huaweiCloud.go b/pkg/huaweiCloud/huaweiCloud.go new file mode 100644 index 0000000..638211a --- /dev/null +++ b/pkg/huaweiCloud/huaweiCloud.go @@ -0,0 +1,35 @@ +package huaweiCloud + +import ( + "github.com/wgpsec/cloudsword/utils" + "github.com/wgpsec/cloudsword/utils/global" +) + +func Modules() []global.Module { + var modules []global.Module + //modules = append(modules, module1()...) + modules = append(modules, module2()...) + //modules = append(modules, module3()...) + //modules = append(modules, module4()...) + return modules +} + +// OBS 模块 + +func module2() []global.Module { + return []global.Module{ + utils.GetModule( + 3201, + 2, + global.HuaweiCloud, + "obs_list_buckets", + global.TeamsSix, + "列出华为云 OBS 对象存储桶", + ` +所需权限: +1. obs:ListBuckets +`, + append(global.BasicOptionsDefault, global.BasicOptionDetail), + ), + } +} diff --git a/pkg/huaweiCloud/utilsOBS.go b/pkg/huaweiCloud/utilsOBS.go new file mode 100644 index 0000000..b1ad2a3 --- /dev/null +++ b/pkg/huaweiCloud/utilsOBS.go @@ -0,0 +1,30 @@ +package huaweiCloud + +import ( + "fmt" + "github.com/huaweicloud/huaweicloud-sdk-go-obs/obs" + "github.com/wgpsec/cloudsword/utils/logger" +) + +func listOBSBuckets() ([]obs.Bucket, error) { + var ( + err error + client *obs.ObsClient + resp *obs.ListBucketsOutput + buckets []obs.Bucket + ) + client, err = OBSClient() + if err == nil { + obsListBucketsInput := &obs.ListBucketsInput{} + obsListBucketsInput.QueryLocation = true + resp, err = client.ListBuckets(obsListBucketsInput) + if err != nil { + logger.Println.Error("列出存储桶时报错,详细信息如下:") + logger.Println.Error(err.Error()) + fmt.Println() + } else { + buckets = append(buckets, resp.Buckets...) + } + } + return buckets, err +} diff --git a/pkg/tencentCloud/2101_list_cloud_assets.go b/pkg/tencentCloud/2101_list_cloud_assets.go new file mode 100644 index 0000000..64c80e0 --- /dev/null +++ b/pkg/tencentCloud/2101_list_cloud_assets.go @@ -0,0 +1,9 @@ +package tencentCloud + +func ListCloudAssets() { + COSListBuckets() + CVMListInstances() + LHListInstances() + CAMListUsers() + CAMListRoles() +} diff --git a/pkg/tencentCloud/2102_create_honey_token.go b/pkg/tencentCloud/2102_create_honey_token.go new file mode 100644 index 0000000..37294eb --- /dev/null +++ b/pkg/tencentCloud/2102_create_honey_token.go @@ -0,0 +1,282 @@ +package tencentCloud + +import ( + "fmt" + cloudaudit "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cloudaudit/v20190319" + cls "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cls/v20201016" + "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common" + "github.com/wgpsec/cloudsword/utils" + "github.com/wgpsec/cloudsword/utils/global" + "github.com/wgpsec/cloudsword/utils/logger" + "strings" +) + +func CreateHoneyToken() { + // 判断用户输入的 Webhook 类型 + var noticeType string + webhook := global.GetBasicOptionValue(global.Webhook) + switch { + case strings.Contains(webhook, "weixin.qq.com"): + noticeType = "WeCom" + case strings.Contains(webhook, "dingtalk.com"): + noticeType = "DingTalk" + case strings.Contains(webhook, "feishu.cn"): + noticeType = "Lark" + default: + noticeType = "Http" + } + // 1. 创建用户 + userName := utils.GenerateRandomName("User") + err := addUser(userName, "此用户由云鉴 2102_create_honey_token 模块自动创建,此用户用于生成访问凭证蜜标使用。") + if err == nil { + // 2. 创建访问凭证 + getUserResponseParams, err := getUser(userName) + if err == nil { + accessKeyDetail, err := createAccessKey(userName, *getUserResponseParams.Uin) + if err != nil { + fmt.Printf(` +已经创建的资源如下,如果想取消使用这个模块,请手动删除以下资源: +访问管理-用户:%s +`, userName) + } else { + accessKeyId := *accessKeyDetail.AccessKeyId + accessKeySecret := *accessKeyDetail.SecretAccessKey + logger.Println.Info(fmt.Sprintf("为 %v 用户创建访问凭证成功。", userName)) + // 3. 创建日志集 + clsClient, err := CLSClient(global.TencentCloudDefaultRegion) + if err == nil { + logSetName := utils.GenerateRandomName("LogSet") + createLogSetRequest := cls.NewCreateLogsetRequest() + createLogSetRequest.LogsetName = common.StringPtr(logSetName) + createLogSetResponse, err := clsClient.CreateLogset(createLogSetRequest) + if err != nil { + logger.Println.Error("创建日志集时报错,详细信息如下:") + logger.Println.Error(err.Error()) + fmt.Printf(` +已经创建的资源如下,如果想取消使用这个模块,请手动删除以下资源: +访问管理-用户:%s +`, userName) + } else { + logger.Println.Info(fmt.Sprintf("日志集 %v 创建成功。", logSetName)) + logSetId := *createLogSetResponse.Response.LogsetId + // 4. 创建日志主题 + createTopicRequest := cls.NewCreateTopicRequest() + topicName := utils.GenerateRandomName("Topic") + createTopicRequest.TopicName = common.StringPtr(topicName) + createTopicRequest.LogsetId = common.StringPtr(logSetId) + createTopicRequest.Describes = common.StringPtr("此日志主题由云鉴 2102_create_honey_token 模块自动创建,此日志主题用于接收跟踪集传过来的数据。") + createTopicResponse, err := clsClient.CreateTopic(createTopicRequest) + if err != nil { + logger.Println.Error("创建日志主题时报错,详细信息如下:") + logger.Println.Error(err.Error()) + fmt.Printf(` +已经创建的资源如下,如果想取消使用这个模块,请手动删除以下资源: +访问管理-用户:%s +日志服务-日志集:%s +`, userName, logSetName) + } else { + logger.Println.Info(fmt.Sprintf("日志主题 %v 创建成功。", topicName)) + TopicId := *createTopicResponse.Response.TopicId + // 5. 创建索引 + createIndex := cls.NewCreateIndexRequest() + createIndex.TopicId = common.StringPtr(TopicId) + createIndex.Status = common.BoolPtr(true) + createIndex.MetadataFlag = common.Uint64Ptr(1) + createIndex.IncludeInternalFields = common.BoolPtr(true) + createIndex.Rule = &cls.RuleInfo{ + DynamicIndex: &cls.DynamicIndex{ + Status: common.BoolPtr(true), + }, + FullText: &cls.FullTextInfo{ + CaseSensitive: common.BoolPtr(false), + ContainZH: common.BoolPtr(true), + Tokenizer: common.StringPtr(`@&?|#()='\",;:<>[]{}/ \n\t\r\\`), + }, + KeyValue: &cls.RuleKeyValueInfo{}, + } + _, err := clsClient.CreateIndex(createIndex) + if err != nil { + logger.Println.Error("创建索引时报错,详细信息如下:") + logger.Println.Error(err.Error()) + } else { + logger.Println.Info(fmt.Sprintf("日志主题 %v 索引创建成功。", topicName)) + // 6. 创建跟踪集 + cloudAuditClient, err := CloudAuditClient() + if err == nil { + auditTrackName := utils.GenerateRandomName("AuditTrack") + createAuditTrackRequest := cloudaudit.NewCreateAuditTrackRequest() + createAuditTrackRequest.Name = common.StringPtr(auditTrackName) + createAuditTrackRequest.Status = common.Uint64Ptr(1) + createAuditTrackRequest.Storage = &cloudaudit.Storage{ + StorageType: common.StringPtr("cls"), + StorageRegion: common.StringPtr(global.TencentCloudDefaultRegion), + StorageName: common.StringPtr(TopicId), + StoragePrefix: common.StringPtr("/"), + } + createAuditTrackRequest.ActionType = common.StringPtr("*") + createAuditTrackRequest.ResourceType = common.StringPtr("*") + createAuditTrackRequest.EventNames = common.StringPtrs([]string{"*"}) + _, err := cloudAuditClient.CreateAuditTrack(createAuditTrackRequest) + if err != nil { + logger.Println.Error("创建跟踪集时报错,详细信息如下:") + logger.Println.Error(err.Error()) + fmt.Printf(` +已经创建的资源如下,如果想取消使用这个模块,请手动删除以下资源: +访问管理-用户:%s +日志服务-日志集:%s +日志服务-日志主题:%s +`, userName, logSetName, topicName) + } else { + logger.Println.Info(fmt.Sprintf("跟踪集 %v 创建成功。", auditTrackName)) + // 7. 创建通知内容模版 + noticeContentName := utils.GenerateRandomName("NoticeContent") + createNoticeContentRequest := cls.NewCreateNoticeContentRequest() + createNoticeContentRequest.Name = common.StringPtr(noticeContentName) + createNoticeContentRequest.Type = common.Uint64Ptr(0) + createNoticeContentRequest.NoticeContents = []*cls.NoticeContent{ + { + Type: common.StringPtr(noticeType), + TriggerContent: &cls.NoticeContentInfo{ + Title: common.StringPtr("【紧急告警】蜜标凭证被调用,发现入侵行为"), + Content: common.StringPtr(` +告警策略:{{.Alarm}} +告警级别:{{.Level_zh}} +攻击 IP:{{.QueryLog[0][0].content.sourceIPAddress}} +攻击区域:{{.QueryLog[0][0].content.eventRegion}} +攻击服务:{{.QueryLog[0][0].content.resourceType}} +攻击操作:{{.QueryLog[0][0].content.eventName}} +攻击结果:{{.QueryLog[0][0].content.errorMessage}} +攻击请求头:{{.QueryLog[0][0].content.userAgent}} +触发时间:{{.StartTime}} + +详细报告:[{{.DetailUrl}}]({{.DetailUrl}}) +查询数据:[{{.QueryUrl}}]({{.QueryUrl}}) +{{- if .CanSilent}} +屏蔽告警:[{{.SilentUrl}}]({{.SilentUrl}}) +{{- end}} +`), + }, + RecoveryContent: &cls.NoticeContentInfo{ + Title: common.StringPtr("攻击者已停止调用蜜标凭证"), + Content: common.StringPtr(` +告警策略:{{.Alarm}} +告警级别:{{.Level_zh}} +触发时间:{{.StartTime}} +恢复时间:{{.NotifyTime}} + +详细报告:[{{.DetailUrl}}]({{.DetailUrl}}) +查询数据:[{{.QueryUrl}}]({{.QueryUrl}}) +{{- if .CanSilent}} +屏蔽告警:[{{.SilentUrl}}]({{.SilentUrl}}) +{{- end}} +`), + }, + }, + } + createNoticeContentResponse, err := clsClient.CreateNoticeContent(createNoticeContentRequest) + if err != nil { + logger.Println.Error("创建通知模版时报错,详细信息如下:") + logger.Println.Error(err.Error()) + fmt.Printf(` +已经创建的资源如下,如果想取消使用这个模块,请手动删除以下资源: +访问管理-用户:%s +操作审计-跟踪集:%s +日志服务-日志集:%s +日志服务-日志主题:%s +`, userName, auditTrackName, logSetName, topicName) + } else { + logger.Println.Info(fmt.Sprintf("通知模块 %v 创建成功。", noticeContentName)) + noticeContentId := *createNoticeContentResponse.Response.NoticeContentId + // 8. 创建通知渠道组 + alarmNoticeName := utils.GenerateRandomName("AlarmNotice") + createAlarmNoticeRequest := cls.NewCreateAlarmNoticeRequest() + createAlarmNoticeRequest.Name = common.StringPtr(alarmNoticeName) + createAlarmNoticeRequest.NoticeRules = []*cls.NoticeRule{ + { + Rule: common.StringPtr("{\"Value\":\"AND\",\"Type\":\"Operation\",\"Children\":[{\"Type\":\"Condition\",\"Value\":\"Level\",\"Children\":[{\"Value\":\"In\",\"Type\":\"Compare\"},{\"Value\":\"[2]\",\"Type\":\"Value\"}]}]}"), + WebCallbacks: []*cls.WebCallback{ + { + CallbackType: common.StringPtr(noticeType), + Url: common.StringPtr(webhook), + NoticeContentId: common.StringPtr(noticeContentId), + }, + }, + }} + createAlarmNoticeResponse, err := clsClient.CreateAlarmNotice(createAlarmNoticeRequest) + if err != nil { + logger.Println.Error("创建通知渠道组时报错,详细信息如下:") + logger.Println.Error(err.Error()) + fmt.Printf(` +已经创建的资源如下,如果想取消使用这个模块,请手动删除以下资源: +访问管理-用户:%s +操作审计-跟踪集:%s +日志服务-日志集:%s +日志服务-日志主题:%s +日志服务-通知模版:%s +`, userName, auditTrackName, logSetName, topicName, noticeContentName) + } else { + alarmNoticeId := *createAlarmNoticeResponse.Response.AlarmNoticeId + // 9. 创建告警策略 + alarmName := utils.GenerateRandomName("Alarm") + createAlarmRequest := cls.NewCreateAlarmRequest() + createAlarmRequest.Name = common.StringPtr(alarmName) + createAlarmRequest.AlarmTargets = []*cls.AlarmTarget{ + { + Query: common.StringPtr(fmt.Sprintf("userIdentity.secretId:%s", accessKeyId)), + Number: common.Int64Ptr(1), + StartTimeOffset: common.Int64Ptr(-10), + EndTimeOffset: common.Int64Ptr(0), + LogsetId: common.StringPtr(logSetId), + TopicId: common.StringPtr(TopicId), + SyntaxRule: common.Uint64Ptr(1), + }, + } + createAlarmRequest.MonitorTime = &cls.MonitorTime{ + Type: common.StringPtr("Period"), + Time: common.Int64Ptr(5), + } + createAlarmRequest.TriggerCount = common.Int64Ptr(1) + createAlarmRequest.AlarmPeriod = common.Int64Ptr(5) + createAlarmRequest.AlarmNoticeIds = common.StringPtrs([]string{alarmNoticeId}) + createAlarmRequest.AlarmLevel = common.Uint64Ptr(2) + createAlarmRequest.Condition = common.StringPtr("[$1.__QUERYCOUNT__]> 0") + createAlarmRequest.Status = common.BoolPtr(true) + createAlarmRequest.GroupTriggerStatus = common.BoolPtr(false) + _, err := clsClient.CreateAlarm(createAlarmRequest) + if err != nil { + logger.Println.Error("创建告警策略时报错,详细信息如下:") + logger.Println.Error(err.Error()) + } else { + logger.Println.Info(fmt.Sprintf("告警策略 %v 创建成功。", alarmName)) + logger.Println.Info("云访问凭证蜜标创建成功,您的云访问凭证蜜标如下:") + fmt.Printf(` +AccessKeyId: %v +AccessKeySecret: %v + +注意事项: +1. 这个功能所返回的访问凭证是没有添加任何权限的,但是为了避免一些意外情况,建议在非生产环境下使用此功能。 +2. 这个功能初始化需要 10 分钟左右的时间,当初始化完成后,从蜜标凭证被调用到接收到告警大概会经历 5 分钟左右的时间。 +`, accessKeyId, accessKeySecret) + } + fmt.Printf(` +已经创建的资源如下,如果想取消使用这个模块,请手动删除以下资源: +访问管理-用户:%s +操作审计-跟踪集:%s +日志服务-日志集:%s +日志服务-日志主题:%s +日志服务-通知模版:%s +日志服务-通知渠道组:%s +日志服务-告警策略:%s +`, userName, auditTrackName, logSetName, topicName, noticeContentName, alarmNoticeName, alarmName) + } + } + } + } + } + } + } + } + } + } + } +} diff --git a/pkg/tencentCloud/2201_cos_list_buckets.go b/pkg/tencentCloud/2201_cos_list_buckets.go new file mode 100644 index 0000000..5d91032 --- /dev/null +++ b/pkg/tencentCloud/2201_cos_list_buckets.go @@ -0,0 +1,41 @@ +package tencentCloud + +import ( + "fmt" + "github.com/wgpsec/cloudsword/utils" + "github.com/wgpsec/cloudsword/utils/global" + "github.com/wgpsec/cloudsword/utils/logger" + "strings" +) + +// cos_list_buckets + +func COSListBuckets() { + buckets := listCOSBuckets() + if len(buckets) == 0 { + logger.Println.Info("没有找到存储桶。\n") + } else { + logger.Println.Info("找到以下存储桶:") + for _, b := range buckets { + if global.GetBasicOptionValue(global.Detail) == global.False { + fmt.Println(b.Name) + } else { + fmt.Printf("\n名称:%v\n", b.Name) + fmt.Printf("区域:%v\n", b.Region) + + bucketTags, err := getBucketTagging(b.Name, b.Region) + if err == nil { + fmt.Printf("存储桶标签:%v\n", bucketTags) + } + + fmt.Printf("访问地址:https://%v.cos.%v.myqcloud.com\n", b.Name, b.Region) + creationDate, err := utils.GetUTC8TimeType2(b.CreationDate) + if err == nil { + fmt.Printf("创建时间:%v\n", creationDate) + } + fmt.Println(strings.Repeat("=", 60)) + } + } + fmt.Println() + } +} diff --git a/pkg/tencentCloud/2301_cvm_list_instances.go b/pkg/tencentCloud/2301_cvm_list_instances.go new file mode 100644 index 0000000..a5f0239 --- /dev/null +++ b/pkg/tencentCloud/2301_cvm_list_instances.go @@ -0,0 +1,131 @@ +package tencentCloud + +import ( + "encoding/json" + "fmt" + "github.com/wgpsec/cloudsword/utils" + "github.com/wgpsec/cloudsword/utils/global" + "github.com/wgpsec/cloudsword/utils/logger" + "strings" +) + +// cvm_list_instances + +func CVMListInstances() { + cvmInstances, err := GetCVMResource() + if err == nil { + // 输出 CVM 资源 + if len(cvmInstances) == 0 { + logger.Println.Warn("未找到 CVM 弹性计算实例,出现这种情况一般是由于没有实例或没有权限。\n") + } else { + logger.Println.Info("找到以下 CVM 弹性计算实例:") + for n, i := range cvmInstances { + fmt.Printf("\n=================== %d %v ===================\n", n+1, *i.InstanceId) + // 提取 CVM 的 IP 地址 + var ( + PublicIpAddressList []string + PrivateIpAddress string + PublicIpAddress string + ) + if len(i.PublicIpAddresses) > 0 { + for _, v := range i.PublicIpAddresses { + PublicIpAddressList = append(PublicIpAddressList, *v) + } + PublicIpAddress = strings.Join(PublicIpAddressList, ",") + } + if len(i.PrivateIpAddresses) > 0 { + PrivateIpAddress = *i.PrivateIpAddresses[0] + } + + if global.GetBasicOptionValue(global.Detail) == global.False { + // 输出 CVM 基础信息 + utils.PrintfNotNilString("\n实例 ID:", i.InstanceId) + utils.PrintfNotNilString("实例名称:", i.InstanceName) + utils.PrintfNotNilString("实例区域:", i.Placement.Zone) + utils.PrintfNotNilString("实例状态:", i.InstanceState) + utils.PrintfNotNilString("操作系统类型:", i.OsName) + fmt.Printf("私有 IP 地址:%v\n", PrivateIpAddress) + fmt.Printf("公有 IP 地址:%v\n", PublicIpAddress) + utils.PrintfNotNilInt64("默认登录端口:", i.DefaultLoginPort) + utils.PrintfNotNilString("默认登录用户:", i.DefaultLoginUser) + utils.PrintfNotNilString("登录密码:", i.LoginSettings.Password) + } else { + // 输出 CVM 详细信息 + fmt.Printf("\n基础信息\n========\n") + utils.PrintfNotNilString("实例 ID:", i.InstanceId) + utils.PrintfNotNilString("实例名称:", i.InstanceName) + utils.PrintfNotNilString("实例区域:", i.Placement.Zone) + utils.PrintfNotNilString("实例状态:", i.InstanceState) + utils.PrintfNotNilString("实例角色:", i.CamRoleName) + utils.PrintfNotNilInt64("默认登录端口:", i.DefaultLoginPort) + utils.PrintfNotNilString("默认登录用户:", i.DefaultLoginUser) + utils.PrintfNotNilString("登录密码:", i.LoginSettings.Password) + + fmt.Printf("\n操作系统信息\n============\n") + utils.PrintfNotNilInt64("虚拟 CPU 数量:", i.CPU) + utils.PrintfNotNilInt64("内存大小(单位:GB):", i.Memory) + if i.GPUInfo != nil { + utils.PrintfNotNilFloat64("GPU 数量:", i.GPUInfo.GPUCount) + utils.PrintfNotNilString("GPU 类型:", i.GPUInfo.GPUType) + } + utils.PrintfNotNilString("实例规格:", i.InstanceType) + utils.PrintfNotNilString("操作系统类型:", i.OsName) + utils.PrintfNotNilString("操作系统镜像 ID:", i.ImageId) + + fmt.Printf("\n磁盘信息\n============\n") + utils.PrintfNotNilString("磁盘 ID:", i.SystemDisk.DiskId) + utils.PrintfNotNilString("磁盘名称:", i.SystemDisk.DiskName) + utils.PrintfNotNilInt64("磁盘大小(单位:GB):", i.SystemDisk.DiskSize) + utils.PrintfNotNilString("磁盘类型:", i.SystemDisk.DiskType) + fmt.Println() + for _, disk := range i.DataDisks { + utils.PrintfNotNilString("磁盘 ID:", disk.DiskId) + utils.PrintfNotNilString("磁盘名称:", disk.DiskName) + utils.PrintfNotNilInt64("磁盘大小(单位:GB):", disk.DiskSize) + utils.PrintfNotNilString("磁盘类型:", disk.DiskType) + utils.PrintfNotNilString("快照 ID:", disk.SnapshotId) + fmt.Println() + } + + fmt.Printf("\n网络信息\n========\n") + utils.PrintfNotNilString("VPC ID:", i.VirtualPrivateCloud.VpcId) + utils.PrintfNotNilString("子网 ID:", i.VirtualPrivateCloud.SubnetId) + var securityGroupIds []string + for _, v := range i.SecurityGroupIds { + securityGroupIds = append(securityGroupIds, *v) + } + fmt.Printf("安全组:%v\n", strings.Join(securityGroupIds, ",")) + fmt.Printf("私有 IP 地址:%v\n", PrivateIpAddress) + fmt.Printf("公有 IP 地址:%v\n", PublicIpAddress) + utils.PrintfNotNilString("带宽包 ID:", i.InternetAccessible.BandwidthPackageId) + utils.PrintfNotNilString("网络计费类型:", i.InternetAccessible.InternetChargeType) + utils.PrintfNotNilInt64("公网出带宽最大值(单位:Mbps):", i.InternetAccessible.InternetMaxBandwidthOut) + + fmt.Printf("\n其他信息========\n") + jsonBytes, err := json.Marshal(i.Tags) + if err == nil { + fmt.Printf("实例标签:%s\n", jsonBytes) + } + utils.PrintfNotNilString("计费类型:", i.InstanceChargeType) + utils.PrintfNotNilString("许可类型:", i.LicenseType) + utils.PrintfNotNilString("实例全局唯一ID:", i.Uuid) + + if i.CreatedTime != nil { + creationTime, err := utils.GetUTC8TimeType2(*i.CreatedTime) + if err == nil { + fmt.Printf("创建时间:%v\n", creationTime) + } + } + + if i.ExpiredTime != nil { + expiredTime, err := utils.GetUTC8TimeType2(*i.ExpiredTime) + if err == nil { + fmt.Printf("过期时间:%v\n", expiredTime) + } + } + } + } + fmt.Println() + } + } +} diff --git a/pkg/tencentCloud/2302_lh_list_instances.go b/pkg/tencentCloud/2302_lh_list_instances.go new file mode 100644 index 0000000..88c092f --- /dev/null +++ b/pkg/tencentCloud/2302_lh_list_instances.go @@ -0,0 +1,102 @@ +package tencentCloud + +import ( + "encoding/json" + "fmt" + "github.com/wgpsec/cloudsword/utils" + "github.com/wgpsec/cloudsword/utils/global" + "github.com/wgpsec/cloudsword/utils/logger" + "strings" +) + +// lh_list_instances + +func LHListInstances() { + lhInstances, err := GetLHResource() + if err == nil { + // 输出 LH 资源 + if len(lhInstances) == 0 { + logger.Println.Warn("未找到 LH 弹性计算实例,出现这种情况一般是由于没有实例或没有权限。\n") + } else { + logger.Println.Info("找到以下 LH 弹性计算实例:") + for n, i := range lhInstances { + fmt.Printf("\n=================== %d %v ===================\n", n+1, *i.InstanceId) + // 提取 LH 的 IP 地址 + var ( + PublicIpAddressList []string + PrivateIpAddress string + PublicIpAddress string + ) + if len(i.PublicAddresses) > 0 { + for _, v := range i.PublicAddresses { + PublicIpAddressList = append(PublicIpAddressList, *v) + } + PublicIpAddress = strings.Join(PublicIpAddressList, ",") + } + if len(i.PrivateAddresses) > 0 { + PrivateIpAddress = *i.PrivateAddresses[0] + } + + if global.GetBasicOptionValue(global.Detail) == global.False { + // 输出 ECS 基础信息 + utils.PrintfNotNilString("\n实例 ID:", i.InstanceId) + utils.PrintfNotNilString("实例名称:", i.InstanceName) + utils.PrintfNotNilString("实例区域:", i.Zone) + utils.PrintfNotNilString("实例状态:", i.InstanceState) + utils.PrintfNotNilString("操作系统类型:", i.OsName) + fmt.Printf("私有 IP 地址:%v\n", PrivateIpAddress) + fmt.Printf("公有 IP 地址:%v\n", PublicIpAddress) + } else { + // 输出 ECS 详细信息 + fmt.Printf("\n基础信息\n========\n") + utils.PrintfNotNilString("实例 ID:", i.InstanceId) + utils.PrintfNotNilString("实例名称:", i.InstanceName) + utils.PrintfNotNilString("实例区域:", i.Zone) + utils.PrintfNotNilString("实例状态:", i.InstanceState) + + fmt.Printf("\n操作系统信息\n============\n") + utils.PrintfNotNilInt64("虚拟 CPU 数量:", i.CPU) + utils.PrintfNotNilInt64("内存大小(单位:GB):", i.Memory) + utils.PrintfNotNilString("操作系统类型:", i.OsName) + utils.PrintfNotNilString("操作系统平台:", i.Platform) + utils.PrintfNotNilString("操作平台类型:", i.PlatformType) + + fmt.Printf("\n磁盘信息\n============\n") + utils.PrintfNotNilString("磁盘 ID:", i.SystemDisk.DiskId) + utils.PrintfNotNilInt64("磁盘大小(单位:GB):", i.SystemDisk.DiskSize) + utils.PrintfNotNilString("磁盘类型:", i.SystemDisk.DiskType) + + fmt.Printf("\n网络信息\n========\n") + fmt.Printf("私有 IP 地址:%v\n", PrivateIpAddress) + fmt.Printf("公有 IP 地址:%v\n", PublicIpAddress) + utils.PrintfNotNilString("网络计费类型:", i.InternetAccessible.InternetChargeType) + utils.PrintfNotNilInt64("公网出带宽最大值(单位:Mbps):", i.InternetAccessible.InternetMaxBandwidthOut) + + fmt.Printf("\n其他信息========\n") + jsonBytes, err := json.Marshal(i.Tags) + if err == nil { + fmt.Printf("实例标签:%s\n", jsonBytes) + } + + utils.PrintfNotNilString("计费类型:", i.InstanceChargeType) + utils.PrintfNotNilString("实例全局唯一ID:", i.Uuid) + + if i.CreatedTime != nil { + creationTime, err := utils.GetUTC8TimeType2(*i.CreatedTime) + if err == nil { + fmt.Printf("创建时间:%v\n", creationTime) + } + } + + if i.ExpiredTime != nil { + expiredTime, err := utils.GetUTC8TimeType2(*i.ExpiredTime) + if err == nil { + fmt.Printf("过期时间:%v\n", expiredTime) + } + } + } + } + fmt.Println() + } + } +} diff --git a/pkg/tencentCloud/2401_cam_list_users.go b/pkg/tencentCloud/2401_cam_list_users.go new file mode 100644 index 0000000..5f13b52 --- /dev/null +++ b/pkg/tencentCloud/2401_cam_list_users.go @@ -0,0 +1,38 @@ +package tencentCloud + +import ( + "fmt" + "github.com/wgpsec/cloudsword/utils" + "github.com/wgpsec/cloudsword/utils/global" + "github.com/wgpsec/cloudsword/utils/logger" +) + +// cam_list_users + +func CAMListUsers() { + users, err := getCAMUsers() + if err == nil { + if len(users) == 0 { + logger.Println.Info("未找到用户。\n") + } else { + logger.Println.Info("找到以下用户:") + for _, user := range users { + if global.GetBasicOptionValue(global.Detail) == global.False { + fmt.Println(*user.Name) + } else { + fmt.Printf("\n用户登录名:%v\n", *user.Name) + utils.PrintfNotNilString("用户昵称:", user.NickName) + fmt.Printf("用户 ID:%v\n", *user.Uin) + fmt.Printf("用户 UID:%v\n", *user.Uid) + fmt.Printf("邮箱:%v\n", *user.Email) + fmt.Printf("手机号:%v\n", *user.PhoneNum) + fmt.Printf("备注:%v\n", *user.Remark) + if user.CreateTime != nil { + fmt.Printf("创建时间:%v\n", *user.CreateTime) + } + } + } + fmt.Println() + } + } +} diff --git a/pkg/tencentCloud/2402_cam_list_roles.go b/pkg/tencentCloud/2402_cam_list_roles.go new file mode 100644 index 0000000..ec8f2ca --- /dev/null +++ b/pkg/tencentCloud/2402_cam_list_roles.go @@ -0,0 +1,46 @@ +package tencentCloud + +import ( + "encoding/json" + "fmt" + "github.com/wgpsec/cloudsword/utils" + "github.com/wgpsec/cloudsword/utils/global" + "github.com/wgpsec/cloudsword/utils/logger" +) + +// cam_list_roles + +func CAMListRoles() { + roles, err := getCAMRoles() + if err == nil { + if len(roles) == 0 { + logger.Println.Info("未找到角色。\n") + } else { + logger.Println.Info("找到以下角色:") + for _, role := range roles { + if global.GetBasicOptionValue(global.Detail) == global.False { + fmt.Println(*role.RoleName) + } else { + fmt.Printf("\n角色名称:%v\n", *role.RoleName) + fmt.Printf("角色 ID:%v\n", *role.RoleId) + utils.PrintfNotNilString("角色类型:", role.RoleType) + utils.PrintfNotNilString("角色资源描述符 ARN:", role.RoleArn) + utils.PrintfNotNilUInt64("角色最大会话时间:", role.SessionDuration) + fmt.Printf("角色描述:%v\n", *role.Description) + fmt.Printf("角色策略文档:%v\n", *role.PolicyDocument) + jsonBytes, err := json.Marshal(role.Tags) + if err == nil { + fmt.Printf("实例标签:%s\n", jsonBytes) + } + if role.AddTime == nil { + fmt.Printf("创建时间:%v\n", *role.AddTime) + } + if role.UpdateTime == nil { + fmt.Printf("更新时间:%v\n", *role.UpdateTime) + } + } + } + fmt.Println() + } + } +} diff --git a/pkg/tencentCloud/2403_cam_create_user.go b/pkg/tencentCloud/2403_cam_create_user.go new file mode 100644 index 0000000..6b2598f --- /dev/null +++ b/pkg/tencentCloud/2403_cam_create_user.go @@ -0,0 +1,13 @@ +package tencentCloud + +import ( + "github.com/wgpsec/cloudsword/utils/global" +) + +// cam_create_user + +func CAMCreateUser() { + userName := global.GetBasicOptionValue(global.UserName) + remark := global.GetBasicOptionValue(global.Description) + _ = addUser(userName, remark) +} diff --git a/pkg/tencentCloud/2404_cam_attach_policy_to_user.go b/pkg/tencentCloud/2404_cam_attach_policy_to_user.go new file mode 100644 index 0000000..452405c --- /dev/null +++ b/pkg/tencentCloud/2404_cam_attach_policy_to_user.go @@ -0,0 +1,36 @@ +package tencentCloud + +import ( + "fmt" + "github.com/wgpsec/cloudsword/utils/global" + "github.com/wgpsec/cloudsword/utils/logger" +) + +// cam_attach_policy_to_user + +func CAMAttachPolicyToUser() { + userName := global.GetBasicOptionValue(global.UserName) + policyName := global.GetBasicOptionValue(global.PolicyName) + getUserResponseParams, err := getUser(userName) + if err == nil { + strategyInfo, err := listPolicies() + if err == nil { + var ( + uin uint64 + policyId uint64 + ) + + for _, policy := range strategyInfo { + if policyName == *policy.PolicyName { + policyId = *policy.PolicyId + } + } + if policyId == 0 { + logger.Println.Error(fmt.Sprintf("预设策略中不存在 %v 策略", policyName)) + } else { + uin = *getUserResponseParams.Uin + _ = attachUserPolicy(userName, policyName, uin, policyId) + } + } + } +} diff --git a/pkg/tencentCloud/2405_cam_create_login_profile.go b/pkg/tencentCloud/2405_cam_create_login_profile.go new file mode 100644 index 0000000..f03c215 --- /dev/null +++ b/pkg/tencentCloud/2405_cam_create_login_profile.go @@ -0,0 +1,23 @@ +package tencentCloud + +import ( + "fmt" + "github.com/wgpsec/cloudsword/utils" + "github.com/wgpsec/cloudsword/utils/global" + "github.com/wgpsec/cloudsword/utils/logger" +) + +// cam_create_login_profile + +func CAMCreateLoginProfile() { + userName := global.GetBasicOptionValue(global.UserName) + password := utils.GenerateRandomPasswords() + err := updateUserWithConsoleLogin(userName, password) + if err == nil { + appIdResponse, err := getUserAppId() + if err == nil { + logger.Println.Info(fmt.Sprintf("为 %v 用户创建 Web 控制台登录配置成功,登录信息如下:", userName)) + fmt.Printf("主账号 ID:%s\n子用户名:%s\n密码:%s\n登录地址:https://cloud.tencent.com/login/subAccount\n", *appIdResponse.OwnerUin, userName, password) + } + } +} diff --git a/pkg/tencentCloud/2406_cam_create_access_key.go b/pkg/tencentCloud/2406_cam_create_access_key.go new file mode 100644 index 0000000..d4c4a08 --- /dev/null +++ b/pkg/tencentCloud/2406_cam_create_access_key.go @@ -0,0 +1,22 @@ +package tencentCloud + +import ( + "fmt" + "github.com/wgpsec/cloudsword/utils/global" + "github.com/wgpsec/cloudsword/utils/logger" +) + +// cam_create_access_key + +func CAMCreateAccessKey() { + userName := global.GetBasicOptionValue(global.UserName) + getUserResponseParams, err := getUser(userName) + if err == nil { + accessKeyDetail, err := createAccessKey(userName, *getUserResponseParams.Uin) + if err == nil { + logger.Println.Info(fmt.Sprintf("为 %v 用户创建访问凭证如下:", userName)) + fmt.Printf("AccessKeyId: %v\nAccessKeySecret: %v\n", *accessKeyDetail.AccessKeyId, + *accessKeyDetail.SecretAccessKey) + } + } +} diff --git a/pkg/tencentCloud/client.go b/pkg/tencentCloud/client.go new file mode 100644 index 0000000..c9737b7 --- /dev/null +++ b/pkg/tencentCloud/client.go @@ -0,0 +1,194 @@ +package tencentCloud + +import ( + "fmt" + cam "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cam/v20190116" + cloudaudit "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cloudaudit/v20190319" + cls "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cls/v20201016" + "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common" + "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile" + cvm "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cvm/v20170312" + lh "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/lighthouse/v20200324" + cos "github.com/tencentyun/cos-go-sdk-v5" + "github.com/wgpsec/cloudsword/utils/global" + "github.com/wgpsec/cloudsword/utils/logger" + "net/http" + "net/url" +) + +// COS + +func COSClient(bucketName, region, nextMarker string) *cos.Client { + AKId := global.GetBasicOptionValue(global.AKId) + AKSecret := global.GetBasicOptionValue(global.AKSecret) + AKToken := global.GetBasicOptionValue(global.AKToken) + if bucketName == global.NULL || region == global.NULL { + cosClient := cos.NewClient(nil, &http.Client{ + Transport: &cos.AuthorizationTransport{ + SecretID: AKId, + SecretKey: AKSecret, + SessionToken: AKToken, + }, + }) + return cosClient + } else { + if nextMarker == global.NULL { + u, _ := url.Parse(fmt.Sprintf("https://%s.cos.%s.myqcloud.com", bucketName, region)) + b := &cos.BaseURL{BucketURL: u} + cosClient := cos.NewClient(b, &http.Client{ + Transport: &cos.AuthorizationTransport{ + SecretID: AKId, + SecretKey: AKSecret, + SessionToken: AKToken, + }, + }) + return cosClient + } else { + u, _ := url.Parse(fmt.Sprintf("https://%s.cos.%s.myqcloud.com/?marker=%s", bucketName, region, nextMarker)) + b := &cos.BaseURL{BucketURL: u} + cosClient := cos.NewClient(b, &http.Client{ + Transport: &cos.AuthorizationTransport{ + SecretID: AKId, + SecretKey: AKSecret, + SessionToken: AKToken, + }, + }) + return cosClient + } + } +} + +// CVM + +func CVMClient(region string) (*cvm.Client, error) { + var ( + credential *common.Credential + client *cvm.Client + err error + ) + AKId := global.GetBasicOptionValue(global.AKId) + AKSecret := global.GetBasicOptionValue(global.AKSecret) + AKToken := global.GetBasicOptionValue(global.AKToken) + if AKToken == global.NULL { + credential = common.NewCredential(AKId, AKSecret) + } else { + credential = common.NewTokenCredential(AKId, AKSecret, AKToken) + } + + cpf := profile.NewClientProfile() + cpf.HttpProfile.Endpoint = "cvm.tencentcloudapi.com" + client, err = cvm.NewClient(credential, region, cpf) + if err != nil { + logger.Println.Error("创建 CVM 客户端时报错,详细信息如下:") + logger.Println.Error(err.Error()) + } + return client, err +} + +// LH + +func LHClient(region string) (*lh.Client, error) { + var ( + credential *common.Credential + client *lh.Client + err error + ) + AKId := global.GetBasicOptionValue(global.AKId) + AKSecret := global.GetBasicOptionValue(global.AKSecret) + AKToken := global.GetBasicOptionValue(global.AKToken) + if AKToken == global.NULL { + credential = common.NewCredential(AKId, AKSecret) + } else { + credential = common.NewTokenCredential(AKId, AKSecret, AKToken) + } + + cpf := profile.NewClientProfile() + cpf.HttpProfile.Endpoint = "lighthouse.tencentcloudapi.com" + client, err = lh.NewClient(credential, region, cpf) + if err != nil { + logger.Println.Error("创建 LH 客户端时报错,详细信息如下:") + logger.Println.Error(err.Error()) + } + return client, err +} + +// CAM + +func CAMClient() (*cam.Client, error) { + var ( + credential *common.Credential + client *cam.Client + err error + ) + AKId := global.GetBasicOptionValue(global.AKId) + AKSecret := global.GetBasicOptionValue(global.AKSecret) + AKToken := global.GetBasicOptionValue(global.AKToken) + if AKToken == global.NULL { + credential = common.NewCredential(AKId, AKSecret) + } else { + credential = common.NewTokenCredential(AKId, AKSecret, AKToken) + } + + cpf := profile.NewClientProfile() + cpf.HttpProfile.Endpoint = "cam.tencentcloudapi.com" + client, err = cam.NewClient(credential, global.NULL, cpf) + if err != nil { + logger.Println.Error("创建 CAM 客户端时报错,详细信息如下:") + logger.Println.Error(err.Error()) + } + return client, err +} + +// CloudAudit + +func CloudAuditClient() (*cloudaudit.Client, error) { + var ( + credential *common.Credential + client *cloudaudit.Client + err error + ) + AKId := global.GetBasicOptionValue(global.AKId) + AKSecret := global.GetBasicOptionValue(global.AKSecret) + AKToken := global.GetBasicOptionValue(global.AKToken) + if AKToken == global.NULL { + credential = common.NewCredential(AKId, AKSecret) + } else { + credential = common.NewTokenCredential(AKId, AKSecret, AKToken) + } + + cpf := profile.NewClientProfile() + cpf.HttpProfile.Endpoint = "cloudaudit.tencentcloudapi.com" + client, err = cloudaudit.NewClient(credential, global.TencentCloudDefaultRegion, cpf) + if err != nil { + logger.Println.Error("创建 Cloud Audit 客户端时报错,详细信息如下:") + logger.Println.Error(err.Error()) + } + return client, err +} + +// CLS + +func CLSClient(region string) (*cls.Client, error) { + var ( + credential *common.Credential + client *cls.Client + err error + ) + AKId := global.GetBasicOptionValue(global.AKId) + AKSecret := global.GetBasicOptionValue(global.AKSecret) + AKToken := global.GetBasicOptionValue(global.AKToken) + if AKToken == global.NULL { + credential = common.NewCredential(AKId, AKSecret) + } else { + credential = common.NewTokenCredential(AKId, AKSecret, AKToken) + } + + cpf := profile.NewClientProfile() + cpf.HttpProfile.Endpoint = "cls.tencentcloudapi.com" + client, err = cls.NewClient(credential, region, cpf) + if err != nil { + logger.Println.Error("创建 CLS 客户端时报错,详细信息如下:") + logger.Println.Error(err.Error()) + } + return client, err +} diff --git a/pkg/tencentCloud/tencentCloud.go b/pkg/tencentCloud/tencentCloud.go new file mode 100644 index 0000000..5bfb219 --- /dev/null +++ b/pkg/tencentCloud/tencentCloud.go @@ -0,0 +1,238 @@ +package tencentCloud + +import ( + "github.com/wgpsec/cloudsword/utils" + "github.com/wgpsec/cloudsword/utils/global" +) + +func Modules() []global.Module { + var modules []global.Module + modules = append(modules, module1()...) + modules = append(modules, module2()...) + modules = append(modules, module3()...) + modules = append(modules, module4()...) + return modules +} + +// 综合性模块 + +func module1() []global.Module { + return []global.Module{ + utils.GetModule(2101, + 4, + global.TencentCloud, + "list_cloud_assets", + global.TeamsSix, + "列出 COS、EVM、LH、RAM 服务资产", + ` +简洁输出所需权限: +1. cos:GetService +2. cvm:DescribeRegions +3. cvm:DescribeInstances +4. lh:DescribeRegions +5. lh:DescribeInstances +6. cam:ListUsers +7. cam:DescribeRoleList + +详细输出所需权限: +1. cos:GetService +2. cos:GetTagging +3. cvm:DescribeRegions +4. cvm:DescribeInstances +5. lh:DescribeRegions +6. lh:DescribeInstances +7. cam:ListUsers +8. cam:DescribeRoleList +`, + append(global.BasicOptionsDefault, global.BasicOptionDetail, global.BasicOptionRegion), + ), utils.GetModule(2102, + 5, + global.TencentCloud, + "create_honey_token", + global.TeamsSix, + "创建云访问凭证蜜标", + ` +所需权限: +1. cam:AddUser +2. cam:GetUser +3. cam:CreateAccessKey +4. cloudaudit:CreateAuditTrack +5. cls:CreateTopic +6. cls:CreateNoticeContent +7. cls:CreateAlarmNotice +8. cls:CreateAlarm + +Webhook 支持的类型有企业微信、钉钉、飞书、自定义接口,各平台 Webhook 生成方式文档地址如下: +1. 企业微信:https://developer.work.weixin.qq.com/document/path/91770 +2. 钉钉:https://open.dingtalk.com/document/orgapp/obtain-the-webhook-address-of-a-custom-robot +3. 飞书:https://open.feishu.cn/document/client-docs/bot-v3/add-custom-bot + +使用说明: +1. 使用该功能前请仔细阅读《使用云访问凭证蜜标及时发现入侵行为》这篇文章,文章地址:https://wiki.teamssix.com/cloudservice/more/use-honeytokens-to-discover-attackers.html +2. 这个功能会创建一个访问凭证蜜标,您可以将创建的访问凭证蜜标放到服务器、工作电脑、环境变量、程序代码、配置文件等易于发现的位置,然后当这个访问凭证被调用时,说明访问凭证已经被泄露了,此时就会触发告警,这样您可以及时知道自己被入侵了。 +3. 这个功能所返回的访问凭证是没有添加任何权限的,但是为了避免一些意外情况,建议在非生产环境下使用此功能。 +4. 这个功能初始化需要 10 分钟左右的时间,当初始化完成后,从蜜标凭证被调用到接收到告警大概会经历 5 分钟左右的时间。 +`, + append(global.BasicOptionsDefault, global.BasicOptionWebhook), + ), + } +} + +// COS 模块 + +func module2() []global.Module { + return []global.Module{ + utils.GetModule( + 2201, + 2, + global.TencentCloud, + "cos_list_buckets", + global.TeamsSix, + "列出腾讯云 COS 对象存储桶", + ` +简洁输出模式所需权限: +1. cos:GetService + +详细输出模式所需权限: +1. cos:GetService +2. cos:GetTagging +`, + append(global.BasicOptionsDefault, global.BasicOptionDetail), + ), + } +} + +// CVM 模块 + +func module3() []global.Module { + return []global.Module{ + utils.GetModule( + 2301, + 2, + global.TencentCloud, + "cvm_list_instances", + global.TeamsSix, + "列出腾讯云 CVM 弹性计算实例", + ` +所需权限: +1. cvm:DescribeRegions +2. cvm:DescribeInstances +`, + append(global.BasicOptionsDefault, global.BasicOptionDetail, global.BasicOptionRegion), + ), + utils.GetModule( + 2302, + 1, + global.TencentCloud, + "lh_list_instances", + global.TeamsSix, + "列出腾讯云 LH 轻量应用服务器", + ` +所需权限: +1. lh:DescribeRegions +2. lh:DescribeInstances +`, + append(global.BasicOptionsDefault, global.BasicOptionDetail, global.BasicOptionRegion), + ), + } +} + +// CAM 模块 + +func module4() []global.Module { + return []global.Module{ + utils.GetModule( + 2401, + 2, + global.TencentCloud, + "cam_list_users", + global.TeamsSix, + "列出腾讯云 CAM 用户", + ` +所需权限: +1. cam:ListUsers +`, + append(global.BasicOptionsDefault, global.BasicOptionDetail), + ), + utils.GetModule( + 2402, + 1, + global.TencentCloud, + "cam_list_roles", + global.TeamsSix, + "列出腾讯云 CAM 角色", + ` +所需权限: +1. cam:DescribeRoleList +`, + append(global.BasicOptionsDefault, global.BasicOptionDetail), + ), + utils.GetModule( + 2403, + 1, + global.TencentCloud, + "cam_create_user", + global.TeamsSix, + "创建腾讯云 CAM 角色", + ` +所需权限: +1. cam:AddUser +`, + append(global.BasicOptionsDefault, global.BasicOptionUserName, global.BasicOptionDescription), + ), + utils.GetModule( + 2404, + 1, + global.TencentCloud, + "cam_attach_policy_to_user", + global.TeamsSix, + "为腾讯云 CAM 用户添加策略", + ` +所需权限: +1. cam:GetUser +2. cam:ListPolicies +3. cam:AttachUserPolicy + +使用频率较高的策略名称(仅供参考): +1. AdministratorAccess +2. ReadOnlyAccess +3. QcloudCamFullAccess +4. QcloudCOSFullAccess +5. QcloudCVMFullAccess + +注意事项: +1. 为用户添加 AdministratorAccess 或 QcloudCamFullAccess 策略是一件高危操作,请根据实际情况酌量进行操作。 +2. 此功能仅支持添加预设策略,自定义策略建议使用控制台或 TCCLI 等工具进行添加,完整的权限策略清单可参考:https://console.cloud.tencent.com/cam/policy +`, + append(global.BasicOptionsDefault, global.BasicOptionUserName, global.BasicOptionPolicyName), + ), + utils.GetModule( + 2405, + 3, + global.TencentCloud, + "cam_create_login_profile", + global.TeamsSix, + "创建腾讯云 CAM 用户 Web 登录配置", + ` +所需权限: +1. cam:UpdateUser +2. cam:GetUserAppId +`, + append(global.BasicOptionsDefault, global.BasicOptionUserName), + ), + utils.GetModule( + 2406, + 1, + global.TencentCloud, + "cam_create_access_key", + global.TeamsSix, + "创建腾讯云 CAM 用户访问凭证", + ` +所需权限: +1. cam:GetUser +2. cam:CreateAccessKey +`, + append(global.BasicOptionsDefault, global.BasicOptionUserName), + ), + } +} diff --git a/pkg/tencentCloud/utilsCAM.go b/pkg/tencentCloud/utilsCAM.go new file mode 100644 index 0000000..4b7ef08 --- /dev/null +++ b/pkg/tencentCloud/utilsCAM.go @@ -0,0 +1,215 @@ +package tencentCloud + +import ( + "fmt" + cam "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cam/v20190116" + "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common" + "github.com/wgpsec/cloudsword/utils/logger" +) + +// 列出 CAM 用户 + +func getCAMUsers() ([]*cam.SubAccountInfo, error) { + client, err := CAMClient() + if err == nil { + requests := cam.NewListUsersRequest() + response, err := client.ListUsers(requests) + if err != nil { + logger.Println.Error("获取 CAM 用户时报错,详细信息如下:") + logger.Println.Error(err.Error()) + return nil, err + } else { + return response.Response.Data, err + } + } else { + return nil, err + } +} + +// 列出 CAM 角色 + +func getCAMRoles() ([]*cam.RoleInfo, error) { + client, err := CAMClient() + if err == nil { + var ( + rp uint64 = 200 + page uint64 = 1 + roles []*cam.RoleInfo + ) + requests := cam.NewDescribeRoleListRequest() + requests.Page = common.Uint64Ptr(page) + requests.Rp = common.Uint64Ptr(rp) + for { + response, err := client.DescribeRoleList(requests) + if err != nil { + logger.Println.Error("获取 CAM 角色时报错,详细信息如下:") + logger.Println.Error(err.Error()) + break + } else { + roles = append(roles, response.Response.List...) + if len(response.Response.List) == int(rp) { + page = page + 1 + requests.Page = &page + } else { + break + } + } + } + return roles, err + } else { + return nil, err + } +} + +// 创建 CAM 用户 + +func addUser(userName, remark string) error { + client, err := CAMClient() + if err == nil { + request := cam.NewAddUserRequest() + request.Name = common.StringPtr(userName) + request.Remark = common.StringPtr(remark) + _, err = client.AddUser(request) + if err != nil { + logger.Println.Error(fmt.Sprintf("创建 %v 用户时报错,详细信息如下:", userName)) + logger.Println.Error(err.Error()) + } else { + logger.Println.Info(fmt.Sprintf("用户 %v 创建成功。", userName)) + } + } + return err +} + +// 列出所有策略 + +func listPolicies() ([]*cam.StrategyInfo, error) { + client, err := CAMClient() + if err == nil { + var ( + page uint64 = 1 + rp uint64 = 200 + policies []*cam.StrategyInfo + ) + + requests := cam.NewListPoliciesRequest() + requests.Rp = common.Uint64Ptr(rp) + requests.Page = common.Uint64Ptr(page) + requests.Scope = common.StringPtr("All") + for { + response, err := client.ListPolicies(requests) + if err != nil { + logger.Println.Error("获取策略时报错,详细信息如下:") + logger.Println.Error(err.Error()) + break + } else { + policies = append(policies, response.Response.List...) + if len(response.Response.List) == int(rp) { + page = page + 1 + requests.Page = &page + } else { + break + } + } + } + return policies, err + } + return nil, err +} + +// 查询用户信息 + +func getUser(userName string) (*cam.GetUserResponseParams, error) { + client, err := CAMClient() + if err == nil { + request := cam.NewGetUserRequest() + request.Name = common.StringPtr(userName) + response, err := client.GetUser(request) + if err != nil { + logger.Println.Error(fmt.Sprintf("查询 %v 用户信息时报错,详细信息如下:", userName)) + logger.Println.Error(err.Error()) + return nil, err + } else { + logger.Println.Info(fmt.Sprintf("查询 %v 用户信息成功。", userName)) + return response.Response, err + } + } else { + return nil, err + } +} + +// 为用户添加策略 + +func attachUserPolicy(userName, policyName string, uin, policyId uint64) error { + client, err := CAMClient() + if err == nil { + request := cam.NewAttachUserPolicyRequest() + request.AttachUin = common.Uint64Ptr(uin) + request.PolicyId = common.Uint64Ptr(policyId) + _, err := client.AttachUserPolicy(request) + if err != nil { + logger.Println.Error(fmt.Sprintf("为 %v 用户添加 %v 策略时报错,详细信息如下:", userName, policyName)) + logger.Println.Error(err.Error()) + } else { + logger.Println.Info(fmt.Sprintf("为 %v 用户添加 %v 策略成功。", userName, policyName)) + } + } + return err +} + +// 为用户启用控制台登录 + +func updateUserWithConsoleLogin(userName, password string) error { + client, err := CAMClient() + if err == nil { + request := cam.NewUpdateUserRequest() + request.Name = common.StringPtr(userName) + request.ConsoleLogin = common.Uint64Ptr(1) + request.Password = common.StringPtr(password) + request.NeedResetPassword = common.Uint64Ptr(0) + _, err := client.UpdateUser(request) + if err != nil { + logger.Println.Error(fmt.Sprintf("为 %v 用户创建 Web 控制台登录配置时报错,详细信息如下:", userName)) + logger.Println.Error(err.Error()) + } + } + return err +} + +// 为用户创建访问凭证 + +func createAccessKey(userName string, uin uint64) (*cam.AccessKeyDetail, error) { + client, err := CAMClient() + if err == nil { + request := cam.NewCreateAccessKeyRequest() + request.TargetUin = common.Uint64Ptr(uin) + response, err := client.CreateAccessKey(request) + if err != nil { + logger.Println.Error(fmt.Sprintf("为 %v 用户创建访问凭证时报错,详细信息如下:", userName)) + logger.Println.Error(err.Error()) + return nil, err + } else { + return response.Response.AccessKey, err + } + } else { + return nil, err + } +} + +// 获取用户 AppId + +func getUserAppId() (*cam.GetUserAppIdResponseParams, error) { + client, err := CAMClient() + if err == nil { + request := cam.NewGetUserAppIdRequest() + response, err := client.GetUserAppId(request) + if err != nil { + logger.Println.Error("获取用户 AppId 时报错,详细信息如下:") + logger.Println.Error(err.Error()) + return nil, err + } else { + return response.Response, err + } + } else { + return nil, err + } +} diff --git a/pkg/tencentCloud/utilsCOS.go b/pkg/tencentCloud/utilsCOS.go new file mode 100644 index 0000000..c519580 --- /dev/null +++ b/pkg/tencentCloud/utilsCOS.go @@ -0,0 +1,63 @@ +package tencentCloud + +import ( + "context" + "encoding/json" + "fmt" + "github.com/tencentyun/cos-go-sdk-v5" + "github.com/wgpsec/cloudsword/utils/global" + "github.com/wgpsec/cloudsword/utils/logger" + "strings" +) + +// 列存储桶 + +func listCOSBuckets() []cos.Bucket { + var ( + NextMarker string + buckets []cos.Bucket + ) + for { + response, _, err := COSClient(global.NULL, global.NULL, NextMarker).Service.Get(context.Background()) + if err != nil { + logger.Println.Error("列出存储桶时报错,详细信息如下:") + logger.Println.Error(err.Error()) + fmt.Println() + break + } else { + for _, bucket := range response.Buckets { + buckets = append(buckets, bucket) + } + if response.IsTruncated { + NextMarker = response.NextMarker + } else { + break + } + } + } + return buckets +} + +// 获取存储桶标签 + +func getBucketTagging(bucketName, region string) (string, error) { + response, _, err := COSClient(bucketName, region, global.NULL).Bucket.GetTagging(context.Background()) + if err != nil { + if strings.Contains(err.Error(), "The TagSet does not exist.") { + return global.NULL, err + } else { + logger.Println.Error(fmt.Sprintf("获取 %v 存储桶的标签信息时报错,详细信息如下:", bucketName)) + logger.Println.Error(err.Error()) + return global.NULL, err + } + } else { + jsonBytes, err := json.Marshal(response.TagSet) + if err != nil { + logger.Println.Error(fmt.Sprintf("解析 %v 存储桶的标签信息时报错,详细信息如下:", bucketName)) + logger.Println.Error(err.Error()) + return global.NULL, err + } else { + return string(jsonBytes), err + } + } +} diff --git a/pkg/tencentCloud/utilsCVM.go b/pkg/tencentCloud/utilsCVM.go new file mode 100644 index 0000000..7187ddf --- /dev/null +++ b/pkg/tencentCloud/utilsCVM.go @@ -0,0 +1,110 @@ +package tencentCloud + +import ( + "fmt" + "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common" + cvm "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cvm/v20170312" + "github.com/wgpsec/cloudsword/utils/global" + "github.com/wgpsec/cloudsword/utils/logger" + "sync" +) + +var CVMInstances []*cvm.Instance + +// 获取 CVM 区域 + +func CVMDescribeRegions() ([]string, error) { + var regions []string + CVMInstances = nil + region := global.GetBasicOptionValue(global.Region) + if region == global.All { + client, err := CVMClient(global.TencentCloudDefaultRegion) + if err == nil { + cvmRequest := cvm.NewDescribeRegionsRequest() + cvmRequest.SetScheme("https") + cvmResponse, err := client.DescribeRegions(cvmRequest) + if err != nil { + logger.Println.Error("获取 CVM 区域时报错,详细信息如下:") + logger.Println.Error(err.Error()) + return regions, err + } else { + for _, region := range cvmResponse.Response.RegionSet { + regions = append(regions, *region.Region) + } + return regions, err + } + } else { + return regions, err + } + } else { + regions = append(regions, region) + return regions, nil + } +} + +// 获取 CVM 弹性计算实例 + +func GetCVMResource() ([]*cvm.Instance, error) { + regions, err := CVMDescribeRegions() + if err == nil { + var ( + threads = 3 + wg sync.WaitGroup + ) + taskCh := make(chan string, threads) + for i := 0; i < threads; i++ { + wg.Add(1) + go func() { + err = CVMDescribeInstances(taskCh, &wg) + }() + } + for _, item := range regions { + taskCh <- item + } + close(taskCh) + wg.Wait() + return CVMInstances, err + } else { + return nil, err + } +} + +func CVMDescribeInstances(ch <-chan string, wg *sync.WaitGroup) error { + defer wg.Done() + var ( + err error + client *cvm.Client + response *cvm.DescribeInstancesResponse + limit int64 = 100 + offset int64 = 0 + ) + for region := range ch { + logger.Println.Info(fmt.Sprintf("正在获取 %s 区域下的腾讯云 CVM 资源信息", region)) + client, err = CVMClient(region) + if err == nil { + for { + request := cvm.NewDescribeInstancesRequest() + request.Limit = common.Int64Ptr(limit) + request.SetScheme("https") + response, err = client.DescribeInstances(request) + if err != nil { + logger.Println.Error(fmt.Sprintf("获取 %v 区域下的 CVM 弹性计算实例报错,详细信息如下:", region)) + logger.Println.Error(err.Error()) + break + } else { + if len(response.Response.InstanceSet) > 0 { + logger.Println.Warn(fmt.Sprintf("在 %s 区域下获取到 %d 条 CVM 资源", region, len(response.Response.InstanceSet))) + CVMInstances = append(CVMInstances, response.Response.InstanceSet...) + } + if len(response.Response.InstanceSet) == int(limit) { + offset = offset + limit + request.Offset = common.Int64Ptr(offset) + } else { + break + } + } + } + } + } + return err +} diff --git a/pkg/tencentCloud/utilsLH.go b/pkg/tencentCloud/utilsLH.go new file mode 100644 index 0000000..ec8d554 --- /dev/null +++ b/pkg/tencentCloud/utilsLH.go @@ -0,0 +1,110 @@ +package tencentCloud + +import ( + "fmt" + "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common" + lh "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/lighthouse/v20200324" + "github.com/wgpsec/cloudsword/utils/global" + "github.com/wgpsec/cloudsword/utils/logger" + "sync" +) + +var LHInstances []*lh.Instance + +// 获取 LH 区域 + +func LHDescribeRegions() ([]string, error) { + var regions []string + LHInstances = nil + region := global.GetBasicOptionValue(global.Region) + if region == global.All { + client, err := LHClient(global.TencentCloudDefaultRegion) + if err == nil { + lhRequest := lh.NewDescribeRegionsRequest() + lhRequest.SetScheme("https") + lhResponse, err := client.DescribeRegions(lhRequest) + if err != nil { + logger.Println.Error("获取 LH 区域时报错,详细信息如下:") + logger.Println.Error(err.Error()) + return regions, err + } else { + for _, region := range lhResponse.Response.RegionSet { + regions = append(regions, *region.Region) + } + return regions, err + } + } else { + return regions, err + } + } else { + regions = append(regions, region) + return regions, nil + } +} + +// 获取 LH 弹性计算实例 + +func GetLHResource() ([]*lh.Instance, error) { + regions, err := LHDescribeRegions() + if err == nil { + var ( + threads = 3 + wg sync.WaitGroup + ) + taskCh := make(chan string, threads) + for i := 0; i < threads; i++ { + wg.Add(1) + go func() { + err = LHDescribeInstances(taskCh, &wg) + }() + } + for _, item := range regions { + taskCh <- item + } + close(taskCh) + wg.Wait() + return LHInstances, err + } else { + return nil, err + } +} + +func LHDescribeInstances(ch <-chan string, wg *sync.WaitGroup) error { + defer wg.Done() + var ( + err error + client *lh.Client + response *lh.DescribeInstancesResponse + limit int64 = 100 + offset int64 = 0 + ) + for region := range ch { + logger.Println.Info(fmt.Sprintf("正在获取 %s 区域下的腾讯云 LH 资源信息", region)) + client, err = LHClient(region) + if err == nil { + for { + request := lh.NewDescribeInstancesRequest() + request.Limit = common.Int64Ptr(limit) + request.SetScheme("https") + response, err = client.DescribeInstances(request) + if err != nil { + logger.Println.Error(fmt.Sprintf("获取 %v 区域下的 LH 弹性计算实例报错,详细信息如下:", region)) + logger.Println.Error(err.Error()) + break + } else { + if len(response.Response.InstanceSet) > 0 { + logger.Println.Warn(fmt.Sprintf("在 %s 区域下获取到 %d 条 LH 资源", region, len(response.Response.InstanceSet))) + LHInstances = append(LHInstances, response.Response.InstanceSet...) + } + if len(response.Response.InstanceSet) == int(limit) { + offset = offset + limit + request.Offset = common.Int64Ptr(offset) + } else { + break + } + } + } + } + } + return err +} diff --git a/static/1201_aliyun_oss_list_buckets.gif b/static/1201_aliyun_oss_list_buckets.gif new file mode 100644 index 0000000..2bfe5b3 Binary files /dev/null and b/static/1201_aliyun_oss_list_buckets.gif differ diff --git a/static/WgpSecBot.png b/static/WgpSecBot.png new file mode 100644 index 0000000..3ec7472 Binary files /dev/null and b/static/WgpSecBot.png differ diff --git a/static/add_group.png b/static/add_group.png new file mode 100644 index 0000000..f86e926 Binary files /dev/null and b/static/add_group.png differ diff --git a/static/buy-coffee.png b/static/buy-coffee.png new file mode 100644 index 0000000..7b86b69 Binary files /dev/null and b/static/buy-coffee.png differ diff --git a/static/header.jpg b/static/header.jpg new file mode 100755 index 0000000..1cf88f3 Binary files /dev/null and b/static/header.jpg differ diff --git a/static/teamssix.png b/static/teamssix.png new file mode 100644 index 0000000..2ea3fa3 Binary files /dev/null and b/static/teamssix.png differ diff --git a/static/wgpsec.png b/static/wgpsec.png new file mode 100644 index 0000000..0219f0d Binary files /dev/null and b/static/wgpsec.png differ diff --git a/utils/global/global.go b/utils/global/global.go new file mode 100644 index 0000000..38b624e --- /dev/null +++ b/utils/global/global.go @@ -0,0 +1,259 @@ +package global + +import ( + "fmt" + "strconv" +) + +const ( + + // 版本信息 + Team = "WgpSec" + Name = "CloudSword" + Version = "0.0.1" + UpdateDate = "2024.12.21" + + // 云提供商 + + Aliyun = "aliyun" + TencentCloud = "tencent_cloud" + HuaweiCloud = "huawei_cloud" + BaiduCloud = "baidu_cloud" + + // 基础选项 + + AKId = "ak_id" + AKSecret = "ak_secret" + AKToken = "ak_token" + //UserAgent = "user_agent" + //Proxy = "Proxy" + Detail = "detail" + Region = "region" + ResultFilePath = "result_file_path" + MaxResults = "max_results" + QueryValue = "query_value" + BucketName = "bucket_name" + UserName = "user_name" + Description = "description" + PolicyName = "policy_name" + Webhook = "webhook" + + // 环境变量 + + CloudSwordAccessKeyID = "CLOUD_SWORD_ACCESS_KEY_ID" + CloudSwordAccessKeySecret = "CLOUD_SWORD_ACCESS_KEY_SECRET" + CloudSwordSecurityToken = "CLOUD_SWORD_SECURITY_TOKEN" + //CloudSwordUserAgent = "CLOUD_SWORD_USER_AGENT" + //CloudSwordProxy = "CLOUD_SWORD_PROXY" + CloudSwordDetail = "CLOUD_SWORD_DETAIL" + + // 布尔值 + + True = "true" + False = "false" + + // 默认区域 + + AliyunDefaultRegion = "cn-hangzhou" + TencentCloudDefaultRegion = "ap-guangzhou" + HuaweiCloudDefaultRegion = "cn-north-4" + + // 其他 + + DefaultPrefix = Name + " > " + TeamsSix = "TeamsSix" + Tab = "\t" + NULL = "" + All = "all" +) + +type Module struct { + ID int + Provider Provider + Name string + ModuleProvider string + Introduce string + Level int + Info string + BasicOptions []BasicOptions +} + +type Provider struct { + Name string + EnName string +} + +type BasicOptions struct { + Key string + Value string + Required bool + Introduce string +} + +type BasicOptionsWithId struct { + Id int + BasicOptions []BasicOptions +} + +var BasicOptionsWithIds []BasicOptionsWithId + +// BasicOptions + +var BasicOptionsFull []BasicOptions + +var BasicOptionsDefault = []BasicOptions{ + { + Key: AKId, + Value: "", + Required: true, + Introduce: "访问凭证 ID", + }, + { + Key: AKSecret, + Value: "", + Required: true, + Introduce: "访问凭证 Secret", + }, + { + Key: AKToken, + Value: "", + Required: false, + Introduce: "可选,访问凭证的临时令牌部分", + }, + //{ + // Key: Proxy, + // Value: "", + // Required: false, + // Introduce: "可选,代理访问,支持 Socks5、HTTPS 和 HTTP", + //}, + //{ + // Key: UserAgent, + // Value: fmt.Sprintf("%s/%s (%s; %s; %s)", Name, Version, runtime.Version(), runtime.GOOS, runtime.GOARCH), + // Required: true, + // Introduce: "可选,请求 UA 头", + //}, +} + +var BasicOptionDetail = BasicOptions{ + Key: Detail, + Value: False, + Required: true, + Introduce: "设置详细输出模式(true 或 false)", +} + +var BasicOptionRegion = BasicOptions{ + Key: Region, + Value: All, + Required: true, + Introduce: "设置要列出的区域", +} + +var BasicOptionResultFilePath = BasicOptions{ + Key: ResultFilePath, + Value: NULL, + Required: false, + Introduce: "设置结果导出路径,默认导出格式为 xlsx 格式", +} + +var BasicOptionMaxResults = BasicOptions{ + Key: MaxResults, + Value: strconv.Itoa(50), + Required: true, + Introduce: "设置最大结果数量输出限制,-1 代表不限制", +} + +var BasicOptionQueryValue = BasicOptions{ + Key: QueryValue, + Value: NULL, + Required: true, + Introduce: "设置要查询的内容", +} + +var BasicOptionBucketName = BasicOptions{ + Key: BucketName, + Value: NULL, + Required: true, + Introduce: "设置 Bucket 名称", +} + +var BasicOptionUserName = BasicOptions{ + Key: UserName, + Value: NULL, + Required: true, + Introduce: "设置用户名称", +} + +var BasicOptionDescription = BasicOptions{ + Key: Description, + Value: fmt.Sprintf("此资源由%v创建", Name), + Required: false, + Introduce: "设置资源描述", +} + +var BasicOptionPolicyName = BasicOptions{ + Key: PolicyName, + Value: NULL, + Required: true, + Introduce: "设置策略名称", +} + +var BasicOptionWebhook = BasicOptions{ + Key: Webhook, + Value: NULL, + Required: true, + Introduce: "设置通知 Webhook 地址。", +} + +//var MoreInfo = ` +//Proxy 代理格式配置示例: +// +// http://127.0.0.1:8080 +// https://127.0.0.1:8080 +// socks5://127.0.0.1:1080 +// socks5://user:password@127.0.0.1:1080 +//` + +func init() { + BasicOptionsFull = append(BasicOptionsFull, BasicOptionsDefault...) + BasicOptionsFull = append( + BasicOptionsFull, + BasicOptionDetail, + BasicOptionRegion, + BasicOptionQueryValue, + BasicOptionMaxResults, + BasicOptionResultFilePath, + BasicOptionBucketName, + BasicOptionUserName, + BasicOptionDescription, + BasicOptionPolicyName, + BasicOptionWebhook, + ) +} + +func GetBasicOptionsWithId(id int) []BasicOptions { + var basicOptions []BasicOptions + for _, option := range BasicOptionsWithIds { + if option.Id == id { + basicOptions = option.BasicOptions + } + } + return basicOptions +} + +func GetBasicOptionValue(key string) string { + var value string + for _, option := range BasicOptionsFull { + if option.Key == key { + value = option.Value + } + } + return value +} + +func UpdateBasicOptionValue(key string, value string) { + for k, option := range BasicOptionsFull { + if option.Key == key { + BasicOptionsFull[k].Value = value + } + } +} diff --git a/utils/http.go b/utils/http.go new file mode 100644 index 0000000..5dc5bf1 --- /dev/null +++ b/utils/http.go @@ -0,0 +1,80 @@ +package utils + +import ( + "context" + "fmt" + "github.com/wgpsec/cloudsword/utils/global" + "github.com/wgpsec/cloudsword/utils/logger" + "strings" + + "github.com/google/go-github/v39/github" + "github.com/hashicorp/go-version" +) + +//type UserAgentTransport struct { +// Base http.RoundTripper +// UserAgent string +//} +// +//func HttpTransport() func(*http.Transport) { +// proxyStr := global.GetBasicOptionValue(global.Proxy) +// if proxyStr == "" { +// return nil +// } +// parsedURL, err := url.Parse(proxyStr) +// logger.Println.Error(err.Error()) +// +// return func(transport *http.Transport) { +// switch parsedURL.Scheme { +// case "http", "https": +// transport = &http.Transport{ +// Proxy: http.ProxyURL(parsedURL), +// } +// case "socks5": +// auth := &proxy.Auth{} +// if parsedURL.User != nil { +// auth.User = parsedURL.User.Username() +// auth.Password, _ = parsedURL.User.Password() +// } else { +// auth = nil +// } +// +// dialer, err := proxy.SOCKS5("tcp", parsedURL.Host, auth, proxy.Direct) +// if err == nil { +// transport.DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) { +// return dialer.(proxy.ContextDialer).DialContext(ctx, network, addr) +// } +// } else { +// logger.Println.Error(err.Error()) +// } +// default: +// logger.Println.Error("代理类型错误,请输入正确的代理类型。") +// } +// } +//} +// +//func (t *UserAgentTransport) RoundTrip(req *http.Request) (*http.Response, error) { +// req.Header.Set("User-Agent", t.UserAgent) +// return t.Base.RoundTrip(req) +//} + +// 检查是否有新版本 + +func CheckVersion() { + currentVer := global.Version + client := github.NewClient(nil) + release, _, err := client.Repositories.GetLatestRelease(context.Background(), global.Team, global.Name) + if err == nil { + current, err := version.NewVersion(currentVer) + if err == nil { + if release != nil && release.TagName != nil { + releaseVer := strings.TrimPrefix(*release.TagName, "v") + latest, err := version.NewVersion(releaseVer) + if err == nil && latest.GreaterThan(current) { + logger.Println.Warnf(fmt.Sprintf("发现新版本! 当前版本: %s, 最新版本: %s\n\n", currentVer, releaseVer)) + return + } + } + } + } +} diff --git a/utils/logger/logger.go b/utils/logger/logger.go new file mode 100644 index 0000000..34c9050 --- /dev/null +++ b/utils/logger/logger.go @@ -0,0 +1,67 @@ +package logger + +import ( + "fmt" + "time" + + "github.com/fatih/color" +) + +type Logger struct{} + +var Println = &Logger{} +var Printf = &Logger{} + +func (l *Logger) logWithNewline(level string, levelColor *color.Color, format string, args ...interface{}) { + timestamp := time.Now().Format("2006-01-02 15:04:05") + levelInfo := levelColor.Sprintf("[%s] %s", level, timestamp) + message := fmt.Sprintf(format, args...) + fmt.Printf("%s %s\n", levelInfo, message) +} + +func (l *Logger) logWithoutNewline(level string, levelColor *color.Color, format string, args ...interface{}) { + timestamp := time.Now().Format("2006-01-02 15:04:05") + levelInfo := levelColor.Sprintf("[%s] %s", level, timestamp) + message := fmt.Sprintf(format, args...) + fmt.Printf("%s %s", levelInfo, message) +} + +func (l *Logger) Debug(format string, args ...interface{}) { + l.logWithNewline("DEBUG", color.New(color.FgBlue), format, args...) +} + +func (l *Logger) Info(format string, args ...interface{}) { + l.logWithNewline("INFO", color.New(color.FgGreen), format, args...) +} + +func (l *Logger) Warn(format string, args ...interface{}) { + l.logWithNewline("WARN", color.New(color.FgYellow), format, args...) +} + +func (l *Logger) Error(format string, args ...interface{}) { + l.logWithNewline("ERROR", color.New(color.FgRed), format, args...) +} + +func (l *Logger) Fatal(format string, args ...interface{}) { + l.logWithNewline("FATAL", color.New(color.FgMagenta), format, args...) +} + +func (l *Logger) Debugf(format string, args ...interface{}) { + l.logWithoutNewline("DEBUG", color.New(color.FgBlue), format, args...) +} + +func (l *Logger) Infof(format string, args ...interface{}) { + l.logWithoutNewline("INFO", color.New(color.FgGreen), format, args...) +} + +func (l *Logger) Warnf(format string, args ...interface{}) { + l.logWithoutNewline("WARN", color.New(color.FgYellow), format, args...) +} + +func (l *Logger) Errorf(format string, args ...interface{}) { + l.logWithoutNewline("ERROR", color.New(color.FgRed), format, args...) +} + +func (l *Logger) Fatalf(format string, args ...interface{}) { + l.logWithoutNewline("FATAL", color.New(color.FgMagenta), format, args...) +} diff --git a/utils/table.go b/utils/table.go new file mode 100644 index 0000000..302bec2 --- /dev/null +++ b/utils/table.go @@ -0,0 +1,50 @@ +package utils + +import ( + "github.com/charmbracelet/bubbles/table" + "github.com/charmbracelet/lipgloss" +) + +func GenerateTable(headers []string, rows [][]string, columnWidths []int) string { + // 定义表格样式 + baseStyle := lipgloss.NewStyle(). + BorderStyle(lipgloss.NormalBorder()). + BorderForeground(lipgloss.Color("240")) + + // 创建表格 + t := table.New( + table.WithColumns(createColumns(headers, columnWidths)), + table.WithRows(createRows(rows)), + table.WithFocused(false), + table.WithHeight(len(rows)+1), + ) + + // 设置表格样式 + s := table.DefaultStyles() + s.Header = s.Header. + BorderStyle(lipgloss.NormalBorder()). + BorderForeground(lipgloss.Color("240")). + BorderBottom(true). + Bold(true) + s.Selected = s.Selected.Foreground(lipgloss.Color("255")) + t.SetStyles(s) + + // 直接渲染并打印表格 + return baseStyle.Render(t.View()) +} + +func createColumns(headers []string, widths []int) []table.Column { + columns := make([]table.Column, len(headers)) + for i, header := range headers { + columns[i] = table.Column{Title: header, Width: widths[i]} + } + return columns +} + +func createRows(rows [][]string) []table.Row { + tableRows := make([]table.Row, len(rows)) + for i, row := range rows { + tableRows[i] = row + } + return tableRows +} diff --git a/utils/text.go b/utils/text.go new file mode 100644 index 0000000..4ffbdd5 --- /dev/null +++ b/utils/text.go @@ -0,0 +1,239 @@ +package utils + +import ( + "fmt" + "github.com/AlecAivazis/survey/v2" + "github.com/wgpsec/cloudsword/utils/global" + "github.com/wgpsec/cloudsword/utils/logger" + "math/rand" + "sort" + "strings" + "time" +) + +func GetModuleName(m global.Module) string { + return fmt.Sprintf("%d_%s_%s", m.ID, m.Provider.EnName, m.Name) +} + +func GetModule(id, level int, provider, name, moduleProvider, introduce, moreInfo string, basicOptions []global.BasicOptions) global.Module { + var ( + ids []int + ) + sort.Slice(basicOptions, func(i, j int) bool { + return basicOptions[i].Key < basicOptions[j].Key + }) + + // update global.BasicOptionsWithIds + for _, BasicOptionsWithId := range global.BasicOptionsWithIds { + ids = append(ids, BasicOptionsWithId.Id) + } + if !InInt(id, ids) { + global.BasicOptionsWithIds = append(global.BasicOptionsWithIds, global.BasicOptionsWithId{ + Id: id, + BasicOptions: basicOptions, + }) + } + + for n, BasicOptionsWithId := range global.BasicOptionsWithIds { + if BasicOptionsWithId.Id == id { + for _, fullBasicOption := range global.BasicOptionsFull { + for m, basicOptionWithId := range BasicOptionsWithId.BasicOptions { + if basicOptionWithId.Key == fullBasicOption.Key { + global.BasicOptionsWithIds[n].BasicOptions[m].Value = global.GetBasicOptionValue(fullBasicOption.Key) + } + } + } + } + } + + return global.Module{ + ID: id, + Provider: getProvider(provider), + Name: name, + ModuleProvider: moduleProvider, + Introduce: introduce, + Level: level, + Info: generateDetailedInformation(id, level, getProvider(provider).Name, name, moduleProvider, introduce, + GenerateTable(GetBasicOptions(global.GetBasicOptionsWithId(id))), moreInfo), + BasicOptions: global.GetBasicOptionsWithId(id), + } +} + +func GetBasicOptions(basicOptions []global.BasicOptions) ([]string, [][]string, []int) { + header := []string{"名称", "必选", "当前设置", "描述"} + rows := [][]string{} + for _, b := range basicOptions { + if b.Required { + rows = append(rows, []string{b.Key, global.True, b.Value, b.Introduce}) + } else { + rows = append(rows, []string{b.Key, global.False, b.Value, b.Introduce}) + } + } + return header, rows, []int{15, 8, 40, 50} +} + +func generateDetailedInformation(id, level int, provider, name, moduleProvider, introduce, basicOptions, moreInfo string) string { + header := []string{"类型", "信息"} + rows := [][]string{} + rows = append(rows, []string{"ID", fmt.Sprintf("%d", id)}) + rows = append(rows, []string{"云提供商", provider}) + rows = append(rows, []string{"名称", name}) + rows = append(rows, []string{"推荐评级", strings.Repeat("★", level)}) + rows = append(rows, []string{"模块提供者", moduleProvider}) + rows = append(rows, []string{"模块简介", introduce}) + + return fmt.Sprintf(` + 介绍: +%s + + 操作: +%s +%s`, GenerateTable(header, rows, []int{15, 40}), basicOptions, moreInfo) +} + +func getProvider(provider string) global.Provider { + var p global.Provider + switch provider { + case global.Aliyun: + p = global.Provider{"阿里云", global.Aliyun} + case global.TencentCloud: + p = global.Provider{"腾讯云", global.TencentCloud} + case global.HuaweiCloud: + p = global.Provider{"华为云", global.HuaweiCloud} + case global.BaiduCloud: + p = global.Provider{"百度云", global.BaiduCloud} + } + return p +} + +// 文本处理 + +func Contains(arr []string, char string) bool { + for _, c := range arr { + if c == char { + return true + } + } + return false +} + +func InInt(i int, int_array []int) bool { + for _, element := range int_array { + if i == element { + return true + } + } + return false +} + +// 将空指针转为空字符串 + +func ConvertedNullPointer(p *string) string { + if p == nil { + return global.NULL + } else { + return *p + } +} + +// 如果为空则不输出 + +func PrintfNotNilString(p string, s *string) { + if s != nil { + fmt.Printf("%s%s\n", p, *s) + } +} + +func PrintfNotNilInt32(p string, s *int32) { + if s != nil { + fmt.Printf("%s%d\n", p, *s) + } +} + +func PrintfNotNilInt64(p string, s *int64) { + if s != nil { + fmt.Printf("%s%d\n", p, *s) + } +} + +func PrintfNotNilUInt64(p string, s *uint64) { + if s != nil { + fmt.Printf("%s%d\n", p, *s) + } +} + +func PrintfNotNilFloat64(p string, s *float64) { + if s != nil { + fmt.Printf("%s%f\n", p, *s) + } +} + +// 将 bytes 类型转为人类可阅读的形式 + +func FormatBytes(bytes int64) string { + const unit = 1024 + if bytes < unit { + return fmt.Sprintf("%d B", bytes) + } + div, exp := int64(unit), 0 + for n := bytes / unit; n >= unit; n /= unit { + div *= unit + exp++ + } + return fmt.Sprintf("%.1f %cB", float64(bytes)/float64(div), "KMGTPE"[exp]) +} + +// 让用户判断是否继续操作 + +func SurveyConfirm(message string) bool { + var bool_ bool + prompt := &survey.Confirm{ + Message: message, + Default: true, + } + err := survey.AskOne(prompt, &bool_) + logger.Println.Error(err.Error()) + return bool_ +} + +// 生成随机名称 + +func GenerateRandomName(s string) string { + return fmt.Sprintf("CloudSword%s%s", s, generateNumber(6)) +} + +// 生成指定位数的数字 +func generateNumber(length int) string { + const hexChars = "0123456789" + var sb strings.Builder + sb.Grow(length) + + for i := 0; i < length; i++ { + sb.WriteByte(hexChars[rand.Intn(len(hexChars))]) + } + + return sb.String() +} + +// 创建随机密码 + +func GenerateRandomPasswords() string { + rand.Seed(time.Now().UnixNano()) + digits := "0123456789" + specials := "%@#$" + all := "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + + "abcdefghijklmnopqrstuvwxyz" + + digits + specials + length := 16 + buf := make([]byte, length) + buf[0] = digits[rand.Intn(len(digits))] + buf[1] = specials[rand.Intn(len(specials))] + for i := 2; i < length; i++ { + buf[i] = all[rand.Intn(len(all))] + } + rand.Shuffle(len(buf), func(i, j int) { + buf[i], buf[j] = buf[j], buf[i] + }) + str := string(buf) + return str +} diff --git a/utils/time.go b/utils/time.go new file mode 100644 index 0000000..223c14c --- /dev/null +++ b/utils/time.go @@ -0,0 +1,72 @@ +package utils + +import ( + "fmt" + "github.com/wgpsec/cloudsword/utils/global" + "github.com/wgpsec/cloudsword/utils/logger" + "time" +) + +func FormatTime(t *time.Time) string { + if t == nil { + return "" + } + return t.Format("2006-01-02 15:04:05") +} + +func GetUTC8TimeType1(t string) (string, error) { + inputTime, err := time.Parse("2006-01-02T15:04Z", t) + if err != nil { + logger.Println.Error(fmt.Sprintf("解析 %v 时间格式时报错,详细信息如下:", t)) + logger.Println.Error(err.Error()) + return global.NULL, err + } else { + loc, err := time.LoadLocation("Asia/Shanghai") + if err != nil { + logger.Println.Error("修改时间地区时报错,详细信息如下:") + logger.Println.Error(err.Error()) + return global.NULL, err + } else { + utc8Time := inputTime.In(loc) + return utc8Time.Format("2006-01-02 15:04:05"), err + } + } +} + +func GetUTC8TimeType2(t string) (string, error) { + inputTime, err := time.Parse("2006-01-02T15:04:05Z", t) + if err != nil { + logger.Println.Error(fmt.Sprintf("解析 %v 时间格式时报错,详细信息如下:", t)) + logger.Println.Error(err.Error()) + return global.NULL, err + } else { + loc, err := time.LoadLocation("Asia/Shanghai") + if err != nil { + logger.Println.Error("修改时间地区时报错,详细信息如下:") + logger.Println.Error(err.Error()) + return global.NULL, err + } else { + utc8Time := inputTime.In(loc) + return utc8Time.Format("2006-01-02 15:04:05"), err + } + } +} + +func GetUTC8TimeType3(t string) (string, error) { + inputTime, err := time.Parse("2006-01-02T15:04:05.999Z", t) + if err != nil { + logger.Println.Error(fmt.Sprintf("解析 %v 时间格式时报错,详细信息如下:", t)) + logger.Println.Error(err.Error()) + return global.NULL, err + } else { + loc, err := time.LoadLocation("Asia/Shanghai") + if err != nil { + logger.Println.Error("修改时间地区时报错,详细信息如下:") + logger.Println.Error(err.Error()) + return global.NULL, err + } else { + utc8Time := inputTime.In(loc) + return utc8Time.Format("2006-01-02 15:04:05"), err + } + } +}