├── .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 | 153 | 156 | 159 | 160 |
151 | TeamsSix
TeamsSix
152 |
154 | ShuBo6
ShuBo6
155 |
157 | tari
tari
158 |
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 | --------------------------------------------------------------------------------