├── .github
├── ISSUE_TEMPLATE
│ ├── bug.yml
│ ├── feat.yml
│ └── perf.yml
└── workflows
│ └── release.yaml
├── .gitignore
├── .goreleaser.yaml
├── CHANGELOG.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── cmd
├── banner.go
├── configFile.go
├── options.go
└── runner.go
├── go.mod
├── go.sum
├── main.go
├── pkg
├── inventory
│ └── inventory.go
├── providers
│ ├── aliyun
│ │ ├── aliyun.go
│ │ ├── domain.go
│ │ ├── ecs.go
│ │ ├── fc.go
│ │ ├── fc3.go
│ │ ├── oss.go
│ │ └── rds.go
│ ├── baidu
│ │ ├── baidu.go
│ │ ├── bcc.go
│ │ └── bos.go
│ ├── huawei
│ │ ├── huawei.go
│ │ └── obs.go
│ ├── liantong
│ │ ├── liantong.go
│ │ └── oss.go
│ ├── qiniu
│ │ ├── kodo.go
│ │ └── qiniu.go
│ ├── tencent
│ │ ├── cos.go
│ │ ├── cvm.go
│ │ ├── lh.go
│ │ └── tencent.go
│ ├── tianyi
│ │ ├── oos.go
│ │ └── tianyi.go
│ └── yidong
│ │ ├── eos.go
│ │ └── yidong.go
└── schema
│ ├── schema.go
│ └── validate
│ └── validate.go
├── static
├── 39155974.jpeg
├── 41125338.png
├── 49087564.jpeg
├── buy-coffee.png
├── lc-httpx.png
├── list-cloud-run.png
├── logo.png
├── teamssix.png
└── wgpsec.png
└── utils
├── const.go
└── utils.go
/.github/ISSUE_TEMPLATE/bug.yml:
--------------------------------------------------------------------------------
1 | name: Bug 反馈
2 | description: 提交一个 issue 帮助改进这个项目
3 | title: "[Bug] 在这里输入你的标题"
4 | labels: ["bug"]
5 | assignees:
6 | - teamssix
7 | body:
8 | - type: markdown
9 | attributes:
10 | value: |
11 | 感谢您花时间提交这份 issue
12 | - type: textarea
13 | id: what-happened
14 | attributes:
15 | label: 描述你遇到的问题
16 | value: "详细描述你所遇到的问题"
17 | validations:
18 | required: true
19 | - type: textarea
20 | attributes:
21 | label: 复现步骤
22 | description: 复现这个问题的步骤
23 | placeholder: |
24 | 1. 在 xxx 情况下
25 | 2. 执行了 xxx 命令
26 | 3. 出现了 xxx 错误
27 | validations:
28 | required: true
29 | - type: dropdown
30 | id: system
31 | attributes:
32 | label: 操作系统
33 | description: 你在哪个操作系统下运行的 LC ?
34 | options:
35 | - MacOS
36 | - Linux
37 | - Windows
38 | validations:
39 | required: true
40 | - type: dropdown
41 | id: system-type
42 | attributes:
43 | label: 系统类型
44 | description: 你在哪个系统类型下运行的 LC ?
45 | options:
46 | - amd64
47 | - amd32
48 | - arm64
49 | - arm32
50 | validations:
51 | required: true
52 | - type: dropdown
53 | id: cf-version
54 | attributes:
55 | label: LC 版本
56 | description: 你运行的是 LC 的哪个版本?
57 | options:
58 | - 最新的 (Latest)
59 | - 0.0.1
60 | validations:
61 | required: true
62 | - type: textarea
63 | attributes:
64 | label: 补充信息
65 | description: |
66 | 链接?参考资料?任何可以给我们提供更多关于你所遇到的问题的背景资料的东西
67 |
68 | 提示:你可以通过点击这个区域来突出显示它,然后将文件拖入,从而附上图片或其他文件。
69 | validations:
70 | required: false
71 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feat.yml:
--------------------------------------------------------------------------------
1 | name: 新功能反馈
2 | description: 提交一个功能需求帮助完善这个项目
3 | title: "[Feat] 在这里输入你的标题"
4 | labels: ["enhancement"]
5 | assignees:
6 | - teamssix
7 | body:
8 | - type: markdown
9 | attributes:
10 | value: |
11 | 感谢您花时间提交这份 issue
12 | - type: textarea
13 | id: new-features
14 | attributes:
15 | label: 描述你希望增加的功能
16 | value: "详细描述你希望增加的功能,并且描述为什么想要增加这个功能以及意义,描述的越完善该反馈越有可能被采纳。"
17 | validations:
18 | required: true
19 | - type: textarea
20 | attributes:
21 | label: 补充信息 (Anything else?)
22 | description: |
23 | 链接?参考资料?任何可以给我们提供更多关于你所遇到的问题的背景资料的东西。
24 |
25 | 提示:你可以通过点击这个区域来突出显示它,然后将文件拖入,从而附上图片或其他文件。
26 | validations:
27 | required: false
28 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/perf.yml:
--------------------------------------------------------------------------------
1 | name: 功能优化反馈
2 | description: 提交一个功能优化需求帮助改进这个项目
3 | title: "[Perf] 在这里输入你的标题"
4 | labels: ["performance"]
5 | assignees:
6 | - teamssix
7 | body:
8 | - type: markdown
9 | attributes:
10 | value: |
11 | 感谢您花时间提交这份 issue
12 | - type: textarea
13 | id: performance
14 | attributes:
15 | label: 描述你希望优化的功能
16 | value: "详细描述你希望优化的功能,并且描述为什么想要对这个功能进行优化,描述的越完善该反馈越有可能被采纳。"
17 | validations:
18 | required: true
19 | - type: textarea
20 | attributes:
21 | label: 补充信息
22 | description: |
23 | 链接?参考资料?任何可以给我们提供更多关于你所遇到的问题的背景资料的东西
24 |
25 | 提示:你可以通过点击这个区域来突出显示它,然后将文件拖入,从而附上图片或其他文件。
26 | validations:
27 | required: false
28 |
--------------------------------------------------------------------------------
/.github/workflows/release.yaml:
--------------------------------------------------------------------------------
1 | name: Release
2 | on:
3 | push:
4 | tags:
5 | - 'v*'
6 | env:
7 | GO_VERSION: 1.22.1
8 |
9 | jobs:
10 | goreleaser:
11 | runs-on: ubuntu-latest
12 | steps:
13 | - name: Checkout Source Code
14 | uses: actions/checkout@v3
15 | with:
16 | fetch-depth: 0
17 | - name: Setup Go Environment
18 | uses: actions/setup-go@v3
19 | with:
20 | go-version: ${{ env.GO_VERSION }}
21 |
22 | - name: Run GoReleaser
23 | uses: goreleaser/goreleaser-action@v6
24 | with:
25 | version: 1.26.2
26 | args: release --clean
27 | env:
28 | CGO_ENABLED: 0
29 | GITHUB_TOKEN: ${{ secrets.RELEASE_GH_TOKEN }}
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # If you prefer the allow list template instead of the deny list, see community template:
2 | # https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
3 | #
4 | # Binaries for programs and plugins
5 | *.exe
6 | *.exe~
7 | *.dll
8 | *.so
9 | *.dylib
10 |
11 | # Test binary, built with `go test -c`
12 | *.test
13 |
14 | # Output of the go coverage tool, specifically when used with LiteIDE
15 | *.out
16 |
17 | # Dependency directories (remove the comment below to include it)
18 | # vendor/
19 | tmp
20 | test
21 | build
22 |
23 | # Go workspace file
24 | go.work
25 | .DS_Store
26 | .idea
27 | lc
28 | lc.bak
29 |
--------------------------------------------------------------------------------
/.goreleaser.yaml:
--------------------------------------------------------------------------------
1 | project_name: lc
2 | before:
3 | hooks:
4 | - go mod tidy
5 | - go generate ./...
6 | builds:
7 | - env:
8 | - CGO_ENABLED=0
9 | goos:
10 | - freebsd
11 | - darwin
12 | - windows
13 | - linux
14 | goarch:
15 | - 386
16 | - amd64
17 | - arm
18 | - arm64
19 | archives:
20 | - name_template: "{{ .ProjectName }}_{{ .Tag }}_{{ .Os }}_{{ .Arch }}"
21 | id: homebrew
22 | files:
23 | - README.md
24 | - CHANGELOG.md
25 | - LICENSE
26 | - static/*
27 | format_overrides:
28 | - goos: windows
29 | format: zip
30 | checksum:
31 | name_template: "checksums.txt"
32 | snapshot:
33 | name_template: "{{ incpatch .Version }}"
34 | changelog:
35 | sort: asc
36 | filters:
37 | exclude:
38 | - "^docs:"
39 | - "^doc:"
40 | - "^ci:"
41 | - "^Merge pull request"
42 | brews:
43 | - ids:
44 | - homebrew
45 | name: lc
46 | tap:
47 | owner: wgpsec
48 | name: homebrew-tap
49 | branch: master
50 | folder: Formula
51 | url_template: "https://github.com/wgpsec/lc/releases/download/{{ .Tag }}/{{ .ArtifactName }}"
52 | homepage: "https://wiki.teamssix.com/lc"
53 | description: "LC (List Cloud) is a multi-cloud attack surface asset enumeration tool."
54 | skip_upload: auto
55 | install: |-
56 | bin.install "lc"
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Change Log
2 |
3 | ## [v1.1.0](https://github.com/wgpsec/lc/releases/tag/v1.1.0) 2024.10.6
4 |
5 | * 支持列出阿里云 FC 函数计算服务
6 | * 支持列出阿里云域名服务
7 | * 支持在列出时指定要列出的云服务类型
8 | * 修复阿里云 ECS 在绑定 EIP 时无法列出 IP 的 Bug
9 |
10 | ## [v1.0.1](https://github.com/wgpsec/lc/releases/tag/v1.0.1) 2024.5.15
11 |
12 | * 支持列出移动云 EOS 对象存储服务
13 | * 修复了一个 Bug
14 |
15 | ## [v1.0.0](https://github.com/wgpsec/lc/releases/tag/v1.0.0) 2024.4.20
16 |
17 | * 支持列出阿里云 RDS 数据库服务
18 | * 支持列出腾讯云 COS 对象存储服务
19 | * 支持列出七牛云 Kodo 对象存储服务
20 | * 增加详细日志输出
21 |
22 | ## [v0.0.1](https://github.com/wgpsec/lc/releases/tag/v0.0.1) 2024.4.6
23 |
24 | * 首次提交代码
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | ## 为 LC 贡献代码
2 |
3 | 首先很高兴你看到这个,非常欢迎您和我一起完善 LC,让我们一起编写一个好用的、有价值的、有意义的工具。
4 |
5 | ### 是否发现了 bug?
6 |
7 | 如果你发现了程序中的 bug,建议可以先在 [issue](https://github.com/wgpsec/lc/issues) 中进行搜索,看看以前有没有人提交过相同的 bug。
8 |
9 | ### 发现了 bug,并准备自己为 LC 打补丁
10 |
11 | 如果你发现了 bug,而且准备自己去修补它的话,则可以先把 LC Fork 到自己的仓库中,然后修复完后,提交 PR 到 LC 的 dev 分支下,另外记得在 PR 中详细说明 bug 的产生条件以及你是如何修复的,这可以帮助你更快的使这个 PR 通过。
12 |
13 | ### 为 LC 增加新功能
14 |
15 | 如果你发现 LC 现在的功能不能满足自己的需求,并打算自己去增加上这个功能,则可以先把 LC Fork 到自己的仓库中,然后编写完相应的功能后,提交 PR 到 LC 的 dev 分支下,另外记得在 PR 中详细说明增加的功能以及为什么要增加它,这可以帮助你更快的使这个 PR 通过。
16 |
17 | 建议师傅在增加新功能后,可以去完善对应的操作手册,操作手册项目地址:[github.com/teamssix/twiki](https://github.com/teamssix/twiki),先 fork 操作手册项目,然后在 [github.com/teamssix/twiki/tree/main/docs/lc](https://github.com/teamssix/twiki/tree/main/docs/lc) 目录下编辑 LC 操作手册的文档,最后提 pr 到 T Wiki 项目的 beta 分支下就行。
18 |
19 | ## 使你的 PR 更规范
20 |
21 | ### 规范 Git commit
22 |
23 | commit message 应尽可能的规范,为了保证和其他 commit 统一,建议 commit message 采用英文描述,并符合下面的格式:
24 |
25 | ```yaml
26 | type: subject
27 | ```
28 |
29 | type 是 commit 的类别,subject 是对这个 commit 的英文描述,例如下面的示例:
30 | * feat: add object download function
31 | * perf: optimize the display of update progress bar
32 | 关于 Git commit 的更多规范要求可以参见这篇文章:[如何规范你的Git commit?](https://zhuanlan.zhihu.com/p/182553920)
33 |
34 | ### 规范代码格式
35 |
36 | 代码在编写完后,应使用 `go fmt` 和 `goimports`对代码进行格式化,从而使代码看起来更加整洁。
37 |
38 | 在 goland 中可以设置当代码保存时自动格式化代码,配置的步骤可以参见这篇文章:[GoLand:设置gofmt与goimports,保存时自动格式化代码](https://blog.csdn.net/qq_32907195/article/details/116755338)
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2024 Wolf Group Security Team
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4 |
5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6 |
7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
8 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |

2 | LC(List Cloud)多云攻击面资产梳理工具
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | 功能 •
15 | 安装 •
16 | 配置 •
17 | 使用 •
18 | 支持的云服务商
19 |
20 |
21 | LC(List Cloud)是一个多云攻击面资产梳理的工具,使用 LC 可以让甲方蓝队在管理多云时快速梳理出可能暴露在公网上的资产。
22 |
23 | ## 功能
24 |
25 | - 列出多个配置的云资产
26 | - 支持多个云服务商
27 | - 支持多个云服务
28 | - 支持过滤内网 IP
29 | - 高度可扩展性,可方便添加更多云服务商和云服务
30 | - 可以使用管道符和其他工具结合使用
31 |
32 | 运行截图:
33 |
34 |
35 |
36 |
37 | ### 支持列出的云服务
38 |
39 | | 序号 | 云服务商 | 服务名称 |
40 | |:--:|:----:|:-----------:|
41 | | 1 | 阿里云 | ECS 云服务器 |
42 | | 2 | 阿里云 | OSS 对象存储 |
43 | | 3 | 阿里云 | RDS 数据库 |
44 | | 4 | 阿里云 | FC 函数计算 |
45 | | 5 | 阿里云 | Domain 域名服务 |
46 | | 6 | 腾讯云 | CVM 云服务器 |
47 | | 7 | 腾讯云 | LH 轻量应用服务器 |
48 | | 8 | 腾讯云 | COS 对象存储 |
49 | | 9 | 华为云 | OBS 对象存储 |
50 | | 10 | 天翼云 | OOS 对象存储 |
51 | | 11 | 百度云 | BOS 对象存储 |
52 | | 12 | 百度云 | BCC 云服务器 |
53 | | 13 | 联通云 | OSS 对象存储 |
54 | | 14 | 七牛云 | Kodo 对象存储 |
55 | | 15 | 移动云 | EOS 对象存储 |
56 |
57 | ## 使用手册
58 |
59 | 详细使用手册请参见:[LC 使用手册](https://wiki.teamssix.com/lc)
60 |
61 | ## 安装
62 |
63 | ### 使用 brew 安装
64 |
65 | 安装
66 |
67 | ```bash
68 | brew tap wgpsec/tap
69 | brew install wgpsec/tap/lc
70 | ```
71 |
72 | 更新
73 |
74 | ```bash
75 | brew update
76 | brew upgrade lc
77 | ```
78 |
79 | ### 下载二进制文件
80 |
81 | 直接在 LC 下载地址:[github.com/wgpsec/lc/releases](https://github.com/wgpsec/lc/releases) 中下载系统对应的压缩文件,解压后在命令行中运行即可。
82 |
83 | ## 用法
84 |
85 | ```sh
86 | lc -h
87 | ```
88 |
89 | 使用 `-h` 参数查看 lc 的帮助信息,这是目前 lc 所支持的用法。
90 |
91 | ```yaml
92 | lc (list cloud) 是一个多云攻击面资产梳理工具
93 |
94 | Usage:
95 | lc [flags]
96 |
97 | Flags:
98 | 配置:
99 | -c, -config string 指定配置文件路径 (default "$HOME/.config/lc/config.yaml")
100 | -t, -threads int 指定扫描的线程数量 (default 3)
101 |
102 | 过滤:
103 | -cs, -cloud-services string[] 指定要列出的服务 (default ["all"])
104 | -i, -id string[] 指定要使用的配置(以逗号分隔)
105 | -p, -provider string[] 指定要使用的云服务商(以逗号分隔)
106 | -ep, -exclude-private 从输出的结果中排除私有 IP
107 |
108 | 输出:
109 | -o, -output string 将结果输出到指定的文件中
110 | -s, -silent 只输出结果
111 | -v, -version 输出工具的版本
112 | -debug 输出调试日志信息
113 | ```
114 |
115 | ## 简单上手
116 |
117 | 在第一次使用时,LC 会在 `$HOME/.config/lc` 目录下创建一个 `config.yaml`,因此在第一次执行 `lc` 命令后,将您的云访问凭证填写到 `$HOME/.config/lc/config.yaml` 文件中后,就可以开始正式使用 LC 了。
118 |
119 | 直接运行 `lc` 命令来列举您的云上资产。
120 |
121 | ```sh
122 | lc
123 | ```
124 |
125 | 如果没有列举出结果,那么可能是因为本身云上没有资产,或者访问凭证的权限不足,这里我们建议为访问凭证赋予全局可读权限即可。
126 |
127 | 如果要排除结果中的内网 IP,只需要加上 `-ep` 参数。
128 |
129 | ```sh
130 | lc -ep
131 | ```
132 |
133 | 如果想把 LC 和其他工具结合使用,例如使用 httpx 检测资产是否能从公网访问,那么可以使用下面的命令。
134 |
135 | ```sh
136 | lc -ep -s | httpx -sc -title -silent
137 | ```
138 |
139 |
140 |
141 | 更多用法可以查看 [LC 使用手册](https://wiki.teamssix.com/lc)
142 |
143 | ## 贡献者
144 |
145 | 十分欢迎各位师傅为 LC 项目贡献代码,如果您想为该项目贡献代码,请参见贡献说明:[CONTRIBUTING](https://github.com/wgpsec/lc/blob/master/CONTRIBUTING.md)
146 |
147 |
148 |
149 |
150 |
151 |  TeamsSix
152 | |
153 |
154 |  ShuBo6
155 | |
156 |
157 |  tari
158 | |
159 |
160 |
161 |
162 |
163 | ## 致谢
164 |
165 | 十分感谢 [projectdiscovery](https://github.com/projectdiscovery) 的 [cloudlist](https://github.com/projectdiscovery/cloudlist) 项目以及 projectdiscovery 团队的开源精神,得益于 cloudlist 的 MIT 协议,这为本项目起到了非常大的帮助。
166 |
167 | 本项目也以 MIT 协议开源,共同助力人类开源事业的进步与发展。
168 |
169 | ## 协议
170 |
171 | LC 在 [MIT](https://github.com/wgpsec/lc/blob/main/LICENSE) 协议下授权使用。
172 |
173 | ## 更多
174 |
175 | 下面这个是我们狼组安全团队的公众号,欢迎师傅关注,有想法一起加入狼组的师傅也可以投递简历至 admin#wgpsec.org 加入我们。
176 |
177 | > 发送邮件时,注意将 # 改为 @
178 |
179 |
180 |
181 | 如果你对云安全比较感兴趣,可以看我的另外一个项目 [Awesome Cloud Security](https://github.com/teamssix/awesome-cloud-security),这里收录了很多国内外的云安全资源,另外在我的[云安全文库](https://wiki.teamssix.com/)里有大量的云安全方向的笔记和文章,这应该是国内还不错的云安全学习资料。
182 |
183 | 下面这个是我的个人微信公众号,在 TeamsSix 公众号里可以与我进行联系,后续关于 LC 的动态我也会发布到我的公众号里。
184 |
185 |
186 |
187 | 如果您感觉这个项目还不错,也欢迎扫描下面打赏码进行赞赏。
188 |
189 |
190 |
191 | 感谢您使用我的工具
192 |
--------------------------------------------------------------------------------
/cmd/banner.go:
--------------------------------------------------------------------------------
1 | package cmd
2 |
3 | import "github.com/projectdiscovery/gologger"
4 |
5 | const banner = `
6 | __ _ __ ________ __
7 | / / (_)____/ /_ / ____/ /___ __ ______/ /
8 | / / / / ___/ __/ / / / / __ \/ / / / __ /
9 | / /___/ (__ ) /_ / /___/ / /_/ / /_/ / /_/ /
10 | /_____/_/____/\__/ \____/_/\____/\__,_/\__,_/
11 | `
12 |
13 | const version = "1.1.0"
14 | const versionDate = "2024-10-6"
15 |
16 | func showBanner() {
17 | gologger.Print().Msgf("%s\n", banner)
18 | gologger.Print().Msgf("\t %s %s\n\t github.com/wgpsec/lc\n\n", version, versionDate)
19 | }
20 |
--------------------------------------------------------------------------------
/cmd/configFile.go:
--------------------------------------------------------------------------------
1 | package cmd
2 |
3 | const defaultConfigFile = `# # lc (list cloud) 的云服务商配置文件
4 |
5 | # # 配置文件说明
6 |
7 | # # provider 是云服务商的名字
8 | # - provider: provider_name
9 | # # id 是当前配置文件的名字
10 | # id: test
11 | # # access_key 是这个云的访问凭证 Key 部分
12 | # access_key:
13 | # # secret_key 是这个云的访问凭证 Secret 部分
14 | # secret_key:
15 | # # (可选)session_token 是这个云的访问凭证 session token 部分,仅在访问凭证是临时访问配置时才需要填写这部分的内容
16 | # session_token:
17 |
18 | # # 阿里云
19 | # # 访问凭证获取地址:https://ram.console.aliyun.com
20 | # - provider: aliyun
21 | # id: aliyun_default
22 | # cloud_services: ecs,oss,rds,fc,domain
23 | # access_key:
24 | # secret_key:
25 | # session_token:
26 |
27 | # # 腾讯云
28 | # # 访问凭证获取地址:https://console.cloud.tencent.com/cam
29 | # - provider: tencent
30 | # id: tencent_cloud_default
31 | # cloud_services: cvm,lh,cos
32 | # access_key:
33 | # secret_key:
34 | # session_token:
35 |
36 | # # 华为云
37 | # # 访问凭证获取地址:https://console.huaweicloud.com/iam
38 | # - provider: huawei
39 | # id: huawei_cloud_default
40 | # cloud_services: obs
41 | # access_key:
42 | # secret_key:
43 | # session_token:
44 |
45 | # # 天翼云
46 | # # 访问凭证获取地址:https://oos-cn.ctyun.cn/oos/ctyun/iam/dist/index.html#/certificate
47 | # - provider: tianyi
48 | # id: tianyi_cloud_default
49 | # cloud_services: oos
50 | # access_key:
51 | # secret_key:
52 |
53 | # # 百度云
54 | # # 访问凭证获取地址:https://console.bce.baidu.com/iam/
55 | # - provider: baidu
56 | # id: baidu_cloud_default
57 | # cloud_services: bos,bcc
58 | # access_key:
59 | # secret_key:
60 | # session_token:
61 |
62 | # # 联通云
63 | # # 访问凭证获取地址:https://console.cucloud.cn/console/uiam
64 | # - provider: liantong
65 | # id: liantong_cloud_default
66 | # cloud_services: oss
67 | # access_key:
68 | # secret_key:
69 | # session_token:
70 |
71 | # # 七牛云
72 | # # 访问凭证获取地址:https://portal.qiniu.com/developer/user/key
73 | # - provider: qiniu
74 | # id: qiniu_cloud_default
75 | # cloud_services: kodo
76 | # access_key:
77 | # secret_key:
78 |
79 | # # 移动云
80 | # # 访问凭证获取地址:https://console.ecloud.10086.cn/api/page/eos-console-web/CIDC-RP-00/eos/key
81 | # - provider: yidong
82 | # id: yidong_cloud_default
83 | # cloud_services: eos
84 | # access_key:
85 | # secret_key:
86 | # session_token:
87 | `
88 |
--------------------------------------------------------------------------------
/cmd/options.go:
--------------------------------------------------------------------------------
1 | package cmd
2 |
3 | import (
4 | "github.com/projectdiscovery/gologger/levels"
5 | fileutil "github.com/projectdiscovery/utils/file"
6 | "os"
7 | "os/user"
8 | "path/filepath"
9 |
10 | "github.com/projectdiscovery/goflags"
11 | "github.com/projectdiscovery/gologger"
12 | )
13 |
14 | type Options struct {
15 | Threads int // Threads 设置线程数量
16 | Silent bool // Silent 只展示结果
17 | Debug bool // Debug 显示详细的输出信息
18 | Version bool // Version 返回工具版本
19 | ExcludePrivate bool // ExcludePrivate 从结果中排除私有 IP
20 | Config string // Config 指定配置文件路径
21 | Output string // Output 将结果写入到文件中
22 | Provider goflags.StringSlice // Provider 指定要列出的云服务商
23 | Id goflags.StringSlice // Id 指定要列出的对象
24 | CloudServices goflags.StringSlice // CloudServices 指定要列出的服务
25 | }
26 |
27 | var (
28 | defaultConfigLocation = filepath.Join(userHomeDir(), ".config/lc/config.yaml")
29 | )
30 |
31 | func ParseOptions() *Options {
32 | options := &Options{}
33 | flagSet := goflags.NewFlagSet()
34 | flagSet.SetDescription(`lc (list cloud) 是一个多云攻击面资产梳理工具`)
35 |
36 | flagSet.CreateGroup("config", "配置",
37 | flagSet.StringVarP(&options.Config, "config", "c", defaultConfigLocation, "指定配置文件路径"),
38 | flagSet.IntVarP(&options.Threads, "threads", "t", 3, "指定扫描的线程数量"),
39 | )
40 | flagSet.CreateGroup("filter", "过滤",
41 | flagSet.StringSliceVarP(&options.CloudServices, "cloud-services", "cs", goflags.StringSlice{"all"}, "指定要列出的服务",
42 | goflags.NormalizedStringSliceOptions),
43 | flagSet.StringSliceVarP(&options.Id, "id", "i", nil, "指定要使用的配置(以逗号分隔)", goflags.NormalizedStringSliceOptions),
44 | flagSet.StringSliceVarP(&options.Provider, "provider", "p", nil, "指定要使用的云服务商(以逗号分隔)", goflags.NormalizedStringSliceOptions),
45 | flagSet.BoolVarP(&options.ExcludePrivate, "exclude-private", "ep", false, "从输出的结果中排除私有 IP"),
46 | )
47 | flagSet.CreateGroup("output", "输出",
48 | flagSet.StringVarP(&options.Output, "output", "o", "", "将结果输出到指定的文件中"),
49 | flagSet.BoolVarP(&options.Silent, "silent", "s", false, "只输出结果"),
50 | flagSet.BoolVarP(&options.Version, "version", "v", false, "输出工具的版本"),
51 | flagSet.BoolVar(&options.Debug, "debug", false, "输出调试日志信息"),
52 | )
53 | _ = flagSet.Parse()
54 | options.configureOutput()
55 | showBanner()
56 | if options.Version {
57 | gologger.Info().Msgf("当前版本:%s, 发布日期:%s", version, versionDate)
58 | os.Exit(0)
59 | }
60 | checkAndCreateConfigFile(options)
61 | return options
62 | }
63 |
64 | func (options *Options) configureOutput() {
65 | if options.Silent {
66 | gologger.DefaultLogger.SetMaxLevel(levels.LevelSilent)
67 | }
68 | if options.Debug {
69 | gologger.DefaultLogger.SetMaxLevel(levels.LevelDebug)
70 | }
71 | }
72 |
73 | func userHomeDir() string {
74 | usr, err := user.Current()
75 | if err != nil {
76 | gologger.Fatal().Msgf("Could not get user home directory: %s\n", err)
77 | }
78 | return usr.HomeDir
79 | }
80 |
81 | func checkAndCreateConfigFile(options *Options) {
82 | if options.Config == "" || !fileutil.FileExists(defaultConfigLocation) {
83 | err := os.MkdirAll(filepath.Dir(options.Config), os.ModePerm)
84 | if err != nil {
85 | gologger.Warning().Msgf("无法创建配置文件:%s\n", err)
86 | }
87 | if !fileutil.FileExists(defaultConfigLocation) {
88 | if writeErr := os.WriteFile(defaultConfigLocation, []byte(defaultConfigFile), os.ModePerm); writeErr != nil {
89 | gologger.Warning().Msgf("Could not write default output to %s: %s\n", defaultConfigLocation, writeErr)
90 | }
91 | }
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/cmd/runner.go:
--------------------------------------------------------------------------------
1 | package cmd
2 |
3 | import (
4 | "bytes"
5 | "context"
6 | "fmt"
7 | "github.com/projectdiscovery/gologger"
8 | "github.com/wgpsec/lc/pkg/inventory"
9 | "github.com/wgpsec/lc/pkg/schema"
10 | "github.com/wgpsec/lc/utils"
11 | "os"
12 | )
13 |
14 | type Runner struct {
15 | config schema.Options
16 | options *Options
17 | }
18 |
19 | func New(options *Options) (*Runner, error) {
20 | if options.Config == "" {
21 | options.Config = defaultConfigLocation
22 | gologger.Print().Msgf("使用默认配置文件: %s\n", options.Config)
23 | }
24 | checkAndCreateConfigFile(options)
25 | config, err := utils.ReadConfig(options.Config)
26 | if err != nil {
27 | return nil, err
28 | }
29 | return &Runner{config: config, options: options}, nil
30 | }
31 |
32 | func (r *Runner) Enumerate() {
33 | var (
34 | err error
35 | finalConfig schema.Options
36 | )
37 |
38 | if r.config, err = utils.ReadConfig(r.options.Config); err != nil {
39 | gologger.Fatal().Msgf("程序配置文件无效,请检查后重试,错误:%s", err)
40 | }
41 |
42 | for _, item := range r.config {
43 | if len(r.options.Provider) != 0 || len(r.options.Id) != 0 {
44 | if len(r.options.Provider) != 0 && !utils.Contains(r.options.Provider, item[utils.Provider]) {
45 | continue
46 | }
47 | if len(r.options.Id) != 0 && !utils.Contains(r.options.Id, item[utils.Id]) {
48 | continue
49 | }
50 | finalConfig = append(finalConfig, item)
51 | } else {
52 | finalConfig = append(finalConfig, item)
53 | }
54 | }
55 |
56 | inventory, err := inventory.New(finalConfig, r.options.CloudServices)
57 | if err != nil {
58 | gologger.Fatal().Msgf("%s", err)
59 | }
60 | var output *os.File
61 | if r.options.Output != "" {
62 | outputFile, err := os.Create(r.options.Output)
63 | if err != nil {
64 | gologger.Fatal().Msgf("无法创建导出的文件 %s: %s\n", r.options.Output, err)
65 | }
66 | output = outputFile
67 | }
68 | builder := &bytes.Buffer{}
69 | schema.SetThreads(r.options.Threads)
70 | for _, provider := range inventory.Providers {
71 | gologger.Info().Msgf("正在列出 %s (%s) 的资产\n", provider.Name(), provider.ID())
72 | instances, err := provider.Resources(context.Background(), r.options.CloudServices)
73 | if err != nil {
74 | gologger.Error().Msgf("无法获取 %s(%s)的资产: %s\n", provider.Name(), provider.ID(), err)
75 | continue
76 | }
77 | var Count int
78 | for _, instance := range instances.GetItems() {
79 | builder.Reset()
80 | if instance.DNSName != "" {
81 | Count++
82 | builder.WriteString(instance.DNSName)
83 | builder.WriteRune('\n')
84 | output.WriteString(builder.String()) //nolint
85 | builder.Reset()
86 | gologger.Silent().Msgf("%s", instance.DNSName)
87 | }
88 | if instance.PublicIPv4 != "" {
89 | Count++
90 | builder.WriteString(instance.PublicIPv4)
91 | builder.WriteRune('\n')
92 | output.WriteString(builder.String())
93 | builder.Reset()
94 | gologger.Silent().Msgf("%s", instance.PublicIPv4)
95 | }
96 | if instance.PrivateIpv4 != "" && !r.options.ExcludePrivate {
97 | Count++
98 | builder.WriteString(instance.PrivateIpv4)
99 | builder.WriteRune('\n')
100 | output.WriteString(builder.String())
101 | builder.Reset()
102 | gologger.Silent().Msgf("%s", instance.PrivateIpv4)
103 | }
104 | }
105 | if Count == 0 {
106 | gologger.Info().Msgf("在 %s (%s) 下未发现资产,这可能是由于权限不足或没有资产,您可以在确认有相关权限后再进行尝试。", provider.Name(), provider.ID())
107 | }
108 | if !r.options.Silent {
109 | fmt.Println()
110 | }
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/wgpsec/lc
2 |
3 | go 1.22.1
4 |
5 | require (
6 | github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.9
7 | github.com/alibabacloud-go/domain-20180129/v4 v4.2.0
8 | github.com/alibabacloud-go/fc-20230330/v4 v4.1.3
9 | github.com/alibabacloud-go/fc-open-20210406/v2 v2.0.12
10 | github.com/alibabacloud-go/tea-utils/v2 v2.0.6
11 | github.com/aliyun/alibaba-cloud-sdk-go v1.62.712
12 | github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible
13 | github.com/aws/aws-sdk-go v1.51.16
14 | github.com/baidubce/bce-sdk-go v0.9.173
15 | github.com/huaweicloud/huaweicloud-sdk-go-obs v3.23.12+incompatible
16 | github.com/projectdiscovery/goflags v0.1.46
17 | github.com/projectdiscovery/gologger v1.1.12
18 | github.com/projectdiscovery/utils v0.0.87
19 | github.com/qiniu/go-sdk/v7 v7.20.0
20 | github.com/teamssix/oos-go-sdk v0.0.1
21 | github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.893
22 | github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cvm v1.0.893
23 | github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/lighthouse v1.0.893
24 | github.com/tencentyun/cos-go-sdk-v5 v0.7.47
25 | gopkg.in/yaml.v3 v3.0.1
26 | )
27 |
28 | require (
29 | github.com/alibabacloud-go/alibabacloud-gateway-fc-util v0.0.7 // indirect
30 | github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.4 // indirect
31 | github.com/alibabacloud-go/debug v1.0.0 // indirect
32 | github.com/alibabacloud-go/endpoint-util v1.1.0 // indirect
33 | github.com/alibabacloud-go/openapi-util v0.1.0 // indirect
34 | github.com/alibabacloud-go/tea v1.2.2 // indirect
35 | github.com/alibabacloud-go/tea-utils v1.3.1 // indirect
36 | github.com/alibabacloud-go/tea-xml v1.1.3 // indirect
37 | github.com/aliyun/credentials-go v1.3.1 // indirect
38 | github.com/andybalholm/brotli v1.0.6 // indirect
39 | github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
40 | github.com/aymerick/douceur v0.2.0 // indirect
41 | github.com/clbanning/mxj v1.8.4 // indirect
42 | github.com/clbanning/mxj/v2 v2.5.5 // indirect
43 | github.com/cnf/structhash v0.0.0-20201127153200-e1b16c1ebc08 // indirect
44 | github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 // indirect
45 | github.com/gofrs/flock v0.8.1 // indirect
46 | github.com/golang/snappy v0.0.4 // indirect
47 | github.com/google/go-querystring v1.1.0 // indirect
48 | github.com/gorilla/css v1.0.0 // indirect
49 | github.com/jmespath/go-jmespath v0.4.0 // indirect
50 | github.com/json-iterator/go v1.1.12 // indirect
51 | github.com/klauspost/compress v1.16.7 // indirect
52 | github.com/klauspost/pgzip v1.2.5 // indirect
53 | github.com/logrusorgru/aurora v2.0.3+incompatible // indirect
54 | github.com/mholt/archiver/v3 v3.5.1 // indirect
55 | github.com/microcosm-cc/bluemonday v1.0.25 // indirect
56 | github.com/miekg/dns v1.1.56 // indirect
57 | github.com/mitchellh/mapstructure v1.4.3 // indirect
58 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
59 | github.com/modern-go/reflect2 v1.0.2 // indirect
60 | github.com/mozillazg/go-httpheader v0.2.1 // indirect
61 | github.com/nwaples/rardecode v1.1.3 // indirect
62 | github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b // indirect
63 | github.com/pierrec/lz4/v4 v4.1.2 // indirect
64 | github.com/pkg/errors v0.9.1 // indirect
65 | github.com/projectdiscovery/blackrock v0.0.1 // indirect
66 | github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d // indirect
67 | github.com/tidwall/gjson v1.14.3 // indirect
68 | github.com/tidwall/match v1.1.1 // indirect
69 | github.com/tidwall/pretty v1.2.0 // indirect
70 | github.com/tjfoc/gmsm v1.3.2 // indirect
71 | github.com/ulikunitz/xz v0.5.11 // indirect
72 | github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect
73 | golang.org/x/exp v0.0.0-20221205204356-47842c84f3db // indirect
74 | golang.org/x/mod v0.12.0 // indirect
75 | golang.org/x/net v0.20.0 // indirect
76 | golang.org/x/sync v0.6.0 // indirect
77 | golang.org/x/sys v0.17.0 // indirect
78 | golang.org/x/text v0.14.0 // indirect
79 | golang.org/x/time v0.5.0 // indirect
80 | golang.org/x/tools v0.13.0 // indirect
81 | gopkg.in/djherbis/times.v1 v1.3.0 // indirect
82 | gopkg.in/ini.v1 v1.67.0 // indirect
83 | )
84 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
2 | github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
3 | github.com/HdrHistogram/hdrhistogram-go v1.1.2/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo=
4 | github.com/QcloudApi/qcloud_sign_golang v0.0.0-20141224014652-e4130a326409/go.mod h1:1pk82RBxDY/JZnPQrtqHlUFfCctgdorsd9M06fMynOM=
5 | github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw=
6 | github.com/alibabacloud-go/alibabacloud-gateway-fc-util v0.0.7 h1:RDatRb9RG39HjkevgzTeiVoDDaamoB+12GHNairp3Ag=
7 | github.com/alibabacloud-go/alibabacloud-gateway-fc-util v0.0.7/go.mod h1:H0RPHXHP/ICfEQrKzQcCqXI15jcV4zaDPCOAmh3U9O8=
8 | github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.4 h1:iC9YFYKDGEy3n/FtqJnOkZsene9olVspKmkX5A2YBEo=
9 | github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.4/go.mod h1:sCavSAvdzOjul4cEqeVtvlSaSScfNsTQ+46HwlTL1hc=
10 | github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.5/go.mod h1:kUe8JqFmoVU7lfBauaDD5taFaW7mBI+xVsyHutYtabg=
11 | github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.9 h1:fxMCrZatZfXq5nLcgkmWBXmU3FLC1OR+m/SqVtMqflk=
12 | github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.9/go.mod h1:bb+Io8Sn2RuM3/Rpme6ll86jMyFSrD1bxeV/+v61KeU=
13 | github.com/alibabacloud-go/debug v0.0.0-20190504072949-9472017b5c68/go.mod h1:6pb/Qy8c+lqua8cFpEy7g39NRRqOWc3rOwAy8m5Y2BY=
14 | github.com/alibabacloud-go/debug v1.0.0 h1:3eIEQWfay1fB24PQIEzXAswlVJtdQok8f3EVN5VrBnA=
15 | github.com/alibabacloud-go/debug v1.0.0/go.mod h1:8gfgZCCAC3+SCzjWtY053FrOcd4/qlH6IHTI4QyICOc=
16 | github.com/alibabacloud-go/domain-20180129/v4 v4.2.0 h1:PAEt76VDoXbQODWeN9PTwYhA5NoEbJSK/yzGX2DZXrQ=
17 | github.com/alibabacloud-go/domain-20180129/v4 v4.2.0/go.mod h1:q0n3wgGRndhuZsAXFVCFtiSR8+W0so85qYtKLzR2b18=
18 | github.com/alibabacloud-go/endpoint-util v1.1.0 h1:r/4D3VSw888XGaeNpP994zDUaxdgTSHBbVfZlzf6b5Q=
19 | github.com/alibabacloud-go/endpoint-util v1.1.0/go.mod h1:O5FuCALmCKs2Ff7JFJMudHs0I5EBgecXXxZRyswlEjE=
20 | github.com/alibabacloud-go/fc-20230330/v4 v4.1.3 h1:/4/A8pD6WBoy3LdDASyrQ36cBF7KhN6nIHGKPpcdsBE=
21 | github.com/alibabacloud-go/fc-20230330/v4 v4.1.3/go.mod h1:bwrdz9JVW4Qyhk4rmKZAQgJojRM58UJGXD5v+cQD83Y=
22 | github.com/alibabacloud-go/fc-open-20210406/v2 v2.0.12 h1:A3D8Mp6qf8DfR6Dt5MpS8aDVaWfS4N85T5CvGUvgrjM=
23 | github.com/alibabacloud-go/fc-open-20210406/v2 v2.0.12/go.mod h1:F5c0E5UB3k8v6neTtw3FBcJ1YCNFzVoL1JPRHTe33u4=
24 | github.com/alibabacloud-go/openapi-util v0.1.0 h1:0z75cIULkDrdEhkLWgi9tnLe+KhAFE/r5Pb3312/eAY=
25 | github.com/alibabacloud-go/openapi-util v0.1.0/go.mod h1:sQuElr4ywwFRlCCberQwKRFhRzIyG4QTP/P4y1CJ6Ws=
26 | github.com/alibabacloud-go/tea v1.1.0/go.mod h1:IkGyUSX4Ba1V+k4pCtJUc6jDpZLFph9QMy2VUPTwukg=
27 | github.com/alibabacloud-go/tea v1.1.7/go.mod h1:/tmnEaQMyb4Ky1/5D+SE1BAsa5zj/KeGOFfwYm3N/p4=
28 | github.com/alibabacloud-go/tea v1.1.8/go.mod h1:/tmnEaQMyb4Ky1/5D+SE1BAsa5zj/KeGOFfwYm3N/p4=
29 | github.com/alibabacloud-go/tea v1.1.17/go.mod h1:nXxjm6CIFkBhwW4FQkNrolwbfon8Svy6cujmKFUq98A=
30 | github.com/alibabacloud-go/tea v1.2.1/go.mod h1:qbzof29bM/IFhLMtJPrgTGK3eauV5J2wSyEUo4OEmnA=
31 | github.com/alibabacloud-go/tea v1.2.2 h1:aTsR6Rl3ANWPfqeQugPglfurloyBJY85eFy7Gc1+8oU=
32 | github.com/alibabacloud-go/tea v1.2.2/go.mod h1:CF3vOzEMAG+bR4WOql8gc2G9H3EkH3ZLAQdpmpXMgwk=
33 | github.com/alibabacloud-go/tea-utils v1.3.1 h1:iWQeRzRheqCMuiF3+XkfybB3kTgUXkXX+JMrqfLeB2I=
34 | github.com/alibabacloud-go/tea-utils v1.3.1/go.mod h1:EI/o33aBfj3hETm4RLiAxF/ThQdSngxrpF8rKUDJjPE=
35 | github.com/alibabacloud-go/tea-utils/v2 v2.0.4/go.mod h1:sj1PbjPodAVTqGTA3olprfeeqqmwD0A5OQz94o9EuXQ=
36 | github.com/alibabacloud-go/tea-utils/v2 v2.0.6 h1:ZkmUlhlQbaDC+Eba/GARMPy6hKdCLiSke5RsN5LcyQ0=
37 | github.com/alibabacloud-go/tea-utils/v2 v2.0.6/go.mod h1:qxn986l+q33J5VkialKMqT/TTs3E+U9MJpd001iWQ9I=
38 | github.com/alibabacloud-go/tea-xml v1.1.3 h1:7LYnm+JbOq2B+T/B0fHC4Ies4/FofC4zHzYtqw7dgt0=
39 | github.com/alibabacloud-go/tea-xml v1.1.3/go.mod h1:Rq08vgCcCAjHyRi/M7xlHKUykZCEtyBy9+DPF6GgEu8=
40 | github.com/aliyun/alibaba-cloud-sdk-go v1.62.712 h1:lM7JnA9dEdDFH9XOgRNQMDTQnOjlLkDTNA7c0aWTQ30=
41 | github.com/aliyun/alibaba-cloud-sdk-go v1.62.712/go.mod h1:SOSDHfe1kX91v3W5QiBsWSLqeLxImobbMX1mxrFHsVQ=
42 | github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible h1:8psS8a+wKfiLt1iVDX79F7Y6wUM49Lcha2FMXt4UM8g=
43 | github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8=
44 | github.com/aliyun/credentials-go v1.1.2/go.mod h1:ozcZaMR5kLM7pwtCMEpVmQ242suV6qTJya2bDq4X1Tw=
45 | github.com/aliyun/credentials-go v1.3.1 h1:uq/0v7kWrxmoLGpqjx7vtQ/s03f0zR//0br/xWDTE28=
46 | github.com/aliyun/credentials-go v1.3.1/go.mod h1:8jKYhQuDawt8x2+fusqa1Y6mPxemTsBEN04dgcAcYz0=
47 | github.com/andybalholm/brotli v1.0.1/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
48 | github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI=
49 | github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
50 | github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so=
51 | github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
52 | github.com/aws/aws-sdk-go v1.51.16 h1:vnWKK8KjbftEkuPX8bRj3WHsLy1uhotn0eXptpvrxJI=
53 | github.com/aws/aws-sdk-go v1.51.16/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk=
54 | github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
55 | github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
56 | github.com/baidubce/bce-sdk-go v0.9.173 h1:anS5s8KC/CSL3Z0+ZSyo5s8nJtKtJiFDyAJpZmKl37E=
57 | github.com/baidubce/bce-sdk-go v0.9.173/go.mod h1:zbYJMQwE4IZuyrJiFO8tO8NbtYiKTFTbwh4eIsqjVdg=
58 | github.com/clbanning/mxj v1.8.4 h1:HuhwZtbyvyOw+3Z1AowPkU87JkJUSv751ELWaiTpj8I=
59 | github.com/clbanning/mxj v1.8.4/go.mod h1:BVjHeAH+rl9rs6f+QIpeRl0tfu10SXn1pUSa5PVGJng=
60 | github.com/clbanning/mxj/v2 v2.5.5 h1:oT81vUeEiQQ/DcHbzSytRngP6Ky9O+L+0Bw0zSJag9E=
61 | github.com/clbanning/mxj/v2 v2.5.5/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s=
62 | github.com/cnf/structhash v0.0.0-20201127153200-e1b16c1ebc08 h1:ox2F0PSMlrAAiAdknSRMDrAr8mfxPCfSZolH+/qQnyQ=
63 | github.com/cnf/structhash v0.0.0-20201127153200-e1b16c1ebc08/go.mod h1:pCxVEbcm3AMg7ejXyorUXi6HQCzOIBf7zEDVPtw0/U4=
64 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
65 | github.com/dave/jennifer v1.6.1/go.mod h1:nXbxhEmQfOZhWml3D1cDK5M1FLnMSozpbFN/m3RmGZc=
66 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
67 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
68 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
69 | github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
70 | github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 h1:iFaUwBSo5Svw6L7HYpRu/0lE3e0BaElwnNO1qkNQxBY=
71 | github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5/go.mod h1:qssHWj60/X5sZFNxpG4HBPDHVqxNm4DfnCKgrbZOT+s=
72 | github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY=
73 | github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
74 | github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
75 | github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
76 | github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
77 | github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs=
78 | github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
79 | github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
80 | github.com/go-playground/validator/v10 v10.8.0/go.mod h1:9JhgTzTaE31GZDpH/HSvHiRJrJ3iKAgqqH0Bl/Ocjdk=
81 | github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw=
82 | github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
83 | github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A=
84 | github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
85 | github.com/golang/snappy v0.0.2/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
86 | github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
87 | github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
88 | github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
89 | github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
90 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
91 | github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
92 | github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
93 | github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
94 | github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
95 | github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
96 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
97 | github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
98 | github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4=
99 | github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
100 | github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
101 | github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
102 | github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY=
103 | github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c=
104 | github.com/huaweicloud/huaweicloud-sdk-go-obs v3.23.12+incompatible h1:6WXo8ZNdlLKO1drIRTaoArVwyMqvG1gXU30VmNHxqk8=
105 | github.com/huaweicloud/huaweicloud-sdk-go-obs v3.23.12+incompatible/go.mod h1:l7VUhRbTKCzdOacdT4oWCwATKyvZqUOlOqr0Ous3k4s=
106 | github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho=
107 | github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
108 | github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
109 | github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
110 | github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
111 | github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
112 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
113 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
114 | github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
115 | github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes=
116 | github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
117 | github.com/klauspost/compress v1.11.4/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
118 | github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I=
119 | github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
120 | github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
121 | github.com/klauspost/pgzip v1.2.5 h1:qnWYvvKqedOF2ulHpMG72XQol4ILEJ8k2wwRl/Km8oE=
122 | github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
123 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
124 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
125 | github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
126 | github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
127 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
128 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
129 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
130 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
131 | github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
132 | github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8=
133 | github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
134 | github.com/mholt/archiver/v3 v3.5.1 h1:rDjOBX9JSF5BvoJGvjqK479aL70qh9DIpZCl+k7Clwo=
135 | github.com/mholt/archiver/v3 v3.5.1/go.mod h1:e3dqJ7H78uzsRSEACH1joayhuSyhnonssnDhppzS1L4=
136 | github.com/microcosm-cc/bluemonday v1.0.25 h1:4NEwSfiJ+Wva0VxN5B8OwMicaJvD8r9tlJWm9rtloEg=
137 | github.com/microcosm-cc/bluemonday v1.0.25/go.mod h1:ZIOjCQp1OrzBBPIJmfX4qDYFuhU02nx4bn030ixfHLE=
138 | github.com/miekg/dns v1.1.56 h1:5imZaSeoRNvpM9SzWNhEcP9QliKiz20/dA2QabIGVnE=
139 | github.com/miekg/dns v1.1.56/go.mod h1:cRm6Oo2C8TY9ZS/TqsSrseAcncm74lfK5G+ikN2SWWY=
140 | github.com/mitchellh/mapstructure v1.4.3 h1:OVowDSCllw/YjdLkam3/sm7wEtOy59d8ndGgCcyj8cs=
141 | github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
142 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
143 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
144 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
145 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
146 | github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
147 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
148 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
149 | github.com/mozillazg/go-httpheader v0.2.1 h1:geV7TrjbL8KXSyvghnFm+NyTux/hxwueTSrwhe88TQQ=
150 | github.com/mozillazg/go-httpheader v0.2.1/go.mod h1:jJ8xECTlalr6ValeXYdOF8fFUISeBAdw6E61aqQma60=
151 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
152 | github.com/nwaples/rardecode v1.1.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0=
153 | github.com/nwaples/rardecode v1.1.3 h1:cWCaZwfM5H7nAD6PyEdcVnczzV8i/JtotnyW/dD9lEc=
154 | github.com/nwaples/rardecode v1.1.3/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0=
155 | github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b h1:FfH+VrHHk6Lxt9HdVS0PXzSXFyS2NbZKXv33FYPol0A=
156 | github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b/go.mod h1:AC62GU6hc0BrNm+9RK9VSiwa/EUe1bkIeFORAMcHvJU=
157 | github.com/pierrec/lz4/v4 v4.1.2 h1:qvY3YFXRQE/XB8MlLzJH7mSzBs74eA2gg52YTk6jUPM=
158 | github.com/pierrec/lz4/v4 v4.1.2/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
159 | github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
160 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
161 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
162 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
163 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
164 | github.com/projectdiscovery/blackrock v0.0.1 h1:lHQqhaaEFjgf5WkuItbpeCZv2DUIE45k0VbGJyft6LQ=
165 | github.com/projectdiscovery/blackrock v0.0.1/go.mod h1:ANUtjDfaVrqB453bzToU+YB4cUbvBRpLvEwoWIwlTss=
166 | github.com/projectdiscovery/goflags v0.1.46 h1:JlYvFxJcimKJGWYbygiFBN052MWrbls/kKiwOKpLzEE=
167 | github.com/projectdiscovery/goflags v0.1.46/go.mod h1:X7A6ELNgczyOyEy2gyNC/tJTuhtwQk6ZLyzsnDVlZkw=
168 | github.com/projectdiscovery/gologger v1.1.12 h1:uX/QkQdip4PubJjjG0+uk5DtyAi1ANPJUvpmimXqv4A=
169 | github.com/projectdiscovery/gologger v1.1.12/go.mod h1:DI8nywPLERS5mo8QEA9E7gd5HZ3Je14SjJBH3F5/kLw=
170 | github.com/projectdiscovery/utils v0.0.87 h1:9+RiTEhpUB/vk6XJUVpysNWJ2aCTD7WuyoyAcNnbIzk=
171 | github.com/projectdiscovery/utils v0.0.87/go.mod h1:jGK450sL9AVDTjaPwEs9za8NVeEC9xE97IWNoK138kI=
172 | github.com/qiniu/dyn v1.3.0/go.mod h1:E8oERcm8TtwJiZvkQPbcAh0RL8jO1G0VXJMW3FAWdkk=
173 | github.com/qiniu/go-sdk/v7 v7.20.0 h1:pK2tk2qWpNtY0MWjc32oRlf3EHt6BaeWexl74jXkOTg=
174 | github.com/qiniu/go-sdk/v7 v7.20.0/go.mod h1:ZnEP1rOOi7weF+yzM2qZMHI0z1ht+KjVuNAuKTQW3aM=
175 | github.com/qiniu/x v1.10.5/go.mod h1:03Ni9tj+N2h2aKnAz+6N0Xfl8FwMEDRC2PAlxekASDs=
176 | github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
177 | github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
178 | github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
179 | github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d h1:hrujxIzL1woJ7AwssoOcM/tq5JjjG2yYOc8odClEiXA=
180 | github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d/go.mod h1:uugorj2VCxiV1x+LzaIdVa9b4S4qGAcH6cbhh4qVxOU=
181 | github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
182 | github.com/smartystreets/assertions v1.1.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo=
183 | github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
184 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
185 | github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
186 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
187 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
188 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
189 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
190 | github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
191 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
192 | github.com/teamssix/oos-go-sdk v0.0.1 h1:Ozw906Zq5etJE0jO2J4lf7qPovMlrRl0s3O3fgbxmc4=
193 | github.com/teamssix/oos-go-sdk v0.0.1/go.mod h1:DkPCnEyqY5g1huLUL7yLMW82oV/HjijsrYCkUGIRnmM=
194 | github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.563/go.mod h1:7sCQWVkxcsR38nffDW057DRGk8mUjK1Ing/EFOK8s8Y=
195 | github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.893 h1:SBqwWPolKFcczzMvO2qJXWGfqVVgfz3bfwurSyAoVgM=
196 | github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.893/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
197 | github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cvm v1.0.893 h1:qqaNVCVFbx4GtGTq/VZPjp9KLJ2ePgNZvigjejlNEHs=
198 | github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cvm v1.0.893/go.mod h1:grHsvmlujG6y+fIwjTOSozdqdVBzrNj9heWCqkV/HVM=
199 | github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/kms v1.0.563/go.mod h1:uom4Nvi9W+Qkom0exYiJ9VWJjXwyxtPYTkKkaLMlfE0=
200 | github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/lighthouse v1.0.893 h1:k3k+GYPr3Rs1/eE8Mg/DTvOQBJilM1PtWbHLZeKdOnY=
201 | github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/lighthouse v1.0.893/go.mod h1:JGIpPI1z4VbFLCsgTDB+vVEDf8YnPfnnQQaSh7oqTfM=
202 | github.com/tencentyun/cos-go-sdk-v5 v0.7.47 h1:uoS4Sob16qEYoapkqJq1D1Vnsy9ira9BfNUMtoFYTI4=
203 | github.com/tencentyun/cos-go-sdk-v5 v0.7.47/go.mod h1:DH9US8nB+AJXqwu/AMOrCFN1COv3dpytXuJWHgdg7kE=
204 | github.com/tidwall/gjson v1.14.3 h1:9jvXn7olKEHU1S9vwoMGliaT8jq1vJ7IH/n9zD9Dnlw=
205 | github.com/tidwall/gjson v1.14.3/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
206 | github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
207 | github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
208 | github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
209 | github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
210 | github.com/tjfoc/gmsm v1.3.2 h1:7JVkAn5bvUJ7HtU08iW6UiD+UTmJTIToHCfeFzkcCxM=
211 | github.com/tjfoc/gmsm v1.3.2/go.mod h1:HaUcFuY0auTiaHB9MHFGCPx5IaLhTUd2atbCFBQXn9w=
212 | github.com/uber/jaeger-client-go v2.30.0+incompatible h1:D6wyKGCecFaSRUpo8lCVbaOOb6ThwMmTEbhRwtKR97o=
213 | github.com/uber/jaeger-client-go v2.30.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk=
214 | github.com/uber/jaeger-lib v2.4.1+incompatible h1:td4jdvLcExb4cBISKIpHuGoVXh+dVKhn2Um6rjCsSsg=
215 | github.com/uber/jaeger-lib v2.4.1+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U=
216 | github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
217 | github.com/ulikunitz/xz v0.5.9/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
218 | github.com/ulikunitz/xz v0.5.11 h1:kpFauv27b6ynzBNT/Xy+1k+fK4WswhN/6PN5WhFAGw8=
219 | github.com/ulikunitz/xz v0.5.11/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
220 | github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo=
221 | github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos=
222 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
223 | github.com/yuin/goldmark v1.1.30/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
224 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
225 | go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
226 | go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
227 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
228 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
229 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
230 | golang.org/x/crypto v0.0.0-20191219195013-becbf705a915/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
231 | golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
232 | golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
233 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
234 | golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
235 | golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I=
236 | golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
237 | golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
238 | golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
239 | golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
240 | golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
241 | golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
242 | golang.org/x/exp v0.0.0-20221205204356-47842c84f3db h1:D/cFflL63o2KSLJIwjlcIt8PR064j/xsmdEJL/YvY/o=
243 | golang.org/x/exp v0.0.0-20221205204356-47842c84f3db/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
244 | golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
245 | golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
246 | golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
247 | golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
248 | golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
249 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
250 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
251 | golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
252 | golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc=
253 | golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
254 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
255 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
256 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
257 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
258 | golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
259 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
260 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
261 | golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
262 | golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
263 | golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
264 | golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
265 | golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ=
266 | golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo=
267 | golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
268 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
269 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
270 | golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
271 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
272 | golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
273 | golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
274 | golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
275 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
276 | golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
277 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
278 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
279 | golang.org/x/sys v0.0.0-20200509044756-6aff5f38e54f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
280 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
281 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
282 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
283 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
284 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
285 | golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
286 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
287 | golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
288 | golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
289 | golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
290 | golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
291 | golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
292 | golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
293 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
294 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
295 | golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
296 | golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
297 | golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
298 | golang.org/x/term v0.9.0/go.mod h1:M6DEAAIenWoTxdKrOltXcmDY3rSplQUkrvaDU5FcQyo=
299 | golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
300 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
301 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
302 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
303 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
304 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
305 | golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
306 | golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
307 | golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
308 | golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
309 | golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
310 | golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
311 | golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
312 | golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
313 | golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
314 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
315 | golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
316 | golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
317 | golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
318 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
319 | golang.org/x/tools v0.0.0-20200509030707-2212a7e161a5/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
320 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
321 | golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
322 | golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ=
323 | golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
324 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
325 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
326 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
327 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
328 | gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo=
329 | gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0=
330 | gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw=
331 | gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc=
332 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
333 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
334 | gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
335 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
336 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
337 | gopkg.in/djherbis/times.v1 v1.3.0 h1:uxMS4iMtH6Pwsxog094W0FYldiNnfY/xba00vq6C2+o=
338 | gopkg.in/djherbis/times.v1 v1.3.0/go.mod h1:AQlg6unIsrsCEdQYhTzERy542dz6SFdQFZFv6mUY0P8=
339 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
340 | gopkg.in/ini.v1 v1.56.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
341 | gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
342 | gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
343 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
344 | gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
345 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
346 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
347 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
348 | gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
349 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
350 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
351 | rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
352 |
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | // 感谢 github.com/projectdiscovery/cloudlist 项目, 得益
2 | // 于 cloudlist 的 MIT 开源协议,为这个 Weekend Project 提
3 | // 供了大量帮助, 此项目也将以 MIT 协议开源,助力安全开源项目发展。
4 |
5 | // Thank you to the github.com/projectdiscovery/cloudlist
6 | // project, which has provided substantial assistance to this
7 | // Weekend Project thanks to its use of the MIT open-source
8 | // license. As a result, this project will also adopt the MIT
9 | // license, joining forces to promote the development of
10 | // open-source initiatives for the benefit of humanity.
11 |
12 | package main
13 |
14 | import (
15 | "github.com/projectdiscovery/gologger"
16 | "github.com/wgpsec/lc/cmd"
17 | "io"
18 | )
19 |
20 | func main() {
21 | options := cmd.ParseOptions()
22 | runner, err := cmd.New(options)
23 | if err != nil {
24 | gologger.Info().Msg("使用 -h 或 --help 参数查看 lc 的帮助信息。")
25 | if err == io.EOF {
26 | gologger.Fatal().Msgf("配置文件为空,请在配置文件中填写上云服务商的访问配置,配置文件地址:%s\n", options.Config)
27 | } else {
28 | gologger.Fatal().Msgf("%s", err)
29 | }
30 | }
31 | runner.Enumerate()
32 | }
33 |
--------------------------------------------------------------------------------
/pkg/inventory/inventory.go:
--------------------------------------------------------------------------------
1 | package inventory
2 |
3 | import (
4 | "fmt"
5 | "github.com/projectdiscovery/goflags"
6 | "github.com/wgpsec/lc/pkg/providers/aliyun"
7 | "github.com/wgpsec/lc/pkg/providers/baidu"
8 | "github.com/wgpsec/lc/pkg/providers/huawei"
9 | "github.com/wgpsec/lc/pkg/providers/liantong"
10 | "github.com/wgpsec/lc/pkg/providers/qiniu"
11 | "github.com/wgpsec/lc/pkg/providers/tencent"
12 | "github.com/wgpsec/lc/pkg/providers/tianyi"
13 | "github.com/wgpsec/lc/pkg/providers/yidong"
14 | "github.com/wgpsec/lc/pkg/schema"
15 | "github.com/wgpsec/lc/utils"
16 | )
17 |
18 | type Inventory struct {
19 | Providers []schema.Provider
20 | }
21 |
22 | func New(options schema.Options, cs goflags.StringSlice) (*Inventory, error) {
23 | inventory := &Inventory{}
24 |
25 | for _, block := range options {
26 | value, ok := block.GetMetadata(utils.Provider)
27 | if !ok {
28 | continue
29 | }
30 | provider, err := nameToProvider(value, block, cs)
31 | if err != nil {
32 | return nil, err
33 | }
34 | inventory.Providers = append(inventory.Providers, provider)
35 | }
36 | return inventory, nil
37 | }
38 |
39 | func nameToProvider(value string, block schema.OptionBlock, cs goflags.StringSlice) (schema.Provider, error) {
40 | switch value {
41 | case utils.Aliyun:
42 | return aliyun.New(block, cs)
43 | case utils.Tencent:
44 | return tencent.New(block, cs)
45 | case utils.Huawei:
46 | return huawei.New(block, cs)
47 | case utils.TianYi:
48 | return tianyi.New(block, cs)
49 | case utils.Baidu:
50 | return baidu.New(block, cs)
51 | case utils.LianTong:
52 | return liantong.New(block, cs)
53 | case utils.QiNiu:
54 | return qiniu.New(block, cs)
55 | case utils.YiDong:
56 | return yidong.New(block, cs)
57 | default:
58 | return nil, fmt.Errorf("发现无效的云服务商名: %s", value)
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/pkg/providers/aliyun/aliyun.go:
--------------------------------------------------------------------------------
1 | package aliyun
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | openapi "github.com/alibabacloud-go/darabonba-openapi/v2/client"
7 | domain "github.com/alibabacloud-go/domain-20180129/v4/client"
8 | "github.com/alibabacloud-go/tea/tea"
9 | "github.com/aliyun/alibaba-cloud-sdk-go/sdk"
10 | "github.com/aliyun/alibaba-cloud-sdk-go/sdk/auth/credentials"
11 | "github.com/aliyun/alibaba-cloud-sdk-go/services/ecs"
12 | "github.com/aliyun/alibaba-cloud-sdk-go/services/rds"
13 | "github.com/aliyun/alibaba-cloud-sdk-go/services/sts"
14 | "github.com/aliyun/aliyun-oss-go-sdk/oss"
15 | "github.com/projectdiscovery/goflags"
16 | "github.com/projectdiscovery/gologger"
17 | "github.com/wgpsec/lc/pkg/schema"
18 | "github.com/wgpsec/lc/utils"
19 | "strings"
20 | )
21 |
22 | type Provider struct {
23 | id string
24 | provider string
25 | config providerConfig
26 | ossClient *oss.Client
27 | domainClient *domain.Client
28 | ecsRegions *ecs.DescribeRegionsResponse
29 | rdsRegions *rds.DescribeRegionsResponse
30 | fcRegions []FcRegion
31 | cloudServices []string
32 | identity *sts.GetCallerIdentityResponse
33 | }
34 |
35 | type providerConfig struct {
36 | accessKeyID string
37 | accessKeySecret string
38 | sessionToken string
39 | okST bool
40 | }
41 |
42 | func New(options schema.OptionBlock, cs goflags.StringSlice) (*Provider, error) {
43 | var (
44 | region = "cn-beijing"
45 | ossClient *oss.Client
46 | ecsClient *ecs.Client
47 | rdsClient *rds.Client
48 | stsClient *sts.Client
49 | domainClient *domain.Client
50 | err error
51 |
52 | identity *sts.GetCallerIdentityResponse
53 | ecsRegions *ecs.DescribeRegionsResponse
54 | rdsRegions *rds.DescribeRegionsResponse
55 | fcRegions []FcRegion
56 |
57 | cloudServices []string
58 | )
59 | accessKeyID, ok := options.GetMetadata(utils.AccessKey)
60 | if !ok {
61 | return nil, &utils.ErrNoSuchKey{Name: utils.AccessKey}
62 | }
63 | accessKeySecret, ok := options.GetMetadata(utils.SecretKey)
64 | if !ok {
65 | return nil, &utils.ErrNoSuchKey{Name: utils.SecretKey}
66 | }
67 | id, _ := options.GetMetadata(utils.Id)
68 | sessionToken, okST := options.GetMetadata(utils.SessionToken)
69 |
70 | config := providerConfig{
71 | accessKeyID: accessKeyID,
72 | accessKeySecret: accessKeySecret,
73 | sessionToken: sessionToken,
74 | okST: okST,
75 | }
76 | if okST {
77 | gologger.Debug().Msg("找到阿里云访问临时访问凭证")
78 | } else {
79 | gologger.Debug().Msg("找到阿里云访问永久访问凭证")
80 | }
81 | if cs[0] == "all" {
82 | cloudServicesResult, _ := options.GetMetadata(utils.CloudServices)
83 | cloudServices = strings.Split(cloudServicesResult, ",")
84 | } else {
85 | cloudServices = cs
86 | }
87 | for _, cloudService := range cloudServices {
88 | switch cloudService {
89 | case "ecs":
90 | // ecs client
91 | ecsConfig := sdk.NewConfig()
92 | if okST {
93 | credential := credentials.NewStsTokenCredential(accessKeyID, accessKeySecret, sessionToken)
94 | ecsClient, err = ecs.NewClientWithOptions(region, ecsConfig, credential)
95 | if err != nil {
96 | return nil, err
97 | }
98 | } else {
99 | credential := credentials.NewAccessKeyCredential(accessKeyID, accessKeySecret)
100 | ecsClient, err = ecs.NewClientWithOptions(region, ecsConfig, credential)
101 | if err != nil {
102 | return nil, err
103 | }
104 | }
105 | gologger.Debug().Msg("阿里云 ECS 客户端创建成功")
106 | // ecs regions
107 | ecsRegions, err = ecsClient.DescribeRegions(ecs.CreateDescribeRegionsRequest())
108 | if err != nil {
109 | return nil, err
110 | }
111 | gologger.Debug().Msg("阿里云 ECS 区域信息获取成功")
112 | case "oss":
113 | // oss client
114 | ossClient, err = oss.New(fmt.Sprintf("oss-%s.aliyuncs.com", region), accessKeyID, accessKeySecret)
115 | if err != nil {
116 | return nil, err
117 | }
118 | if okST {
119 | ossClient.Config.SecurityToken = sessionToken
120 | }
121 | gologger.Debug().Msg("阿里云 OSS 客户端创建成功")
122 | case "rds":
123 | // rds client
124 | rdsConfig := sdk.NewConfig()
125 | if okST {
126 | credential := credentials.NewStsTokenCredential(accessKeyID, accessKeySecret, sessionToken)
127 | rdsClient, err = rds.NewClientWithOptions(region, rdsConfig, credential)
128 | if err != nil {
129 | return nil, err
130 | }
131 | } else {
132 | credential := credentials.NewAccessKeyCredential(accessKeyID, accessKeySecret)
133 | rdsClient, err = rds.NewClientWithOptions(region, rdsConfig, credential)
134 | if err != nil {
135 | return nil, err
136 | }
137 | }
138 | gologger.Debug().Msg("阿里云 RDS 客户端创建成功")
139 |
140 | //rds regions
141 | rdsRegions, err = rdsClient.DescribeRegions(rds.CreateDescribeRegionsRequest())
142 | if err != nil {
143 | return nil, err
144 | }
145 | gologger.Debug().Msg("阿里云 RDS 区域信息获取成功")
146 | case "fc":
147 | // sts GetCallerIdentity
148 | stsConfig := sdk.NewConfig()
149 | if okST {
150 | credential := credentials.NewStsTokenCredential(accessKeyID, accessKeySecret, sessionToken)
151 | stsClient, err = sts.NewClientWithOptions(region, stsConfig, credential)
152 | } else {
153 | credential := credentials.NewAccessKeyCredential(accessKeyID, accessKeySecret)
154 | stsClient, err = sts.NewClientWithOptions(region, stsConfig, credential)
155 | }
156 | if err != nil {
157 | return nil, err
158 | }
159 |
160 | stsReq := sts.CreateGetCallerIdentityRequest()
161 | stsReq.SetScheme("HTTPS")
162 | identity, err = stsClient.GetCallerIdentity(stsReq)
163 | if err != nil {
164 | return nil, err
165 | }
166 | gologger.Debug().Msg("阿里云 STS 信息获取成功")
167 |
168 | // fc regions
169 | fcRegions, err = GetFcRegions()
170 | if err != nil {
171 | return nil, err
172 | }
173 |
174 | gologger.Debug().Msgf("阿里云 FC 区域信息获取成功, 共 %d 个\n", len(fcRegions))
175 |
176 | case "domain":
177 | // domain client
178 | credential := &openapi.Config{AccessKeyId: tea.String(accessKeyID), AccessKeySecret: tea.String(accessKeySecret),
179 | SecurityToken: tea.String(sessionToken)}
180 | domainClient, err = domain.NewClient(credential)
181 | if err != nil {
182 | return nil, err
183 | gologger.Debug().Msg("阿里云 Domain 客户端创建成功")
184 | }
185 | }
186 | }
187 | return &Provider{
188 | provider: utils.Aliyun, id: id, config: config, identity: identity,
189 | ossClient: ossClient, ecsRegions: ecsRegions, rdsRegions: rdsRegions, fcRegions: fcRegions, cloudServices: cloudServices,
190 | domainClient: domainClient,
191 | }, nil
192 | }
193 |
194 | func (p *Provider) Resources(ctx context.Context, cs goflags.StringSlice) (*schema.Resources, error) {
195 | finalList := schema.NewResources()
196 | for _, cloudService := range p.cloudServices {
197 | switch cloudService {
198 | case "ecs":
199 | // ecs
200 | ecsProvider := &instanceProvider{id: p.id, provider: p.provider, ecsRegions: p.ecsRegions, config: p.config}
201 | ecsList, err := ecsProvider.GetEcsResource(ctx)
202 | gologger.Info().Msgf("获取到 %d 条阿里云 ECS 信息", len(ecsList.GetItems()))
203 | if err != nil {
204 | return nil, err
205 | }
206 | finalList.Merge(ecsList)
207 | case "rds":
208 | // rds
209 | rdsProvider := &dbInstanceProvider{id: p.id, provider: p.provider, rdsRegions: p.rdsRegions, config: p.config}
210 | rdsList, err := rdsProvider.GetRdsResource(ctx)
211 | if err != nil {
212 | return nil, err
213 | }
214 | gologger.Info().Msgf("获取到 %d 条阿里云 RDS 信息", len(rdsList.GetItems()))
215 | finalList.Merge(rdsList)
216 | case "oss":
217 | // oss
218 | ossProvider := &ossProvider{ossClient: p.ossClient, id: p.id, provider: p.provider}
219 | buckets, err := ossProvider.GetResource(ctx)
220 | if err != nil {
221 | return nil, err
222 | }
223 | gologger.Info().Msgf("获取到 %d 条阿里云 OSS 信息", len(buckets.GetItems()))
224 | finalList.Merge(buckets)
225 | case "fc":
226 | // fc
227 | fcProvider := &functionProvider{
228 | id: p.id, provider: p.provider, config: p.config,
229 | fcRegions: p.fcRegions, identity: p.identity,
230 | }
231 | fcList, err := fcProvider.GetResource()
232 | if err != nil {
233 | return nil, err
234 | }
235 | finalList.Merge(fcList)
236 |
237 | // fc 3.0
238 | fc3Provider := &function3Provider{
239 | id: p.id, provider: p.provider, config: p.config,
240 | fcRegions: p.fcRegions, identity: p.identity,
241 | }
242 | fc3List, err := fc3Provider.GetResource()
243 | if err != nil {
244 | return nil, err
245 | }
246 | gologger.Info().Msgf("获取到 %d 条阿里云 FC 信息", len(fcList.GetItems())+len(fc3List.GetItems()))
247 | finalList.Merge(fc3List)
248 | case "domain":
249 | // domain
250 | domainProvider := &domainProvider{id: p.id, provider: p.provider, domainClient: p.domainClient}
251 | domainList, err := domainProvider.GetResource(ctx)
252 | if err != nil {
253 | return nil, err
254 | }
255 | gologger.Info().Msgf("获取到 %d 条阿里云 Domain 信息", len(domainList.GetItems()))
256 | finalList.Merge(domainList)
257 | }
258 | }
259 | return finalList, nil
260 | }
261 |
262 | func (p *Provider) Name() string {
263 | return p.provider
264 | }
265 | func (p *Provider) ID() string {
266 | return p.id
267 | }
268 |
--------------------------------------------------------------------------------
/pkg/providers/aliyun/domain.go:
--------------------------------------------------------------------------------
1 | package aliyun
2 |
3 | import (
4 | "context"
5 | domain "github.com/alibabacloud-go/domain-20180129/v4/client"
6 | util "github.com/alibabacloud-go/tea-utils/v2/service"
7 | "github.com/alibabacloud-go/tea/tea"
8 | "github.com/projectdiscovery/gologger"
9 | "github.com/wgpsec/lc/pkg/schema"
10 | )
11 |
12 | type domainProvider struct {
13 | id string
14 | provider string
15 | domainClient *domain.Client
16 | }
17 |
18 | func (d *domainProvider) GetResource(ctx context.Context) (*schema.Resources, error) {
19 | domainList := schema.NewResources()
20 | gologger.Debug().Msg("正在获取阿里云 Domain 资源信息")
21 | queryDomainListRequest := &domain.QueryDomainListRequest{
22 | PageNum: tea.Int32(1),
23 | PageSize: tea.Int32(10),
24 | }
25 | runtime := &util.RuntimeOptions{}
26 | response, err := d.domainClient.QueryDomainListWithOptions(queryDomainListRequest, runtime)
27 | if err != nil {
28 | return nil, err
29 | }
30 | for _, domainResult := range response.Body.Data.Domain {
31 | domainList.Append(&schema.Resource{
32 | ID: d.id,
33 | Public: true,
34 | DNSName: *domainResult.DomainName,
35 | Provider: d.provider,
36 | })
37 | }
38 | return domainList, nil
39 | }
40 |
--------------------------------------------------------------------------------
/pkg/providers/aliyun/ecs.go:
--------------------------------------------------------------------------------
1 | package aliyun
2 |
3 | import (
4 | "context"
5 | "github.com/aliyun/alibaba-cloud-sdk-go/sdk"
6 | "github.com/aliyun/alibaba-cloud-sdk-go/sdk/auth/credentials"
7 | "github.com/aliyun/alibaba-cloud-sdk-go/services/ecs"
8 | "github.com/projectdiscovery/gologger"
9 | "github.com/wgpsec/lc/pkg/schema"
10 | "sync"
11 | )
12 |
13 | type instanceProvider struct {
14 | id string
15 | provider string
16 | config providerConfig
17 | ecsRegions *ecs.DescribeRegionsResponse
18 | }
19 |
20 | var ecsList = schema.NewResources()
21 |
22 | func (d *instanceProvider) GetEcsResource(ctx context.Context) (*schema.Resources, error) {
23 | var (
24 | threads int
25 | err error
26 | wg sync.WaitGroup
27 | regions []string
28 | )
29 | threads = schema.GetThreads()
30 |
31 | for _, region := range d.ecsRegions.Regions.Region {
32 | regions = append(regions, region.RegionId)
33 | }
34 |
35 | taskCh := make(chan string, threads)
36 | for i := 0; i < threads; i++ {
37 | wg.Add(1)
38 | go func() {
39 | err = d.describeEcsInstances(taskCh, &wg)
40 | if err != nil {
41 | return
42 | }
43 | }()
44 | }
45 | for _, item := range regions {
46 | taskCh <- item
47 | }
48 | close(taskCh)
49 | wg.Wait()
50 | return ecsList, nil
51 | }
52 |
53 | func (d *instanceProvider) describeEcsInstances(ch <-chan string, wg *sync.WaitGroup) error {
54 | defer wg.Done()
55 | var (
56 | err error
57 | ecsClient *ecs.Client
58 | response *ecs.DescribeInstancesResponse
59 | )
60 | for region := range ch {
61 | ecsConfig := sdk.NewConfig()
62 | if d.config.okST {
63 | credential := credentials.NewStsTokenCredential(d.config.accessKeyID, d.config.accessKeySecret, d.config.sessionToken)
64 | ecsClient, err = ecs.NewClientWithOptions(region, ecsConfig, credential)
65 | if err != nil {
66 | continue
67 | }
68 | } else {
69 | credential := credentials.NewAccessKeyCredential(d.config.accessKeyID, d.config.accessKeySecret)
70 | ecsClient, err = ecs.NewClientWithOptions(region, ecsConfig, credential)
71 | if err != nil {
72 | continue
73 | }
74 | }
75 | gologger.Debug().Msgf("正在获取 %s 区域下的阿里云 ECS 资源信息", region)
76 | request := ecs.CreateDescribeInstancesRequest()
77 | for {
78 | response, err = ecsClient.DescribeInstances(request)
79 | if err != nil {
80 | break
81 | }
82 | if len(response.Instances.Instance) > 0 {
83 | gologger.Warning().Msgf("在 %s 区域下获取到 %d 条 ECS 资源", region, len(response.Instances.Instance))
84 | }
85 | for _, instance := range response.Instances.Instance {
86 | var (
87 | ipv4 []string
88 | privateIPv4 string
89 | )
90 | if len(instance.PublicIpAddress.IpAddress) > 0 {
91 | ipv4 = append(ipv4, instance.PublicIpAddress.IpAddress...)
92 | }
93 | if len(instance.EipAddress.IpAddress) > 0 {
94 | ipv4 = append(ipv4, instance.EipAddress.IpAddress)
95 | }
96 | if len(instance.NetworkInterfaces.NetworkInterface[0].PrivateIpSets.PrivateIpSet) > 0 {
97 | privateIPv4 = instance.NetworkInterfaces.NetworkInterface[0].PrivateIpSets.PrivateIpSet[0].PrivateIpAddress
98 | }
99 | if len(ipv4) > 0 {
100 | for _, v := range ipv4 {
101 | ecsList.Append(&schema.Resource{
102 | ID: d.id,
103 | Provider: d.provider,
104 | PublicIPv4: v,
105 | PrivateIpv4: privateIPv4,
106 | Public: true,
107 | })
108 | }
109 | } else {
110 | ecsList.Append(&schema.Resource{
111 | ID: d.id,
112 | Provider: d.provider,
113 | PublicIPv4: "",
114 | PrivateIpv4: privateIPv4,
115 | Public: false,
116 | })
117 | }
118 |
119 | }
120 | if response.NextToken == "" {
121 | gologger.Debug().Msgf("NextToken 为空,已终止获取")
122 | break
123 | }
124 | gologger.Debug().Msgf("NextToken 不为空,正在获取下一页数据")
125 | request.NextToken = response.NextToken
126 | }
127 | }
128 | return err
129 | }
130 |
--------------------------------------------------------------------------------
/pkg/providers/aliyun/fc.go:
--------------------------------------------------------------------------------
1 | package aliyun
2 |
3 | import (
4 | "encoding/json"
5 | "errors"
6 | "fmt"
7 | openapi "github.com/alibabacloud-go/darabonba-openapi/v2/client"
8 | fc "github.com/alibabacloud-go/fc-open-20210406/v2/client"
9 | "github.com/aliyun/alibaba-cloud-sdk-go/services/sts"
10 | "github.com/projectdiscovery/gologger"
11 | "github.com/wgpsec/lc/pkg/schema"
12 | "io"
13 | "net/http"
14 | "strings"
15 | "sync"
16 | )
17 |
18 | type functionProvider struct {
19 | id string
20 | identity *sts.GetCallerIdentityResponse
21 | provider string
22 | config providerConfig
23 | fcRegions []FcRegion
24 | }
25 |
26 | type FcRegionsResp struct {
27 | Code int `json:"code"`
28 | Data struct {
29 | Type string `json:"type"`
30 | Endpoints []FcRegion `json:"endpoints"`
31 | } `json:"data"`
32 | }
33 |
34 | type FcRegion struct {
35 | RegionId string `json:"regionId"`
36 | RegionName string `json:"regionName"`
37 | AreaId string `json:"areaId"`
38 | AreaName string `json:"areaName"`
39 | Public string `json:"public"`
40 | VPC string `json:"vpc"`
41 | }
42 |
43 | type FcTriggerConfig struct {
44 | Method []string `json:"method"`
45 | AuthType string `json:"authType"`
46 | DisableURLInternet bool `json:"disableURLInternet"`
47 | }
48 |
49 | var fcList = schema.NewResources()
50 | var fcResourceMap = sync.Map{}
51 |
52 | func (f *functionProvider) GetResource() (*schema.Resources, error) {
53 | var (
54 | threads int
55 | err error
56 | wg sync.WaitGroup
57 | regions []string
58 | )
59 |
60 | for _, region := range f.fcRegions {
61 | if !strings.Contains(region.RegionId, "finance") {
62 | regions = append(regions, region.RegionId)
63 | }
64 | }
65 |
66 | threads = schema.GetThreads()
67 | taskCh := make(chan string, threads)
68 | for i := 0; i < threads; i++ {
69 | wg.Add(1)
70 | go func() {
71 | err = f.describeFcService(taskCh, &wg)
72 | if err != nil {
73 | return
74 | }
75 | }()
76 | }
77 | for _, item := range regions {
78 | taskCh <- item
79 | }
80 | close(taskCh)
81 | wg.Wait()
82 |
83 | taskCh = make(chan string, threads)
84 | for i := 0; i < threads; i++ {
85 | wg.Add(1)
86 | go func() {
87 | err = f.describeFcCustomDomains(taskCh, &wg)
88 | if err != nil {
89 | return
90 | }
91 | }()
92 | }
93 | for _, item := range regions {
94 | taskCh <- item
95 | }
96 | close(taskCh)
97 | wg.Wait()
98 |
99 | return fcList, nil
100 | }
101 |
102 | func (f *functionProvider) newFcConfig(region string) *openapi.Config {
103 | endpoint := fmt.Sprintf("%s.%s.fc.aliyuncs.com", f.identity.AccountId, region)
104 | return &openapi.Config{
105 | AccessKeyId: &f.config.accessKeyID,
106 | AccessKeySecret: &f.config.accessKeySecret,
107 | SecurityToken: &f.config.sessionToken,
108 | Endpoint: &endpoint,
109 | RegionId: ®ion,
110 | }
111 | }
112 |
113 | // describeFcCustomDomains 经测试, 就算 fc 禁用公网访问, 如有自定义域名, 能自定义域名+路由直接访问函数
114 | func (f *functionProvider) describeFcCustomDomains(ch <-chan string, wg *sync.WaitGroup) error {
115 | defer wg.Done()
116 | var (
117 | err error
118 | fcClient *fc.Client
119 | domainRes *fc.ListCustomDomainsResponse
120 | )
121 |
122 | for region := range ch {
123 |
124 | if _, ok := fcResourceMap.Load(region); !ok {
125 | gologger.Debug().Msgf("%s 区域下的阿里云无 FC 函数, 跳过获取自定义域名", region)
126 | continue
127 | }
128 |
129 | gologger.Debug().Msgf("正在获取 %s 区域下的阿里云 FC 自定义域名资源信息", region)
130 | fcConfig := f.newFcConfig(region)
131 | fcClient, err = fc.NewClient(fcConfig)
132 | if err != nil {
133 | gologger.Debug().Msgf("%s endpoint NewClient err: %s", *fcConfig.Endpoint, err)
134 | break
135 | }
136 |
137 | lcdReq := &fc.ListCustomDomainsRequest{}
138 | for {
139 | domainRes, err = fcClient.ListCustomDomains(lcdReq)
140 | if err != nil {
141 | gologger.Debug().Msgf("%s endpoint ListCustomDomains err: %s", *fcClient.Endpoint, err)
142 | continue
143 | }
144 | for _, cd := range domainRes.Body.CustomDomains {
145 | fcList.Append(&schema.Resource{
146 | ID: f.id,
147 | Provider: f.provider,
148 | // FIXME 目前 lc 输出结果并没有 region 区分, 但在控制台 FC 中很难识别是哪个区
149 | // 因为控制台鼠标指针放到可用区并不会显示数量.... 所以目前先这样显示
150 | // 此外, 有的 url 不会拼接 cn-shanghai 之类的
151 | DNSName: fmt.Sprintf("%s://%s#%s", strings.ToLower(*cd.Protocol), *cd.DomainName, region),
152 | // 如果想判断内外网, 目前接口没有字段能表示是公网还是内网, 只能 dns 查询 CNAME
153 | // 结果是否为 -internal.fc.aliyuncs.com 结尾
154 | })
155 | }
156 | if domainRes.Body.NextToken == nil {
157 | break
158 | }
159 | gologger.Debug().Msgf("NextToken 不为空,正在获取下一页数据")
160 | lcdReq.NextToken = domainRes.Body.NextToken
161 | }
162 | }
163 | return err
164 | }
165 |
166 | func (f *functionProvider) describeFcService(ch <-chan string, wg *sync.WaitGroup) error {
167 | defer wg.Done()
168 | var (
169 | err error
170 | fcClient *fc.Client
171 | )
172 |
173 | for region := range ch {
174 | gologger.Debug().Msgf("正在获取 %s 区域下的阿里云 FC 资源信息", region)
175 |
176 | fcConfig := f.newFcConfig(region)
177 | fcClient, err = fc.NewClient(fcConfig)
178 | if err != nil {
179 | gologger.Debug().Msgf("%s endpoint NewClient err: %s", *fcConfig.Endpoint, err)
180 | break
181 | }
182 |
183 | err = f.processFcService(fcClient)
184 | if err != nil {
185 | gologger.Debug().Msgf("%s endpoint ListServices err: %s", *fcClient.Endpoint, err)
186 | }
187 | }
188 |
189 | return err
190 | }
191 |
192 | func (f *functionProvider) processFcService(fcClient *fc.Client) error {
193 | lsReq := &fc.ListServicesRequest{}
194 | for {
195 | serviceRes, err := fcClient.ListServices(lsReq)
196 | if err != nil {
197 | return err
198 | }
199 |
200 | for _, s := range serviceRes.Body.Services {
201 | err = f.processFcFunction(fcClient, s)
202 | if err != nil {
203 | gologger.Debug().Msgf("%s endpoint ListFunctions err: %s", *fcClient.Endpoint, err)
204 | break
205 | }
206 | }
207 |
208 | if serviceRes.Body.NextToken == nil {
209 | break
210 | }
211 | gologger.Debug().Msgf(
212 | "%s region's serviceRes NextToken 不为空 %s,正在获取下一页数据",
213 | *fcClient.RegionId, *serviceRes.Body.NextToken,
214 | )
215 | lsReq.NextToken = serviceRes.Body.NextToken
216 | }
217 |
218 | return nil
219 | }
220 |
221 | func (f *functionProvider) processFcFunction(fcClient *fc.Client, s *fc.ListServicesResponseBodyServices) error {
222 | lfReq := &fc.ListFunctionsRequest{}
223 | for {
224 | funcRes, err := fcClient.ListFunctions(s.ServiceName, lfReq)
225 | if err != nil {
226 | return err
227 | }
228 |
229 | // speed up for describeFcCustomDomains
230 | if len(funcRes.Body.Functions) > 0 {
231 | fcResourceMap.Store(*fcClient.RegionId, true)
232 | }
233 |
234 | for _, ft := range funcRes.Body.Functions {
235 | err = f.processFcTrigger(fcClient, s, ft)
236 | if err != nil {
237 | gologger.Debug().Msgf(
238 | "%s endpoint [%s]-[%s] ListTriggers err: %s",
239 | *fcClient.Endpoint, *s.ServiceName, *ft.FunctionName, err,
240 | )
241 | }
242 | }
243 |
244 | if funcRes.Body.NextToken == nil {
245 | break
246 | }
247 | gologger.Debug().Msgf(
248 | "%s service's funcRes NextToken 不为空 %s,正在获取下一页数据",
249 | *s.ServiceName, *funcRes.Body.NextToken,
250 | )
251 | lfReq.NextToken = funcRes.Body.NextToken
252 | }
253 |
254 | return nil
255 | }
256 |
257 | func (f *functionProvider) processFcTrigger(
258 | fcClient *fc.Client, s *fc.ListServicesResponseBodyServices, ft *fc.ListFunctionsResponseBodyFunctions,
259 | ) error {
260 | ltReq := &fc.ListTriggersRequest{}
261 | for {
262 | triggerRes, err := fcClient.ListTriggers(s.ServiceName, ft.FunctionName, ltReq)
263 | if err != nil {
264 | return err
265 | }
266 | for _, t := range triggerRes.Body.Triggers {
267 | if t.TriggerType != nil && strings.ToLower(*t.TriggerType) == "http" {
268 | var ftc FcTriggerConfig
269 | err = json.Unmarshal([]byte(*t.TriggerConfig), &ftc)
270 | if err != nil {
271 | gologger.Debug().Msgf("%s endpoint Unmarshal FcTriggerConfig err: %s", *fcClient.Endpoint, err)
272 | continue
273 | }
274 | if ftc.DisableURLInternet {
275 | continue
276 | }
277 | if t.UrlInternet == nil {
278 | gologger.Debug().Msgf(
279 | "%s endpoint %s - %s enable internet access but url not found, skip",
280 | *fcClient.Endpoint, *s.ServiceName, *ft.FunctionName,
281 | )
282 | continue
283 | }
284 | fcList.Append(&schema.Resource{
285 | ID: f.id,
286 | Provider: f.provider,
287 | // FIXME 目前 lc 输出结果并没有分区一说, 但在 fc 中很难识别是哪个区
288 | // 因为控制台鼠标指针放到可用区并不会显示数量.... 所以目前先这样显示
289 | // 此外, 有的 url 不会拼接 cn-shanghai 之类的
290 | DNSName: fmt.Sprintf("%s#%s", *t.UrlInternet, *fcClient.RegionId),
291 | Public: ftc.DisableURLInternet,
292 | })
293 | }
294 | }
295 | if triggerRes.Body.NextToken == nil {
296 | break
297 | }
298 |
299 | gologger.Debug().Msgf(
300 | "%s service %s function triggerRes NextToken 不为空 %s,正在获取下一页数据",
301 | *s.ServiceName, *ft.FunctionName, *triggerRes.Body.NextToken,
302 | )
303 | ltReq.NextToken = triggerRes.Body.NextToken
304 | }
305 | return nil
306 | }
307 |
308 | // GetFcRegions 貌似阿里云没有提供 SDK 获取可用区, 只能抓接口拿了
309 | func GetFcRegions() ([]FcRegion, error) {
310 | resp, err := http.Get("https://next.api.aliyun.com/meta/v1/products/FC-Open/endpoints.json")
311 | if err != nil {
312 | return nil, errors.New(fmt.Sprintf("Error fetching URL: %v\n", err))
313 | }
314 | defer resp.Body.Close()
315 |
316 | body, err := io.ReadAll(resp.Body)
317 | if err != nil {
318 | return nil, errors.New(fmt.Sprintf("Error reading response body: %v\n", err))
319 | }
320 |
321 | var endpoints FcRegionsResp
322 | err = json.Unmarshal(body, &endpoints)
323 | if err != nil {
324 | return nil, errors.New(fmt.Sprintf("Error decoding JSON: %v\n", err))
325 | }
326 |
327 | return endpoints.Data.Endpoints, nil
328 | }
329 |
--------------------------------------------------------------------------------
/pkg/providers/aliyun/fc3.go:
--------------------------------------------------------------------------------
1 | package aliyun
2 |
3 | import (
4 | "fmt"
5 | openapi "github.com/alibabacloud-go/darabonba-openapi/v2/client"
6 | fc "github.com/alibabacloud-go/fc-20230330/v4/client"
7 | "github.com/aliyun/alibaba-cloud-sdk-go/services/sts"
8 | "github.com/projectdiscovery/gologger"
9 | "github.com/wgpsec/lc/pkg/schema"
10 | "strings"
11 | "sync"
12 | )
13 |
14 | type function3Provider struct {
15 | id string
16 | identity *sts.GetCallerIdentityResponse
17 | provider string
18 | config providerConfig
19 | fcRegions []FcRegion
20 | }
21 |
22 | var fc3List = schema.NewResources()
23 |
24 | func (f *function3Provider) GetResource() (*schema.Resources, error) {
25 | var (
26 | threads int
27 | err error
28 | wg sync.WaitGroup
29 | regions []string
30 | )
31 |
32 | for _, region := range f.fcRegions {
33 | if !strings.Contains(region.RegionId, "finance") {
34 | regions = append(regions, region.RegionId)
35 | }
36 | }
37 |
38 | threads = schema.GetThreads()
39 | taskCh := make(chan string, threads)
40 | for i := 0; i < threads; i++ {
41 | wg.Add(1)
42 | go func() {
43 | err = f.listCustomDomains(taskCh, &wg)
44 | if err != nil {
45 | return
46 | }
47 | }()
48 | }
49 | for _, item := range regions {
50 | taskCh <- item
51 | }
52 | close(taskCh)
53 | wg.Wait()
54 |
55 | return fc3List, nil
56 | }
57 |
58 | func (f *function3Provider) newFcConfig(region string) *openapi.Config {
59 | endpoint := fmt.Sprintf("%s.%s.fc.aliyuncs.com", f.identity.AccountId, region)
60 | return &openapi.Config{
61 | AccessKeyId: &f.config.accessKeyID,
62 | AccessKeySecret: &f.config.accessKeySecret,
63 | SecurityToken: &f.config.sessionToken,
64 | Endpoint: &endpoint,
65 | RegionId: ®ion,
66 | }
67 | }
68 |
69 | func (f *function3Provider) listCustomDomains(ch <-chan string, wg *sync.WaitGroup) error {
70 | defer wg.Done()
71 | var (
72 | err error
73 | fcClient *fc.Client
74 | domainRes *fc.ListCustomDomainsResponse
75 | )
76 |
77 | for region := range ch {
78 | gologger.Debug().Msgf("正在获取 %s 区域下的阿里云 FC 3.0 自定义域名资源信息", region)
79 | fcConfig := f.newFcConfig(region)
80 | fcClient, err = fc.NewClient(fcConfig)
81 | if err != nil {
82 | gologger.Debug().Msgf("%s endpoint NewClient err: %s", *fcConfig.Endpoint, err)
83 | break
84 | }
85 |
86 | lcdReq := &fc.ListCustomDomainsRequest{}
87 | domainRes, err = fcClient.ListCustomDomains(lcdReq)
88 | if err != nil {
89 | gologger.Debug().Msgf("%s endpoint ListCustomDomains err: %s", *fcClient.Endpoint, err)
90 | continue
91 | }
92 | for _, cd := range domainRes.Body.CustomDomains {
93 | fc3List.Append(&schema.Resource{
94 | ID: f.id,
95 | Provider: f.provider,
96 | DNSName: fmt.Sprintf("%s://%s", strings.ToLower(*cd.Protocol), *cd.DomainName),
97 | })
98 | }
99 | }
100 | return err
101 | }
102 |
--------------------------------------------------------------------------------
/pkg/providers/aliyun/oss.go:
--------------------------------------------------------------------------------
1 | package aliyun
2 |
3 | import (
4 | "context"
5 | "github.com/aliyun/aliyun-oss-go-sdk/oss"
6 | "github.com/projectdiscovery/gologger"
7 | "github.com/wgpsec/lc/pkg/schema"
8 | "strings"
9 | )
10 |
11 | type ossProvider struct {
12 | id string
13 | provider string
14 | ossClient *oss.Client
15 | }
16 |
17 | func (d *ossProvider) GetResource(ctx context.Context) (*schema.Resources, error) {
18 | ossList := schema.NewResources()
19 | marker := oss.Marker("")
20 | gologger.Debug().Msg("正在获取阿里云 OSS 资源信息")
21 | for {
22 | response, err := d.ossClient.ListBuckets(oss.MaxKeys(1000), marker)
23 | if err != nil {
24 | break
25 | }
26 | marker = oss.Marker(response.NextMarker)
27 | for _, bucket := range response.Buckets {
28 | endpointBuilder := &strings.Builder{}
29 | endpointBuilder.WriteString(bucket.Name)
30 | endpointBuilder.WriteString(".oss-" + bucket.Region)
31 | endpointBuilder.WriteString(".aliyuncs.com")
32 | ossList.Append(&schema.Resource{
33 | ID: d.id,
34 | Public: true,
35 | DNSName: endpointBuilder.String(),
36 | Provider: d.provider,
37 | })
38 | }
39 | if !response.IsTruncated {
40 | break
41 | }
42 | }
43 | return ossList, nil
44 | }
45 |
--------------------------------------------------------------------------------
/pkg/providers/aliyun/rds.go:
--------------------------------------------------------------------------------
1 | package aliyun
2 |
3 | import (
4 | "context"
5 | "github.com/aliyun/alibaba-cloud-sdk-go/sdk"
6 | "github.com/aliyun/alibaba-cloud-sdk-go/sdk/auth/credentials"
7 | "github.com/aliyun/alibaba-cloud-sdk-go/services/rds"
8 | "github.com/projectdiscovery/gologger"
9 | "github.com/wgpsec/lc/pkg/schema"
10 | "github.com/wgpsec/lc/utils"
11 | "sync"
12 | )
13 |
14 | type dbInstanceProvider struct {
15 | id string
16 | provider string
17 | config providerConfig
18 | rdsRegions *rds.DescribeRegionsResponse
19 | }
20 |
21 | type rdsInstance struct {
22 | dbId string
23 | region string
24 | }
25 |
26 | var rdsInstances []rdsInstance
27 | var rdsList = schema.NewResources()
28 |
29 | func (d *dbInstanceProvider) GetRdsResource(ctx context.Context) (*schema.Resources, error) {
30 | var (
31 | threads int
32 | err error
33 | wg sync.WaitGroup
34 | regions []string
35 | )
36 | threads = schema.GetThreads()
37 |
38 | for _, region := range d.rdsRegions.Regions.RDSRegion {
39 | regions = append(regions, region.RegionId)
40 | }
41 | regions = utils.RemoveRepeatedElement(regions)
42 |
43 | taskCh := make(chan string, threads)
44 | for i := 0; i < threads; i++ {
45 | wg.Add(1)
46 | go func() {
47 | err = d.describeRdsInstances(taskCh, &wg)
48 | if err != nil {
49 | return
50 | }
51 | }()
52 | }
53 | for _, item := range regions {
54 | taskCh <- item
55 | }
56 | close(taskCh)
57 | wg.Wait()
58 | err = d.GetRdsConnectionString(ctx)
59 | if err != nil {
60 | return nil, err
61 | }
62 | return rdsList, nil
63 | }
64 |
65 | func (d *dbInstanceProvider) describeRdsInstances(ch <-chan string, wg *sync.WaitGroup) error {
66 | defer wg.Done()
67 | var (
68 | err error
69 | rdsClient *rds.Client
70 | response *rds.DescribeDBInstancesResponse
71 | )
72 | for region := range ch {
73 | rdsConfig := sdk.NewConfig()
74 | if d.config.okST {
75 | credential := credentials.NewStsTokenCredential(d.config.accessKeyID, d.config.accessKeySecret, d.config.sessionToken)
76 | rdsClient, err = rds.NewClientWithOptions(region, rdsConfig, credential)
77 | if err != nil {
78 | continue
79 | }
80 | } else {
81 | credential := credentials.NewAccessKeyCredential(d.config.accessKeyID, d.config.accessKeySecret)
82 | rdsClient, err = rds.NewClientWithOptions(region, rdsConfig, credential)
83 | if err != nil {
84 | continue
85 | }
86 | }
87 | gologger.Debug().Msgf("正在获取 %s 区域下的阿里云 RDS 资源信息", region)
88 | request := rds.CreateDescribeDBInstancesRequest()
89 | for {
90 | response, err = rdsClient.DescribeDBInstances(request)
91 | if err != nil {
92 | break
93 | }
94 | if len(response.Items.DBInstance) > 0 {
95 | gologger.Warning().Msgf("在 %s 区域下获取到 %d 条 RDS 资源", region, len(response.Items.DBInstance))
96 | }
97 | for _, DBInstance := range response.Items.DBInstance {
98 | rdsInstances = append(rdsInstances, rdsInstance{
99 | dbId: DBInstance.DBInstanceId,
100 | region: region,
101 | })
102 | }
103 | if response.NextToken == "" {
104 | gologger.Debug().Msgf("NextToken 为空,已终止获取")
105 | break
106 | }
107 | gologger.Debug().Msgf("NextToken 不为空,正在获取下一页数据")
108 | request.NextToken = response.NextToken
109 | }
110 | }
111 | return err
112 | }
113 |
114 | func (d *dbInstanceProvider) GetRdsConnectionString(ctx context.Context) error {
115 | var (
116 | private string
117 | public string
118 | err error
119 | rdsClient *rds.Client
120 | response *rds.DescribeDBInstanceNetInfoResponse
121 | )
122 | for _, dbInstance := range rdsInstances {
123 | gologger.Debug().Msgf("正在获取 %s RDS 实例的连接信息", dbInstance.dbId)
124 | rdsConfig := sdk.NewConfig()
125 | if d.config.okST {
126 | credential := credentials.NewStsTokenCredential(d.config.accessKeyID, d.config.accessKeySecret, d.config.sessionToken)
127 | rdsClient, err = rds.NewClientWithOptions(dbInstance.region, rdsConfig, credential)
128 | if err != nil {
129 | continue
130 | }
131 | } else {
132 | credential := credentials.NewAccessKeyCredential(d.config.accessKeyID, d.config.accessKeySecret)
133 | rdsClient, err = rds.NewClientWithOptions(dbInstance.region, rdsConfig, credential)
134 | if err != nil {
135 | continue
136 | }
137 | }
138 | request := rds.CreateDescribeDBInstanceNetInfoRequest()
139 | request.DBInstanceId = dbInstance.dbId
140 |
141 | response, err = rdsClient.DescribeDBInstanceNetInfo(request)
142 | if err != nil {
143 | return nil
144 | }
145 | for _, DBInstanceNetInfo := range response.DBInstanceNetInfos.DBInstanceNetInfo {
146 | if DBInstanceNetInfo.IPType == "Private" {
147 | private = DBInstanceNetInfo.ConnectionString
148 | } else if DBInstanceNetInfo.IPType == "Public" {
149 | public = DBInstanceNetInfo.ConnectionString
150 | }
151 | }
152 | rdsList.Append(&schema.Resource{
153 | ID: d.id,
154 | Provider: d.provider,
155 | PublicIPv4: public,
156 | PrivateIpv4: private,
157 | Public: public != "",
158 | })
159 | }
160 | return err
161 | }
162 |
--------------------------------------------------------------------------------
/pkg/providers/baidu/baidu.go:
--------------------------------------------------------------------------------
1 | package baidu
2 |
3 | import (
4 | "context"
5 | "github.com/baidubce/bce-sdk-go/auth"
6 | "github.com/baidubce/bce-sdk-go/services/bos"
7 | "github.com/projectdiscovery/goflags"
8 | "github.com/projectdiscovery/gologger"
9 | "github.com/wgpsec/lc/pkg/schema"
10 | "github.com/wgpsec/lc/utils"
11 | "strings"
12 | )
13 |
14 | type Provider struct {
15 | id string
16 | provider string
17 | bosClient *bos.Client
18 | config providerConfig
19 | cloudServices []string
20 | }
21 |
22 | type providerConfig struct {
23 | accessKeyID string
24 | accessKeySecret string
25 | sessionToken string
26 | okST bool
27 | }
28 |
29 | func New(options schema.OptionBlock, cs goflags.StringSlice) (*Provider, error) {
30 | var (
31 | endpoint = "https://bj.bcebos.com"
32 | err error
33 | bosClient *bos.Client
34 | cloudServices []string
35 | )
36 | accessKeyID, ok := options.GetMetadata(utils.AccessKey)
37 | if !ok {
38 | return nil, &utils.ErrNoSuchKey{Name: utils.AccessKey}
39 | }
40 | accessKeySecret, ok := options.GetMetadata(utils.SecretKey)
41 | if !ok {
42 | return nil, &utils.ErrNoSuchKey{Name: utils.SecretKey}
43 | }
44 | id, _ := options.GetMetadata(utils.Id)
45 | sessionToken, okST := options.GetMetadata(utils.SessionToken)
46 |
47 | if okST {
48 | gologger.Debug().Msg("找到百度云访问临时访问凭证")
49 | } else {
50 | gologger.Debug().Msg("找到百度云访问永久访问凭证")
51 | }
52 |
53 | if cs[0] == "all" {
54 | cloudServicesResult, _ := options.GetMetadata(utils.CloudServices)
55 | cloudServices = strings.Split(cloudServicesResult, ",")
56 | } else {
57 | cloudServices = cs
58 | }
59 | for _, cloudService := range cloudServices {
60 | switch cloudService {
61 | case "bos":
62 | // bos client
63 | if okST {
64 | bosClient, err = bos.NewClient(accessKeyID, accessKeySecret, "")
65 | if err != nil {
66 | return nil, err
67 | }
68 | stsCredential, err := auth.NewSessionBceCredentials(
69 | accessKeyID,
70 | accessKeySecret,
71 | sessionToken)
72 | if err != nil {
73 | return nil, err
74 | }
75 | bosClient.Config.Credentials = stsCredential
76 | } else {
77 | clientConfig := bos.BosClientConfiguration{
78 | Ak: accessKeyID,
79 | Sk: accessKeySecret,
80 | Endpoint: endpoint,
81 | RedirectDisabled: false,
82 | }
83 | bosClient, err = bos.NewClientWithConfig(&clientConfig)
84 | }
85 | if err != nil {
86 | return nil, err
87 | }
88 | }
89 | }
90 | config := providerConfig{
91 | accessKeyID: accessKeyID,
92 | accessKeySecret: accessKeySecret,
93 | sessionToken: sessionToken,
94 | okST: okST,
95 | }
96 |
97 | return &Provider{provider: utils.Baidu, id: id, bosClient: bosClient, config: config, cloudServices: cloudServices}, nil
98 | }
99 |
100 | func (p *Provider) Resources(ctx context.Context, cs goflags.StringSlice) (*schema.Resources, error) {
101 | finalList := schema.NewResources()
102 | for _, cloudService := range p.cloudServices {
103 | switch cloudService {
104 | case "bcc":
105 | bccProvider := &instanceProvider{provider: p.provider, id: p.id, config: p.config}
106 | lists, err := bccProvider.GetResource(ctx)
107 | if err != nil {
108 | return nil, err
109 | }
110 | gologger.Info().Msgf("获取到 %d 条百度云 BCC 信息", len(lists.GetItems()))
111 | finalList.Merge(lists)
112 | case "bos":
113 | bosProvider := &bosProvider{bosClient: p.bosClient, id: p.id, provider: p.provider}
114 | buckets, err := bosProvider.GetResource(ctx)
115 | if err != nil {
116 | return nil, err
117 | }
118 | gologger.Info().Msgf("获取到 %d 条百度云 BOS 信息", len(buckets.GetItems()))
119 | finalList.Merge(buckets)
120 | }
121 | }
122 | return finalList, nil
123 | }
124 |
125 | func (p *Provider) Name() string {
126 | return p.provider
127 | }
128 | func (p *Provider) ID() string {
129 | return p.id
130 | }
131 |
--------------------------------------------------------------------------------
/pkg/providers/baidu/bcc.go:
--------------------------------------------------------------------------------
1 | package baidu
2 |
3 | import (
4 | "context"
5 | "github.com/baidubce/bce-sdk-go/auth"
6 | "github.com/baidubce/bce-sdk-go/services/bcc"
7 | "github.com/baidubce/bce-sdk-go/services/bcc/api"
8 | "github.com/wgpsec/lc/pkg/schema"
9 | "sync"
10 | )
11 |
12 | type instanceProvider struct {
13 | id string
14 | provider string
15 | config providerConfig
16 | }
17 |
18 | var list = schema.NewResources()
19 |
20 | func (d *instanceProvider) GetResource(ctx context.Context) (*schema.Resources, error) {
21 | var (
22 | threads int
23 | err error
24 | wg sync.WaitGroup
25 | )
26 | var endpoints = []string{
27 | "https://bcc.bj.baidubce.com",
28 | "https://bcc.gz.baidubce.com",
29 | "https://bcc.su.baidubce.com",
30 | "https://bcc.hkg.baidubce.com",
31 | "https://bcc.fwh.baidubce.com",
32 | "https://bcc.bd.baidubce.com",
33 | "https://bcc.cd.baidubce.com",
34 | "https://bcc.nj.baidubce.com",
35 | "https://bcc.fsh.baidubce.com",
36 | }
37 | threads = schema.GetThreads()
38 |
39 | taskCh := make(chan string, threads)
40 | for i := 0; i < threads; i++ {
41 | wg.Add(1)
42 | go func() {
43 | err = d.describeInstances(taskCh, &wg)
44 | if err != nil {
45 | return
46 | }
47 | }()
48 | }
49 | for _, item := range endpoints {
50 | taskCh <- item
51 | }
52 | close(taskCh)
53 | wg.Wait()
54 | return list, nil
55 | }
56 |
57 | func (d *instanceProvider) describeInstances(ch <-chan string, wg *sync.WaitGroup) error {
58 | defer wg.Done()
59 | var (
60 | err error
61 | bccClient *bcc.Client
62 | )
63 | for endpoint := range ch {
64 | if d.config.okST {
65 | bccClient, err = bcc.NewClient(d.config.accessKeyID, d.config.accessKeySecret, "")
66 | if err != nil {
67 | continue
68 | }
69 | stsCredential, err := auth.NewSessionBceCredentials(
70 | d.config.accessKeyID,
71 | d.config.accessKeySecret,
72 | d.config.sessionToken)
73 | if err != nil {
74 | continue
75 | }
76 | bccClient.Config.Credentials = stsCredential
77 | } else {
78 | bccClient, err = bcc.NewClient(d.config.accessKeyID, d.config.accessKeySecret, endpoint)
79 | if err != nil {
80 | continue
81 | }
82 | }
83 | listArgs := &api.ListInstanceArgs{}
84 | for {
85 | response, err := bccClient.ListInstances(listArgs)
86 | if err != nil {
87 | break
88 | }
89 | for _, instance := range response.Instances {
90 | var (
91 | ipv4 string
92 | privateIPv4 string
93 | )
94 | ipv4 = instance.PublicIP
95 | privateIPv4 = instance.InternalIP
96 | list.Append(&schema.Resource{
97 | ID: d.id,
98 | Provider: d.provider,
99 | PublicIPv4: ipv4,
100 | PrivateIpv4: privateIPv4,
101 | Public: ipv4 != "",
102 | })
103 | }
104 | if response.NextMarker == "" {
105 | break
106 | }
107 | listArgs.Marker = response.NextMarker
108 | }
109 | }
110 | return err
111 | }
112 |
--------------------------------------------------------------------------------
/pkg/providers/baidu/bos.go:
--------------------------------------------------------------------------------
1 | package baidu
2 |
3 | import (
4 | "context"
5 | "github.com/baidubce/bce-sdk-go/services/bos"
6 | "github.com/projectdiscovery/gologger"
7 | "github.com/wgpsec/lc/pkg/schema"
8 | "strings"
9 | )
10 |
11 | type bosProvider struct {
12 | id string
13 | provider string
14 | bosClient *bos.Client
15 | }
16 |
17 | func (d *bosProvider) GetResource(ctx context.Context) (*schema.Resources, error) {
18 | var list = schema.NewResources()
19 | gologger.Debug().Msg("正在获取百度云 BOS 资源信息")
20 | response, err := d.bosClient.ListBuckets()
21 | if err != nil {
22 | return nil, err
23 | }
24 | for _, bucket := range response.Buckets {
25 | endpointBuilder := &strings.Builder{}
26 | endpointBuilder.WriteString(bucket.Name)
27 | endpointBuilder.WriteString("." + bucket.Location)
28 | endpointBuilder.WriteString(".bcebos.com")
29 | list.Append(&schema.Resource{
30 | ID: d.id,
31 | Public: true,
32 | DNSName: endpointBuilder.String(),
33 | Provider: d.provider,
34 | })
35 | }
36 | return list, nil
37 | }
38 |
--------------------------------------------------------------------------------
/pkg/providers/huawei/huawei.go:
--------------------------------------------------------------------------------
1 | package huawei
2 |
3 | import (
4 | "context"
5 | "github.com/huaweicloud/huaweicloud-sdk-go-obs/obs"
6 | "github.com/projectdiscovery/goflags"
7 | "github.com/projectdiscovery/gologger"
8 | "github.com/wgpsec/lc/pkg/schema"
9 | "github.com/wgpsec/lc/utils"
10 | "strings"
11 | )
12 |
13 | type Provider struct {
14 | id string
15 | provider string
16 | obsClient *obs.ObsClient
17 | cloudServices []string
18 | }
19 |
20 | func New(options schema.OptionBlock, cs goflags.StringSlice) (*Provider, error) {
21 | var (
22 | region = "cn-north-4"
23 | err error
24 | obsClient *obs.ObsClient
25 | cloudServices []string
26 | )
27 | accessKeyID, ok := options.GetMetadata(utils.AccessKey)
28 | if !ok {
29 | return nil, &utils.ErrNoSuchKey{Name: utils.AccessKey}
30 | }
31 | accessKeySecret, ok := options.GetMetadata(utils.SecretKey)
32 | if !ok {
33 | return nil, &utils.ErrNoSuchKey{Name: utils.SecretKey}
34 | }
35 | id, _ := options.GetMetadata(utils.Id)
36 | sessionToken, okST := options.GetMetadata(utils.SessionToken)
37 |
38 | if okST {
39 | gologger.Debug().Msg("找到华为云访问临时访问凭证")
40 | } else {
41 | gologger.Debug().Msg("找到华为云访问永久访问凭证")
42 | }
43 | if cs[0] == "all" {
44 | cloudServicesResult, _ := options.GetMetadata(utils.CloudServices)
45 | cloudServices = strings.Split(cloudServicesResult, ",")
46 | } else {
47 | cloudServices = cs
48 | }
49 | for _, cloudService := range cloudServices {
50 | switch cloudService {
51 | case "obs":
52 | // obs client
53 | if okST {
54 | obsClient, err = obs.New(accessKeyID, accessKeySecret, "https://obs."+region+".myhuaweicloud.com", obs.WithSecurityToken(sessionToken))
55 | } else {
56 | obsClient, err = obs.New(accessKeyID, accessKeySecret, "https://obs."+region+".myhuaweicloud.com")
57 | }
58 | if err != nil {
59 | return nil, err
60 | }
61 | }
62 | }
63 |
64 | return &Provider{provider: utils.Huawei, id: id, obsClient: obsClient, cloudServices: cloudServices}, nil
65 | }
66 |
67 | func (p *Provider) Resources(ctx context.Context, cs goflags.StringSlice) (*schema.Resources, error) {
68 | finalList := schema.NewResources()
69 | for _, cloudService := range p.cloudServices {
70 | switch cloudService {
71 | case "obs":
72 | obsProvider := &obsProvider{obsClient: p.obsClient, id: p.id, provider: p.provider}
73 | buckets, err := obsProvider.GetResource(ctx)
74 | if err != nil {
75 | return nil, err
76 | }
77 | gologger.Info().Msgf("获取到 %d 条华为云 OBS 信息", len(buckets.GetItems()))
78 | finalList.Merge(buckets)
79 | }
80 | }
81 | return finalList, nil
82 | }
83 |
84 | func (p *Provider) Name() string {
85 | return p.provider
86 | }
87 | func (p *Provider) ID() string {
88 | return p.id
89 | }
90 |
--------------------------------------------------------------------------------
/pkg/providers/huawei/obs.go:
--------------------------------------------------------------------------------
1 | package huawei
2 |
3 | import (
4 | "context"
5 | "github.com/huaweicloud/huaweicloud-sdk-go-obs/obs"
6 | "github.com/wgpsec/lc/pkg/schema"
7 | "strings"
8 | )
9 |
10 | type obsProvider struct {
11 | id string
12 | provider string
13 | obsClient *obs.ObsClient
14 | }
15 |
16 | func (d *obsProvider) GetResource(ctx context.Context) (*schema.Resources, error) {
17 | var list = schema.NewResources()
18 | response, err := d.obsClient.ListBuckets(&obs.ListBucketsInput{QueryLocation: true})
19 | if err != nil {
20 | return nil, err
21 | }
22 | for _, bucket := range response.Buckets {
23 | endpointBuilder := &strings.Builder{}
24 | endpointBuilder.WriteString(bucket.Name)
25 | endpointBuilder.WriteString(".obs." + bucket.Location)
26 | endpointBuilder.WriteString(".myhuaweicloud.com")
27 | list.Append(&schema.Resource{
28 | ID: d.id,
29 | Public: true,
30 | DNSName: endpointBuilder.String(),
31 | Provider: d.provider,
32 | })
33 | }
34 | return list, nil
35 | }
36 |
--------------------------------------------------------------------------------
/pkg/providers/liantong/liantong.go:
--------------------------------------------------------------------------------
1 | package liantong
2 |
3 | import (
4 | "context"
5 | "github.com/projectdiscovery/goflags"
6 | "github.com/projectdiscovery/gologger"
7 | "github.com/wgpsec/lc/pkg/schema"
8 | "github.com/wgpsec/lc/utils"
9 | "strings"
10 | )
11 |
12 | type Provider struct {
13 | id string
14 | provider string
15 | config providerConfig
16 | cloudServices []string
17 | }
18 |
19 | type providerConfig struct {
20 | accessKeyID string
21 | accessKeySecret string
22 | sessionToken string
23 | cloudServices []string
24 | }
25 |
26 | func New(options schema.OptionBlock, cs goflags.StringSlice) (*Provider, error) {
27 | var cloudServices []string
28 | accessKeyID, ok := options.GetMetadata(utils.AccessKey)
29 | if !ok {
30 | return nil, &utils.ErrNoSuchKey{Name: utils.AccessKey}
31 | }
32 | accessKeySecret, ok := options.GetMetadata(utils.SecretKey)
33 | if !ok {
34 | return nil, &utils.ErrNoSuchKey{Name: utils.SecretKey}
35 | }
36 | id, _ := options.GetMetadata(utils.Id)
37 | sessionToken, stsOk := options.GetMetadata(utils.SessionToken)
38 | if stsOk {
39 | gologger.Debug().Msg("找到联通云临时访问凭证")
40 | } else {
41 | gologger.Debug().Msg("找到联通云永久访问凭证")
42 | }
43 | if cs[0] == "all" {
44 | cloudServicesResult, _ := options.GetMetadata(utils.CloudServices)
45 | cloudServices = strings.Split(cloudServicesResult, ",")
46 | } else {
47 | cloudServices = cs
48 | }
49 | config := providerConfig{
50 | accessKeyID: accessKeyID,
51 | accessKeySecret: accessKeySecret,
52 | sessionToken: sessionToken,
53 | }
54 | return &Provider{id: id, provider: utils.LianTong, config: config, cloudServices: cloudServices}, nil
55 | }
56 |
57 | func (p *Provider) Name() string {
58 | return p.provider
59 | }
60 |
61 | func (p *Provider) ID() string {
62 | return p.id
63 | }
64 |
65 | func (p *Provider) Resources(ctx context.Context, cs goflags.StringSlice) (*schema.Resources, error) {
66 | finalList := schema.NewResources()
67 | for _, cloudService := range p.cloudServices {
68 | switch cloudService {
69 | case "oss":
70 | ossProvider := &ossProvider{config: p.config, id: p.id, provider: p.provider}
71 | buckets, err := ossProvider.GetResource(ctx)
72 | if err != nil {
73 | return nil, err
74 | }
75 | gologger.Info().Msgf("获取到 %d 条联通云 OSS 信息", len(buckets.GetItems()))
76 | finalList.Merge(buckets)
77 | }
78 | }
79 | return finalList, nil
80 | }
81 |
--------------------------------------------------------------------------------
/pkg/providers/liantong/oss.go:
--------------------------------------------------------------------------------
1 | package liantong
2 |
3 | import (
4 | "context"
5 | "github.com/aws/aws-sdk-go/aws"
6 | "github.com/aws/aws-sdk-go/aws/credentials"
7 | "github.com/aws/aws-sdk-go/aws/session"
8 | "github.com/aws/aws-sdk-go/service/s3"
9 | "github.com/wgpsec/lc/pkg/schema"
10 | "strings"
11 | "sync"
12 | )
13 |
14 | type ossProvider struct {
15 | id string
16 | provider string
17 | config providerConfig
18 | }
19 |
20 | type regions struct {
21 | region string
22 | endpoint string
23 | }
24 |
25 | var list = schema.NewResources()
26 |
27 | func (d *ossProvider) GetResource(ctx context.Context) (*schema.Resources, error) {
28 | var (
29 | threads int
30 | err error
31 | wg sync.WaitGroup
32 | )
33 |
34 | zones := []regions{
35 | {region: "cn-langfang-2", endpoint: "obs-helf.cucloud.cn"},
36 | {region: "cn-xiamen-1", endpoint: "obs-fjxm.cucloud.cn"},
37 | {region: "cn-nanping-1", endpoint: "obs-fjnp.cucloud.cn"},
38 | {region: "cn-ningde-1", endpoint: "obs-fjnd.cucloud.cn"},
39 | {region: "cn-huhehaote-2", endpoint: "obs-nmhhht2.cucloud.cn"},
40 | {region: "cn-guiyang-2", endpoint: "obs-gzgy2.cucloud.cn"},
41 | {region: "cn-chongqing-1", endpoint: "obs-cq.cucloud.cn"},
42 | {region: "cn-shenzhen-1", endpoint: "obs-gdsz.cucloud.cn"},
43 | {region: "cn-shengyang-1", endpoint: "obs-lnsy.cucloud.cn"},
44 | {region: "cn-harbin-1", endpoint: "obs-hlhrb.cucloud.cn"},
45 | {region: "cn-shanghai-1", endpoint: "obs-sh.cucloud.cn"},
46 | //{region: "cn-huhehaote-3", endpoint: "obs-nmhhht3.cucloud.cn"},
47 | //{region: "cn-shijiazhuang-1", endpoint: "obs-hesjz.cucloud.cn"},
48 | {region: "cn-changsha-1", endpoint: "obs-hncs.cucloud.cn"},
49 | }
50 | threads = schema.GetThreads()
51 |
52 | taskCh := make(chan regions, threads)
53 | for i := 0; i < threads; i++ {
54 | wg.Add(1)
55 | go func() {
56 | err = d.listBuckets(taskCh, &wg)
57 | if err != nil {
58 | return
59 | }
60 | }()
61 | }
62 | for _, item := range zones {
63 | taskCh <- item
64 | }
65 | close(taskCh)
66 | wg.Wait()
67 | return list, nil
68 |
69 | }
70 |
71 | func (d *ossProvider) listBuckets(ch <-chan regions, wg *sync.WaitGroup) error {
72 | defer wg.Done()
73 | var err error
74 | for region := range ch {
75 | config := aws.NewConfig()
76 | config.WithRegion(region.region)
77 | config.WithEndpoint("https://" + region.endpoint)
78 | config.WithCredentials(credentials.NewStaticCredentials(d.config.accessKeyID, d.config.accessKeySecret, d.config.sessionToken))
79 | session, err := session.NewSession(config)
80 |
81 | if err != nil {
82 | continue
83 | }
84 | s3Client := s3.New(session)
85 |
86 | listBucketsOutput, err := s3Client.ListBuckets(nil)
87 | if err != nil {
88 | continue
89 | }
90 | for _, bucket := range listBucketsOutput.Buckets {
91 | endpointBuilder := &strings.Builder{}
92 | endpointBuilder.WriteString(aws.StringValue(bucket.Name))
93 | endpointBuilder.WriteString("." + region.endpoint)
94 | list.Append(&schema.Resource{
95 | ID: d.id,
96 | Public: true,
97 | DNSName: endpointBuilder.String(),
98 | Provider: d.provider,
99 | })
100 | }
101 | }
102 | return err
103 | }
104 |
--------------------------------------------------------------------------------
/pkg/providers/qiniu/kodo.go:
--------------------------------------------------------------------------------
1 | package qiniu
2 |
3 | import (
4 | "context"
5 | "github.com/projectdiscovery/gologger"
6 | "github.com/qiniu/go-sdk/v7/auth"
7 | "github.com/qiniu/go-sdk/v7/storage"
8 | "github.com/wgpsec/lc/pkg/schema"
9 | )
10 |
11 | type kodoProvider struct {
12 | id string
13 | provider string
14 | kodoClient *auth.Credentials
15 | }
16 |
17 | func (d *kodoProvider) GetResource(ctx context.Context) (*schema.Resources, error) {
18 | var request storage.BucketV4Input
19 | var list = schema.NewResources()
20 | gologger.Debug().Msg("正在获取七牛云 Kodo 对象存储信息")
21 | cfg := storage.Config{
22 | UseHTTPS: true,
23 | }
24 | bucketManager := storage.NewBucketManager(d.kodoClient, &cfg)
25 | for {
26 | response, err := bucketManager.BucketsV4(&request)
27 | if err != nil {
28 | return nil, err
29 | }
30 | for _, bucket := range response.Buckets {
31 | list.Append(&schema.Resource{
32 | ID: d.id,
33 | Public: true,
34 | DNSName: bucket.Name,
35 | Provider: d.provider,
36 | })
37 | }
38 | if response.IsTruncated {
39 | response.NextMarker = request.Marker
40 | } else {
41 | break
42 | }
43 | }
44 | return list, nil
45 | }
46 |
--------------------------------------------------------------------------------
/pkg/providers/qiniu/qiniu.go:
--------------------------------------------------------------------------------
1 | package qiniu
2 |
3 | import (
4 | "context"
5 | "github.com/projectdiscovery/goflags"
6 | "github.com/projectdiscovery/gologger"
7 | "github.com/qiniu/go-sdk/v7/auth"
8 | "github.com/wgpsec/lc/pkg/schema"
9 | "github.com/wgpsec/lc/utils"
10 | "strings"
11 | )
12 |
13 | type Provider struct {
14 | id string
15 | provider string
16 | kodoClient *auth.Credentials
17 | cloudServices []string
18 | }
19 |
20 | func New(options schema.OptionBlock, cs goflags.StringSlice) (*Provider, error) {
21 | var (
22 | kodoClient *auth.Credentials
23 | cloudServices []string
24 | )
25 | accessKeyID, ok := options.GetMetadata(utils.AccessKey)
26 | if !ok {
27 | return nil, &utils.ErrNoSuchKey{Name: utils.AccessKey}
28 | }
29 | accessKeySecret, ok := options.GetMetadata(utils.SecretKey)
30 | if !ok {
31 | return nil, &utils.ErrNoSuchKey{Name: utils.SecretKey}
32 | }
33 | id, _ := options.GetMetadata(utils.Id)
34 |
35 | gologger.Debug().Msg("找到七牛云访问永久访问凭证")
36 |
37 | if cs[0] == "all" {
38 | cloudServicesResult, _ := options.GetMetadata(utils.CloudServices)
39 | cloudServices = strings.Split(cloudServicesResult, ",")
40 | } else {
41 | cloudServices = cs
42 | }
43 | for _, cloudService := range cloudServices {
44 | switch cloudService {
45 | case "kodo":
46 | // kodo client
47 | kodoClient = auth.New(accessKeyID, accessKeySecret)
48 | }
49 | }
50 | return &Provider{provider: utils.QiNiu, id: id, kodoClient: kodoClient, cloudServices: cloudServices}, nil
51 | }
52 |
53 | func (p *Provider) Resources(ctx context.Context, cs goflags.StringSlice) (*schema.Resources, error) {
54 | finalList := schema.NewResources()
55 | for _, cloudService := range p.cloudServices {
56 | switch cloudService {
57 | case "kodo":
58 | kodoProvider := &kodoProvider{kodoClient: p.kodoClient, id: p.id, provider: p.provider}
59 | buckets, err := kodoProvider.GetResource(ctx)
60 | if err != nil {
61 | return nil, err
62 | }
63 | gologger.Info().Msgf("获取到 %d 条七牛云 Kodo 对象存储信息", len(buckets.GetItems()))
64 | finalList.Merge(buckets)
65 | }
66 | }
67 | return finalList, nil
68 | }
69 |
70 | func (p *Provider) Name() string {
71 | return p.provider
72 | }
73 | func (p *Provider) ID() string {
74 | return p.id
75 | }
76 |
--------------------------------------------------------------------------------
/pkg/providers/tencent/cos.go:
--------------------------------------------------------------------------------
1 | package tencent
2 |
3 | import (
4 | "context"
5 | "github.com/projectdiscovery/gologger"
6 | "github.com/tencentyun/cos-go-sdk-v5"
7 | "github.com/wgpsec/lc/pkg/schema"
8 | "strings"
9 | )
10 |
11 | type cosProvider struct {
12 | id string
13 | provider string
14 | cosClient *cos.Client
15 | }
16 |
17 | func (d *cosProvider) GetResource(ctx context.Context) (*schema.Resources, error) {
18 | cosList := schema.NewResources()
19 | gologger.Debug().Msg("正在获取腾讯云 COS 资源信息")
20 | response, _, err := d.cosClient.Service.Get(context.Background())
21 | if err != nil {
22 | return nil, err
23 | }
24 | for _, bucket := range response.Buckets {
25 | endpointBuilder := &strings.Builder{}
26 | endpointBuilder.WriteString(bucket.Name)
27 | endpointBuilder.WriteString("." + bucket.BucketType)
28 | endpointBuilder.WriteString("." + bucket.Region)
29 | endpointBuilder.WriteString(".myqcloud.com")
30 | cosList.Append(&schema.Resource{
31 | ID: d.id,
32 | Public: true,
33 | DNSName: endpointBuilder.String(),
34 | Provider: d.provider,
35 | })
36 | }
37 | return cosList, nil
38 | }
39 |
--------------------------------------------------------------------------------
/pkg/providers/tencent/cvm.go:
--------------------------------------------------------------------------------
1 | package tencent
2 |
3 | import (
4 | "context"
5 | "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common"
6 | "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile"
7 | cvm "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cvm/v20170312"
8 | lh "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/lighthouse/v20200324"
9 | "github.com/wgpsec/lc/pkg/schema"
10 | "sync"
11 | )
12 |
13 | type instanceProvider struct {
14 | id string
15 | provider string
16 | credential *common.Credential
17 | cvmRegions []*cvm.RegionInfo
18 | lhRegions []*lh.RegionInfo
19 | }
20 |
21 | var cvmList = schema.NewResources()
22 |
23 | func (d *instanceProvider) GetCVMResource(ctx context.Context) (*schema.Resources, error) {
24 | var (
25 | threads int
26 | //err error
27 | wg sync.WaitGroup
28 | regions []string
29 | )
30 | threads = schema.GetThreads()
31 |
32 | for _, region := range d.cvmRegions {
33 | regions = append(regions, *region.Region)
34 | }
35 |
36 | taskCh := make(chan string, threads)
37 | for i := 0; i < threads; i++ {
38 | wg.Add(1)
39 | go func() {
40 | d.describeCVMInstances(taskCh, &wg)
41 | //if err != nil {
42 | // return
43 | //}
44 | }()
45 | }
46 | for _, item := range regions {
47 | taskCh <- item
48 | }
49 | close(taskCh)
50 | wg.Wait()
51 | return cvmList, nil
52 | }
53 |
54 | func (d *instanceProvider) describeCVMInstances(ch <-chan string, wg *sync.WaitGroup) error {
55 | defer wg.Done()
56 | var (
57 | err error
58 | cvmClient *cvm.Client
59 | response *cvm.DescribeInstancesResponse
60 | )
61 | for region := range ch {
62 | cpf := profile.NewClientProfile()
63 | cpf.HttpProfile.Endpoint = "cvm.tencentcloudapi.com"
64 | cvmClient, err = cvm.NewClient(d.credential, region, cpf)
65 | if err != nil {
66 | continue
67 | }
68 | request := cvm.NewDescribeInstancesRequest()
69 | request.Limit = common.Int64Ptr(100)
70 | request.SetScheme("https")
71 | response, err = cvmClient.DescribeInstances(request)
72 | if err != nil {
73 | continue
74 | }
75 | for _, instance := range response.Response.InstanceSet {
76 | var (
77 | ipv4 []string
78 | privateIPv4 string
79 | )
80 |
81 | if len(instance.PublicIpAddresses) > 0 {
82 | for _, v := range instance.PublicIpAddresses {
83 | ipv4 = append(ipv4, *v)
84 | }
85 | }
86 | if len(instance.PrivateIpAddresses) > 0 {
87 | privateIPv4 = *instance.PrivateIpAddresses[0]
88 | }
89 | for _, v := range ipv4 {
90 | cvmList.Append(&schema.Resource{
91 | ID: d.id,
92 | Provider: d.provider,
93 | PublicIPv4: v,
94 | PrivateIpv4: privateIPv4,
95 | Public: v != "",
96 | })
97 | }
98 | }
99 | }
100 | return err
101 | }
102 |
--------------------------------------------------------------------------------
/pkg/providers/tencent/lh.go:
--------------------------------------------------------------------------------
1 | package tencent
2 |
3 | import (
4 | "context"
5 | "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common"
6 | "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile"
7 | lh "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/lighthouse/v20200324"
8 | "github.com/wgpsec/lc/pkg/schema"
9 | "sync"
10 | )
11 |
12 | var lhList = schema.NewResources()
13 |
14 | func (d *instanceProvider) GetLHResource(ctx context.Context) (*schema.Resources, error) {
15 | var (
16 | threads int
17 | err error
18 | wg sync.WaitGroup
19 | regions []string
20 | )
21 | threads = schema.GetThreads()
22 |
23 | for _, region := range d.lhRegions {
24 | regions = append(regions, *region.Region)
25 | }
26 | taskCh := make(chan string, threads)
27 | for i := 0; i < threads; i++ {
28 | wg.Add(1)
29 | go func() {
30 | err = d.describeLHInstances(taskCh, &wg)
31 | if err != nil {
32 | return
33 | }
34 | }()
35 | }
36 | for _, item := range regions {
37 | taskCh <- item
38 | }
39 | close(taskCh)
40 | wg.Wait()
41 | return lhList, nil
42 | }
43 |
44 | func (d *instanceProvider) describeLHInstances(ch <-chan string, wg *sync.WaitGroup) error {
45 | defer wg.Done()
46 | var (
47 | err error
48 | lhClient *lh.Client
49 | response *lh.DescribeInstancesResponse
50 | )
51 | for region := range ch {
52 | cpf := profile.NewClientProfile()
53 | cpf.HttpProfile.Endpoint = "lighthouse.tencentcloudapi.com"
54 | lhClient, err = lh.NewClient(d.credential, region, cpf)
55 | if err != nil {
56 | continue
57 | }
58 | request := lh.NewDescribeInstancesRequest()
59 | request.Limit = common.Int64Ptr(100)
60 | request.SetScheme("https")
61 | response, err = lhClient.DescribeInstances(request)
62 | if err != nil {
63 | continue
64 | }
65 | for _, instance := range response.Response.InstanceSet {
66 | var (
67 | ipv4 []string
68 | privateIPv4 string
69 | )
70 |
71 | if len(instance.PublicAddresses) > 0 {
72 | for _, v := range instance.PublicAddresses {
73 | ipv4 = append(ipv4, *v)
74 | }
75 | }
76 | if len(instance.PrivateAddresses) > 0 {
77 | privateIPv4 = *instance.PrivateAddresses[0]
78 | }
79 | for _, v := range ipv4 {
80 | lhList.Append(&schema.Resource{
81 | ID: d.id,
82 | Provider: d.provider,
83 | PublicIPv4: v,
84 | PrivateIpv4: privateIPv4,
85 | Public: v != "",
86 | })
87 | }
88 | }
89 | }
90 | return err
91 | }
92 |
--------------------------------------------------------------------------------
/pkg/providers/tencent/tencent.go:
--------------------------------------------------------------------------------
1 | package tencent
2 |
3 | import (
4 | "context"
5 | "github.com/projectdiscovery/goflags"
6 | "github.com/projectdiscovery/gologger"
7 | "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common"
8 | "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile"
9 | "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/regions"
10 | cvm "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cvm/v20170312"
11 | lh "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/lighthouse/v20200324"
12 | cos "github.com/tencentyun/cos-go-sdk-v5"
13 | "github.com/wgpsec/lc/pkg/schema"
14 | "github.com/wgpsec/lc/utils"
15 | "net/http"
16 | "strings"
17 | )
18 |
19 | type Provider struct {
20 | id string
21 | provider string
22 | credential *common.Credential
23 | cosClient *cos.Client
24 | cvmRegions []*cvm.RegionInfo
25 | lhRegions []*lh.RegionInfo
26 | cloudServices []string
27 | }
28 |
29 | func New(options schema.OptionBlock, cs goflags.StringSlice) (*Provider, error) {
30 | var (
31 | cosClient *cos.Client
32 | cvmRegions []*cvm.RegionInfo
33 | lhRegions []*lh.RegionInfo
34 | credential *common.Credential
35 | cloudServices []string
36 | )
37 | accessKeyID, ok := options.GetMetadata(utils.AccessKey)
38 | if !ok {
39 | return nil, &utils.ErrNoSuchKey{Name: utils.AccessKey}
40 | }
41 | accessKeySecret, ok := options.GetMetadata(utils.SecretKey)
42 | if !ok {
43 | return nil, &utils.ErrNoSuchKey{Name: utils.SecretKey}
44 | }
45 | id, _ := options.GetMetadata(utils.Id)
46 | sessionToken, okST := options.GetMetadata(utils.SessionToken)
47 |
48 | if okST {
49 | gologger.Debug().Msg("找到腾讯云访问临时访问凭证")
50 | } else {
51 | gologger.Debug().Msg("找到腾讯云访问永久访问凭证")
52 | }
53 |
54 | if okST {
55 | credential = common.NewTokenCredential(accessKeyID, accessKeySecret, sessionToken)
56 | } else {
57 | credential = common.NewCredential(accessKeyID, accessKeySecret)
58 | }
59 |
60 | if cs[0] == "all" {
61 | cloudServicesResult, _ := options.GetMetadata(utils.CloudServices)
62 | cloudServices = strings.Split(cloudServicesResult, ",")
63 | } else {
64 | cloudServices = cs
65 | }
66 | for _, cloudService := range cloudServices {
67 | switch cloudService {
68 | case "cvm":
69 | // cvm regions
70 | cvmCpf := profile.NewClientProfile()
71 | cvmCpf.HttpProfile.Endpoint = "cvm.tencentcloudapi.com"
72 | cvmClient, err := cvm.NewClient(credential, regions.Beijing, cvmCpf)
73 | cvmRequest := cvm.NewDescribeRegionsRequest()
74 | cvmRequest.SetScheme("https")
75 | cvmResponse, err := cvmClient.DescribeRegions(cvmRequest)
76 | if err != nil {
77 | return nil, err
78 | }
79 | cvmRegions = cvmResponse.Response.RegionSet
80 | case "lh":
81 | // lh regions
82 | lhCpf := profile.NewClientProfile()
83 | lhCpf.HttpProfile.Endpoint = "lighthouse.tencentcloudapi.com"
84 | lhClient, err := lh.NewClient(credential, regions.Beijing, lhCpf)
85 | lhRequest := lh.NewDescribeRegionsRequest()
86 | lhResponse, err := lhClient.DescribeRegions(lhRequest)
87 | if err != nil {
88 | return nil, err
89 | }
90 | lhRegions = lhResponse.Response.RegionSet
91 | case "cos":
92 | // cos client
93 | cosClient = cos.NewClient(nil, &http.Client{
94 | Transport: &cos.AuthorizationTransport{
95 | SecretID: accessKeyID,
96 | SecretKey: accessKeySecret,
97 | },
98 | })
99 | }
100 | }
101 |
102 | return &Provider{id: id, provider: utils.Tencent, credential: credential, cvmRegions: cvmRegions, lhRegions: lhRegions, cosClient: cosClient,
103 | cloudServices: cloudServices},
104 | nil
105 | }
106 |
107 | func (p *Provider) Name() string {
108 | return p.provider
109 | }
110 | func (p *Provider) ID() string {
111 | return p.id
112 | }
113 |
114 | func (p *Provider) Resources(ctx context.Context, cs goflags.StringSlice) (*schema.Resources, error) {
115 | finalList := schema.NewResources()
116 | for _, cloudService := range p.cloudServices {
117 | switch cloudService {
118 | case "cvm":
119 | cvmProvider := &instanceProvider{id: p.id, provider: p.provider, cvmRegions: p.cvmRegions, lhRegions: p.lhRegions, credential: p.credential}
120 | cvmList, err := cvmProvider.GetCVMResource(ctx)
121 | if err != nil {
122 | return nil, err
123 | }
124 | gologger.Info().Msgf("获取到 %d 条腾讯云 CVM 信息", len(cvmList.GetItems()))
125 | finalList.Merge(cvmList)
126 | case "lh":
127 | lhProvider := &instanceProvider{id: p.id, provider: p.provider, cvmRegions: p.cvmRegions, lhRegions: p.lhRegions, credential: p.credential}
128 | lhList, err := lhProvider.GetLHResource(ctx)
129 | if err != nil {
130 | return nil, err
131 | }
132 | gologger.Info().Msgf("获取到 %d 条腾讯云 LH 信息", len(lhList.GetItems()))
133 | finalList.Merge(lhList)
134 | case "cos":
135 | cosProvider := &cosProvider{provider: p.provider, id: p.id, cosClient: p.cosClient}
136 | cosList, err := cosProvider.GetResource(ctx)
137 | if err != nil {
138 | return nil, err
139 | }
140 | gologger.Info().Msgf("获取到 %d 条腾讯云 COS 信息", len(cosList.GetItems()))
141 | finalList.Merge(cosList)
142 | }
143 | }
144 | return finalList, nil
145 | }
146 |
--------------------------------------------------------------------------------
/pkg/providers/tianyi/oos.go:
--------------------------------------------------------------------------------
1 | package tianyi
2 |
3 | import (
4 | "context"
5 | "github.com/projectdiscovery/gologger"
6 | "github.com/teamssix/oos-go-sdk/oos"
7 | "github.com/wgpsec/lc/pkg/schema"
8 | "strings"
9 | )
10 |
11 | type oosProvider struct {
12 | id string
13 | provider string
14 | oosClient *oos.Client
15 | }
16 |
17 | func (d *oosProvider) GetResource(ctx context.Context) (*schema.Resources, error) {
18 | var list = schema.NewResources()
19 | gologger.Debug().Msg("正在获取天翼云 OOS 资源信息")
20 | response, err := d.oosClient.ListBuckets()
21 | if err != nil {
22 | return nil, err
23 | }
24 | for _, bucket := range response.Buckets {
25 | endpointBuilder := &strings.Builder{}
26 | endpointBuilder.WriteString(bucket.Name)
27 | endpointBuilder.WriteString(".oos-cn.ctyunapi.cn")
28 | list.Append(&schema.Resource{
29 | ID: d.id,
30 | Public: true,
31 | DNSName: endpointBuilder.String(),
32 | Provider: d.provider,
33 | })
34 | }
35 | return list, nil
36 | }
37 |
--------------------------------------------------------------------------------
/pkg/providers/tianyi/tianyi.go:
--------------------------------------------------------------------------------
1 | package tianyi
2 |
3 | import (
4 | "context"
5 | "github.com/projectdiscovery/goflags"
6 | "github.com/projectdiscovery/gologger"
7 | "github.com/teamssix/oos-go-sdk/oos"
8 | "github.com/wgpsec/lc/pkg/schema"
9 | "github.com/wgpsec/lc/utils"
10 | "strings"
11 | )
12 |
13 | type Provider struct {
14 | id string
15 | provider string
16 | oosClient *oos.Client
17 | cloudServices []string
18 | }
19 |
20 | func New(options schema.OptionBlock, cs goflags.StringSlice) (*Provider, error) {
21 | var (
22 | err error
23 | oosClient *oos.Client
24 | cloudServices []string
25 | )
26 | accessKeyID, ok := options.GetMetadata(utils.AccessKey)
27 | if !ok {
28 | return nil, &utils.ErrNoSuchKey{Name: utils.AccessKey}
29 | }
30 | accessKeySecret, ok := options.GetMetadata(utils.SecretKey)
31 | if !ok {
32 | return nil, &utils.ErrNoSuchKey{Name: utils.SecretKey}
33 | }
34 | id, _ := options.GetMetadata(utils.Id)
35 |
36 | gologger.Debug().Msg("找到天翼云访问永久访问凭证")
37 | if cs[0] == "all" {
38 | cloudServicesResult, _ := options.GetMetadata(utils.CloudServices)
39 | cloudServices = strings.Split(cloudServicesResult, ",")
40 | } else {
41 | cloudServices = cs
42 | }
43 | for _, cloudService := range cloudServices {
44 | switch cloudService {
45 | case "oos":
46 | // oos client
47 | clientOptionV4 := oos.V4Signature(true)
48 | isEnableSha256 := oos.EnableSha256ForPayload(true)
49 | oosClient, err = oos.New("https://oos-cn.ctyunapi.cn", accessKeyID, accessKeySecret, clientOptionV4, isEnableSha256)
50 | if err != nil {
51 | return nil, err
52 | }
53 | }
54 | }
55 | return &Provider{provider: utils.TianYi, id: id, oosClient: oosClient, cloudServices: cloudServices}, nil
56 | }
57 |
58 | func (p *Provider) Resources(ctx context.Context, cs goflags.StringSlice) (*schema.Resources, error) {
59 | finalList := schema.NewResources()
60 | for _, cloudService := range p.cloudServices {
61 | switch cloudService {
62 | case "oos":
63 | oosProvider := &oosProvider{oosClient: p.oosClient, id: p.id, provider: p.provider}
64 | buckets, err := oosProvider.GetResource(ctx)
65 | if err != nil {
66 | return nil, err
67 | }
68 | gologger.Info().Msgf("获取到 %d 条天翼云 OOS 对象存储信息", len(buckets.GetItems()))
69 | finalList.Merge(buckets)
70 | }
71 | }
72 | return finalList, nil
73 | }
74 |
75 | func (p *Provider) Name() string {
76 | return p.provider
77 | }
78 | func (p *Provider) ID() string {
79 | return p.id
80 | }
81 |
--------------------------------------------------------------------------------
/pkg/providers/yidong/eos.go:
--------------------------------------------------------------------------------
1 | package yidong
2 |
3 | import (
4 | "context"
5 | "github.com/aws/aws-sdk-go/aws"
6 | "github.com/aws/aws-sdk-go/aws/credentials"
7 | "github.com/aws/aws-sdk-go/aws/session"
8 | "github.com/aws/aws-sdk-go/service/s3"
9 | "github.com/projectdiscovery/gologger"
10 | "github.com/wgpsec/lc/pkg/schema"
11 | "strings"
12 | "sync"
13 | )
14 |
15 | type eosProvider struct {
16 | id string
17 | provider string
18 | config providerConfig
19 | }
20 |
21 | type regions struct {
22 | region string
23 | endpoint string
24 | }
25 |
26 | var list = schema.NewResources()
27 | var resourcePools = []regions{
28 | {region: "shanghai1", endpoint: "eos-shanghai-1.cmecloud.cn"},
29 | {region: "shanghai2", endpoint: "eos-shanghai-2.cmecloud.cn"},
30 | {region: "shanghai3", endpoint: "eos.shanghai-3.cmecloud.cn"},
31 | {region: "wuxi1", endpoint: "eos-wuxi-1.cmecloud.cn"},
32 | {region: "suzhou4", endpoint: "eos-suzhou-4.cmecloud.cn"},
33 | {region: "fenhu1", endpoint: "eos.fenhu-1.cmecloud.cn"},
34 | {region: "wuxi5", endpoint: "eos-wuxi-5.cmecloud.cn"},
35 | {region: "ningbo1", endpoint: "eos-ningbo-1.cmecloud.cn"},
36 | {region: "ningbo6", endpoint: "eos.ningbo-6.cmecloud.cn"},
37 | {region: "jinan1", endpoint: "eos-jinan-1.cmecloud.cn"},
38 | {region: "jinan4", endpoint: "eos.jinan-4.cmecloud.cn"},
39 | {region: "guangzhou1", endpoint: "eos-guangzhou-1.cmecloud.cn"},
40 | {region: "dongguan1", endpoint: "eos-dongguan-1.cmecloud.cn"},
41 | {region: "dongguan7", endpoint: "eos-dongguan-7.cmecloud.cn"},
42 | {region: "dongguan8", endpoint: "eos-dongguan-8.cmecloud.cn"},
43 | {region: "chengdu1", endpoint: "eos-chengdu-1.cmecloud.cn"},
44 | {region: "chengdu6", endpoint: "eos-chengdu-6.cmecloud.cn"},
45 | {region: "guiyang1", endpoint: "eos-guiyang-1.cmecloud.cn"},
46 | {region: "guiyang4", endpoint: "eos-guiyang-4.cmecloud.cn"},
47 | {region: "chongqing1", endpoint: "eos-chongqing-1.cmecloud.cn"},
48 | {region: "chongqing3", endpoint: "eos-chongqing-3.cmecloud.cn"},
49 | {region: "xian1", endpoint: "eos-xian-1.cmecloud.cn"},
50 | {region: "xian2", endpoint: "eos.xian-2.cmecloud.cn"},
51 | {region: "beijing1", endpoint: "eos-beijing-1.cmecloud.cn"},
52 | {region: "beijing2", endpoint: "eos-beijing-2.cmecloud.cn"},
53 | {region: "beijing4", endpoint: "eos-beijing-4.cmecloud.cn"},
54 | {region: "beijing7", endpoint: "eos-beijing-7.cmecloud.cn"},
55 | {region: "huhehaote1", endpoint: "eos-huhehaote-1.cmecloud.cn"},
56 | {region: "huhehaote6", endpoint: "eos-huhehaote-6.cmecloud.cn"},
57 | {region: "hunan1", endpoint: "eos-hunan-1.cmecloud.cn"},
58 | {region: "zhuzhou1", endpoint: "eos-zhuzhou-1.cmecloud.cn"},
59 | {region: "zhengzhou1", endpoint: "eos-zhengzhou-1.cmecloud.cn"},
60 | {region: "zhengzhou4", endpoint: "eos.zhengzhou-4.cmecloud.cn"},
61 | {region: "tianjin1", endpoint: "eos-tianjin-1.cmecloud.cn"},
62 | {region: "tianjin2", endpoint: "eos.tianjin-2.cmecloud.cn"},
63 | {region: "jilin1", endpoint: "eos-jilin-1.cmecloud.cn"},
64 | {region: "jilin2", endpoint: "eos.jilin-2.cmecloud.cn"},
65 | {region: "hubei1", endpoint: "eos-hubei-1.cmecloud.cn"},
66 | {region: "hubei2", endpoint: "eos.hubei-2.cmecloud.cn"},
67 | {region: "jiangxi1", endpoint: "eos-jiangxi-1.cmecloud.cn"},
68 | {region: "jiangxi2", endpoint: "eos.jiangxi-2.cmecloud.cn"},
69 | {region: "gansu1", endpoint: "eos-gansu-1.cmecloud.cn"},
70 | {region: "gansu2", endpoint: "eos.gansu-2.cmecloud.cn"},
71 | {region: "shanxi1", endpoint: "eos-shanxi-1.cmecloud.cn"},
72 | {region: "shanxi2", endpoint: "eos.shanxi-2.cmecloud.cn"},
73 | {region: "shanxi3", endpoint: "eos.shanxi-3.cmecloud.cn"},
74 | {region: "liaoning1", endpoint: "eos-liaoning-1.cmecloud.cn"},
75 | {region: "liaoning2", endpoint: "eos.liaoning-2.cmecloud.cn"},
76 | {region: "yunnan", endpoint: "eos-yunnan.cmecloud.cn"},
77 | {region: "yunnan2", endpoint: "eos-yunnan-2.cmecloud.cn"},
78 | {region: "hebei1", endpoint: "eos-hebei-1.cmecloud.cn"},
79 | {region: "hebei2", endpoint: "eos.hebei-2.cmecloud.cn"},
80 | {region: "fujian1", endpoint: "eos-fujian-1.cmecloud.cn"},
81 | {region: "fujian2", endpoint: "eos.fujian-2.cmecloud.cn"},
82 | {region: "fujian3", endpoint: "eos.fujian-3.cmecloud.cn"},
83 | {region: "guangxi1", endpoint: "eos-guangxi-1.cmecloud.cn"},
84 | {region: "anhui1", endpoint: "eos-anhui-1.cmecloud.cn"},
85 | {region: "anhui2", endpoint: "eos.anhui-2.cmecloud.cn"},
86 | {region: "hainan1", endpoint: "eos-hainan-1.cmecloud.cn"},
87 | {region: "heilongjiang1", endpoint: "eos-heilongjiang-1.cmecloud.cn"},
88 | {region: "heilongjiang2", endpoint: "eos.heilongjiang-2.cmecloud.cn"},
89 | {region: "xinjiang1", endpoint: "eos-xinjiang-1.cmecloud.cn"},
90 | {region: "xinjiang2", endpoint: "eos.xinjiang-2.cmecloud.cn"},
91 | {region: "ningxia1", endpoint: "eos.ningxia-1.cmecloud.cn"},
92 | {region: "qinghai1", endpoint: "eos.qinghai-1.cmecloud.cn"},
93 | {region: "xizang1", endpoint: "eos.xizang-1.cmecloud.cn"},
94 | }
95 |
96 | func (d *eosProvider) GetResource(ctx context.Context) (*schema.Resources, error) {
97 | var (
98 | threads int
99 | err error
100 | wg sync.WaitGroup
101 | buckets []string
102 | )
103 |
104 | config := aws.NewConfig()
105 | config.WithRegion("beijing1")
106 | config.WithEndpoint("https://eos-beijing-1.cmecloud.cn")
107 | config.WithCredentials(credentials.NewStaticCredentials(d.config.accessKeyID, d.config.accessKeySecret, d.config.sessionToken))
108 | session, err := session.NewSession(config)
109 | if err != nil {
110 | return nil, err
111 | }
112 | s3Client := s3.New(session)
113 |
114 | listBucketsOutput, err := s3Client.ListBuckets(nil)
115 | if err != nil {
116 | return nil, err
117 | }
118 | for _, bucket := range listBucketsOutput.Buckets {
119 | buckets = append(buckets, *bucket.Name)
120 | }
121 | gologger.Debug().Msgf("找到 %d 个移动云 EOS 资源", len(buckets))
122 |
123 | threads = schema.GetThreads()
124 |
125 | taskCh := make(chan string, threads)
126 | for i := 0; i < threads; i++ {
127 | wg.Add(1)
128 | go func() {
129 | err = d.listBuckets(taskCh, &wg, s3Client)
130 | if err != nil {
131 | return
132 | }
133 | }()
134 | }
135 | for _, item := range buckets {
136 | taskCh <- item
137 | }
138 | close(taskCh)
139 | wg.Wait()
140 | return list, nil
141 |
142 | }
143 |
144 | func (d *eosProvider) listBuckets(ch <-chan string, wg *sync.WaitGroup, s3Client *s3.S3) error {
145 | defer wg.Done()
146 | var err error
147 | for bucket := range ch {
148 | bucketLocation, err := s3Client.GetBucketLocation(&s3.GetBucketLocationInput{
149 | Bucket: aws.String(bucket),
150 | })
151 | if err != nil {
152 | continue
153 | }
154 | gologger.Debug().Msgf("%s 的 Location 值为 %s", bucket, *bucketLocation.LocationConstraint)
155 | endpointBuilder := &strings.Builder{}
156 | endpointBuilder.WriteString(bucket)
157 | for _, resourcePool := range resourcePools {
158 | if *bucketLocation.LocationConstraint == resourcePool.region {
159 | endpointBuilder.WriteString("." + resourcePool.endpoint)
160 | }
161 | }
162 |
163 | list.Append(&schema.Resource{
164 | ID: d.id,
165 | Public: true,
166 | DNSName: endpointBuilder.String(),
167 | Provider: d.provider,
168 | })
169 | }
170 | return err
171 | }
172 |
--------------------------------------------------------------------------------
/pkg/providers/yidong/yidong.go:
--------------------------------------------------------------------------------
1 | package yidong
2 |
3 | import (
4 | "context"
5 | "github.com/projectdiscovery/goflags"
6 | "github.com/projectdiscovery/gologger"
7 | "github.com/wgpsec/lc/pkg/schema"
8 | "github.com/wgpsec/lc/utils"
9 | "strings"
10 | )
11 |
12 | type Provider struct {
13 | id string
14 | provider string
15 | config providerConfig
16 | cloudServices []string
17 | }
18 |
19 | type providerConfig struct {
20 | accessKeyID string
21 | accessKeySecret string
22 | sessionToken string
23 | }
24 |
25 | func New(options schema.OptionBlock, cs goflags.StringSlice) (*Provider, error) {
26 | var cloudServices []string
27 |
28 | accessKeyID, ok := options.GetMetadata(utils.AccessKey)
29 | if !ok {
30 | return nil, &utils.ErrNoSuchKey{Name: utils.AccessKey}
31 | }
32 | accessKeySecret, ok := options.GetMetadata(utils.SecretKey)
33 | if !ok {
34 | return nil, &utils.ErrNoSuchKey{Name: utils.SecretKey}
35 | }
36 | id, _ := options.GetMetadata(utils.Id)
37 | sessionToken, stsOk := options.GetMetadata(utils.SessionToken)
38 |
39 | if stsOk {
40 | gologger.Debug().Msg("找到移动云临时访问凭证")
41 | } else {
42 | gologger.Debug().Msg("找到移动云永久访问凭证")
43 | }
44 | if cs[0] == "all" {
45 | cloudServicesResult, _ := options.GetMetadata(utils.CloudServices)
46 | cloudServices = strings.Split(cloudServicesResult, ",")
47 | } else {
48 | cloudServices = cs
49 | }
50 | config := providerConfig{
51 | accessKeyID: accessKeyID,
52 | accessKeySecret: accessKeySecret,
53 | sessionToken: sessionToken,
54 | }
55 | return &Provider{id: id, provider: utils.YiDong, config: config, cloudServices: cloudServices}, nil
56 | }
57 |
58 | func (p *Provider) Name() string {
59 | return p.provider
60 | }
61 |
62 | func (p *Provider) ID() string {
63 | return p.id
64 | }
65 |
66 | func (p *Provider) Resources(ctx context.Context, cs goflags.StringSlice) (*schema.Resources, error) {
67 | finalList := schema.NewResources()
68 | for _, cloudService := range p.cloudServices {
69 | switch cloudService {
70 | case "eos":
71 | eosProvider := &eosProvider{config: p.config, id: p.id, provider: p.provider}
72 | buckets, err := eosProvider.GetResource(ctx)
73 | if err != nil {
74 | return nil, err
75 | }
76 | gologger.Info().Msgf("获取到 %d 条移动云 EOS 信息", len(buckets.GetItems()))
77 | finalList.Merge(buckets)
78 | }
79 | }
80 | return finalList, nil
81 | }
82 |
--------------------------------------------------------------------------------
/pkg/schema/schema.go:
--------------------------------------------------------------------------------
1 | package schema
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "github.com/projectdiscovery/goflags"
7 | "github.com/wgpsec/lc/pkg/schema/validate"
8 | "os"
9 | "strings"
10 | "sync"
11 | )
12 |
13 | var uniqueMap *sync.Map
14 | var validator *validate.Validator
15 | var Threads int
16 |
17 | type Resources struct {
18 | items []*Resource
19 | sync.RWMutex
20 | }
21 |
22 | func (r *Resources) AppendItem(item *Resource) {
23 | r.Lock()
24 | defer r.Unlock()
25 | r.items = append(r.items, item)
26 | }
27 | func (r *Resources) GetItems() []*Resource {
28 | r.RLock()
29 | defer r.RUnlock()
30 | return r.items
31 | }
32 |
33 | type Provider interface {
34 | Name() string
35 | ID() string
36 | Resources(ctx context.Context, cs goflags.StringSlice) (*Resources, error)
37 | }
38 |
39 | type Resource struct {
40 | Public bool `json:"public"`
41 | Provider string `json:"provider"`
42 | ID string `json:"id,omitempty"`
43 | PublicIPv4 string `json:"public_ipv4,omitempty"`
44 | PrivateIpv4 string `json:"private_ipv4,omitempty"`
45 | DNSName string `json:"dns_name,omitempty"`
46 | }
47 |
48 | type Options []OptionBlock
49 | type OptionBlock map[string]string
50 |
51 | func init() {
52 | uniqueMap = &sync.Map{}
53 | var err error
54 | validator, err = validate.NewValidator()
55 | if err != nil {
56 | panic(fmt.Sprintf("无法创建验证器: %s\n", err))
57 | }
58 | }
59 |
60 | // Resources
61 | func (r *Resources) appendResource(resource *Resource, uniqueMap *sync.Map) {
62 | if _, ok := uniqueMap.Load(resource.DNSName); !ok && resource.DNSName != "" {
63 | resourceType := validator.Identify(resource.DNSName)
64 | r.appendResourceWithTypeAndMeta(resourceType, resource.DNSName, resource.ID, resource.Provider)
65 | uniqueMap.Store(resource.DNSName, struct{}{})
66 | }
67 | if _, ok := uniqueMap.Load(resource.PublicIPv4); !ok && resource.PublicIPv4 != "" {
68 | resourceType := validator.Identify(resource.PublicIPv4)
69 | r.appendResourceWithTypeAndMeta(resourceType, resource.PublicIPv4, resource.ID, resource.Provider)
70 | uniqueMap.Store(resource.PublicIPv4, struct{}{})
71 | }
72 | if _, ok := uniqueMap.Load(resource.PrivateIpv4); !ok && resource.PrivateIpv4 != "" {
73 | resourceType := validator.Identify(resource.PrivateIpv4)
74 | r.appendResourceWithTypeAndMeta(resourceType, resource.PrivateIpv4, resource.ID, resource.Provider)
75 | uniqueMap.Store(resource.PrivateIpv4, struct{}{})
76 | }
77 | }
78 |
79 | func (r *Resources) appendResourceWithTypeAndMeta(resourceType validate.ResourceType, item, id, provider string) {
80 | resource := &Resource{
81 | Provider: provider,
82 | ID: id,
83 | }
84 | switch resourceType {
85 | case validate.DNSName:
86 | resource.Public = true
87 | resource.DNSName = item
88 | case validate.PublicIP:
89 | resource.Public = true
90 | resource.PublicIPv4 = item
91 | case validate.PrivateIP:
92 | resource.PrivateIpv4 = item
93 | default:
94 | return
95 | }
96 | r.AppendItem(resource)
97 | }
98 |
99 | func (r *Resources) Append(resource *Resource) {
100 | r.appendResource(resource, uniqueMap)
101 | }
102 |
103 | func (r *Resources) Merge(resources *Resources) {
104 | if resources == nil {
105 | return
106 | }
107 | mergeUniqueMap := &sync.Map{}
108 | for _, item := range resources.GetItems() {
109 | r.appendResource(item, mergeUniqueMap)
110 | }
111 | }
112 |
113 | // OptionBlock
114 |
115 | func (o OptionBlock) GetMetadata(key string) (string, bool) {
116 | data, ok := o[key]
117 | if !ok || data == "" {
118 | return "", false
119 | }
120 | if data[0] == '$' {
121 | envData := os.Getenv(data[1:])
122 | if envData != "" {
123 | return strings.TrimSpace(envData), true
124 | }
125 | }
126 | return strings.TrimSpace(data), true
127 | }
128 |
129 | // Other
130 |
131 | func NewResources() *Resources {
132 | return &Resources{items: make([]*Resource, 0)}
133 | }
134 |
135 | func SetThreads(threads int) {
136 | Threads = threads
137 | }
138 |
139 | func GetThreads() int {
140 | return Threads
141 | }
142 |
--------------------------------------------------------------------------------
/pkg/schema/validate/validate.go:
--------------------------------------------------------------------------------
1 | package validate
2 |
3 | import (
4 | "net"
5 | "regexp"
6 | "strings"
7 | )
8 |
9 | var ipv4PrivateRanges = []string{
10 | "0.0.0.0/8", // 当前网络(仅作为源地址有效)
11 | "10.0.0.0/8", // 私有网络
12 | "100.64.0.0/10", // 共享地址空间
13 | "127.0.0.0/8", // 回路地址
14 | "169.254.0.0/16", // 本地链接(也是许多云提供商的元数据端点)
15 | "172.16.0.0/12", // 私有网络
16 | "192.0.0.0/24", // IETF 协议分配
17 | "192.0.2.0/24", // TEST-NET-1,文档和示例
18 | "192.88.99.0/24", // IPv6 到 IPv4 中继(包括2002::/16)
19 | "192.168.0.0/16", // 私有网络
20 | "198.18.0.0/15", // 网络基准测试
21 | "198.51.100.0/24", // TEST-NET-2,文档和示例
22 | "203.0.113.0/24", // TEST-NET-3,文档和示例
23 | "224.0.0.0/4", // IP组播(以前的 D 类网络)
24 | "240.0.0.0/4", // 预留 (原 E 类网络)
25 | }
26 |
27 | var ipv6PrivateRanges = []string{
28 | "::1/128", // 回路地址
29 | "64:ff9b::/96", // IPv4/IPv6 转换(RFC 6052)
30 | "100::/64", // 丢弃前缀(RFC 6666)
31 | "2001::/32", // Teredo 隧道
32 | "2001:10::/28", // 已弃用(先前为 ORCHID)
33 | "2001:20::/28", // ORCHIDv2
34 | "2001:db8::/32", // 文档和示例源代码中使用的地址
35 | "2002::/16", // 6to4
36 | "fc00::/7", // 唯一本地地址
37 | "fe80::/10", // 链接地址
38 | "ff00::/8", // 多播
39 | }
40 |
41 | type Validator struct {
42 | ipv4 []*net.IPNet
43 | ipv6 []*net.IPNet
44 | rxDNSName *regexp.Regexp
45 | }
46 |
47 | func NewValidator() (*Validator, error) {
48 | validator := &Validator{}
49 | // regex from: https://github.com/asaskevich/govalidator
50 | validator.rxDNSName = regexp.MustCompile(`^([a-zA-Z0-9_]{1}[a-zA-Z0-9_-]{0,62}){1}(\.[a-zA-Z0-9_]{1}[a-zA-Z0-9_-]{0,62})*[\._]?$`)
51 |
52 | err := validator.appendIPv4Ranges(ipv4PrivateRanges)
53 | if err != nil {
54 | return nil, err
55 | }
56 | err = validator.appendIPv6Ranges(ipv6PrivateRanges)
57 | if err != nil {
58 | return nil, err
59 | }
60 | return validator, nil
61 | }
62 |
63 | type ResourceType int
64 |
65 | const (
66 | DNSName ResourceType = iota + 1
67 | PublicIP
68 | PrivateIP
69 | None
70 | )
71 |
72 | func (v *Validator) Identify(item string) ResourceType {
73 | host, port, err := net.SplitHostPort(item)
74 | if err == nil && port != "" {
75 | item = host
76 | }
77 | parsed := net.ParseIP(item)
78 | if v.isDNSName(item, parsed) {
79 | return DNSName
80 | }
81 | if parsed == nil {
82 | return None
83 | }
84 | if strings.Contains(item, ":") {
85 | // 检查 ipv6 私网地址列表
86 | if v.containsIPv6(parsed) {
87 | return PrivateIP
88 | }
89 | return PublicIP
90 | }
91 | // 检查 ipv4 私网地址列表
92 | if v.containsIPv4(parsed) {
93 | return PrivateIP
94 | }
95 | return PublicIP
96 | }
97 |
98 | func (v *Validator) isDNSName(str string, parsed net.IP) bool {
99 | if str == "" || len(strings.Replace(str, ".", "", -1)) > 255 {
100 | return false
101 | }
102 | return !isIP(parsed) && v.rxDNSName.MatchString(str)
103 | }
104 |
105 | func isIP(parsed net.IP) bool {
106 | return parsed != nil
107 | }
108 |
109 | func (v *Validator) appendIPv4Ranges(ranges []string) error {
110 | for _, ip := range ranges {
111 | _, rangeNet, err := net.ParseCIDR(ip)
112 | if err != nil {
113 | return err
114 | }
115 | v.ipv4 = append(v.ipv4, rangeNet)
116 | }
117 | return nil
118 | }
119 |
120 | func (v *Validator) appendIPv6Ranges(ranges []string) error {
121 | for _, ip := range ranges {
122 | _, rangeNet, err := net.ParseCIDR(ip)
123 | if err != nil {
124 | return err
125 | }
126 | v.ipv6 = append(v.ipv6, rangeNet)
127 | }
128 | return nil
129 | }
130 |
131 | func (v *Validator) containsIPv4(IP net.IP) bool {
132 | for _, net := range v.ipv4 {
133 | if net.Contains(IP) {
134 | return true
135 | }
136 | }
137 | return false
138 | }
139 |
140 | func (v *Validator) containsIPv6(IP net.IP) bool {
141 | for _, net := range v.ipv6 {
142 | if net.Contains(IP) {
143 | return true
144 | }
145 | }
146 | return false
147 | }
148 |
--------------------------------------------------------------------------------
/static/39155974.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wgpsec/lc/6b0eafd5eb0d22245a3fc954ba5a037be14fb5d2/static/39155974.jpeg
--------------------------------------------------------------------------------
/static/41125338.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wgpsec/lc/6b0eafd5eb0d22245a3fc954ba5a037be14fb5d2/static/41125338.png
--------------------------------------------------------------------------------
/static/49087564.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wgpsec/lc/6b0eafd5eb0d22245a3fc954ba5a037be14fb5d2/static/49087564.jpeg
--------------------------------------------------------------------------------
/static/buy-coffee.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wgpsec/lc/6b0eafd5eb0d22245a3fc954ba5a037be14fb5d2/static/buy-coffee.png
--------------------------------------------------------------------------------
/static/lc-httpx.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wgpsec/lc/6b0eafd5eb0d22245a3fc954ba5a037be14fb5d2/static/lc-httpx.png
--------------------------------------------------------------------------------
/static/list-cloud-run.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wgpsec/lc/6b0eafd5eb0d22245a3fc954ba5a037be14fb5d2/static/list-cloud-run.png
--------------------------------------------------------------------------------
/static/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wgpsec/lc/6b0eafd5eb0d22245a3fc954ba5a037be14fb5d2/static/logo.png
--------------------------------------------------------------------------------
/static/teamssix.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wgpsec/lc/6b0eafd5eb0d22245a3fc954ba5a037be14fb5d2/static/teamssix.png
--------------------------------------------------------------------------------
/static/wgpsec.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wgpsec/lc/6b0eafd5eb0d22245a3fc954ba5a037be14fb5d2/static/wgpsec.png
--------------------------------------------------------------------------------
/utils/const.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | const (
4 | Provider = "provider"
5 | Id = "id"
6 | CloudServices = "cloud_services"
7 | AccessKey = "access_key"
8 | SecretKey = "secret_key"
9 | SessionToken = "session_token"
10 | )
11 |
12 | const (
13 | Aliyun = "aliyun"
14 | Tencent = "tencent"
15 | Huawei = "huawei"
16 | TianYi = "tianyi"
17 | Baidu = "baidu"
18 | LianTong = "liantong"
19 | QiNiu = "qiniu"
20 | YiDong = "yidong"
21 | )
22 |
--------------------------------------------------------------------------------
/utils/utils.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import (
4 | "fmt"
5 | "github.com/wgpsec/lc/pkg/schema"
6 | "gopkg.in/yaml.v3"
7 | "os"
8 | "strings"
9 | )
10 |
11 | type ErrNoSuchKey struct {
12 | Name string
13 | }
14 |
15 | // 文本处理
16 |
17 | func Contains(s []string, e string) bool {
18 | for _, a := range s {
19 | if strings.EqualFold(a, e) {
20 | return true
21 | }
22 | }
23 | return false
24 | }
25 |
26 | func (e *ErrNoSuchKey) Error() string {
27 | return fmt.Sprintf("no such key: %s", e.Name)
28 | }
29 |
30 | func DivideList(list []string, n int) [][]string {
31 | chunks := make([][]string, n)
32 | chunkSize := len(list) / n
33 | remaining := len(list) % n
34 |
35 | for i := 0; i < n; i++ {
36 | start := i * chunkSize
37 | end := start + chunkSize
38 | if i < remaining {
39 | end++
40 | }
41 | if i == n-1 {
42 | end = len(list)
43 | }
44 | chunks[i] = list[start:end]
45 | }
46 | return chunks
47 | }
48 |
49 | func RemoveRepeatedElement(arr []string) (newArr []string) {
50 | newArr = make([]string, 0)
51 | for i := 0; i < len(arr); i++ {
52 | repeat := false
53 | for j := i + 1; j < len(arr); j++ {
54 | if arr[i] == arr[j] {
55 | repeat = true
56 | break
57 | }
58 | }
59 | if !repeat {
60 | newArr = append(newArr, arr[i])
61 | }
62 | }
63 | return newArr
64 | }
65 |
66 | // 文件处理
67 |
68 | func ReadConfig(configFile string) (schema.Options, error) {
69 | var config schema.Options
70 |
71 | file, err := os.Open(configFile)
72 | if err != nil {
73 | return nil, err
74 | }
75 | defer file.Close()
76 |
77 | if err := yaml.NewDecoder(file).Decode(&config); err != nil {
78 | return nil, err
79 | }
80 | return config, nil
81 | }
82 |
--------------------------------------------------------------------------------